Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 17 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /webcodecs/videoFrame-construction.any.html - WPT Dashboard Interop Dashboard
- /webcodecs/videoFrame-construction.any.worker.html - WPT Dashboard Interop Dashboard
// META: global=window,dedicatedworker
// META: script=/webcodecs/utils.js
// META: script=/webcodecs/videoFrame-utils.js
test(t => {
let image = makeImageBitmap(32, 16);
let frame = new VideoFrame(image, {timestamp: 10});
assert_equals(frame.timestamp, 10, 'timestamp');
assert_equals(frame.duration, null, 'duration');
assert_equals(frame.visibleRect.width, 32, 'visibleRect.width');
assert_equals(frame.visibleRect.height, 16, 'visibleRect.height');
assert_equals(frame.displayWidth, 32, 'displayWidth');
assert_equals(frame.displayHeight, 16, 'displayHeight');
frame.close();
}, 'Test we can construct a VideoFrame.');
test(t => {
let image = makeImageBitmap(32, 16);
let frame = new VideoFrame(image, {timestamp: 10, duration: 15});
frame.close();
assert_equals(frame.format, null, 'format')
assert_equals(frame.timestamp, 10, 'timestamp');
assert_equals(frame.duration, 15, 'duration');
assert_equals(frame.codedWidth, 0, 'codedWidth');
assert_equals(frame.codedHeight, 0, 'codedHeight');
assert_equals(frame.visibleRect, null, 'visibleRect');
assert_equals(frame.displayWidth, 0, 'displayWidth');
assert_equals(frame.displayHeight, 0, 'displayHeight');
assert_equals(frame.colorSpace.primaries, null, 'colorSpace.primaries');
assert_equals(frame.colorSpace.transfer, null, 'colorSpace.transfer');
assert_equals(frame.colorSpace.matrix, null, 'colorSpace.matrix');
assert_equals(frame.colorSpace.fullRange, null, 'colorSpace.fullRange');
assert_true(isFrameClosed(frame));
assert_throws_dom('InvalidStateError', () => frame.clone());
}, 'Test closed VideoFrame.');
test(t => {
let image = makeImageBitmap(32, 16);
let frame = new VideoFrame(image, {timestamp: -10});
assert_equals(frame.timestamp, -10, 'timestamp');
frame.close();
}, 'Test we can construct a VideoFrame with a negative timestamp.');
promise_test(async t => {
verifyTimestampRequiredToConstructFrame(makeImageBitmap(1, 1));
}, 'Test that timestamp is required when constructing VideoFrame from ImageBitmap');
promise_test(async t => {
verifyTimestampRequiredToConstructFrame(makeOffscreenCanvas(16, 16));
}, 'Test that timestamp is required when constructing VideoFrame from OffscreenCanvas');
promise_test(async t => {
let init = {
format: 'I420',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
let i420Frame = new VideoFrame(data, init);
let validFrame = new VideoFrame(i420Frame);
validFrame.close();
}, 'Test that timestamp is NOT required when constructing VideoFrame from another VideoFrame');
test(t => {
let image = makeImageBitmap(1, 1);
let frame = new VideoFrame(image, {timestamp: 10});
assert_equals(frame.visibleRect.width, 1, 'visibleRect.width');
assert_equals(frame.visibleRect.height, 1, 'visibleRect.height');
assert_equals(frame.displayWidth, 1, 'displayWidth');
assert_equals(frame.displayHeight, 1, 'displayHeight');
frame.close();
}, 'Test we can construct an odd-sized VideoFrame.');
test(t => {
// Test only valid for Window contexts.
if (!('document' in self))
return;
let video = document.createElement('video');
assert_throws_dom('InvalidStateError', () => {
let frame = new VideoFrame(video, {timestamp: 10});
})
}, 'Test constructing w/ unusable image argument throws: HAVE_NOTHING <video>.');
promise_test(async t => {
// Test only valid for Window contexts.
if (!('document' in self))
return;
let video = document.createElement('video');
video.src = 'vp9.mp4';
video.autoplay = true;
video.controls = false;
video.muted = false;
document.body.appendChild(video);
const loadVideo = new Promise((resolve) => {
if (video.requestVideoFrameCallback) {
video.requestVideoFrameCallback(resolve);
return;
}
video.onloadeddata = () => resolve();
});
await loadVideo;
let frame = new VideoFrame(video, {timestamp: 10});
assert_equals(frame.codedWidth, 320, 'codedWidth');
assert_equals(frame.codedHeight, 240, 'codedHeight');
assert_equals(frame.timestamp, 10, 'timestamp');
frame.close();
}, 'Test we can construct a VideoFrame from a <video>.');
test(t => {
let canvas = new OffscreenCanvas(0, 0);
assert_throws_dom('InvalidStateError', () => {
let frame = new VideoFrame(canvas, {timestamp: 10});
})
}, 'Test constructing w/ unusable image argument throws: emtpy Canvas.');
test(t => {
let image = makeImageBitmap(32, 16);
image.close();
assert_throws_dom('InvalidStateError', () => {
let frame = new VideoFrame(image, {timestamp: 10});
})
}, 'Test constructing w/ unusable image argument throws: closed ImageBitmap.');
test(t => {
let image = makeImageBitmap(32, 16);
let frame = new VideoFrame(image, {timestamp: 10});
frame.close();
assert_throws_dom('InvalidStateError', () => {
let newFrame = new VideoFrame(frame);
})
}, 'Test constructing w/ unusable image argument throws: closed VideoFrame.');
test(t => {
let init = {
format: 'I420',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
let i420Frame = new VideoFrame(data, init);
let image = makeImageBitmap(32, 16);
assert_throws_js(
TypeError,
() => new VideoFrame(
image,
{timestamp: 10, visibleRect: {x: -1, y: 0, width: 10, height: 10}}),
'negative visibleRect x');
assert_throws_js(
TypeError,
() => new VideoFrame(
image,
{timestamp: 10, visibleRect: {x: 0, y: 0, width: -10, height: 10}}),
'negative visibleRect width');
assert_throws_js(
TypeError,
() => new VideoFrame(
image,
{timestamp: 10, visibleRect: {x: 0, y: 0, width: 10, height: 0}}),
'zero visibleRect height');
assert_throws_js(
TypeError, () => new VideoFrame(image, {
timestamp: 10,
visibleRect: {x: 0, y: Infinity, width: 10, height: 10}
}),
'non finite visibleRect y');
assert_throws_js(
TypeError, () => new VideoFrame(image, {
timestamp: 10,
visibleRect: {x: 0, y: 0, width: 10, height: Infinity}
}),
'non finite visibleRect height');
assert_throws_js(
TypeError,
() => new VideoFrame(
image,
{timestamp: 10, visibleRect: {x: 0, y: 0, width: 33, height: 17}}),
'visibleRect area exceeds coded size');
assert_throws_js(
TypeError,
() => new VideoFrame(
image,
{timestamp: 10, visibleRect: {x: 2, y: 2, width: 32, height: 16}}),
'visibleRect outside coded size');
assert_throws_js(
TypeError,
() => new VideoFrame(image, {timestamp: 10, displayHeight: 10}),
'displayHeight provided without displayWidth');
assert_throws_js(
TypeError, () => new VideoFrame(image, {timestamp: 10, displayWidth: 10}),
'displayWidth provided without displayHeight');
assert_throws_js(
TypeError,
() => new VideoFrame(
image, {timestamp: 10, displayWidth: 0, displayHeight: 10}),
'displayWidth is zero');
assert_throws_js(
TypeError,
() => new VideoFrame(
image, {timestamp: 10, displayWidth: 10, displayHeight: 0}),
'displayHeight is zero');
assert_throws_js(
TypeError,
() => new VideoFrame(
i420Frame, {visibleRect: {x: 1, y: 0, width: 2, height: 2}}),
'visibleRect x is not sample aligned');
assert_throws_js(
TypeError,
() => new VideoFrame(
i420Frame, {visibleRect: {x: 0, y: 1, width: 2, height: 2}}),
'visibleRect y is not sample aligned');
}, 'Test invalid CanvasImageSource constructed VideoFrames');
test(t => {
let init = {
format: 'I420',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
let origFrame = new VideoFrame(data, init);
let cropLeftHalf = new VideoFrame(
origFrame, {visibleRect: {x: 0, y: 0, width: 2, height: 2}});
assert_equals(cropLeftHalf.codedWidth, origFrame.codedWidth);
assert_equals(cropLeftHalf.codedHeight, origFrame.codedHeight);
assert_equals(cropLeftHalf.visibleRect.x, 0);
assert_equals(cropLeftHalf.visibleRect.y, 0);
assert_equals(cropLeftHalf.visibleRect.width, 2);
assert_equals(cropLeftHalf.visibleRect.height, 2);
assert_equals(cropLeftHalf.displayWidth, 2);
assert_equals(cropLeftHalf.displayHeight, 2);
}, 'Test visibleRect metadata override where source display size = visible size');
test(t => {
let init = {
format: 'I420',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
displayWidth: 8,
displayHeight: 2
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
let anamorphicFrame = new VideoFrame(data, init);
let cropRightFrame = new VideoFrame(
anamorphicFrame, {visibleRect: {x: 2, y: 0, width: 2, height: 2}});
assert_equals(cropRightFrame.codedWidth, anamorphicFrame.codedWidth);
assert_equals(cropRightFrame.codedHeight, anamorphicFrame.codedHeight);
assert_equals(cropRightFrame.visibleRect.x, 2);
assert_equals(cropRightFrame.visibleRect.y, 0);
assert_equals(cropRightFrame.visibleRect.width, 2);
assert_equals(cropRightFrame.visibleRect.height, 2);
assert_equals(cropRightFrame.displayWidth, 4, 'cropRightFrame.displayWidth');
assert_equals(cropRightFrame.displayHeight, 2, 'cropRightFrame.displayHeight');
}, 'Test visibleRect metadata override where source display width = 2 * visible width (anamorphic)');
test(t => {
let init = {
format: 'I420',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
displayWidth: 8,
displayHeight: 4
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
let scaledFrame = new VideoFrame(data, init);
let cropRightFrame = new VideoFrame(
scaledFrame, {visibleRect: {x: 2, y: 0, width: 2, height: 2}});
assert_equals(cropRightFrame.codedWidth, scaledFrame.codedWidth);
assert_equals(cropRightFrame.codedHeight, scaledFrame.codedHeight);
assert_equals(cropRightFrame.visibleRect.x, 2);
assert_equals(cropRightFrame.visibleRect.y, 0);
assert_equals(cropRightFrame.visibleRect.width, 2);
assert_equals(cropRightFrame.visibleRect.height, 2);
assert_equals(cropRightFrame.displayWidth, 4, 'cropRightFrame.displayWidth');
assert_equals(cropRightFrame.displayHeight, 4, 'cropRightFrame.displayHeight');
}, 'Test visibleRect metadata override where source display size = 2 * visible size for both width and height');
test(t => {
let image = makeImageBitmap(32, 16);
let scaledFrame = new VideoFrame(image, {
visibleRect: {x: 0, y: 0, width: 2, height: 2},
displayWidth: 10,
displayHeight: 20,
timestamp: 0
});
assert_equals(scaledFrame.codedWidth, 32);
assert_equals(scaledFrame.codedHeight, 16);
assert_equals(scaledFrame.visibleRect.x, 0);
assert_equals(scaledFrame.visibleRect.y, 0);
assert_equals(scaledFrame.visibleRect.width, 2);
assert_equals(scaledFrame.visibleRect.height, 2);
assert_equals(scaledFrame.displayWidth, 10, 'scaledFrame.displayWidth');
assert_equals(scaledFrame.displayHeight, 20, 'scaledFrame.displayHeight');
}, 'Test visibleRect + display size metadata override');
test(t => {
let image = makeImageBitmap(32, 16);
let scaledFrame = new VideoFrame(image,
{
displayWidth: 10, displayHeight: 20,
timestamp: 0
});
assert_equals(scaledFrame.codedWidth, 32);
assert_equals(scaledFrame.codedHeight, 16);
assert_equals(scaledFrame.visibleRect.x, 0);
assert_equals(scaledFrame.visibleRect.y, 0);
assert_equals(scaledFrame.visibleRect.width, 32);
assert_equals(scaledFrame.visibleRect.height, 16);
assert_equals(scaledFrame.displayWidth, 10, 'scaledFrame.displayWidth');
assert_equals(scaledFrame.displayHeight, 20, 'scaledFrame.displayHeight');
}, 'Test display size metadata override');
test(t => {
assert_throws_js(
TypeError,
() => new VideoFrame(
new Uint8Array(1),
{format: 'ABCD', timestamp: 1234, codedWidth: 4, codedHeight: 2}),
'invalid pixel format');
assert_throws_js(
TypeError,
() =>
new VideoFrame(new Uint32Array(1), {format: 'RGBA', timestamp: 1234}),
'missing coded size');
function constructFrame(init) {
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
return new VideoFrame(data, {...init, format: 'I420'});
}
assert_throws_js(
TypeError, () => constructFrame({
timestamp: 1234,
codedWidth: Math.pow(2, 32) - 1,
codedHeight: Math.pow(2, 32) - 1,
}),
'invalid coded size');
assert_throws_js(
TypeError,
() => constructFrame({timestamp: 1234, codedWidth: 4, codedHeight: 0}),
'invalid coded height');
assert_throws_js(
TypeError,
() => constructFrame({timestamp: 1234, codedWidth: 0, codedHeight: 4}),
'invalid coded width');
assert_throws_js(
TypeError, () => constructFrame({
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
visibleRect: {x: 100, y: 100, width: 1, height: 1}
}),
'invalid visible left/right');
assert_throws_js(
TypeError, () => constructFrame({
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
visibleRect: {x: 0, y: 0, width: 0, height: 2}
}),
'invalid visible width');
assert_throws_js(
TypeError, () => constructFrame({
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
visibleRect: {x: 0, y: 0, width: 2, height: 0}
}),
'invalid visible height');
assert_throws_js(
TypeError, () => constructFrame({
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
visibleRect: {x: 0, y: 0, width: -100, height: -100}
}),
'invalid negative visible size');
assert_throws_js(
TypeError, () => constructFrame({
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
displayWidth: Math.pow(2, 32),
}),
'invalid display width');
assert_throws_js(
TypeError, () => constructFrame({
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
displayWidth: Math.pow(2, 32) - 1,
displayHeight: Math.pow(2, 32)
}),
'invalid display height');
}, 'Test invalid buffer constructed VideoFrames');
test(t => {
testBufferConstructedI420Frame('Uint8Array(ArrayBuffer)');
}, 'Test Uint8Array(ArrayBuffer) constructed I420 VideoFrame');
test(t => {
testBufferConstructedI420Frame('ArrayBuffer');
}, 'Test ArrayBuffer constructed I420 VideoFrame');
test(t => {
let fmt = 'I420';
let vfInit = {
format: fmt,
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
colorSpace: {
primaries: 'smpte170m',
matrix: 'bt470bg',
},
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
let frame = new VideoFrame(data, vfInit);
assert_equals(frame.colorSpace.primaries, 'smpte170m', 'color primaries');
assert_true(frame.colorSpace.transfer == null, 'color transfer');
assert_equals(frame.colorSpace.matrix, 'bt470bg', 'color matrix');
assert_true(frame.colorSpace.fullRange == null, 'color range');
}, 'Test planar constructed I420 VideoFrame with colorSpace');
test(t => {
let fmt = 'I420';
let vfInit = {
format: fmt,
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
colorSpace: {
primaries: null,
transfer: null,
matrix: null,
fullRange: null,
},
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
]);
let frame = new VideoFrame(data, vfInit);
assert_true(frame.colorSpace.primaries !== undefined, 'color primaries');
assert_true(frame.colorSpace.transfer !== undefined, 'color transfer');
assert_true(frame.colorSpace.matrix !== undefined, 'color matrix');
assert_true(frame.colorSpace.fullRange !== undefined, 'color range');
}, 'Test planar can construct I420 VideoFrame with null colorSpace values');
test(t => {
let fmt = 'I420A';
let vfInit = {format: fmt, timestamp: 1234, codedWidth: 4, codedHeight: 2};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
8, 7, 6, 5, 4, 3, 2, 1, // a
]);
let frame = new VideoFrame(data, vfInit);
assert_equals(frame.format, fmt, 'plane format');
assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries');
assert_equals(frame.colorSpace.transfer, 'bt709', 'color transfer');
assert_equals(frame.colorSpace.matrix, 'bt709', 'color matrix');
assert_false(frame.colorSpace.fullRange, 'color range');
frame.close();
// Most constraints are tested as part of I420 above.
let y = {offset: 0, stride: 4};
let u = {offset: 8, stride: 2};
let v = {offset: 10, stride: 2};
let a = {offset: 12, stride: 4};
assert_throws_js(TypeError, () => {
let a = {offset: 12, stride: 1};
let frame = new VideoFrame(data, {...vfInit, layout: [y, u, v, a]});
}, 'a stride too small');
assert_throws_js(TypeError, () => {
let frame = new VideoFrame(data.slice(0, 12), vfInit);
}, 'data too small');
}, 'Test buffer constructed I420+Alpha VideoFrame');
test(t => {
let fmt = 'NV12';
let vfInit = {format: fmt, timestamp: 1234, codedWidth: 4, codedHeight: 2};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, 3, 4, // uv
]);
let frame = new VideoFrame(data, vfInit);
assert_equals(frame.format, fmt, 'plane format');
assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries');
assert_equals(frame.colorSpace.transfer, 'bt709', 'color transfer');
assert_equals(frame.colorSpace.matrix, 'bt709', 'color matrix');
assert_false(frame.colorSpace.fullRange, 'color range');
frame.close();
let y = {offset: 0, stride: 4};
let uv = {offset: 8, stride: 4};
assert_throws_js(TypeError, () => {
let y = {offset: 0, stride: 1};
let frame = new VideoFrame(data, {...vfInit, layout: [y, uv]});
}, 'y stride too small');
assert_throws_js(TypeError, () => {
let uv = {offset: 8, stride: 1};
let frame = new VideoFrame(data, {...vfInit, layout: [y, uv]});
}, 'uv stride too small');
assert_throws_js(TypeError, () => {
let frame = new VideoFrame(data.slice(0, 8), vfInit);
}, 'data too small');
}, 'Test buffer constructed NV12 VideoFrame');
test(t => {
let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
]);
let frame = new VideoFrame(data, {...vfInit, format: 'RGBA'});
assert_equals(frame.format, 'RGBA', 'plane format');
assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries');
assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer');
assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix');
assert_true(frame.colorSpace.fullRange, 'color range');
frame.close();
frame = new VideoFrame(data, {...vfInit, format: 'RGBX'});
assert_equals(frame.format, 'RGBX', 'plane format');
assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries');
assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer');
assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix');
assert_true(frame.colorSpace.fullRange, 'color range');
frame.close();
frame = new VideoFrame(data, {...vfInit, format: 'BGRA'});
assert_equals(frame.format, 'BGRA', 'plane format');
assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries');
assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer');
assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix');
assert_true(frame.colorSpace.fullRange, 'color range');
frame.close();
frame = new VideoFrame(data, {...vfInit, format: 'BGRX'});
assert_equals(frame.format, 'BGRX', 'plane format');
assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries');
assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer');
assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix');
assert_true(frame.colorSpace.fullRange, 'color range');
frame.close();
}, 'Test buffer constructed RGB VideoFrames');
test(t => {
let image = makeImageBitmap(32, 16);
let frame = new VideoFrame(image, {timestamp: 0});
assert_true(!!frame);
frame_copy = new VideoFrame(frame);
assert_equals(frame.format, frame_copy.format);
assert_equals(frame.timestamp, frame_copy.timestamp);
assert_equals(frame.codedWidth, frame_copy.codedWidth);
assert_equals(frame.codedHeight, frame_copy.codedHeight);
assert_equals(frame.displayWidth, frame_copy.displayWidth);
assert_equals(frame.displayHeight, frame_copy.displayHeight);
assert_equals(frame.duration, frame_copy.duration);
frame_copy.close();
frame_copy = new VideoFrame(frame, {duration: 1234});
assert_equals(frame.timestamp, frame_copy.timestamp);
assert_equals(frame_copy.duration, 1234);
frame_copy.close();
frame_copy = new VideoFrame(frame, {timestamp: 1234, duration: 456});
assert_equals(frame_copy.timestamp, 1234);
assert_equals(frame_copy.duration, 456);
frame_copy.close();
frame.close();
}, 'Test VideoFrame constructed VideoFrame');
test(t => {
let canvas = makeOffscreenCanvas(16, 16);
let frame = new VideoFrame(canvas, {timestamp: 0});
assert_equals(frame.displayWidth, 16);
assert_equals(frame.displayHeight, 16);
frame.close();
}, 'Test we can construct a VideoFrame from an offscreen canvas.');
test(t => {
let fmt = 'I420';
let vfInit = {
format: fmt,
timestamp: 1234,
codedWidth: 4,
codedHeight: 2,
visibleRect: {x: 0, y: 0, width: 1, height: 1},
};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
8, 7, 6, 5, 4, 3, 2, 1, // a
]);
let frame = new VideoFrame(data, vfInit);
assert_equals(frame.format, fmt, 'format');
assert_equals(frame.visibleRect.x, 0, 'visibleRect.x');
assert_equals(frame.visibleRect.y, 0, 'visibleRect.y');
assert_equals(frame.visibleRect.width, 1, 'visibleRect.width');
assert_equals(frame.visibleRect.height, 1, 'visibleRect.height');
frame.close();
}, 'Test I420 VideoFrame with odd visible size');
test(t => {
let fmt = 'I420A';
let vfInit = {format: fmt, timestamp: 1234, codedWidth: 4, codedHeight: 2};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
8, 7, 6, 5, 4, 3, 2, 1, // a
]);
let frame = new VideoFrame(data, vfInit);
assert_equals(frame.format, fmt, 'plane format');
let alpha_frame_copy = new VideoFrame(frame, {alpha: 'keep'});
assert_equals(alpha_frame_copy.format, 'I420A', 'plane format');
let opaque_frame_copy = new VideoFrame(frame, {alpha: 'discard'});
assert_equals(opaque_frame_copy.format, 'I420', 'plane format');
frame.close();
alpha_frame_copy.close();
opaque_frame_copy.close();
}, 'Test I420A VideoFrame and alpha={keep,discard}');
test(t => {
let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2};
let data = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
]);
let frame = new VideoFrame(data, {...vfInit, format: 'RGBA'});
assert_equals(frame.format, 'RGBA', 'plane format');
let alpha_frame_copy = new VideoFrame(frame, {alpha: 'keep'});
assert_equals(alpha_frame_copy.format, 'RGBA', 'plane format');
let opaque_frame_copy = new VideoFrame(frame, {alpha: 'discard'});
assert_equals(opaque_frame_copy.format, 'RGBX', 'plane format');
alpha_frame_copy.close();
opaque_frame_copy.close();
frame.close();
frame = new VideoFrame(data, {...vfInit, format: 'BGRA'});
assert_equals(frame.format, 'BGRA', 'plane format');
alpha_frame_copy = new VideoFrame(frame, {alpha: 'keep'});
assert_equals(alpha_frame_copy.format, 'BGRA', 'plane format');
opaque_frame_copy = new VideoFrame(frame, {alpha: 'discard'});
assert_equals(opaque_frame_copy.format, 'BGRX', 'plane format');
alpha_frame_copy.close();
opaque_frame_copy.close();
frame.close();
}, 'Test RGBA, BGRA VideoFrames with alpha={keep,discard}');
test(t => {
let canvas = makeOffscreenCanvas(16, 16, {alpha: true});
let frame = new VideoFrame(canvas, {timestamp: 0});
assert_true(
frame.format == 'RGBA' || frame.format == 'BGRA' ||
frame.format == 'I420A',
'plane format should have alpha: ' + frame.format);
frame.close();
frame = new VideoFrame(canvas, {alpha: 'discard', timestamp: 0});
assert_true(
frame.format == 'RGBX' || frame.format == 'BGRX' ||
frame.format == 'I420',
'plane format should not have alpha: ' + frame.format);
frame.close();
}, 'Test a VideoFrame constructed from canvas can drop the alpha channel.');
function testAllYUVPixelFormats() {
const YUVs = [
{
init: {
format: 'I420',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 1 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
])
},
{
init: {
format: 'I420P10',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, // u
1, 2, 3, 4, // v
])
},
{
init: {
format: 'I420P12',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, // u
1, 2, 3, 4, // v
])
},
{
init: {
format: 'I420A',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 1 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, // u
1, 2, // v
1, 2, 3, 4, 5, 6, 7, 8, // a
])
},
{
init: {
format: 'I420AP10',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, // u
1, 2, 3, 4, // v
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a
])
},
{
init: {
format: 'I420AP12',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, // u
1, 2, 3, 4, // v
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a
])
},
{
init: {
format: 'I422',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 1 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, 3, 4, // u
1, 2, 3, 4, // v
])
},
{
init: {
format: 'I422P10',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, // u
1, 2, 3, 4, 5, 6, 7, 8, // v
])
},
{
init: {
format: 'I422P12',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, // u
1, 2, 3, 4, 5, 6, 7, 8, // v
])
},
{
init: {
format: 'I422A',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 1 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, 3, 4, // u
1, 2, 3, 4, // v
1, 2, 3, 4, 5, 6, 7, 8, // a
])
},
{
init: {
format: 'I422AP10',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, // u
1, 2, 3, 4, 5, 6, 7, 8, // v
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a
])
},
{
init: {
format: 'I422AP12',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, // u
1, 2, 3, 4, 5, 6, 7, 8, // v
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a
])
},
{
init: {
format: 'I444',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 1 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, 3, 4, 5, 6, 7, 8, // u
1, 2, 3, 4, 5, 6, 7, 8, // v
])
},
{
init: {
format: 'I444P10',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v
])
},
{
init: {
format: 'I444P12',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v
])
},
{
init: {
format: 'I444A',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 1 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, // y
1, 2, 3, 4, 5, 6, 7, 8, // u
1, 2, 3, 4, 5, 6, 7, 8, // v
1, 2, 3, 4, 5, 6, 7, 8, // a
])
},
{
init: {
format: 'I444AP10',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a
])
},
{
init: {
format: 'I444AP12',
timestamp: 1234,
codedWidth: 4,
codedHeight: 2
},
data: new Uint8Array([ // 2 byte per sample
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a
])
},
];
for (let yuv of YUVs) {
test(t => {
const frame = new VideoFrame(yuv.data, yuv.init);
frame.close();
}, `Test we can construct a ${yuv.init.format} VideoFrame`);
}
}
testAllYUVPixelFormats();