| // 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(); |