summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webrtc/protocol/h264-profile-levels.https.html
blob: cb0b581c30b73d35f63775a1388b14a43112d169 (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
<!doctype html>
<meta charset=utf-8>
<title>RTCPeerConnection H.264 profile levels</title>
<meta name="timeout" content="long">
<script src="../third_party/sdp/sdp.js"></script>
<script src="simulcast.js"></script>
<script src="../RTCPeerConnection-helper.js"></script>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>

function mungeLevel(sdp, level) {
  level_hex = Math.round(level * 10).toString(16);
  return {
    type: sdp.type,
    sdp: sdp.sdp.replace(/(profile-level-id=....)(..)/g,
                         "$1" + level_hex)
  }
}

// Numbers taken from
// https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
let levelTable = {
  1: {mbs: 1485, fs: 99},
  1.1: {mbs: 3000, fs: 396},
  1.2: {mbs: 6000, fs: 396},
  1.3: {mbs: 11880, fs: 396},
  2: {mbs: 11880, fs: 396},
  2.1: {mbs: 19800, fs: 792},
  2.2: {mbs: 20250, fs: 1620},
  3: {mbs: 40500, fs: 1620},
  3.1: {mbs: 108000, fs: 3600},
  3.2: {mbs: 216000, fs: 5120},
  4: {mbs: 245760, fs: 8192},
  4.1: {mbs: 245760, fs: 8192},
  4.2: {mbs: 522240, fs: 8704},
  5: {mbs: 589824, fs: 22800},
  5.1: {mbs: 983040, fs: 36864},
  5.2: {mbs: 2073600, fs: 36864},
  6: {mbs: 4177920, fs: 139264},
  6.1: {mbs: 8355840, fs: 139264},
  6.2: {mbs: 16711680, fs: 139264},
};

function sizeFitsLevel(width, height, fps, level) {
  const frameSizeMacroblocks = width * height / 256;
  const macroblocksPerSecond = frameSizeMacroblocks * fps;
  assert_less_than_equal(frameSizeMacroblocks,
                         levelTable[level].fs, 'frame size');
  assert_less_than_equal(macroblocksPerSecond,
                         levelTable[level].mbs, 'macroblocks/second');
}

// Constant for now, may be variable later.
const framesPerSecond = 30;

for (let level of Object.keys(levelTable)) {
  promise_test(async t => {
    assert_implements('getCapabilities' in RTCRtpSender, 'RTCRtpSender.getCapabilities not supported');
    assert_implements(RTCRtpSender.getCapabilities('video').codecs.find(c => c.mimeType === 'video/H264'), 'H264 not supported');

    const pc1 = new RTCPeerConnection();
    t.add_cleanup(() => pc1.close());
    const pc2 = new RTCPeerConnection();
    t.add_cleanup(() => pc2.close());
    const v = document.createElement('video');

    // Generate the largest video we can get from the attached device.
    // This means platform inconsistency.
    // The fake video in Chrome WPT tests is 3840x2160.
    const stream = await navigator.mediaDevices.getUserMedia(
      {video: {width: 12800, height: 7200, frameRate: framesPerSecond}});
    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
    const transceiver = pc1.addTransceiver(stream.getVideoTracks()[0], {
      streams: [stream],
    });
    preferCodec(transceiver, 'video/H264');

    exchangeIceCandidates(pc1, pc2);
    const trackEvent = new Promise(r => pc2.ontrack = r);

    const offer = await pc1.createOffer();
    await pc1.setLocalDescription(offer),
    await pc2.setRemoteDescription(offer);
    const answer = await pc2.createAnswer();
    await pc2.setLocalDescription(answer);
    await pc1.setRemoteDescription(mungeLevel(answer, level));

    v.srcObject = new MediaStream([(await trackEvent).track]);
    let metadataLoaded = new Promise((resolve) => {
      v.autoplay = true;
      v.id = stream.id
      v.addEventListener('loadedmetadata', () => {
        resolve();
      });
    });
    await metadataLoaded;
    // Ensure that H.264 is in fact used.
    const statsReport = await transceiver.sender.getStats();
    for (const stats of statsReport.values()) {
      if (stats.type === 'outbound-rtp') {
        const activeCodec = stats.codecId;
        const codecStats = statsReport.get(activeCodec);
        assert_implements_optional(codecStats.mimeType ==='video/H264',
                          'Level ' + level + ' H264 video is not supported');
      }
    }
    // TODO(hta): This will not catch situations where the initial size is
    // within the permitted bounds, but resolution or framerate changes to
    // outside the permitted bounds after a while. Should be addressed.
    sizeFitsLevel(v.videoWidth, v.videoHeight, framesPerSecond, level);
  }, 'Level ' + level + ' H264 video is appropriately constrained');

}
</script>