videoFrame-construction.any.js (34396B)
1 // META: global=window,dedicatedworker 2 // META: script=/webcodecs/utils.js 3 // META: script=/webcodecs/videoFrame-utils.js 4 5 test(t => { 6 let image = makeImageBitmap(32, 16); 7 let frame = new VideoFrame(image, {timestamp: 10}); 8 9 assert_equals(frame.timestamp, 10, 'timestamp'); 10 assert_equals(frame.duration, null, 'duration'); 11 assert_equals(frame.visibleRect.width, 32, 'visibleRect.width'); 12 assert_equals(frame.visibleRect.height, 16, 'visibleRect.height'); 13 assert_equals(frame.displayWidth, 32, 'displayWidth'); 14 assert_equals(frame.displayHeight, 16, 'displayHeight'); 15 16 frame.close(); 17 }, 'Test we can construct a VideoFrame.'); 18 19 test(t => { 20 let image = makeImageBitmap(32, 16); 21 let frame = new VideoFrame(image, {timestamp: 10, duration: 15}); 22 frame.close(); 23 24 assert_equals(frame.format, null, 'format') 25 assert_equals(frame.timestamp, 10, 'timestamp'); 26 assert_equals(frame.duration, 15, 'duration'); 27 assert_equals(frame.codedWidth, 0, 'codedWidth'); 28 assert_equals(frame.codedHeight, 0, 'codedHeight'); 29 assert_equals(frame.visibleRect, null, 'visibleRect'); 30 assert_equals(frame.displayWidth, 0, 'displayWidth'); 31 assert_equals(frame.displayHeight, 0, 'displayHeight'); 32 assert_equals(frame.colorSpace.primaries, null, 'colorSpace.primaries'); 33 assert_equals(frame.colorSpace.transfer, null, 'colorSpace.transfer'); 34 assert_equals(frame.colorSpace.matrix, null, 'colorSpace.matrix'); 35 assert_equals(frame.colorSpace.fullRange, null, 'colorSpace.fullRange'); 36 assert_true(isFrameClosed(frame)); 37 38 assert_throws_dom('InvalidStateError', () => frame.clone()); 39 }, 'Test closed VideoFrame.'); 40 41 test(t => { 42 let image = makeImageBitmap(32, 16); 43 let frame = new VideoFrame(image, {timestamp: -10}); 44 assert_equals(frame.timestamp, -10, 'timestamp'); 45 frame.close(); 46 }, 'Test we can construct a VideoFrame with a negative timestamp.'); 47 48 promise_test(async t => { 49 verifyTimestampRequiredToConstructFrame(makeImageBitmap(1, 1)); 50 }, 'Test that timestamp is required when constructing VideoFrame from ImageBitmap'); 51 52 promise_test(async t => { 53 verifyTimestampRequiredToConstructFrame(makeOffscreenCanvas(16, 16)); 54 }, 'Test that timestamp is required when constructing VideoFrame from OffscreenCanvas'); 55 56 promise_test(async t => { 57 let init = { 58 format: 'I420', 59 timestamp: 1234, 60 codedWidth: 4, 61 codedHeight: 2 62 }; 63 let data = new Uint8Array([ 64 1, 2, 3, 4, 5, 6, 7, 8, // y 65 1, 2, // u 66 1, 2, // v 67 ]); 68 let i420Frame = new VideoFrame(data, init); 69 let validFrame = new VideoFrame(i420Frame); 70 validFrame.close(); 71 }, 'Test that timestamp is NOT required when constructing VideoFrame from another VideoFrame'); 72 73 test(t => { 74 let image = makeImageBitmap(1, 1); 75 let frame = new VideoFrame(image, {timestamp: 10}); 76 77 assert_equals(frame.visibleRect.width, 1, 'visibleRect.width'); 78 assert_equals(frame.visibleRect.height, 1, 'visibleRect.height'); 79 assert_equals(frame.displayWidth, 1, 'displayWidth'); 80 assert_equals(frame.displayHeight, 1, 'displayHeight'); 81 82 frame.close(); 83 }, 'Test we can construct an odd-sized VideoFrame.'); 84 85 test(t => { 86 // Test only valid for Window contexts. 87 if (!('document' in self)) 88 return; 89 90 let video = document.createElement('video'); 91 92 assert_throws_dom('InvalidStateError', () => { 93 let frame = new VideoFrame(video, {timestamp: 10}); 94 }) 95 }, 'Test constructing w/ unusable image argument throws: HAVE_NOTHING <video>.'); 96 97 promise_test(async t => { 98 // Test only valid for Window contexts. 99 if (!('document' in self)) 100 return; 101 102 let video = document.createElement('video'); 103 video.src = 'vp9.mp4'; 104 video.autoplay = true; 105 video.controls = false; 106 video.muted = false; 107 document.body.appendChild(video); 108 109 const loadVideo = new Promise((resolve) => { 110 if (video.requestVideoFrameCallback) { 111 video.requestVideoFrameCallback(resolve); 112 return; 113 } 114 video.onloadeddata = () => resolve(); 115 }); 116 await loadVideo; 117 118 let frame = new VideoFrame(video, {timestamp: 10}); 119 assert_equals(frame.codedWidth, 320, 'codedWidth'); 120 assert_equals(frame.codedHeight, 240, 'codedHeight'); 121 assert_equals(frame.timestamp, 10, 'timestamp'); 122 frame.close(); 123 }, 'Test we can construct a VideoFrame from a <video>.'); 124 125 test(t => { 126 let canvas = new OffscreenCanvas(0, 0); 127 128 assert_throws_dom('InvalidStateError', () => { 129 let frame = new VideoFrame(canvas, {timestamp: 10}); 130 }) 131 }, 'Test constructing w/ unusable image argument throws: emtpy Canvas.'); 132 133 test(t => { 134 let image = makeImageBitmap(32, 16); 135 image.close(); 136 137 assert_throws_dom('InvalidStateError', () => { 138 let frame = new VideoFrame(image, {timestamp: 10}); 139 }) 140 }, 'Test constructing w/ unusable image argument throws: closed ImageBitmap.'); 141 142 test(t => { 143 let image = makeImageBitmap(32, 16); 144 let frame = new VideoFrame(image, {timestamp: 10}); 145 frame.close(); 146 147 assert_throws_dom('InvalidStateError', () => { 148 let newFrame = new VideoFrame(frame); 149 }) 150 }, 'Test constructing w/ unusable image argument throws: closed VideoFrame.'); 151 152 test(t => { 153 let init = { 154 format: 'I420', 155 timestamp: 1234, 156 codedWidth: 4, 157 codedHeight: 2 158 }; 159 let data = new Uint8Array([ 160 1, 2, 3, 4, 5, 6, 7, 8, // y 161 1, 2, // u 162 1, 2, // v 163 ]); 164 let i420Frame = new VideoFrame(data, init); 165 let image = makeImageBitmap(32, 16); 166 167 168 assert_throws_js( 169 TypeError, 170 () => new VideoFrame( 171 image, 172 {timestamp: 10, visibleRect: {x: -1, y: 0, width: 10, height: 10}}), 173 'negative visibleRect x'); 174 175 assert_throws_js( 176 TypeError, 177 () => new VideoFrame( 178 image, 179 {timestamp: 10, visibleRect: {x: 0, y: 0, width: -10, height: 10}}), 180 'negative visibleRect width'); 181 182 assert_throws_js( 183 TypeError, 184 () => new VideoFrame( 185 image, 186 {timestamp: 10, visibleRect: {x: 0, y: 0, width: 10, height: 0}}), 187 'zero visibleRect height'); 188 189 assert_throws_js( 190 TypeError, () => new VideoFrame(image, { 191 timestamp: 10, 192 visibleRect: {x: 0, y: Infinity, width: 10, height: 10} 193 }), 194 'non finite visibleRect y'); 195 196 assert_throws_js( 197 TypeError, () => new VideoFrame(image, { 198 timestamp: 10, 199 visibleRect: {x: 0, y: 0, width: 10, height: Infinity} 200 }), 201 'non finite visibleRect height'); 202 203 assert_throws_js( 204 TypeError, 205 () => new VideoFrame( 206 image, 207 {timestamp: 10, visibleRect: {x: 0, y: 0, width: 33, height: 17}}), 208 'visibleRect area exceeds coded size'); 209 210 assert_throws_js( 211 TypeError, 212 () => new VideoFrame( 213 image, 214 {timestamp: 10, visibleRect: {x: 2, y: 2, width: 32, height: 16}}), 215 'visibleRect outside coded size'); 216 217 assert_throws_js( 218 TypeError, 219 () => new VideoFrame(image, {timestamp: 10, displayHeight: 10}), 220 'displayHeight provided without displayWidth'); 221 222 assert_throws_js( 223 TypeError, () => new VideoFrame(image, {timestamp: 10, displayWidth: 10}), 224 'displayWidth provided without displayHeight'); 225 226 assert_throws_js( 227 TypeError, 228 () => new VideoFrame( 229 image, {timestamp: 10, displayWidth: 0, displayHeight: 10}), 230 'displayWidth is zero'); 231 232 assert_throws_js( 233 TypeError, 234 () => new VideoFrame( 235 image, {timestamp: 10, displayWidth: 10, displayHeight: 0}), 236 'displayHeight is zero'); 237 238 assert_throws_js( 239 TypeError, 240 () => new VideoFrame( 241 i420Frame, {visibleRect: {x: 1, y: 0, width: 2, height: 2}}), 242 'visibleRect x is not sample aligned'); 243 244 assert_throws_js( 245 TypeError, 246 () => new VideoFrame( 247 i420Frame, {visibleRect: {x: 0, y: 1, width: 2, height: 2}}), 248 'visibleRect y is not sample aligned'); 249 250 }, 'Test invalid CanvasImageSource constructed VideoFrames'); 251 252 test(t => { 253 let init = { 254 format: 'I420', 255 timestamp: 1234, 256 codedWidth: 4, 257 codedHeight: 2 258 }; 259 let data = new Uint8Array([ 260 1, 2, 3, 4, 5, 6, 7, 8, // y 261 1, 2, // u 262 1, 2, // v 263 ]); 264 let origFrame = new VideoFrame(data, init); 265 266 let cropLeftHalf = new VideoFrame( 267 origFrame, {visibleRect: {x: 0, y: 0, width: 2, height: 2}}); 268 assert_equals(cropLeftHalf.codedWidth, origFrame.codedWidth); 269 assert_equals(cropLeftHalf.codedHeight, origFrame.codedHeight); 270 assert_equals(cropLeftHalf.visibleRect.x, 0); 271 assert_equals(cropLeftHalf.visibleRect.y, 0); 272 assert_equals(cropLeftHalf.visibleRect.width, 2); 273 assert_equals(cropLeftHalf.visibleRect.height, 2); 274 assert_equals(cropLeftHalf.displayWidth, 2); 275 assert_equals(cropLeftHalf.displayHeight, 2); 276 }, 'Test visibleRect metadata override where source display size = visible size'); 277 278 test(t => { 279 let init = { 280 format: 'I420', 281 timestamp: 1234, 282 codedWidth: 4, 283 codedHeight: 2, 284 displayWidth: 8, 285 displayHeight: 2 286 }; 287 let data = new Uint8Array([ 288 1, 2, 3, 4, 5, 6, 7, 8, // y 289 1, 2, // u 290 1, 2, // v 291 ]); 292 let anamorphicFrame = new VideoFrame(data, init); 293 294 let cropRightFrame = new VideoFrame( 295 anamorphicFrame, {visibleRect: {x: 2, y: 0, width: 2, height: 2}}); 296 assert_equals(cropRightFrame.codedWidth, anamorphicFrame.codedWidth); 297 assert_equals(cropRightFrame.codedHeight, anamorphicFrame.codedHeight); 298 assert_equals(cropRightFrame.visibleRect.x, 2); 299 assert_equals(cropRightFrame.visibleRect.y, 0); 300 assert_equals(cropRightFrame.visibleRect.width, 2); 301 assert_equals(cropRightFrame.visibleRect.height, 2); 302 assert_equals(cropRightFrame.displayWidth, 4, 'cropRightFrame.displayWidth'); 303 assert_equals(cropRightFrame.displayHeight, 2, 'cropRightFrame.displayHeight'); 304 }, 'Test visibleRect metadata override where source display width = 2 * visible width (anamorphic)'); 305 306 test(t => { 307 let init = { 308 format: 'I420', 309 timestamp: 1234, 310 codedWidth: 4, 311 codedHeight: 2, 312 displayWidth: 8, 313 displayHeight: 4 314 }; 315 let data = new Uint8Array([ 316 1, 2, 3, 4, 5, 6, 7, 8, // y 317 1, 2, // u 318 1, 2, // v 319 ]); 320 let scaledFrame = new VideoFrame(data, init); 321 322 let cropRightFrame = new VideoFrame( 323 scaledFrame, {visibleRect: {x: 2, y: 0, width: 2, height: 2}}); 324 assert_equals(cropRightFrame.codedWidth, scaledFrame.codedWidth); 325 assert_equals(cropRightFrame.codedHeight, scaledFrame.codedHeight); 326 assert_equals(cropRightFrame.visibleRect.x, 2); 327 assert_equals(cropRightFrame.visibleRect.y, 0); 328 assert_equals(cropRightFrame.visibleRect.width, 2); 329 assert_equals(cropRightFrame.visibleRect.height, 2); 330 assert_equals(cropRightFrame.displayWidth, 4, 'cropRightFrame.displayWidth'); 331 assert_equals(cropRightFrame.displayHeight, 4, 'cropRightFrame.displayHeight'); 332 }, 'Test visibleRect metadata override where source display size = 2 * visible size for both width and height'); 333 334 test(t => { 335 let image = makeImageBitmap(32, 16); 336 337 let scaledFrame = new VideoFrame(image, { 338 visibleRect: {x: 0, y: 0, width: 2, height: 2}, 339 displayWidth: 10, 340 displayHeight: 20, 341 timestamp: 0 342 }); 343 assert_equals(scaledFrame.codedWidth, 32); 344 assert_equals(scaledFrame.codedHeight, 16); 345 assert_equals(scaledFrame.visibleRect.x, 0); 346 assert_equals(scaledFrame.visibleRect.y, 0); 347 assert_equals(scaledFrame.visibleRect.width, 2); 348 assert_equals(scaledFrame.visibleRect.height, 2); 349 assert_equals(scaledFrame.displayWidth, 10, 'scaledFrame.displayWidth'); 350 assert_equals(scaledFrame.displayHeight, 20, 'scaledFrame.displayHeight'); 351 }, 'Test visibleRect + display size metadata override'); 352 353 test(t => { 354 let image = makeImageBitmap(32, 16); 355 356 let scaledFrame = new VideoFrame(image, 357 { 358 displayWidth: 10, displayHeight: 20, 359 timestamp: 0 360 }); 361 assert_equals(scaledFrame.codedWidth, 32); 362 assert_equals(scaledFrame.codedHeight, 16); 363 assert_equals(scaledFrame.visibleRect.x, 0); 364 assert_equals(scaledFrame.visibleRect.y, 0); 365 assert_equals(scaledFrame.visibleRect.width, 32); 366 assert_equals(scaledFrame.visibleRect.height, 16); 367 assert_equals(scaledFrame.displayWidth, 10, 'scaledFrame.displayWidth'); 368 assert_equals(scaledFrame.displayHeight, 20, 'scaledFrame.displayHeight'); 369 }, 'Test display size metadata override'); 370 371 test(t => { 372 assert_throws_js( 373 TypeError, 374 () => new VideoFrame( 375 new Uint8Array(1), 376 {format: 'ABCD', timestamp: 1234, codedWidth: 4, codedHeight: 2}), 377 'invalid pixel format'); 378 379 assert_throws_js( 380 TypeError, 381 () => 382 new VideoFrame(new Uint32Array(1), {format: 'RGBA', timestamp: 1234}), 383 'missing coded size'); 384 385 function constructFrame(init) { 386 let data = new Uint8Array([ 387 1, 2, 3, 4, 5, 6, 7, 8, // y 388 1, 2, // u 389 1, 2, // v 390 ]); 391 return new VideoFrame(data, {...init, format: 'I420'}); 392 } 393 394 assert_throws_js( 395 TypeError, () => constructFrame({ 396 timestamp: 1234, 397 codedWidth: Math.pow(2, 32) - 1, 398 codedHeight: Math.pow(2, 32) - 1, 399 }), 400 'invalid coded size'); 401 assert_throws_js( 402 TypeError, 403 () => constructFrame({timestamp: 1234, codedWidth: 4, codedHeight: 0}), 404 'invalid coded height'); 405 assert_throws_js( 406 TypeError, 407 () => constructFrame({timestamp: 1234, codedWidth: 0, codedHeight: 4}), 408 'invalid coded width'); 409 assert_throws_js( 410 TypeError, () => constructFrame({ 411 timestamp: 1234, 412 codedWidth: 4, 413 codedHeight: 2, 414 visibleRect: {x: 100, y: 100, width: 1, height: 1} 415 }), 416 'invalid visible left/right'); 417 assert_throws_js( 418 TypeError, () => constructFrame({ 419 timestamp: 1234, 420 codedWidth: 4, 421 codedHeight: 2, 422 visibleRect: {x: 0, y: 0, width: 0, height: 2} 423 }), 424 'invalid visible width'); 425 assert_throws_js( 426 TypeError, () => constructFrame({ 427 timestamp: 1234, 428 codedWidth: 4, 429 codedHeight: 2, 430 visibleRect: {x: 0, y: 0, width: 2, height: 0} 431 }), 432 'invalid visible height'); 433 assert_throws_js( 434 TypeError, () => constructFrame({ 435 timestamp: 1234, 436 codedWidth: 4, 437 codedHeight: 2, 438 visibleRect: {x: 0, y: 0, width: -100, height: -100} 439 }), 440 'invalid negative visible size'); 441 assert_throws_js( 442 TypeError, () => constructFrame({ 443 timestamp: 1234, 444 codedWidth: 4, 445 codedHeight: 2, 446 displayWidth: Math.pow(2, 32), 447 }), 448 'invalid display width'); 449 assert_throws_js( 450 TypeError, () => constructFrame({ 451 timestamp: 1234, 452 codedWidth: 4, 453 codedHeight: 2, 454 displayWidth: Math.pow(2, 32) - 1, 455 displayHeight: Math.pow(2, 32) 456 }), 457 'invalid display height'); 458 }, 'Test invalid buffer constructed VideoFrames'); 459 460 test(t => { 461 testBufferConstructedI420Frame('Uint8Array(ArrayBuffer)'); 462 }, 'Test Uint8Array(ArrayBuffer) constructed I420 VideoFrame'); 463 464 test(t => { 465 testBufferConstructedI420Frame('ArrayBuffer'); 466 }, 'Test ArrayBuffer constructed I420 VideoFrame'); 467 468 test(t => { 469 let fmt = 'I420'; 470 let vfInit = { 471 format: fmt, 472 timestamp: 1234, 473 codedWidth: 4, 474 codedHeight: 2, 475 colorSpace: { 476 primaries: 'smpte170m', 477 matrix: 'bt470bg', 478 }, 479 }; 480 let data = new Uint8Array([ 481 1, 2, 3, 4, 5, 6, 7, 8, // y 482 1, 2, // u 483 1, 2, // v 484 ]); 485 let frame = new VideoFrame(data, vfInit); 486 assert_equals(frame.colorSpace.primaries, 'smpte170m', 'color primaries'); 487 assert_true(frame.colorSpace.transfer == null, 'color transfer'); 488 assert_equals(frame.colorSpace.matrix, 'bt470bg', 'color matrix'); 489 assert_true(frame.colorSpace.fullRange == null, 'color range'); 490 }, 'Test planar constructed I420 VideoFrame with colorSpace'); 491 492 test(t => { 493 let fmt = 'I420'; 494 let vfInit = { 495 format: fmt, 496 timestamp: 1234, 497 codedWidth: 4, 498 codedHeight: 2, 499 colorSpace: { 500 primaries: null, 501 transfer: null, 502 matrix: null, 503 fullRange: null, 504 }, 505 }; 506 let data = new Uint8Array([ 507 1, 2, 3, 4, 5, 6, 7, 8, // y 508 1, 2, // u 509 1, 2, // v 510 ]); 511 let frame = new VideoFrame(data, vfInit); 512 assert_true(frame.colorSpace.primaries !== undefined, 'color primaries'); 513 assert_true(frame.colorSpace.transfer !== undefined, 'color transfer'); 514 assert_true(frame.colorSpace.matrix !== undefined, 'color matrix'); 515 assert_true(frame.colorSpace.fullRange !== undefined, 'color range'); 516 }, 'Test planar can construct I420 VideoFrame with null colorSpace values'); 517 518 test(t => { 519 let fmt = 'I420A'; 520 let vfInit = {format: fmt, timestamp: 1234, codedWidth: 4, codedHeight: 2}; 521 let data = new Uint8Array([ 522 1, 2, 3, 4, 5, 6, 7, 8, // y 523 1, 2, // u 524 1, 2, // v 525 8, 7, 6, 5, 4, 3, 2, 1, // a 526 ]); 527 let frame = new VideoFrame(data, vfInit); 528 assert_equals(frame.format, fmt, 'plane format'); 529 assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries'); 530 assert_equals(frame.colorSpace.transfer, 'bt709', 'color transfer'); 531 assert_equals(frame.colorSpace.matrix, 'bt709', 'color matrix'); 532 assert_false(frame.colorSpace.fullRange, 'color range'); 533 frame.close(); 534 535 // Most constraints are tested as part of I420 above. 536 537 let y = {offset: 0, stride: 4}; 538 let u = {offset: 8, stride: 2}; 539 let v = {offset: 10, stride: 2}; 540 let a = {offset: 12, stride: 4}; 541 542 assert_throws_js(TypeError, () => { 543 let a = {offset: 12, stride: 1}; 544 let frame = new VideoFrame(data, {...vfInit, layout: [y, u, v, a]}); 545 }, 'a stride too small'); 546 assert_throws_js(TypeError, () => { 547 let frame = new VideoFrame(data.slice(0, 12), vfInit); 548 }, 'data too small'); 549 }, 'Test buffer constructed I420+Alpha VideoFrame'); 550 551 test(t => { 552 let fmt = 'NV12'; 553 let vfInit = {format: fmt, timestamp: 1234, codedWidth: 4, codedHeight: 2}; 554 let data = new Uint8Array([ 555 1, 2, 3, 4, 5, 6, 7, 8, // y 556 1, 2, 3, 4, // uv 557 ]); 558 let frame = new VideoFrame(data, vfInit); 559 assert_equals(frame.format, fmt, 'plane format'); 560 assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries'); 561 assert_equals(frame.colorSpace.transfer, 'bt709', 'color transfer'); 562 assert_equals(frame.colorSpace.matrix, 'bt709', 'color matrix'); 563 assert_false(frame.colorSpace.fullRange, 'color range'); 564 frame.close(); 565 566 let y = {offset: 0, stride: 4}; 567 let uv = {offset: 8, stride: 4}; 568 569 assert_throws_js(TypeError, () => { 570 let y = {offset: 0, stride: 1}; 571 let frame = new VideoFrame(data, {...vfInit, layout: [y, uv]}); 572 }, 'y stride too small'); 573 assert_throws_js(TypeError, () => { 574 let uv = {offset: 8, stride: 1}; 575 let frame = new VideoFrame(data, {...vfInit, layout: [y, uv]}); 576 }, 'uv stride too small'); 577 assert_throws_js(TypeError, () => { 578 let frame = new VideoFrame(data.slice(0, 8), vfInit); 579 }, 'data too small'); 580 }, 'Test buffer constructed NV12 VideoFrame'); 581 582 test(t => { 583 let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2}; 584 let data = new Uint8Array([ 585 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 586 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 587 ]); 588 let frame = new VideoFrame(data, {...vfInit, format: 'RGBA'}); 589 assert_equals(frame.format, 'RGBA', 'plane format'); 590 assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries'); 591 assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer'); 592 assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix'); 593 assert_true(frame.colorSpace.fullRange, 'color range'); 594 frame.close(); 595 596 frame = new VideoFrame(data, {...vfInit, format: 'RGBX'}); 597 assert_equals(frame.format, 'RGBX', 'plane format'); 598 assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries'); 599 assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer'); 600 assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix'); 601 assert_true(frame.colorSpace.fullRange, 'color range'); 602 frame.close(); 603 604 frame = new VideoFrame(data, {...vfInit, format: 'BGRA'}); 605 assert_equals(frame.format, 'BGRA', 'plane format'); 606 assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries'); 607 assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer'); 608 assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix'); 609 assert_true(frame.colorSpace.fullRange, 'color range'); 610 frame.close(); 611 612 frame = new VideoFrame(data, {...vfInit, format: 'BGRX'}); 613 assert_equals(frame.format, 'BGRX', 'plane format'); 614 assert_equals(frame.colorSpace.primaries, 'bt709', 'color primaries'); 615 assert_equals(frame.colorSpace.transfer, 'iec61966-2-1', 'color transfer'); 616 assert_equals(frame.colorSpace.matrix, 'rgb', 'color matrix'); 617 assert_true(frame.colorSpace.fullRange, 'color range'); 618 frame.close(); 619 }, 'Test buffer constructed RGB VideoFrames'); 620 621 test(t => { 622 let image = makeImageBitmap(32, 16); 623 let frame = new VideoFrame(image, {timestamp: 0}); 624 assert_true(!!frame); 625 626 frame_copy = new VideoFrame(frame); 627 assert_equals(frame.format, frame_copy.format); 628 assert_equals(frame.timestamp, frame_copy.timestamp); 629 assert_equals(frame.codedWidth, frame_copy.codedWidth); 630 assert_equals(frame.codedHeight, frame_copy.codedHeight); 631 assert_equals(frame.displayWidth, frame_copy.displayWidth); 632 assert_equals(frame.displayHeight, frame_copy.displayHeight); 633 assert_equals(frame.duration, frame_copy.duration); 634 frame_copy.close(); 635 636 frame_copy = new VideoFrame(frame, {duration: 1234}); 637 assert_equals(frame.timestamp, frame_copy.timestamp); 638 assert_equals(frame_copy.duration, 1234); 639 frame_copy.close(); 640 641 frame_copy = new VideoFrame(frame, {timestamp: 1234, duration: 456}); 642 assert_equals(frame_copy.timestamp, 1234); 643 assert_equals(frame_copy.duration, 456); 644 frame_copy.close(); 645 646 frame.close(); 647 }, 'Test VideoFrame constructed VideoFrame'); 648 649 test(t => { 650 let canvas = makeOffscreenCanvas(16, 16); 651 let frame = new VideoFrame(canvas, {timestamp: 0}); 652 assert_equals(frame.displayWidth, 16); 653 assert_equals(frame.displayHeight, 16); 654 frame.close(); 655 }, 'Test we can construct a VideoFrame from an offscreen canvas.'); 656 657 test(t => { 658 let fmt = 'I420'; 659 let vfInit = { 660 format: fmt, 661 timestamp: 1234, 662 codedWidth: 4, 663 codedHeight: 2, 664 visibleRect: {x: 0, y: 0, width: 1, height: 1}, 665 }; 666 let data = new Uint8Array([ 667 1, 2, 3, 4, 5, 6, 7, 8, // y 668 1, 2, // u 669 1, 2, // v 670 8, 7, 6, 5, 4, 3, 2, 1, // a 671 ]); 672 let frame = new VideoFrame(data, vfInit); 673 assert_equals(frame.format, fmt, 'format'); 674 assert_equals(frame.visibleRect.x, 0, 'visibleRect.x'); 675 assert_equals(frame.visibleRect.y, 0, 'visibleRect.y'); 676 assert_equals(frame.visibleRect.width, 1, 'visibleRect.width'); 677 assert_equals(frame.visibleRect.height, 1, 'visibleRect.height'); 678 frame.close(); 679 }, 'Test I420 VideoFrame with odd visible size'); 680 681 test(t => { 682 let fmt = 'I420A'; 683 let vfInit = {format: fmt, timestamp: 1234, codedWidth: 4, codedHeight: 2}; 684 let data = new Uint8Array([ 685 1, 2, 3, 4, 5, 6, 7, 8, // y 686 1, 2, // u 687 1, 2, // v 688 8, 7, 6, 5, 4, 3, 2, 1, // a 689 ]); 690 let frame = new VideoFrame(data, vfInit); 691 assert_equals(frame.format, fmt, 'plane format'); 692 693 let alpha_frame_copy = new VideoFrame(frame, {alpha: 'keep'}); 694 assert_equals(alpha_frame_copy.format, 'I420A', 'plane format'); 695 696 let opaque_frame_copy = new VideoFrame(frame, {alpha: 'discard'}); 697 assert_equals(opaque_frame_copy.format, 'I420', 'plane format'); 698 699 frame.close(); 700 alpha_frame_copy.close(); 701 opaque_frame_copy.close(); 702 }, 'Test I420A VideoFrame and alpha={keep,discard}'); 703 704 test(t => { 705 let vfInit = {timestamp: 1234, codedWidth: 4, codedHeight: 2}; 706 let data = new Uint8Array([ 707 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 708 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 709 ]); 710 let frame = new VideoFrame(data, {...vfInit, format: 'RGBA'}); 711 assert_equals(frame.format, 'RGBA', 'plane format'); 712 713 let alpha_frame_copy = new VideoFrame(frame, {alpha: 'keep'}); 714 assert_equals(alpha_frame_copy.format, 'RGBA', 'plane format'); 715 716 let opaque_frame_copy = new VideoFrame(frame, {alpha: 'discard'}); 717 assert_equals(opaque_frame_copy.format, 'RGBX', 'plane format'); 718 719 alpha_frame_copy.close(); 720 opaque_frame_copy.close(); 721 frame.close(); 722 723 frame = new VideoFrame(data, {...vfInit, format: 'BGRA'}); 724 assert_equals(frame.format, 'BGRA', 'plane format'); 725 726 alpha_frame_copy = new VideoFrame(frame, {alpha: 'keep'}); 727 assert_equals(alpha_frame_copy.format, 'BGRA', 'plane format'); 728 729 opaque_frame_copy = new VideoFrame(frame, {alpha: 'discard'}); 730 assert_equals(opaque_frame_copy.format, 'BGRX', 'plane format'); 731 732 alpha_frame_copy.close(); 733 opaque_frame_copy.close(); 734 frame.close(); 735 }, 'Test RGBA, BGRA VideoFrames with alpha={keep,discard}'); 736 737 test(t => { 738 let canvas = makeOffscreenCanvas(16, 16, {alpha: true}); 739 let frame = new VideoFrame(canvas, {timestamp: 0}); 740 assert_true( 741 frame.format == 'RGBA' || frame.format == 'BGRA' || 742 frame.format == 'I420A', 743 'plane format should have alpha: ' + frame.format); 744 frame.close(); 745 746 frame = new VideoFrame(canvas, {alpha: 'discard', timestamp: 0}); 747 assert_true( 748 frame.format == 'RGBX' || frame.format == 'BGRX' || 749 frame.format == 'I420', 750 'plane format should not have alpha: ' + frame.format); 751 frame.close(); 752 }, 'Test a VideoFrame constructed from canvas can drop the alpha channel.'); 753 754 function testAllYUVPixelFormats() { 755 const YUVs = [ 756 { 757 init: { 758 format: 'I420', 759 timestamp: 1234, 760 codedWidth: 4, 761 codedHeight: 2 762 }, 763 data: new Uint8Array([ // 1 byte per sample 764 1, 2, 3, 4, 5, 6, 7, 8, // y 765 1, 2, // u 766 1, 2, // v 767 ]) 768 }, 769 { 770 init: { 771 format: 'I420P10', 772 timestamp: 1234, 773 codedWidth: 4, 774 codedHeight: 2 775 }, 776 data: new Uint8Array([ // 2 byte per sample 777 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 778 1, 2, 3, 4, // u 779 1, 2, 3, 4, // v 780 ]) 781 }, 782 { 783 init: { 784 format: 'I420P12', 785 timestamp: 1234, 786 codedWidth: 4, 787 codedHeight: 2 788 }, 789 data: new Uint8Array([ // 2 byte per sample 790 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 791 1, 2, 3, 4, // u 792 1, 2, 3, 4, // v 793 ]) 794 }, 795 { 796 init: { 797 format: 'I420A', 798 timestamp: 1234, 799 codedWidth: 4, 800 codedHeight: 2 801 }, 802 data: new Uint8Array([ // 1 byte per sample 803 1, 2, 3, 4, 5, 6, 7, 8, // y 804 1, 2, // u 805 1, 2, // v 806 1, 2, 3, 4, 5, 6, 7, 8, // a 807 ]) 808 }, 809 { 810 init: { 811 format: 'I420AP10', 812 timestamp: 1234, 813 codedWidth: 4, 814 codedHeight: 2 815 }, 816 data: new Uint8Array([ // 2 byte per sample 817 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 818 1, 2, 3, 4, // u 819 1, 2, 3, 4, // v 820 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a 821 ]) 822 }, 823 { 824 init: { 825 format: 'I420AP12', 826 timestamp: 1234, 827 codedWidth: 4, 828 codedHeight: 2 829 }, 830 data: new Uint8Array([ // 2 byte per sample 831 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 832 1, 2, 3, 4, // u 833 1, 2, 3, 4, // v 834 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a 835 ]) 836 }, 837 { 838 init: { 839 format: 'I422', 840 timestamp: 1234, 841 codedWidth: 4, 842 codedHeight: 2 843 }, 844 data: new Uint8Array([ // 1 byte per sample 845 1, 2, 3, 4, 5, 6, 7, 8, // y 846 1, 2, 3, 4, // u 847 1, 2, 3, 4, // v 848 ]) 849 }, 850 { 851 init: { 852 format: 'I422P10', 853 timestamp: 1234, 854 codedWidth: 4, 855 codedHeight: 2 856 }, 857 data: new Uint8Array([ // 2 byte per sample 858 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 859 1, 2, 3, 4, 5, 6, 7, 8, // u 860 1, 2, 3, 4, 5, 6, 7, 8, // v 861 ]) 862 }, 863 { 864 init: { 865 format: 'I422P12', 866 timestamp: 1234, 867 codedWidth: 4, 868 codedHeight: 2 869 }, 870 data: new Uint8Array([ // 2 byte per sample 871 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 872 1, 2, 3, 4, 5, 6, 7, 8, // u 873 1, 2, 3, 4, 5, 6, 7, 8, // v 874 ]) 875 }, 876 { 877 init: { 878 format: 'I422A', 879 timestamp: 1234, 880 codedWidth: 4, 881 codedHeight: 2 882 }, 883 data: new Uint8Array([ // 1 byte per sample 884 1, 2, 3, 4, 5, 6, 7, 8, // y 885 1, 2, 3, 4, // u 886 1, 2, 3, 4, // v 887 1, 2, 3, 4, 5, 6, 7, 8, // a 888 ]) 889 }, 890 { 891 init: { 892 format: 'I422AP10', 893 timestamp: 1234, 894 codedWidth: 4, 895 codedHeight: 2 896 }, 897 data: new Uint8Array([ // 2 byte per sample 898 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 899 1, 2, 3, 4, 5, 6, 7, 8, // u 900 1, 2, 3, 4, 5, 6, 7, 8, // v 901 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a 902 903 ]) 904 }, 905 { 906 init: { 907 format: 'I422AP12', 908 timestamp: 1234, 909 codedWidth: 4, 910 codedHeight: 2 911 }, 912 data: new Uint8Array([ // 2 byte per sample 913 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 914 1, 2, 3, 4, 5, 6, 7, 8, // u 915 1, 2, 3, 4, 5, 6, 7, 8, // v 916 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a 917 918 ]) 919 }, 920 { 921 init: { 922 format: 'I444', 923 timestamp: 1234, 924 codedWidth: 4, 925 codedHeight: 2 926 }, 927 data: new Uint8Array([ // 1 byte per sample 928 1, 2, 3, 4, 5, 6, 7, 8, // y 929 1, 2, 3, 4, 5, 6, 7, 8, // u 930 1, 2, 3, 4, 5, 6, 7, 8, // v 931 ]) 932 }, 933 { 934 init: { 935 format: 'I444P10', 936 timestamp: 1234, 937 codedWidth: 4, 938 codedHeight: 2 939 }, 940 data: new Uint8Array([ // 2 byte per sample 941 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 942 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u 943 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v 944 ]) 945 }, 946 { 947 init: { 948 format: 'I444P12', 949 timestamp: 1234, 950 codedWidth: 4, 951 codedHeight: 2 952 }, 953 data: new Uint8Array([ // 2 byte per sample 954 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 955 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u 956 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v 957 ]) 958 }, 959 { 960 init: { 961 format: 'I444A', 962 timestamp: 1234, 963 codedWidth: 4, 964 codedHeight: 2 965 }, 966 data: new Uint8Array([ // 1 byte per sample 967 1, 2, 3, 4, 5, 6, 7, 8, // y 968 1, 2, 3, 4, 5, 6, 7, 8, // u 969 1, 2, 3, 4, 5, 6, 7, 8, // v 970 1, 2, 3, 4, 5, 6, 7, 8, // a 971 ]) 972 }, 973 { 974 init: { 975 format: 'I444AP10', 976 timestamp: 1234, 977 codedWidth: 4, 978 codedHeight: 2 979 }, 980 data: new Uint8Array([ // 2 byte per sample 981 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 982 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u 983 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v 984 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a 985 ]) 986 }, 987 { 988 init: { 989 format: 'I444AP12', 990 timestamp: 1234, 991 codedWidth: 4, 992 codedHeight: 2 993 }, 994 data: new Uint8Array([ // 2 byte per sample 995 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // y 996 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // u 997 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // v 998 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, // a 999 ]) 1000 }, 1001 ]; 1002 1003 for (let yuv of YUVs) { 1004 test(t => { 1005 const frame = new VideoFrame(yuv.data, yuv.init); 1006 assert_equals(frame.format, yuv.init.format); 1007 assert_equals(frame.timestamp, yuv.init.timestamp); 1008 // User Agent may choose more optimal coded size allocations. 1009 assert_less_than_equal(yuv.init.codedWidth, frame.codedWidth); 1010 assert_less_than_equal(yuv.init.codedHeight, frame.codedHeight); 1011 frame.close(); 1012 }, `Test we can construct a ${yuv.init.format} VideoFrame`); 1013 } 1014 } 1015 testAllYUVPixelFormats();