File: video-encoder-rescaling.https.any.js

package info (click to toggle)
firefox 143.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,617,328 kB
  • sloc: cpp: 7,478,492; javascript: 6,417,157; ansic: 3,720,058; python: 1,396,372; xml: 627,523; asm: 438,677; java: 186,156; sh: 63,477; makefile: 19,171; objc: 13,059; perl: 12,983; yacc: 4,583; cs: 3,846; pascal: 3,405; lex: 1,720; ruby: 1,003; exp: 762; php: 436; lisp: 258; awk: 247; sql: 66; sed: 53; csh: 10
file content (246 lines) | stat: -rw-r--r-- 8,996 bytes parent folder | download | duplicates (12)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// META: global=window,dedicatedworker
// META: variant=?av1
// META: variant=?vp8
// META: variant=?vp9_p0
// META: variant=?h264_avc
// META: variant=?h264_annexb

let BASECONFIG = null;
promise_setup(async () => {
  const config = {
    '?av1': { codec: 'av01.0.04M.08' },
    '?vp8': { codec: 'vp8' },
    '?vp9_p0': { codec: 'vp09.00.10.08' },
    '?h264_avc': { codec: 'avc1.42001E', avc: { format: 'avc' } },
    '?h264_annexb': { codec: 'avc1.42001E', avc: { format: 'annexb' } },
  }[location.search];
  BASECONFIG = config;
  BASECONFIG.framerate = 30;
  BASECONFIG.bitrate = 3000000;
});

function scaleFrame(oneFrame, scaleSize) {
  const { w: width, h: height } = scaleSize;
  return new Promise(async (resolve, reject) => {
    let encodedResult;
    const encoder = new VideoEncoder({
      output: (chunk, metadata) => {
        encodedResult = { chunk, metadata };
      },
      error: (error) => {
        reject(error);
      },
    });

    const encoderConfig = {
      ...BASECONFIG,
      width,
      height,
    };
    encoder.configure(encoderConfig);

    encoder.encode(oneFrame);
    await encoder.flush();

    let decodedResult;
    const decoder = new VideoDecoder({
      output(frame) {
        decodedResult = frame;
      },
      error: (error) => {
        reject(error);
      },
    });

    decoder.configure(encodedResult.metadata.decoderConfig);
    decoder.decode(encodedResult.chunk);
    await decoder.flush();

    encoder.close();
    decoder.close();

    resolve(decodedResult);
  });
}

// This function determines which quadrant of a rectangle (width * height)
// a point (x, y) falls into, and returns the corresponding color for that
// quadrant. The rectangle is divided into four quadrants:
//    <        w        >
//  ^ +--------+--------+
//    | (0, 0) | (1, 0) |
//  h +--------+--------+
//    | (0, 1) | (1, 1) |
//  v +--------+--------+
//
// The colors array must contain at least four colors, each corresponding
// to one of the quadrants:
// - colors[0] : top-left (0, 0)
// - colors[1] : top-right (1, 0)
// - colors[2] : bottom-left (0, 1)
// - colors[3] : bottom-right (1, 1)
function getColor(x, y, width, height, colors, channel) {
  // Determine which quadrant (x, y) belongs to.
  const xIndex = x * 2 >= width ? 1 : 0;
  const yIndex = y * 2 >= height ? 1 : 0;

  const index = yIndex * 2 + xIndex;
  return colors[index][channel];
}


// All channel paramaters are arrays with the index being the channel
// channelOffset: The offset for each channel in allocated data array.
// channelWidth: The width of ecah channel in pixels
// channelPlaneWidths: the width of the channel used to calculate the image's memory size.
//   For interleaved data, only the first width is set to the width of the full data in bytes; see RGBX for an example.
// channelStrides: The stride (in bytes) for each channel.
// channelSteps: The step size in bytes to move from one pixel to the next horizontally within the same row
// channelHeights: The height (in bytes) for each channel.
// channelFourColor: The four colors encoded in the color format of the channels
//
function createImageData({ channelOffsets, channelWidths, channelPlaneWidths, channelStrides, channelSteps, channelHeights, channelFourColors }) {
  let memSize = 0;
  for (let chan = 0; chan < 3; chan++) {
    memSize += channelHeights[chan] * channelPlaneWidths[chan];
  }
  let data = new Uint8Array(memSize);
  for (let chan = 0; chan < 3; chan++) {
    for (let y = 0; y < channelHeights[chan]; y++) {
      for (let x = 0; x < channelWidths[chan]; x++) {
        data[channelOffsets[chan] + Math.floor(channelStrides[chan] * y) + Math.floor(channelSteps[chan] * x)] =
          getColor(x, y, channelWidths[chan], channelHeights[chan], channelFourColors, chan);
      }
    }
  }
  return data;
}

function testImageData(data, { channelOffsets, channelWidths, channelStrides, channelSteps, channelHeights, channelFourColors }) {
  let err = 0.;
  for (let chan = 0; chan < 3; chan++) {
    for (let y = 0; y < channelHeights[chan]; y++) {
      for (let x = 0; x < channelWidths[chan]; x++) {
        const curdata = data[channelOffsets[chan] + Math.floor(channelStrides[chan] *  y) + Math.floor(channelSteps[chan] * x)];
        const diff = curdata - getColor(x, y, channelWidths[chan], channelHeights[chan], channelFourColors, chan);
        err += Math.abs(diff);
      }
    }
  }
  return err / data.length / 3 / 255 * 4;
}

