Source code

Revision control

Copy as Markdown

Other Tools

// META: global=window,dedicatedworker
// META: script=/webcodecs/videoFrame-utils.js
function makeRGBA_2x2() {
const data = new Uint8Array([
1,2,3,4, 5,6,7,8,
9,10,11,12, 13,14,15,16,
]);
const init = {
format: 'RGBA',
timestamp: 0,
codedWidth: 2,
codedHeight: 2,
};
return new VideoFrame(data, init);
}
const NV12_DATA = new Uint8Array([
1, 2, 3, 4, // y
5, 6, 7, 8,
9, 10, 11, 12 // uv
]);
function makeNV12_4x2() {
const init = {
format: 'NV12',
timestamp: 0,
codedWidth: 4,
codedHeight: 2,
};
return new VideoFrame(NV12_DATA, init);
}
promise_test(async t => {
const frame = makeI420_4x2();
frame.close();
assert_throws_dom('InvalidStateError', () => frame.allocationSize(), 'allocationSize()');
let data = new Uint8Array(12);
await promise_rejects_dom(t, 'InvalidStateError', frame.copyTo(data), 'copyTo()');
}, 'Test closed frame.');
promise_test(async t => {
const destination = new ArrayBuffer(I420_DATA.length);
await testI420_4x2_copyTo(destination);
}, 'Test copying I420 frame to a non-shared ArrayBuffer');
promise_test(async t => {
const destination = new Uint8Array(I420_DATA.length);
await testI420_4x2_copyTo(destination);
}, 'Test copying I420 frame to a non-shared ArrayBufferView');
promise_test(async t => {
const frame = makeRGBA_2x2();
const expectedLayout = [
{offset: 0, stride: 8},
];
const expectedData = new Uint8Array([
1,2,3,4, 5,6,7,8,
9,10,11,12, 13,14,15,16,
]);
assert_equals(frame.allocationSize(), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data);
assert_layout_equals(layout, expectedLayout);
assert_buffer_equals(data, expectedData);
}, 'Test RGBA frame.');
promise_test(async t => {
const frame = makeNV12_4x2();
const expectedLayout = [
{offset: 0, stride: 4},
{offset: 8, stride: 4},
];
const expectedData = new Uint8Array([
1,2,3,4,
5,6,7,8,
9,10,11,12
]);
assert_equals(frame.allocationSize(), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data);
assert_layout_equals(layout, expectedLayout);
assert_buffer_equals(data, expectedData);
}, 'Test NV12 frame.');
promise_test(async t => {
const frame = makeI420_4x2();
const data = new Uint8Array(11);
await promise_rejects_js(t, TypeError, frame.copyTo(data));
}, 'Test undersized buffer.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
layout: [{offset: 0, stride: 4}],
};
assert_throws_js(TypeError, () => frame.allocationSize(options));
const data = new Uint8Array(12);
await promise_rejects_js(t, TypeError, frame.copyTo(data, options));
}, 'Test incorrect plane count.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
layout: [
{offset: 4, stride: 4},
{offset: 0, stride: 2},
{offset: 2, stride: 2},
],
};
const expectedData = new Uint8Array([
9, 10, // u
11, 12, // v
1, 2, 3, 4, // y
5, 6, 7, 8,
]);
assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data, options);
assert_layout_equals(layout, options.layout);
assert_buffer_equals(data, expectedData);
}, 'Test I420 stride and offset work.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
layout: [
{offset: 9, stride: 5},
{offset: 1, stride: 3},
{offset: 5, stride: 3},
],
};
const expectedData = new Uint8Array([
0,
9, 10, 0, // u
0,
11, 12, 0, // v
0,
1, 2, 3, 4, 0, // y
5, 6, 7, 8, 0,
]);
assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data, options);
assert_layout_equals(layout, options.layout);
assert_buffer_equals(data, expectedData);
}, 'Test I420 stride and offset with padding.');
promise_test(async t => {
const init = {
format: 'I420A',
timestamp: 0,
codedWidth: 4,
codedHeight: 2,
};
const buf = new Uint8Array([
1, 2, 3, 4, // y
5, 6, 7, 8,
9, 10, // u
11, 12, // v
13, 14, 15, 16, // a
17, 18, 19, 20,
]);
const frame = new VideoFrame(buf, init);
const options = {
layout: [
{offset: 12, stride: 4},
{offset: 8, stride: 2},
{offset: 10, stride: 2},
{offset: 0, stride: 4},
],
};
const expectedData = new Uint8Array([
13, 14, 15, 16, // a
17, 18, 19, 20,
9, 10, // u
11, 12, // v
1, 2, 3, 4, // y
5, 6, 7, 8,
]);
assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data, options);
assert_layout_equals(layout, options.layout);
assert_buffer_equals(data, expectedData);
}, 'Test I420A stride and offset work.');
promise_test(async t => {
const init = {
format: 'NV12',
timestamp: 0,
codedWidth: 4,
codedHeight: 2,
};
const buf = new Uint8Array([
1, 2, 3, 4, // y
5, 6, 7, 8,
9, 10, 11, 12 // uv
]);
const frame = new VideoFrame(buf, init);
const options = {
layout: [
{offset: 4, stride: 4},
{offset: 0, stride: 4},
],
};
const expectedData = new Uint8Array([
9, 10, 11, 12, // uv
1, 2, 3, 4, // y
5, 6, 7, 8
]);
assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data, options);
assert_layout_equals(layout, options.layout);
assert_buffer_equals(data, expectedData);
}, 'Test NV12 stride and offset work.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
layout: [
{offset: 0, stride: 1},
{offset: 8, stride: 2},
{offset: 10, stride: 2},
],
};
assert_throws_js(TypeError, () => frame.allocationSize(options));
const data = new Uint8Array(12);
await promise_rejects_js(t, TypeError, frame.copyTo(data, options));
}, 'Test invalid stride.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
layout: [
{offset: 0, stride: 4},
{offset: 8, stride: 2},
{offset: 2 ** 32 - 2, stride: 2},
],
};
assert_throws_js(TypeError, () => frame.allocationSize(options));
const data = new Uint8Array(12);
await promise_rejects_js(t, TypeError, frame.copyTo(data, options));
}, 'Test address overflow.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
rect: frame.codedRect,
};
const expectedLayout = [
{offset: 0, stride: 4},
{offset: 8, stride: 2},
{offset: 10, stride: 2},
];
const expectedData = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
9, 10, // u
11, 12 // v
]);
assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data, options);
assert_layout_equals(layout, expectedLayout);
assert_buffer_equals(data, expectedData);
}, 'Test codedRect.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
rect: {x: 0, y: 0, width: 4, height: 0},
};
assert_throws_js(TypeError, () => frame.allocationSize(options));
const data = new Uint8Array(12);
await promise_rejects_js(t, TypeError, frame.copyTo(data, options));
}, 'Test empty rect.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
rect: {x: 2, y: 0, width: 2, height: 2},
};
const expectedLayout = [
{offset: 0, stride: 2},
{offset: 4, stride: 1},
{offset: 5, stride: 1},
];
const expectedData = new Uint8Array([
3, 4, // y
7, 8,
10, // u
12 // v
]);
assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()');
const data = new Uint8Array(expectedData.length);
const layout = await frame.copyTo(data, options);
assert_layout_equals(layout, expectedLayout);
assert_buffer_equals(data, expectedData);
}, 'Test left crop.');
promise_test(async t => {
const frame = makeI420_4x2();
const options = {
rect: {x: 0, y: 0, width: 4, height: 4},
};
assert_throws_js(TypeError, () => frame.allocationSize(options));
const data = new Uint8Array(12);
await promise_rejects_js(t, TypeError, frame.copyTo(data, options));
}, 'Test invalid rect.');
promise_test(async t => {
let init = {
format: 'I420',
timestamp: 1234,
codedWidth: 8,
codedHeight: 16,
visibleRect: {
x: 2,
y: 2,
width: 4,
height: 8,
},
colorSpace: {
primaries: 'smpte170m',
transfer: 'smpte170m',
matrix: 'smpte170m',
fullRange: false,
}
};
// Define YUV values for BT.601 red.
const redY = 76;
const redU = 84;
const redV = 255;
const ySize = init.codedWidth * init.codedHeight;
const uvSize = ySize / 4;
let data = new Uint8Array(ySize + 2 * uvSize);
fillYUV(data, init.codedWidth, init.codedHeight, init.visibleRect, redY, redU,
redV);
let frame = new VideoFrame(data, init);
assert_equals(frame.codedWidth, init.visibleRect.width);
assert_equals(frame.codedHeight, init.visibleRect.height);
assert_equals(frame.visibleRect.x, 0);
assert_equals(frame.visibleRect.y, 0);
assert_equals(frame.visibleRect.width, init.visibleRect.width);
assert_equals(frame.visibleRect.height, init.visibleRect.height);
assert_equals(frame.displayWidth, init.visibleRect.width);
assert_equals(frame.displayHeight, init.visibleRect.height);
let options = {rect: frame.visibleRect};
let copied_data = new Uint8Array(frame.allocationSize(options));
await frame.copyTo(copied_data, options);
// We could write a bunch of code to carefully slice out the visible region
// of `data`, but it's simpler and works better with testharness.js operators
// to just create a fresh packed version for direct comparison.
let packed_data = new Uint8Array(frame.allocationSize(options));
fillYUV(packed_data, frame.codedWidth, frame.codedHeight, frame.visibleRect,
redY, redU, redV);
assert_array_equals(copied_data, packed_data, `Copied frame data incorrect.`);
}, 'copyTo from byte data with non-default visibleRect');