summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webcodecs/per-frame-qp-encoding.https.any.js
blob: 3207fa8356c83ef9566348f9246d0f6570068615 (plain)
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
// META: global=window,dedicatedworker
// META: script=/webcodecs/video-encoder-utils.js
// META: variant=?av1
// META: variant=?vp9_p0
// META: variant=?vp9_p2

function get_config() {
  const config = {
    '?av1': {codec: 'av01.0.04M.08'},
    '?vp8': {codec: 'vp8'},
    '?vp9_p0': {codec: 'vp09.00.10.08'},
    '?vp9_p2': {codec: 'vp09.02.10.10'},
    '?h264': {codec: 'avc1.42001E', avc: {format: 'annexb'}}
  }[location.search];
  config.width = 320;
  config.height = 200;
  config.bitrate = 1000000;
  config.bitrateMode = 'quantizer';
  config.framerate = 30;
  return config;
}

function get_qp_range() {
  switch (location.search) {
    case '?av1':
      return {min: 1, max: 63};
    case '?vp9_p0':
      return {min: 1, max: 63};
    case '?vp9_p2':
      return {min: 1, max: 63};
  }
  return null;
}

function set_qp(options, value) {
  switch (location.search) {
    case '?av1':
      options.av1 = {quantizer: value};
      return;
    case '?vp9_p0':
      options.vp9 = {quantizer: value};
      return;
    case '?vp9_p2':
      options.vp9 = {quantizer: value};
      return;
  }
}

async function per_frame_qp_test(t, encoder_config, qp_range, validate_result) {
  const w = encoder_config.width;
  const h = encoder_config.height;
  await checkEncoderSupport(t, encoder_config);

  let frames_to_encode = 24;
  let frames_decoded = 0;
  let frames_encoded = 0;
  let chunks = [];
  let corrupted_frames = [];

  const encoder_init = {
    output(chunk, metadata) {
      frames_encoded++;
      chunks.push(chunk);
    },
    error(e) {
      assert_unreached(e.message);
    }
  };

  let encoder = new VideoEncoder(encoder_init);
  encoder.configure(encoder_config);

  let qp = qp_range.min;
  for (let i = 0; i < frames_to_encode; i++) {
    let frame = createDottedFrame(w, h, i);
    if (qp < qp_range.max) {
      qp++;
    } else {
      qp = qp_range.min;
    }
    let encode_options = {keyFrame: false};
    set_qp(encode_options, qp);
    encoder.encode(frame, encode_options);
    frame.close();
  }
  await encoder.flush();

  let decoder = new VideoDecoder({
    output(frame) {
      frames_decoded++;
      // Check that we have intended number of dots and no more.
      // Completely black frame shouldn't pass the test.
      if (validate_result && !validateBlackDots(frame, frame.timestamp) ||
          validateBlackDots(frame, frame.timestamp + 1)) {
        corrupted_frames.push(frame.timestamp)
      }
      frame.close();
    },
    error(e) {
      assert_unreached(e.message);
    }
  });

  let decoder_config = {
    codec: encoder_config.codec,
    codedWidth: w,
    codedHeight: h,
  };
  decoder.configure(decoder_config);

  for (let chunk of chunks) {
    decoder.decode(chunk);
  }
  await decoder.flush();

  encoder.close();
  decoder.close();
  assert_equals(frames_encoded, frames_to_encode);
  assert_equals(chunks.length, frames_to_encode);
  assert_equals(frames_decoded, frames_to_encode);
  assert_equals(
      corrupted_frames.length, 0, `corrupted_frames: ${corrupted_frames}`);
}

promise_test(async t => {
  let config = get_config();
  let range = get_qp_range();
  return per_frame_qp_test(t, config, range, false);
}, 'Frame QP encoding, full range');

promise_test(async t => {
  let config = get_config();
  return per_frame_qp_test(t, config, {min: 1, max: 20}, true);
}, 'Frame QP encoding, good range with validation');