function rgb2yuv(rgb) {
  let y = rgb[0] * .299000 + rgb[1] * .587000 + rgb[2] * .114000
  let u = rgb[0] * -.168736 + rgb[1] * -.331264 + rgb[2] * .500000 + 128
  let v = rgb[0] * .500000 + rgb[1] * -.418688 + rgb[2] * -.081312 + 128

  y = Math.floor(y);
  u = Math.floor(u);
  v = Math.floor(v);
  return [
    y, u, v
  ]
}

function createChannelParameters(channelParams, x, y) {
  return {
    channelOffsets: channelParams.channelOffsetsConstant.map(
      (cont, index) => cont + channelParams.channelOffsetsSize[index] *
        x * y),
    channelWidths: channelParams.channelWidths.map((width) => Math.floor(width * x)),
    channelPlaneWidths: channelParams.channelPlaneWidths.map((width) => Math.floor(width * x)),
    channelStrides: channelParams.channelStrides.map((width) => Math.floor(width * x)),
    channelSteps: channelParams.channelSteps.map((height) => height),
    channelHeights: channelParams.channelHeights.map((height) => Math.floor(height * y)),
    channelFourColors: channelParams.channelFourColors
  }
}


const scaleTests = [
  { from: { w: 64, h: 64 }, to: { w: 128, h: 128 } }, // Factor 2
  { from: { w: 128, h: 128 }, to: { w: 128, h: 128 } }, // Factor 1
  { from: { w: 128, h: 128 }, to: { w: 64, h: 64 } }, // Factor 0.5
  { from: { w: 32, h: 32 }, to: { w: 96, h: 96 } }, // Factor 3
  { from: { w: 192, h: 192 }, to: { w: 64, h: 64 } }, // Factor 1/3
  { from: { w: 64, h: 32 }, to: { w: 128, h: 64 } }, // Factor 2
  { from: { w: 128, h: 256 }, to: { w: 64, h: 128 } }, // Factor 0.5
  { from: { w: 64, h: 64 }, to: { w: 128, h: 192 } }, // Factor 2 (w) and 3 (h)
  { from: { w: 128, h: 192 }, to: { w: 64, h: 64 } }, // Factor 0.5 (w) and 1/3 (h)
]
const fourColors = [[255, 255, 0], [255, 0, 0], [0, 255, 0], [0, 0, 255]];
const pixelFormatChannelParameters = [
  { // RGBX
    channelOffsetsConstant: [0, 1, 2],
    channelOffsetsSize: [0, 0, 0],
    channelPlaneWidths: [4, 0, 0], // only used for allocation
    channelWidths: [1, 1, 1],
    channelStrides: [4, 4, 4], // scaled by width
    channelSteps: [4, 4, 4],
    channelHeights: [1, 1, 1],  // scaled by height
    channelFourColors: fourColors.map((col) => col), // just clone,
    format: 'RGBX'
  },
  { // I420
    channelOffsetsConstant: [0, 0, 0],
    channelOffsetsSize: [0, 1, 1.25],
    channelPlaneWidths: [1, 0.5, 0.5],
    channelWidths: [1, 0.5, 0.5],
    channelStrides: [1, 0.5, 0.5], // scaled by width
    channelSteps: [1, 1, 1],
    channelHeights: [1, 0.5, 0.5],  // scaled by height
    channelFourColors: fourColors.map((col) => rgb2yuv(col)), // just clone
    format: 'I420'
  }
]

for (const scale of scaleTests) {
  for (const channelParams of pixelFormatChannelParameters) {
    promise_test(async t => {
      const inputChannelParameters = createChannelParameters(channelParams, scale.from.w, scale.from.h);
      const inputData = createImageData(inputChannelParameters);
      const inputFrame = new VideoFrame(inputData, {
        timestamp: 0,
        displayWidth: scale.from.w,
        displayHeight: scale.from.h,
        codedWidth: scale.from.w,
        codedHeight: scale.from.h,
        format: channelParams.format
      });
      const outputFrame = await scaleFrame(inputFrame, scale.to);
      const outputArrayBuffer = new Uint8Array(outputFrame.allocationSize({ format: 'RGBX' }));
      const layout = await outputFrame.copyTo(outputArrayBuffer, { format: 'RGBX' });
      const stride = layout[0].stride
      const offset = layout[0].offset

      const error = testImageData(outputArrayBuffer, {
        channelOffsets: [offset, offset + 1, offset + 2],
        channelWidths: [
          outputFrame.visibleRect.width, outputFrame.visibleRect.width,
          outputFrame.visibleRect.width
        ],
        channelStrides: [stride, stride, stride],
        channelSteps: [4, 4, 4],
        channelHeights: [
          outputFrame.visibleRect.height, outputFrame.visibleRect.height,
          outputFrame.visibleRect.height
        ],
        channelFourColors: fourColors.map((col) => col)
      });
      outputFrame.close();
      assert_approx_equals(error, 0, 0.05, 'Scaled Image differs too much! Scaling from '
        + scale.from.w + ' x ' + scale.from.h
        + ' to '
        + scale.to.w + ' x ' + scale.to.h
        + ' Format:' +
        channelParams.format
      );
    }, 'Scaling Image in Encoding from '
    + scale.from.w + ' x ' + scale.from.h
    + ' to '
    + scale.to.w + ' x ' + scale.to.h
    + ' Format: ' +
    channelParams.format);
  }
}