const I420_DATA = new Uint8Array([ 1, 2, 3, 4, // y 5, 6, 7, 8, 9, 10, // u 11, 12, // v ]); function makeI420_4x2() { const init = { format: 'I420', timestamp: 0, codedWidth: 4, codedHeight: 2, }; return new VideoFrame(I420_DATA, init); } function testBufferConstructedI420Frame(bufferType) { let fmt = 'I420'; let vfInit = {format: fmt, timestamp: 1234, codedWidth: 4, codedHeight: 2}; let buffer; if (bufferType == 'SharedArrayBuffer' || bufferType == 'Uint8Array(SharedArrayBuffer)') { buffer = new SharedArrayBuffer(I420_DATA.length); } else { assert_true(bufferType == 'ArrayBuffer' || bufferType == 'Uint8Array(ArrayBuffer)'); buffer = new ArrayBuffer(I420_DATA.length); } let bufferView = new Uint8Array(buffer); bufferView.set(I420_DATA); let data = bufferType.startsWith('Uint8Array') ? bufferView : buffer; 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 u = {offset: 8, stride: 2}; let v = {offset: 10, stride: 2}; assert_throws_js(TypeError, () => { let y = {offset: 0, stride: 1}; let frame = new VideoFrame(data, {...vfInit, layout: [y, u, v]}); }, 'y stride too small'); assert_throws_js(TypeError, () => { let u = {offset: 8, stride: 1}; let frame = new VideoFrame(data, {...vfInit, layout: [y, u, v]}); }, 'u stride too small'); assert_throws_js(TypeError, () => { let v = {offset: 10, stride: 1}; let frame = new VideoFrame(data, {...vfInit, layout: [y, u, v]}); }, 'v stride too small'); assert_throws_js(TypeError, () => { let frame = new VideoFrame(data.slice(0, 8), vfInit); }, 'data too small'); } function assert_buffer_equals(actual, expected) { assert_true(expected instanceof Uint8Array, 'actual instanceof Uint8Array'); if (actual instanceof ArrayBuffer || (typeof(SharedArrayBuffer) != 'undefined' && actual instanceof SharedArrayBuffer)) { actual = new Uint8Array(actual); } else { assert_true(actual instanceof Uint8Array, 'expected instanceof Uint8Array, ArrayBuffer, or SharedArrayBuffer'); } assert_equals(actual.length, expected.length, 'buffer length'); for (let i = 0; i < actual.length; i++) { if (actual[i] != expected[i]) { assert_equals(actual[i], expected[i], 'buffer contents at index ' + i); } } } function assert_layout_equals(actual, expected) { assert_equals(actual.length, expected.length, 'layout planes'); for (let i = 0; i < actual.length; i++) { assert_object_equals(actual[i], expected[i], 'plane ' + i + ' layout'); } } async function testI420_4x2_copyTo(destination) { const frame = makeI420_4x2(); const expectedLayout = [ {offset: 0, stride: 4}, {offset: 8, stride: 2}, {offset: 10, stride: 2}, ]; const expectedData = new Uint8Array([ 1, 2, 3, 4, // y 5, 6, 7, 8, 9, 10, // u 11, 12 // v ]); assert_equals(frame.allocationSize(), expectedData.length, 'allocationSize()'); const layout = await frame.copyTo(destination); assert_layout_equals(layout, expectedLayout); assert_buffer_equals(destination, expectedData); } function verifyTimestampRequiredToConstructFrame(imageSource) { assert_throws_js( TypeError, () => new VideoFrame(imageSource), 'timestamp required to construct VideoFrame from this source'); let validFrame = new VideoFrame(imageSource, {timestamp: 0}); validFrame.close(); }