tor-browser

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

video-encoder-rescaling.https.any.js (8996B)


      1 // META: global=window,dedicatedworker
      2 // META: variant=?av1
      3 // META: variant=?vp8
      4 // META: variant=?vp9_p0
      5 // META: variant=?h264_avc
      6 // META: variant=?h264_annexb
      7 
      8 let BASECONFIG = null;
      9 promise_setup(async () => {
     10  const config = {
     11    '?av1': { codec: 'av01.0.04M.08' },
     12    '?vp8': { codec: 'vp8' },
     13    '?vp9_p0': { codec: 'vp09.00.10.08' },
     14    '?h264_avc': { codec: 'avc1.42001E', avc: { format: 'avc' } },
     15    '?h264_annexb': { codec: 'avc1.42001E', avc: { format: 'annexb' } },
     16  }[location.search];
     17  BASECONFIG = config;
     18  BASECONFIG.framerate = 30;
     19  BASECONFIG.bitrate = 3000000;
     20 });
     21 
     22 function scaleFrame(oneFrame, scaleSize) {
     23  const { w: width, h: height } = scaleSize;
     24  return new Promise(async (resolve, reject) => {
     25    let encodedResult;
     26    const encoder = new VideoEncoder({
     27      output: (chunk, metadata) => {
     28        encodedResult = { chunk, metadata };
     29      },
     30      error: (error) => {
     31        reject(error);
     32      },
     33    });
     34 
     35    const encoderConfig = {
     36      ...BASECONFIG,
     37      width,
     38      height,
     39    };
     40    encoder.configure(encoderConfig);
     41 
     42    encoder.encode(oneFrame);
     43    await encoder.flush();
     44 
     45    let decodedResult;
     46    const decoder = new VideoDecoder({
     47      output(frame) {
     48        decodedResult = frame;
     49      },
     50      error: (error) => {
     51        reject(error);
     52      },
     53    });
     54 
     55    decoder.configure(encodedResult.metadata.decoderConfig);
     56    decoder.decode(encodedResult.chunk);
     57    await decoder.flush();
     58 
     59    encoder.close();
     60    decoder.close();
     61 
     62    resolve(decodedResult);
     63  });
     64 }
     65 
     66 // This function determines which quadrant of a rectangle (width * height)
     67 // a point (x, y) falls into, and returns the corresponding color for that
     68 // quadrant. The rectangle is divided into four quadrants:
     69 //    <        w        >
     70 //  ^ +--------+--------+
     71 //    | (0, 0) | (1, 0) |
     72 //  h +--------+--------+
     73 //    | (0, 1) | (1, 1) |
     74 //  v +--------+--------+
     75 //
     76 // The colors array must contain at least four colors, each corresponding
     77 // to one of the quadrants:
     78 // - colors[0] : top-left (0, 0)
     79 // - colors[1] : top-right (1, 0)
     80 // - colors[2] : bottom-left (0, 1)
     81 // - colors[3] : bottom-right (1, 1)
     82 function getColor(x, y, width, height, colors, channel) {
     83  // Determine which quadrant (x, y) belongs to.
     84  const xIndex = x * 2 >= width ? 1 : 0;
     85  const yIndex = y * 2 >= height ? 1 : 0;
     86 
     87  const index = yIndex * 2 + xIndex;
     88  return colors[index][channel];
     89 }
     90 
     91 
     92 // All channel paramaters are arrays with the index being the channel
     93 // channelOffset: The offset for each channel in allocated data array.
     94 // channelWidth: The width of ecah channel in pixels
     95 // channelPlaneWidths: the width of the channel used to calculate the image's memory size.
     96 //   For interleaved data, only the first width is set to the width of the full data in bytes; see RGBX for an example.
     97 // channelStrides: The stride (in bytes) for each channel.
     98 // channelSteps: The step size in bytes to move from one pixel to the next horizontally within the same row
     99 // channelHeights: The height (in bytes) for each channel.
    100 // channelFourColor: The four colors encoded in the color format of the channels
    101 //
    102 function createImageData({ channelOffsets, channelWidths, channelPlaneWidths, channelStrides, channelSteps, channelHeights, channelFourColors }) {
    103  let memSize = 0;
    104  for (let chan = 0; chan < 3; chan++) {
    105    memSize += channelHeights[chan] * channelPlaneWidths[chan];
    106  }
    107  let data = new Uint8Array(memSize);
    108  for (let chan = 0; chan < 3; chan++) {
    109    for (let y = 0; y < channelHeights[chan]; y++) {
    110      for (let x = 0; x < channelWidths[chan]; x++) {
    111        data[channelOffsets[chan] + Math.floor(channelStrides[chan] * y) + Math.floor(channelSteps[chan] * x)] =
    112          getColor(x, y, channelWidths[chan], channelHeights[chan], channelFourColors, chan);
    113      }
    114    }
    115  }
    116  return data;
    117 }
    118 
    119 function testImageData(data, { channelOffsets, channelWidths, channelStrides, channelSteps, channelHeights, channelFourColors }) {
    120  let err = 0.;
    121  for (let chan = 0; chan < 3; chan++) {
    122    for (let y = 0; y < channelHeights[chan]; y++) {
    123      for (let x = 0; x < channelWidths[chan]; x++) {
    124        const curdata = data[channelOffsets[chan] + Math.floor(channelStrides[chan] *  y) + Math.floor(channelSteps[chan] * x)];
    125        const diff = curdata - getColor(x, y, channelWidths[chan], channelHeights[chan], channelFourColors, chan);
    126        err += Math.abs(diff);
    127      }
    128    }
    129  }
    130  return err / data.length / 3 / 255 * 4;
    131 }
    132 
    133 function rgb2yuv(rgb) {
    134  let y = rgb[0] * .299000 + rgb[1] * .587000 + rgb[2] * .114000
    135  let u = rgb[0] * -.168736 + rgb[1] * -.331264 + rgb[2] * .500000 + 128
    136  let v = rgb[0] * .500000 + rgb[1] * -.418688 + rgb[2] * -.081312 + 128
    137 
    138  y = Math.floor(y);
    139  u = Math.floor(u);
    140  v = Math.floor(v);
    141  return [
    142    y, u, v
    143  ]
    144 }
    145 
    146 function createChannelParameters(channelParams, x, y) {
    147  return {
    148    channelOffsets: channelParams.channelOffsetsConstant.map(
    149      (cont, index) => cont + channelParams.channelOffsetsSize[index] *
    150        x * y),
    151    channelWidths: channelParams.channelWidths.map((width) => Math.floor(width * x)),
    152    channelPlaneWidths: channelParams.channelPlaneWidths.map((width) => Math.floor(width * x)),
    153    channelStrides: channelParams.channelStrides.map((width) => Math.floor(width * x)),
    154    channelSteps: channelParams.channelSteps.map((height) => height),
    155    channelHeights: channelParams.channelHeights.map((height) => Math.floor(height * y)),
    156    channelFourColors: channelParams.channelFourColors
    157  }
    158 }
    159 
    160 
    161 const scaleTests = [
    162  { from: { w: 64, h: 64 }, to: { w: 128, h: 128 } }, // Factor 2
    163  { from: { w: 128, h: 128 }, to: { w: 128, h: 128 } }, // Factor 1
    164  { from: { w: 128, h: 128 }, to: { w: 64, h: 64 } }, // Factor 0.5
    165  { from: { w: 32, h: 32 }, to: { w: 96, h: 96 } }, // Factor 3
    166  { from: { w: 192, h: 192 }, to: { w: 64, h: 64 } }, // Factor 1/3
    167  { from: { w: 64, h: 32 }, to: { w: 128, h: 64 } }, // Factor 2
    168  { from: { w: 128, h: 256 }, to: { w: 64, h: 128 } }, // Factor 0.5
    169  { from: { w: 64, h: 64 }, to: { w: 128, h: 192 } }, // Factor 2 (w) and 3 (h)
    170  { from: { w: 128, h: 192 }, to: { w: 64, h: 64 } }, // Factor 0.5 (w) and 1/3 (h)
    171 ]
    172 const fourColors = [[255, 255, 0], [255, 0, 0], [0, 255, 0], [0, 0, 255]];
    173 const pixelFormatChannelParameters = [
    174  { // RGBX
    175    channelOffsetsConstant: [0, 1, 2],
    176    channelOffsetsSize: [0, 0, 0],
    177    channelPlaneWidths: [4, 0, 0], // only used for allocation
    178    channelWidths: [1, 1, 1],
    179    channelStrides: [4, 4, 4], // scaled by width
    180    channelSteps: [4, 4, 4],
    181    channelHeights: [1, 1, 1],  // scaled by height
    182    channelFourColors: fourColors.map((col) => col), // just clone,
    183    format: 'RGBX'
    184  },
    185  { // I420
    186    channelOffsetsConstant: [0, 0, 0],
    187    channelOffsetsSize: [0, 1, 1.25],
    188    channelPlaneWidths: [1, 0.5, 0.5],
    189    channelWidths: [1, 0.5, 0.5],
    190    channelStrides: [1, 0.5, 0.5], // scaled by width
    191    channelSteps: [1, 1, 1],
    192    channelHeights: [1, 0.5, 0.5],  // scaled by height
    193    channelFourColors: fourColors.map((col) => rgb2yuv(col)), // just clone
    194    format: 'I420'
    195  }
    196 ]
    197 
    198 for (const scale of scaleTests) {
    199  for (const channelParams of pixelFormatChannelParameters) {
    200    promise_test(async t => {
    201      const inputChannelParameters = createChannelParameters(channelParams, scale.from.w, scale.from.h);
    202      const inputData = createImageData(inputChannelParameters);
    203      const inputFrame = new VideoFrame(inputData, {
    204        timestamp: 0,
    205        displayWidth: scale.from.w,
    206        displayHeight: scale.from.h,
    207        codedWidth: scale.from.w,
    208        codedHeight: scale.from.h,
    209        format: channelParams.format
    210      });
    211      const outputFrame = await scaleFrame(inputFrame, scale.to);
    212      const outputArrayBuffer = new Uint8Array(outputFrame.allocationSize({ format: 'RGBX' }));
    213      const layout = await outputFrame.copyTo(outputArrayBuffer, { format: 'RGBX' });
    214      const stride = layout[0].stride
    215      const offset = layout[0].offset
    216 
    217      const error = testImageData(outputArrayBuffer, {
    218        channelOffsets: [offset, offset + 1, offset + 2],
    219        channelWidths: [
    220          outputFrame.visibleRect.width, outputFrame.visibleRect.width,
    221          outputFrame.visibleRect.width
    222        ],
    223        channelStrides: [stride, stride, stride],
    224        channelSteps: [4, 4, 4],
    225        channelHeights: [
    226          outputFrame.visibleRect.height, outputFrame.visibleRect.height,
    227          outputFrame.visibleRect.height
    228        ],
    229        channelFourColors: fourColors.map((col) => col)
    230      });
    231      outputFrame.close();
    232      assert_approx_equals(error, 0, 0.05, 'Scaled Image differs too much! Scaling from '
    233        + scale.from.w + ' x ' + scale.from.h
    234        + ' to '
    235        + scale.to.w + ' x ' + scale.to.h
    236        + ' Format:' +
    237        channelParams.format
    238      );
    239    }, 'Scaling Image in Encoding from '
    240    + scale.from.w + ' x ' + scale.from.h
    241    + ' to '
    242    + scale.to.w + ' x ' + scale.to.h
    243    + ' Format: ' +
    244    channelParams.format);
    245  }
    246 }