| // META: global=window,dedicatedworker | |
| promise_test(async t => { | |
| let fmt = 'RGBA'; | |
| const rgb_plane = [ | |
| 0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF, 0x02, 0xD0, | |
| 0xBA, 0xDF, 0x03, 0xD0 | |
| ]; | |
| let data = new Uint8Array(rgb_plane); | |
| let unused_buffer = new ArrayBuffer(123); | |
| let init = { | |
| format: fmt, | |
| timestamp: 1234, | |
| codedWidth: 2, | |
| codedHeight: 2, | |
| visibleRect: {x: 0, y: 0, width: 2, height: 2}, | |
| transfer: [data.buffer, unused_buffer] | |
| }; | |
| assert_equals(data.length, 16, 'data.length'); | |
| assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength'); | |
| let frame = new VideoFrame(data, init); | |
| assert_equals(frame.format, fmt, 'format'); | |
| assert_equals(data.length, 0, 'data.length after detach'); | |
| assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach'); | |
| const options = { | |
| rect: {x: 0, y: 0, width: init.codedWidth, height: init.codedHeight} | |
| }; | |
| let size = frame.allocationSize(options); | |
| let output_data = new Uint8Array(size); | |
| let layout = await frame.copyTo(output_data, options); | |
| let expected_data = new Uint8Array(rgb_plane); | |
| assert_equals(expected_data.length, size, 'expected_data size'); | |
| for (let i = 0; i < size; i++) { | |
| assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`); | |
| } | |
| frame.close(); | |
| }, 'Test transfering ArrayBuffer to VideoFrame'); | |
| promise_test(async t => { | |
| let fmt = 'I420'; | |
| const i420_planes = [0xAA, 0xBB, 0xCC]; | |
| let data = new Uint8Array(i420_planes); | |
| let init = { | |
| format: fmt, | |
| timestamp: 1234, | |
| codedWidth: 1, | |
| codedHeight: 1, | |
| visibleRect: {x: 0, y: 0, width: 1, height: 1}, | |
| transfer: [data.buffer] | |
| }; | |
| let frame = new VideoFrame(data, init); | |
| assert_equals(data.length, 0, 'data.length after detach'); | |
| const options = { | |
| rect: {x: 0, y: 0, width: init.codedWidth, height: init.codedHeight} | |
| }; | |
| let size = frame.allocationSize(options); | |
| let output_data = new Uint8Array(size); | |
| let layout = await frame.copyTo(output_data, options); | |
| let expected_data = new Uint8Array(i420_planes); | |
| assert_equals(expected_data.length, size, 'expected_data size'); | |
| assert_equals(layout[0].stride, 1, 'layout[0].stride'); | |
| assert_equals(layout[0].offset, 0, 'layout[0].offset'); | |
| assert_equals(layout[1].stride, 1, 'layout[1].stride'); | |
| assert_equals(layout[1].offset, 1, 'layout[1].offset'); | |
| assert_equals(layout[2].stride, 1, 'layout[2].stride'); | |
| assert_equals(layout[2].offset, 2, 'layout[2].offset'); | |
| assert_equals(expected_data.length, size, 'expected_data size'); | |
| for (let i = 0; i < size; i++) { | |
| assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`); | |
| } | |
| frame.close(); | |
| }, 'Test transfering buffers to VideoFrame with uneven samples'); | |
| promise_test(async t => { | |
| const rgb_plane = [ | |
| 0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF, 0x02, 0xD0, | |
| 0xBA, 0xDF, 0x03, 0xD0 | |
| ]; | |
| let data = new Uint8Array(rgb_plane); | |
| let detached_buffer = new ArrayBuffer(123); | |
| // Detach `detached_buffer` | |
| structuredClone({x: detached_buffer}, {transfer: [detached_buffer]}); | |
| let init = { | |
| format: 'RGBA', | |
| timestamp: 1234, | |
| codedWidth: 2, | |
| codedHeight: 2, | |
| visibleRect: {x: 0, y: 0, width: 2, height: 2}, | |
| transfer: [data.buffer, detached_buffer] | |
| }; | |
| try { | |
| new VideoFrame(data, init); | |
| } catch (error) { | |
| assert_equals(error.name, 'DataCloneError', 'error.name'); | |
| } | |
| // `data.buffer` didn't get detached | |
| assert_equals(data.length, 16, 'data.length'); | |
| }, 'Test transfering detached buffer to VideoFrame'); | |
| promise_test(async t => { | |
| const rgb_plane = [ | |
| 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, | |
| 0xEE, 0xEE, 0xEE, 0xEE | |
| ]; | |
| const padding_size = 6; | |
| let arraybuffer = new ArrayBuffer(padding_size + 16 /* pixels */); | |
| let data = new Uint8Array(arraybuffer, padding_size); | |
| data.set(rgb_plane); | |
| let init = { | |
| format: 'RGBA', | |
| timestamp: 1234, | |
| codedWidth: 2, | |
| codedHeight: 2, | |
| visibleRect: {x: 0, y: 0, width: 2, height: 2}, | |
| transfer: [arraybuffer] | |
| }; | |
| let frame = new VideoFrame(data, init); | |
| assert_equals(data.length, 0, 'data.length after detach'); | |
| assert_equals(arraybuffer.byteLength, 0, 'arraybuffer after detach'); | |
| const options = { | |
| rect: {x: 0, y: 0, width: init.codedWidth, height: init.codedHeight} | |
| }; | |
| let size = frame.allocationSize(options); | |
| let output_data = new Uint8Array(size); | |
| let layout = await frame.copyTo(output_data, options); | |
| for (let i = 0; i < size; i++) { | |
| assert_equals(output_data[i], 0xEE, `output_data[${i}]`); | |
| } | |
| }, 'Test transfering view of an ArrayBuffer to VideoFrame'); | |
| promise_test(async t => { | |
| const rgb_plane = [ | |
| 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, | |
| 0xEE, 0xEE, 0xEE, 0xEE | |
| ]; | |
| const padding_size = 6; | |
| let arraybuffer = new ArrayBuffer(padding_size + 16 /* pixels */); | |
| let data = new Uint8Array(arraybuffer, padding_size); | |
| data.set(rgb_plane); | |
| let init = { | |
| format: 'RGBA', | |
| timestamp: 1234, | |
| codedWidth: 2, | |
| codedHeight: 2, | |
| visibleRect: {x: 0, y: 0, width: 2, height: 2}, | |
| transfer: [arraybuffer, arraybuffer] | |
| }; | |
| try { | |
| new VideoFrame(data, init); | |
| } catch (error) { | |
| assert_equals(error.name, 'DataCloneError', 'error.name'); | |
| } | |
| // `data.buffer` didn't get detached | |
| assert_equals(data.length, 16, 'data.length'); | |
| }, 'Test transfering same array buffer twice to VideoFrame'); | |
| promise_test(async t => { | |
| const bytes = [ 0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF ]; | |
| let data = new Uint8Array(bytes); | |
| let unused_buffer = new ArrayBuffer(123); | |
| let init = { | |
| type: 'key', | |
| timestamp: 0, | |
| data: data, | |
| transfer: [data.buffer, unused_buffer] | |
| }; | |
| assert_equals(data.length, 10, 'data.length'); | |
| assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength'); | |
| let chunk = new EncodedAudioChunk(init); | |
| assert_equals(data.length, 0, 'data.length after detach'); | |
| assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach'); | |
| let output_data = new Uint8Array(chunk.byteLength); | |
| chunk.copyTo(output_data); | |
| let expected_data = new Uint8Array(bytes); | |
| assert_equals(expected_data.length, chunk.byteLength, 'expected_data size'); | |
| for (let i = 0; i < chunk.byteLength; i++) { | |
| assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`); | |
| } | |
| }, 'Test transfering ArrayBuffer to EncodedAudioChunk'); | |
| promise_test(async t => { | |
| const bytes = [ 0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF ]; | |
| let data = new Uint8Array(bytes); | |
| let unused_buffer = new ArrayBuffer(123); | |
| let init = { | |
| type: 'key', | |
| timestamp: 0, | |
| data: data, | |
| transfer: [data.buffer, unused_buffer] | |
| }; | |
| assert_equals(data.length, 10, 'data.length'); | |
| assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength'); | |
| let chunk = new EncodedVideoChunk(init); | |
| assert_equals(data.length, 0, 'data.length after detach'); | |
| assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach'); | |
| let output_data = new Uint8Array(chunk.byteLength); | |
| chunk.copyTo(output_data); | |
| let expected_data = new Uint8Array(bytes); | |
| assert_equals(expected_data.length, chunk.byteLength, 'expected_data size'); | |
| for (let i = 0; i < chunk.byteLength; i++) { | |
| assert_equals(expected_data[i], output_data[i], `expected_data[${i}]`); | |
| } | |
| }, 'Test transfering ArrayBuffer to EncodedVideoChunk'); | |
| promise_test(async t => { | |
| const bytes = [0xBA, 0xDF, 0x00, 0xD0, 0xBA, 0xDF, 0x01, 0xD0, 0xBA, 0xDF]; | |
| let data = new Uint8Array(bytes); | |
| let unused_buffer = new ArrayBuffer(123); | |
| let init = { | |
| type: 'key', | |
| timestamp: 0, | |
| numberOfFrames: data.length, | |
| numberOfChannels: 1, | |
| sampleRate: 10000, | |
| format: 'u8', | |
| data: data, | |
| transfer: [data.buffer, unused_buffer] | |
| }; | |
| assert_equals(data.length, 10, 'data.length'); | |
| assert_equals(unused_buffer.byteLength, 123, 'unused_buffer.byteLength'); | |
| let audio_data = new AudioData(init); | |
| assert_equals(data.length, 0, 'data.length after detach'); | |
| assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach'); | |
| let readback_data = new Uint8Array(bytes.length); | |
| audio_data.copyTo(readback_data, {planeIndex: 0, format: 'u8'}); | |
| let expected_data = new Uint8Array(bytes); | |
| for (let i = 0; i < expected_data.length; i++) { | |
| assert_equals(expected_data[i], readback_data[i], `expected_data[${i}]`); | |
| } | |
| }, 'Test transfering ArrayBuffer to AudioData'); | |
| promise_test(async t => { | |
| let sample_rate = 48000; | |
| let total_duration_s = 1; | |
| let data_count = 10; | |
| let chunks = []; | |
| let encoder_init = { | |
| error: t.unreached_func('Encoder error'), | |
| output: (chunk, metadata) => { | |
| chunks.push(chunk); | |
| } | |
| }; | |
| let encoder = new AudioEncoder(encoder_init); | |
| let config = { | |
| codec: 'opus', | |
| sampleRate: sample_rate, | |
| numberOfChannels: 2, | |
| bitrate: 256000, // 256kbit | |
| }; | |
| encoder.configure(config); | |
| let timestamp_us = 0; | |
| const data_duration_s = total_duration_s / data_count; | |
| const frames = data_duration_s * config.sampleRate; | |
| for (let i = 0; i < data_count; i++) { | |
| let buffer = new Float32Array(frames * config.numberOfChannels); | |
| let data = new AudioData({ | |
| timestamp: timestamp_us, | |
| data: buffer, | |
| numberOfChannels: config.numberOfChannels, | |
| numberOfFrames: frames, | |
| sampleRate: config.sampleRate, | |
| format: 'f32-planar', | |
| transfer: [buffer.buffer] | |
| }); | |
| timestamp_us += data_duration_s * 1_000_000; | |
| assert_equals(buffer.length, 0, 'buffer.length after detach'); | |
| encoder.encode(data); | |
| } | |
| await encoder.flush(); | |
| encoder.close(); | |
| assert_greater_than(chunks.length, 0); | |
| }, 'Encoding from AudioData with transferred buffer'); | |
| promise_test(async t => { | |
| let unused_buffer = new ArrayBuffer(123); | |
| let support = await ImageDecoder.isTypeSupported('image/png'); | |
| assert_implements_optional( | |
| support, 'Optional codec image/png not supported.'); | |
| let buffer = await fetch('four-colors.png').then(response => { | |
| return response.arrayBuffer(); | |
| }); | |
| let decoder = new ImageDecoder( | |
| {data: buffer, type: 'image/png', transfer: [buffer, unused_buffer]}); | |
| assert_equals(buffer.byteLength, 0, 'buffer.byteLength after detach'); | |
| assert_equals(unused_buffer.byteLength, 0, 'unused_buffer after detach'); | |
| let result = await decoder.decode(); | |
| assert_equals(result.image.displayWidth, 320); | |
| assert_equals(result.image.displayHeight, 240); | |
| }, 'Test transfering ArrayBuffer to ImageDecoder.'); | |
| promise_test(async t => { | |
| // Not a valid PNG but that shouldn't matter. | |
| let arraybuffer = new ArrayBuffer(16); | |
| let data = new Uint8Array(arraybuffer); | |
| let init = { | |
| type: 'image/png', | |
| data: data, | |
| transfer: [arraybuffer, arraybuffer] | |
| }; | |
| assert_throws_dom('DataCloneError', () => new ImageDecoder(init)); | |
| // `data.buffer` didn't get detached | |
| assert_equals(data.length, 16, 'data.length'); | |
| }, 'Test transfering same array buffer twice to ImageDecoder'); | |
| promise_test(async t => { | |
| for (const bad_sample_rate of [0, -1]) { | |
| let buffer = new ArrayBuffer(32); | |
| let unused_buffer = new ArrayBuffer(16); | |
| assert_throws_js(TypeError, () => new AudioData({ | |
| format: 'f32', | |
| sampleRate: bad_sample_rate, | |
| numberOfFrames: 8, | |
| numberOfChannels: 1, | |
| timestamp: 0, | |
| data: buffer, | |
| transfer: [buffer, unused_buffer] | |
| }), | |
| `sampleRate ${bad_sample_rate} should throw TypeError`); | |
| assert_equals(buffer.byteLength, 32, 'buffer should not be detached'); | |
| assert_equals(unused_buffer.byteLength, 16, | |
| 'unused_buffer should not be detached'); | |
| // Verify the buffer is still usable and transferable on a subsequent valid | |
| // call. | |
| let audio_data = new AudioData({ | |
| format: 'f32', | |
| sampleRate: 48000, | |
| numberOfFrames: 8, | |
| numberOfChannels: 1, | |
| timestamp: 0, | |
| data: buffer, | |
| transfer: [buffer, unused_buffer] | |
| }); | |
| assert_equals(buffer.byteLength, 0, 'buffer detached after valid call'); | |
| assert_equals(unused_buffer.byteLength, 0, | |
| 'unused_buffer detached after valid call'); | |
| audio_data.close(); | |
| } | |
| }, 'Test throwing AudioData constructor does not detach transfer buffers'); | |
| promise_test(async t => { | |
| let unused_buffer = new ArrayBuffer(32); | |
| assert_throws_js(TypeError, () => new ImageDecoder({ | |
| data: new Uint8Array(0), | |
| type: 'image/png', | |
| transfer: [unused_buffer] | |
| })); | |
| assert_equals(unused_buffer.byteLength, 32, | |
| 'unused_buffer should not be detached'); | |
| }, 'Test throwing ImageDecoder constructor does not detach transfer buffers'); | |
| promise_test(async t => { | |
| let buffer = new ArrayBuffer(32); | |
| let unused_buffer = new ArrayBuffer(16); | |
| assert_throws_js(TypeError, () => new VideoFrame(buffer, { | |
| format: 'I420', | |
| codedWidth: 4, | |
| codedHeight: 4, | |
| displayWidth: 0, | |
| displayHeight: 4, | |
| timestamp: 0, | |
| transfer: [buffer, unused_buffer] | |
| })); | |
| assert_equals(buffer.byteLength, 32, 'buffer should not be detached'); | |
| assert_equals(unused_buffer.byteLength, 16, | |
| 'unused_buffer should not be detached'); | |
| }, 'Test throwing VideoFrame constructor does not detach transfer buffers'); |