tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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();