mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-30 04:13:51 +02:00
Change VideoSampleResource interface, validate return values, add more proper VideoSample.copyTo() logic, fix decoder/encoder color space issues
This commit is contained in:
@@ -154,7 +154,7 @@ test('copyTo and plane layouts', async () => {
|
||||
|
||||
expect(layout).toEqual([{
|
||||
offset: 0,
|
||||
stride: 1300 * 4,
|
||||
stride: 1280 * 4,
|
||||
}]);
|
||||
|
||||
using clone = sample.clone();
|
||||
@@ -162,7 +162,7 @@ test('copyTo and plane layouts', async () => {
|
||||
|
||||
expect(clonedLayout).toEqual([{
|
||||
offset: 0,
|
||||
stride: 1300 * 4,
|
||||
stride: 1280 * 4,
|
||||
}]);
|
||||
}
|
||||
|
||||
@@ -186,6 +186,184 @@ test('copyTo and plane layouts', async () => {
|
||||
offset: 1280 * 720 + (1280 / 2) * (720 / 2),
|
||||
stride: 1280 / 2,
|
||||
}]);
|
||||
|
||||
const rgbLayout = await sample.copyTo(buffer, { format: 'RGBA' });
|
||||
|
||||
expect(rgbLayout).toEqual([{
|
||||
offset: 0,
|
||||
stride: 4 * 1280,
|
||||
}]);
|
||||
}
|
||||
});
|
||||
|
||||
test('RGB conversion for ArrayBuffer-backed data', async () => {
|
||||
// 2x2 RGBA image with distinct, easily-verifiable pixels
|
||||
const src = new Uint8Array([
|
||||
10, 20, 30, 255, 40, 50, 60, 255,
|
||||
70, 80, 90, 255, 100, 110, 120, 255,
|
||||
]);
|
||||
|
||||
{
|
||||
// RGBA -> BGRA: R and B should swap
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: 2,
|
||||
codedHeight: 2,
|
||||
format: 'RGBA',
|
||||
});
|
||||
|
||||
const dest = new Uint8Array(2 * 2 * 4);
|
||||
const layout = await sample.copyTo(dest, { format: 'BGRA' });
|
||||
|
||||
expect(layout).toEqual([{ offset: 0, stride: 2 * 4 }]);
|
||||
expect(Array.from(dest)).toEqual([
|
||||
30, 20, 10, 255, 60, 50, 40, 255,
|
||||
90, 80, 70, 255, 120, 110, 100, 255,
|
||||
]);
|
||||
}
|
||||
|
||||
{
|
||||
// RGBA -> RGBA: no swap, bytes copied through unchanged
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: 2,
|
||||
codedHeight: 2,
|
||||
format: 'RGBA',
|
||||
});
|
||||
|
||||
const dest = new Uint8Array(2 * 2 * 4);
|
||||
await sample.copyTo(dest, { format: 'RGBA' });
|
||||
|
||||
expect(Array.from(dest)).toEqual(Array.from(src));
|
||||
}
|
||||
|
||||
{
|
||||
// RGBA -> BGRX: R and B should swap
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: 2,
|
||||
codedHeight: 2,
|
||||
format: 'RGBA',
|
||||
});
|
||||
|
||||
const dest = new Uint8Array(2 * 2 * 4);
|
||||
await sample.copyTo(dest, { format: 'BGRX' });
|
||||
|
||||
expect(Array.from(dest)).toEqual([
|
||||
30, 20, 10, 255, 60, 50, 40, 255,
|
||||
90, 80, 70, 255, 120, 110, 100, 255,
|
||||
]);
|
||||
}
|
||||
|
||||
{
|
||||
// BGRA -> RGBA: R and B should swap
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: 2,
|
||||
codedHeight: 2,
|
||||
format: 'BGRA',
|
||||
});
|
||||
|
||||
const dest = new Uint8Array(2 * 2 * 4);
|
||||
await sample.copyTo(dest, { format: 'RGBA' });
|
||||
|
||||
expect(Array.from(dest)).toEqual([
|
||||
30, 20, 10, 255, 60, 50, 40, 255,
|
||||
90, 80, 70, 255, 120, 110, 100, 255,
|
||||
]);
|
||||
}
|
||||
|
||||
{
|
||||
// BGRA -> BGRA: no swap
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: 2,
|
||||
codedHeight: 2,
|
||||
format: 'BGRA',
|
||||
});
|
||||
|
||||
const dest = new Uint8Array(2 * 2 * 4);
|
||||
await sample.copyTo(dest, { format: 'BGRA' });
|
||||
|
||||
expect(Array.from(dest)).toEqual(Array.from(src));
|
||||
}
|
||||
});
|
||||
|
||||
test('crop (rect option) for ArrayBuffer-backed data', async () => {
|
||||
// 4x4 RGBA image where each pixel's R/G channels encode (x, y)
|
||||
const width = 4;
|
||||
const height = 4;
|
||||
const src = new Uint8Array(width * height * 4);
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < width; x++) {
|
||||
const i = (y * width + x) * 4;
|
||||
src[i] = x;
|
||||
src[i + 1] = y;
|
||||
src[i + 2] = 0;
|
||||
src[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Crop a 2x2 region at (1, 1)
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: width,
|
||||
codedHeight: height,
|
||||
format: 'RGBA',
|
||||
});
|
||||
|
||||
const size = sample.allocationSize({ rect: { x: 1, y: 1, width: 2, height: 2 } });
|
||||
expect(size).toBe(2 * 2 * 4);
|
||||
|
||||
const dest = new Uint8Array(size);
|
||||
const layout = await sample.copyTo(dest, { rect: { x: 1, y: 1, width: 2, height: 2 } });
|
||||
|
||||
expect(layout).toEqual([{ offset: 0, stride: 2 * 4 }]);
|
||||
expect(Array.from(dest)).toEqual([
|
||||
1, 1, 0, 255, 2, 1, 0, 255,
|
||||
1, 2, 0, 255, 2, 2, 0, 255,
|
||||
]);
|
||||
}
|
||||
|
||||
{
|
||||
// Crop an offset rect at the top-right corner
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: width,
|
||||
codedHeight: height,
|
||||
format: 'RGBA',
|
||||
});
|
||||
|
||||
const dest = new Uint8Array(2 * 1 * 4);
|
||||
const layout = await sample.copyTo(dest, { rect: { x: 2, y: 0, width: 2, height: 1 } });
|
||||
|
||||
expect(layout).toEqual([{ offset: 0, stride: 2 * 4 }]);
|
||||
expect(Array.from(dest)).toEqual([
|
||||
2, 0, 0, 255, 3, 0, 0, 255,
|
||||
]);
|
||||
}
|
||||
|
||||
{
|
||||
// Crop combined with a custom destination stride (padding between rows)
|
||||
using sample = new VideoSample(src.slice(), {
|
||||
timestamp: 0,
|
||||
codedWidth: width,
|
||||
codedHeight: height,
|
||||
format: 'RGBA',
|
||||
});
|
||||
|
||||
const stride = 3 * 4; // wider than the crop, leaving trailing padding
|
||||
const dest = new Uint8Array(stride * 2);
|
||||
const layout = await sample.copyTo(dest, {
|
||||
rect: { x: 0, y: 2, width: 2, height: 2 },
|
||||
layout: [{ offset: 0, stride }],
|
||||
});
|
||||
|
||||
expect(layout).toEqual([{ offset: 0, stride }]);
|
||||
// Row 0 of crop (y=2): pixels (0,2) and (1,2), then 4 bytes of untouched padding
|
||||
expect(Array.from(dest.subarray(0, 8))).toEqual([0, 2, 0, 255, 1, 2, 0, 255]);
|
||||
expect(Array.from(dest.subarray(stride, stride + 8))).toEqual([0, 3, 0, 255, 1, 3, 0, 255]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -201,12 +379,7 @@ test('null format', async () => {
|
||||
expect(() => sample.allocationSize()).toThrow('when format is null');
|
||||
await expect(async () => sample.copyTo(new ArrayBuffer())).rejects.toThrow('when format is null');
|
||||
|
||||
// Even this throws :(
|
||||
// See https://github.com/Vanilagy/mediabunny/issues/267
|
||||
expect(() => sample.allocationSize({ format: 'RGBA' })).toThrow('when format is null');
|
||||
|
||||
// Uncomment this if the RGBA conversion works again:
|
||||
/*
|
||||
// BUT: See https://github.com/Vanilagy/mediabunny/issues/267
|
||||
const size = sample.allocationSize({ format: 'RGBA' });
|
||||
expect(size).toBe(1280 * 720 * 4);
|
||||
const buffer = new ArrayBuffer(size);
|
||||
@@ -223,5 +396,4 @@ test('null format', async () => {
|
||||
await sample.copyTo(buffer, { format: 'RGBX' });
|
||||
await sample.copyTo(buffer, { format: 'BGRA' });
|
||||
await sample.copyTo(buffer, { format: 'BGRX' });
|
||||
*/
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user