videoFrame-copyTo.any.js (10917B)
1 // META: global=window,dedicatedworker 2 // META: script=/webcodecs/videoFrame-utils.js 3 4 function makeRGBA_2x2() { 5 const data = new Uint8Array([ 6 1,2,3,4, 5,6,7,8, 7 9,10,11,12, 13,14,15,16, 8 ]); 9 const init = { 10 format: 'RGBA', 11 timestamp: 0, 12 codedWidth: 2, 13 codedHeight: 2, 14 }; 15 return new VideoFrame(data, init); 16 } 17 18 const NV12_DATA = new Uint8Array([ 19 1, 2, 3, 4, // y 20 5, 6, 7, 8, 21 9, 10, 11, 12 // uv 22 ]); 23 24 function makeNV12_4x2() { 25 const init = { 26 format: 'NV12', 27 timestamp: 0, 28 codedWidth: 4, 29 codedHeight: 2, 30 }; 31 return new VideoFrame(NV12_DATA, init); 32 } 33 34 promise_test(async t => { 35 const frame = makeI420_4x2(); 36 frame.close(); 37 38 assert_throws_dom('InvalidStateError', () => frame.allocationSize(), 'allocationSize()'); 39 40 let data = new Uint8Array(12); 41 await promise_rejects_dom(t, 'InvalidStateError', frame.copyTo(data), 'copyTo()'); 42 }, 'Test closed frame.'); 43 44 promise_test(async t => { 45 const destination = new ArrayBuffer(I420_DATA.length); 46 await testI420_4x2_copyTo(destination); 47 }, 'Test copying I420 frame to a non-shared ArrayBuffer'); 48 49 promise_test(async t => { 50 const destination = new Uint8Array(I420_DATA.length); 51 await testI420_4x2_copyTo(destination); 52 }, 'Test copying I420 frame to a non-shared ArrayBufferView'); 53 54 promise_test(async t => { 55 const frame = makeRGBA_2x2(); 56 const expectedLayout = [ 57 {offset: 0, stride: 8}, 58 ]; 59 const expectedData = new Uint8Array([ 60 1,2,3,4, 5,6,7,8, 61 9,10,11,12, 13,14,15,16, 62 ]); 63 assert_equals(frame.allocationSize(), expectedData.length, 'allocationSize()'); 64 const data = new Uint8Array(expectedData.length); 65 const layout = await frame.copyTo(data); 66 assert_layout_equals(layout, expectedLayout); 67 assert_buffer_equals(data, expectedData); 68 }, 'Test RGBA frame.'); 69 70 promise_test(async t => { 71 const frame = makeNV12_4x2(); 72 const expectedLayout = [ 73 {offset: 0, stride: 4}, 74 {offset: 8, stride: 4}, 75 ]; 76 const expectedData = new Uint8Array([ 77 1,2,3,4, 78 5,6,7,8, 79 9,10,11,12 80 ]); 81 assert_equals(frame.allocationSize(), expectedData.length, 'allocationSize()'); 82 const data = new Uint8Array(expectedData.length); 83 const layout = await frame.copyTo(data); 84 assert_layout_equals(layout, expectedLayout); 85 assert_buffer_equals(data, expectedData); 86 }, 'Test NV12 frame.'); 87 88 promise_test(async t => { 89 const frame = makeI420_4x2(); 90 const data = new Uint8Array(11); 91 await promise_rejects_js(t, TypeError, frame.copyTo(data)); 92 }, 'Test undersized buffer.'); 93 94 promise_test(async t => { 95 const frame = makeI420_4x2(); 96 const options = { 97 layout: [{offset: 0, stride: 4}], 98 }; 99 assert_throws_js(TypeError, () => frame.allocationSize(options)); 100 const data = new Uint8Array(12); 101 await promise_rejects_js(t, TypeError, frame.copyTo(data, options)); 102 }, 'Test incorrect plane count.'); 103 104 promise_test(async t => { 105 const frame = makeI420_4x2(); 106 const options = { 107 layout: [ 108 {offset: 4, stride: 4}, 109 {offset: 0, stride: 2}, 110 {offset: 2, stride: 2}, 111 ], 112 }; 113 const expectedData = new Uint8Array([ 114 9, 10, // u 115 11, 12, // v 116 1, 2, 3, 4, // y 117 5, 6, 7, 8, 118 ]); 119 assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()'); 120 const data = new Uint8Array(expectedData.length); 121 const layout = await frame.copyTo(data, options); 122 assert_layout_equals(layout, options.layout); 123 assert_buffer_equals(data, expectedData); 124 }, 'Test I420 stride and offset work.'); 125 126 promise_test(async t => { 127 const frame = makeI420_4x2(); 128 const options = { 129 layout: [ 130 {offset: 9, stride: 5}, 131 {offset: 1, stride: 3}, 132 {offset: 5, stride: 3}, 133 ], 134 }; 135 const expectedData = new Uint8Array([ 136 0, 137 9, 10, 0, // u 138 0, 139 11, 12, 0, // v 140 0, 141 1, 2, 3, 4, 0, // y 142 5, 6, 7, 8, 0, 143 ]); 144 assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()'); 145 const data = new Uint8Array(expectedData.length); 146 const layout = await frame.copyTo(data, options); 147 assert_layout_equals(layout, options.layout); 148 assert_buffer_equals(data, expectedData); 149 }, 'Test I420 stride and offset with padding.'); 150 151 promise_test(async t => { 152 const init = { 153 format: 'I420A', 154 timestamp: 0, 155 codedWidth: 4, 156 codedHeight: 2, 157 }; 158 const buf = new Uint8Array([ 159 1, 2, 3, 4, // y 160 5, 6, 7, 8, 161 9, 10, // u 162 11, 12, // v 163 13, 14, 15, 16, // a 164 17, 18, 19, 20, 165 ]); 166 const frame = new VideoFrame(buf, init); 167 const options = { 168 layout: [ 169 {offset: 12, stride: 4}, 170 {offset: 8, stride: 2}, 171 {offset: 10, stride: 2}, 172 {offset: 0, stride: 4}, 173 ], 174 }; 175 const expectedData = new Uint8Array([ 176 13, 14, 15, 16, // a 177 17, 18, 19, 20, 178 9, 10, // u 179 11, 12, // v 180 1, 2, 3, 4, // y 181 5, 6, 7, 8, 182 ]); 183 assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()'); 184 const data = new Uint8Array(expectedData.length); 185 const layout = await frame.copyTo(data, options); 186 assert_layout_equals(layout, options.layout); 187 assert_buffer_equals(data, expectedData); 188 }, 'Test I420A stride and offset work.'); 189 190 promise_test(async t => { 191 const init = { 192 format: 'NV12', 193 timestamp: 0, 194 codedWidth: 4, 195 codedHeight: 2, 196 }; 197 const buf = new Uint8Array([ 198 1, 2, 3, 4, // y 199 5, 6, 7, 8, 200 9, 10, 11, 12 // uv 201 ]); 202 const frame = new VideoFrame(buf, init); 203 const options = { 204 layout: [ 205 {offset: 4, stride: 4}, 206 {offset: 0, stride: 4}, 207 ], 208 }; 209 const expectedData = new Uint8Array([ 210 9, 10, 11, 12, // uv 211 1, 2, 3, 4, // y 212 5, 6, 7, 8 213 ]); 214 assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()'); 215 const data = new Uint8Array(expectedData.length); 216 const layout = await frame.copyTo(data, options); 217 assert_layout_equals(layout, options.layout); 218 assert_buffer_equals(data, expectedData); 219 }, 'Test NV12 stride and offset work.'); 220 221 promise_test(async t => { 222 const frame = makeI420_4x2(); 223 const options = { 224 layout: [ 225 {offset: 0, stride: 1}, 226 {offset: 8, stride: 2}, 227 {offset: 10, stride: 2}, 228 ], 229 }; 230 assert_throws_js(TypeError, () => frame.allocationSize(options)); 231 const data = new Uint8Array(12); 232 await promise_rejects_js(t, TypeError, frame.copyTo(data, options)); 233 }, 'Test invalid stride.'); 234 235 promise_test(async t => { 236 const frame = makeI420_4x2(); 237 const options = { 238 layout: [ 239 {offset: 0, stride: 4}, 240 {offset: 8, stride: 2}, 241 {offset: 2 ** 32 - 2, stride: 2}, 242 ], 243 }; 244 assert_throws_js(TypeError, () => frame.allocationSize(options)); 245 const data = new Uint8Array(12); 246 await promise_rejects_js(t, TypeError, frame.copyTo(data, options)); 247 }, 'Test address overflow.'); 248 249 promise_test(async t => { 250 const frame = makeI420_4x2(); 251 const options = { 252 rect: frame.codedRect, 253 }; 254 const expectedLayout = [ 255 {offset: 0, stride: 4}, 256 {offset: 8, stride: 2}, 257 {offset: 10, stride: 2}, 258 ]; 259 const expectedData = new Uint8Array([ 260 1, 2, 3, 4, 5, 6, 7, 8, // y 261 9, 10, // u 262 11, 12 // v 263 ]); 264 assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()'); 265 const data = new Uint8Array(expectedData.length); 266 const layout = await frame.copyTo(data, options); 267 assert_layout_equals(layout, expectedLayout); 268 assert_buffer_equals(data, expectedData); 269 }, 'Test codedRect.'); 270 271 promise_test(async t => { 272 const frame = makeI420_4x2(); 273 const options = { 274 rect: {x: 0, y: 0, width: 4, height: 0}, 275 }; 276 assert_throws_js(TypeError, () => frame.allocationSize(options)); 277 const data = new Uint8Array(12); 278 await promise_rejects_js(t, TypeError, frame.copyTo(data, options)); 279 }, 'Test empty rect.'); 280 281 promise_test(async t => { 282 const frame = makeI420_4x2(); 283 const options = { 284 rect: {x: 2, y: 0, width: 2, height: 2}, 285 }; 286 const expectedLayout = [ 287 {offset: 0, stride: 2}, 288 {offset: 4, stride: 1}, 289 {offset: 5, stride: 1}, 290 ]; 291 const expectedData = new Uint8Array([ 292 3, 4, // y 293 7, 8, 294 10, // u 295 12 // v 296 ]); 297 assert_equals(frame.allocationSize(options), expectedData.length, 'allocationSize()'); 298 const data = new Uint8Array(expectedData.length); 299 const layout = await frame.copyTo(data, options); 300 assert_layout_equals(layout, expectedLayout); 301 assert_buffer_equals(data, expectedData); 302 }, 'Test left crop.'); 303 304 promise_test(async t => { 305 const frame = makeI420_4x2(); 306 const options = { 307 rect: {x: 0, y: 0, width: 4, height: 4}, 308 }; 309 assert_throws_js(TypeError, () => frame.allocationSize(options)); 310 const data = new Uint8Array(12); 311 await promise_rejects_js(t, TypeError, frame.copyTo(data, options)); 312 }, 'Test invalid rect.'); 313 314 promise_test(async t => { 315 let init = { 316 format: 'I420', 317 timestamp: 1234, 318 codedWidth: 8, 319 codedHeight: 16, 320 visibleRect: { 321 x: 2, 322 y: 2, 323 width: 4, 324 height: 8, 325 }, 326 colorSpace: { 327 primaries: 'smpte170m', 328 transfer: 'smpte170m', 329 matrix: 'smpte170m', 330 fullRange: false, 331 } 332 }; 333 334 // Define YUV values for BT.601 red. 335 const redY = 76; 336 const redU = 84; 337 const redV = 255; 338 339 const ySize = init.codedWidth * init.codedHeight; 340 const uvSize = ySize / 4; 341 let data = new Uint8Array(ySize + 2 * uvSize); 342 fillYUV(data, init.codedWidth, init.codedHeight, init.visibleRect, redY, redU, 343 redV); 344 345 let frame = new VideoFrame(data, init); 346 assert_equals(frame.codedWidth, init.visibleRect.width); 347 assert_equals(frame.codedHeight, init.visibleRect.height); 348 assert_equals(frame.visibleRect.x, 0); 349 assert_equals(frame.visibleRect.y, 0); 350 assert_equals(frame.visibleRect.width, init.visibleRect.width); 351 assert_equals(frame.visibleRect.height, init.visibleRect.height); 352 assert_equals(frame.displayWidth, init.visibleRect.width); 353 assert_equals(frame.displayHeight, init.visibleRect.height); 354 355 let options = {rect: frame.visibleRect}; 356 let copied_data = new Uint8Array(frame.allocationSize(options)); 357 await frame.copyTo(copied_data, options); 358 359 // We could write a bunch of code to carefully slice out the visible region 360 // of `data`, but it's simpler and works better with testharness.js operators 361 // to just create a fresh packed version for direct comparison. 362 let packed_data = new Uint8Array(frame.allocationSize(options)); 363 fillYUV(packed_data, frame.codedWidth, frame.codedHeight, frame.visibleRect, 364 redY, redU, redV); 365 366 assert_array_equals(copied_data, packed_data, `Copied frame data incorrect.`); 367 }, 'copyTo from byte data with non-default visibleRect');