| 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(); | |
| } | |
| // Fills a visible region within `data` with the given YUV values. | |
| function fillYUV(data, codedWidth, codedHeight, visibleRect, v_y, v_u, v_v) { | |
| for (let y = 0; y < codedHeight; y++) { | |
| for (let x = 0; x < codedWidth; x++) { | |
| data[y * codedWidth + x] = | |
| (x >= visibleRect.x && y < visibleRect.y + visibleRect.height) ? v_y | |
| : 0; | |
| } | |
| } | |
| const uvWidth = codedWidth / 2; | |
| const uvHeight = codedHeight / 2; | |
| const uvPlaneSize = uvWidth * uvHeight; | |
| const uvOffset = codedWidth * codedHeight; | |
| const uvX = visibleRect.x / 2; | |
| const uvMaxY = (visibleRect.y + visibleRect.height) / 2; | |
| for (let y = 0; y < uvHeight; y++) { | |
| for (let x = 0; x < uvWidth; x++) { | |
| if (x >= uvX && y < uvMaxY) { | |
| data[uvOffset + y * uvWidth + x] = v_u; | |
| data[uvOffset + uvPlaneSize + y * uvWidth + x] = v_v; | |
| } else { | |
| data[uvOffset + y * uvWidth + x] = | |
| data[uvOffset + uvPlaneSize + y * uvWidth + x] = 128; | |
| } | |
| } | |
| } | |
| } |