From 8d1695f3080e01ed47ad4b2519995152d9d34237 Mon Sep 17 00:00:00 2001 From: Eugene Zemtsov Date: Tue, 3 Oct 2023 14:54:11 -0700 Subject: [PATCH] webcodecs: Transfer array buffer into VideoFrame ctor This should allow to avoid mem copy while constructing a VideoFrame from an ArrayBuffer. I2S: https://groups.google.com/a/chromium.org/g/blink-dev/c/yZS0DLCwl6w/m/5bm9hEoWAQAJ Bug: 1446808 Change-Id: Ifbc1fb1739a0bc19dc6db27943fff0cecae0156d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4529012 Reviewed-by: Dale Curtis Reviewed-by: Nate Chapin Commit-Queue: Eugene Zemtsov Cr-Commit-Position: refs/heads/main@{#1204865} --- webcodecs/transfering.https.any.js | 133 +++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 webcodecs/transfering.https.any.js diff --git a/webcodecs/transfering.https.any.js b/webcodecs/transfering.https.any.js new file mode 100644 index 00000000000000..b9487e589d833d --- /dev/null +++ b/webcodecs/transfering.https.any.js @@ -0,0 +1,133 @@ +// 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 => { + 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');