tor-browser

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

h264-profile-levels.https.html (4325B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTCPeerConnection H.264 profile levels</title>
      4 <meta name="timeout" content="long">
      5 <script src="../third_party/sdp/sdp.js"></script>
      6 <script src="simulcast.js"></script>
      7 <script src="../RTCPeerConnection-helper.js"></script>
      8 <script src="/resources/testharness.js"></script>
      9 <script src="/resources/testharnessreport.js"></script>
     10 <script>
     11 
     12 function mungeLevel(sdp, level) {
     13  level_hex = Math.round(level * 10).toString(16).padStart(2, '0');
     14  return {
     15    type: sdp.type,
     16    sdp: sdp.sdp.replace(/(profile-level-id=....)(..)/g,
     17                         "$1" + level_hex)
     18  }
     19 }
     20 
     21 // Numbers taken from
     22 // https://en.wikipedia.org/wiki/Advanced_Video_Coding#Levels
     23 let levelTable = {
     24  1: {mbs: 1485, fs: 99},
     25  1.1: {mbs: 3000, fs: 396},
     26  1.2: {mbs: 6000, fs: 396},
     27  1.3: {mbs: 11880, fs: 396},
     28  2: {mbs: 11880, fs: 396},
     29  2.1: {mbs: 19800, fs: 792},
     30  2.2: {mbs: 20250, fs: 1620},
     31  3: {mbs: 40500, fs: 1620},
     32  3.1: {mbs: 108000, fs: 3600},
     33  3.2: {mbs: 216000, fs: 5120},
     34  4: {mbs: 245760, fs: 8192},
     35  4.1: {mbs: 245760, fs: 8192},
     36  4.2: {mbs: 522240, fs: 8704},
     37  5: {mbs: 589824, fs: 22800},
     38  5.1: {mbs: 983040, fs: 36864},
     39  5.2: {mbs: 2073600, fs: 36864},
     40  6: {mbs: 4177920, fs: 139264},
     41  6.1: {mbs: 8355840, fs: 139264},
     42  6.2: {mbs: 16711680, fs: 139264},
     43 };
     44 
     45 function sizeFitsLevel(width, height, fps, level) {
     46  const frameSizeMacroblocks = width * height / 256;
     47  const macroblocksPerSecond = frameSizeMacroblocks * fps;
     48  assert_less_than_equal(frameSizeMacroblocks,
     49                         levelTable[level].fs, 'frame size');
     50  assert_less_than_equal(macroblocksPerSecond,
     51                         levelTable[level].mbs, 'macroblocks/second');
     52 }
     53 
     54 // Constant for now, may be variable later.
     55 const framesPerSecond = 30;
     56 
     57 for (let level of Object.keys(levelTable)) {
     58  promise_test(async t => {
     59    assert_implements('getCapabilities' in RTCRtpSender, 'RTCRtpSender.getCapabilities not supported');
     60    assert_implements(RTCRtpSender.getCapabilities('video').codecs.find(c => c.mimeType === 'video/H264'), 'H264 not supported');
     61 
     62    const pc1 = new RTCPeerConnection();
     63    t.add_cleanup(() => pc1.close());
     64    const pc2 = new RTCPeerConnection();
     65    t.add_cleanup(() => pc2.close());
     66    const v = document.createElement('video');
     67 
     68    // Generate the largest video we can get from the attached device.
     69    // This means platform inconsistency.
     70    // The fake video in Chrome WPT tests is 3840x2160.
     71    const stream = await navigator.mediaDevices.getUserMedia(
     72      {video: {width: 12800, height: 7200, frameRate: framesPerSecond}});
     73    t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
     74    const transceiver = pc1.addTransceiver(stream.getVideoTracks()[0], {
     75      streams: [stream],
     76    });
     77    preferCodec(transceiver, 'video/H264');
     78 
     79    exchangeIceCandidates(pc1, pc2);
     80    const trackEvent = new Promise(r => pc2.ontrack = r);
     81 
     82    const offer = await pc1.createOffer();
     83    await pc1.setLocalDescription(offer),
     84    await pc2.setRemoteDescription(offer);
     85    const answer = await pc2.createAnswer();
     86    await pc2.setLocalDescription(answer);
     87    await pc1.setRemoteDescription(mungeLevel(answer, level));
     88 
     89    v.srcObject = new MediaStream([(await trackEvent).track]);
     90    let metadataLoaded = new Promise((resolve) => {
     91      v.autoplay = true;
     92      v.id = stream.id
     93      v.addEventListener('loadedmetadata', () => {
     94        resolve();
     95      });
     96    });
     97    await metadataLoaded;
     98    // Ensure that H.264 is in fact used.
     99    const statsReport = await transceiver.sender.getStats();
    100    for (const stats of statsReport.values()) {
    101      if (stats.type === 'outbound-rtp') {
    102        const activeCodec = stats.codecId;
    103        const codecStats = statsReport.get(activeCodec);
    104        assert_implements_optional(codecStats.mimeType ==='video/H264',
    105                          'Level ' + level + ' H264 video is not supported');
    106      }
    107    }
    108    // TODO(hta): This will not catch situations where the initial size is
    109    // within the permitted bounds, but resolution or framerate changes to
    110    // outside the permitted bounds after a while. Should be addressed.
    111    sizeFitsLevel(v.videoWidth, v.videoHeight, framesPerSecond, level);
    112  }, 'Level ' + level + ' H264 video is appropriately constrained');
    113 
    114 }
    115 </script>