tor-browser

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

test_peerConnection_simulcastOffer.html (7315B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <script type="application/javascript" src="pc.js"></script>
      5  <script type="application/javascript" src="parser_rtp.js"></script>
      6  <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
      7  <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
      8  <script type="application/javascript" src="simulcast.js"></script>
      9  <script type="application/javascript" src="stats.js"></script>
     10 </head>
     11 <body>
     12 <pre id="test">
     13 <script type="application/javascript">
     14  createHTML({
     15    bug: "1231507",
     16    title: "Basic video-only peer connection with Simulcast offer",
     17    visible: true
     18  });
     19 
     20  const isAndroid = navigator.userAgent.includes("Android");
     21  async function doTest(codec) {
     22    const recvCodecs = RTCRtpReceiver.getCapabilities("video")?.codecs;
     23    isnot(recvCodecs, null, "Expected recv capabilities");
     24    isnot(recvCodecs.length, 0, "Expected some recv codecs");
     25    if (!recvCodecs || !recvCodecs.length) {
     26      return;
     27    }
     28 
     29    const filteredRecvCodecs = recvCodecs.filter(recvCodec => {
     30      if (recvCodec.mimeType != codec.mimeType) {
     31        return false;
     32      }
     33      if (codec.sdpFmtpLineRegex && !recvCodec.sdpFmtpLine.match(codec.sdpFmtpLineRegex)) {
     34        return false;
     35      }
     36      return true;
     37    });
     38    is(
     39      filteredRecvCodecs.length,
     40      1,
     41      `Should match a single recv codec\nOriginal recv codecs:\n${JSON.stringify(
     42        recvCodecs,
     43        null,
     44        2
     45      )}\nFiltered recv codecs:\n${JSON.stringify(
     46        filteredRecvCodecs,
     47        null,
     48        2
     49      )}\nRequired codec: ${JSON.stringify(codec)}`
     50    );
     51    if (!filteredRecvCodecs.length) {
     52      return;
     53 
     54    }
     55    const [recvCodec] = filteredRecvCodecs;
     56 
     57    const offerer = new RTCPeerConnection();
     58    const answerer = new RTCPeerConnection();
     59 
     60    const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
     61    offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
     62    answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
     63 
     64    const metadataToBeLoaded = [];
     65    answerer.ontrack = (e) => {
     66      metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
     67    };
     68 
     69    // One send transceiver, that will be used to send both simulcast streams
     70    const emitter = new VideoFrameEmitter(
     71      CaptureStreamTestHelper2D.prototype.green,
     72      CaptureStreamTestHelper2D.prototype.red, 64, 64,
     73      {
     74        // With H264 on desktop we use fakeopenh264 whose encoder passes along
     75        // some metadata to be interpreted by its decoder. It encodes the
     76        // average color of all pixels and decodes a frame at the correct
     77        // resolution filled with that color. Thus, for H264, fill the entire
     78        // frame with the given color.
     79        fillEntireFrame: !isAndroid && codec.mimeType.match(/H264/i)
     80      }
     81    );
     82    const videoStream = emitter.stream();
     83    const sendEncodings = [
     84            { rid: '0', maxBitrate: 40000 },
     85            { rid: '1', maxBitrate: 40000, scaleResolutionDownBy: 2 }
     86          ];
     87    offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings});
     88    emitter.start();
     89 
     90    const sender = offerer.getSenders()[0];
     91 
     92    const offer = await offerer.createOffer();
     93 
     94    const mungedOffer = ridToMid(offer);
     95    info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
     96 
     97    await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
     98    await offerer.setLocalDescription(offer);
     99 
    100    const recvTransceivers = answerer.getTransceivers();
    101    const rids = recvTransceivers.map(t => t.mid);
    102    is(rids.length, 2, 'Should have 2 mids in offer');
    103    ok(rids[0] != '', 'First mid should be non-empty');
    104    ok(rids[1] != '', 'Second mid should be non-empty');
    105    info(`rids: ${JSON.stringify(rids)}`);
    106 
    107    for (const transceiver of recvTransceivers) {
    108      transceiver.setCodecPreferences([recvCodec]);
    109    }
    110 
    111    const answer = await answerer.createAnswer();
    112 
    113    const mungedAnswer = midToRid(answer);
    114    info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
    115    await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
    116    await answerer.setLocalDescription(answer);
    117 
    118    is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
    119    info('Waiting for 2 loadedmetadata events');
    120    const videoElems = await Promise.all(metadataToBeLoaded);
    121 
    122    const statsReady =
    123      Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
    124 
    125    if (!codec.mimeType.includes("H264") || !isAndroid) {
    126      const helper = new VideoStreamHelper();
    127      info('Waiting for first video element to start playing');
    128      await helper.checkVideoPlaying(videoElems[0]);
    129      info('Waiting for second video element to start playing');
    130      await helper.checkVideoPlaying(videoElems[1]);
    131    }
    132 
    133    is(videoElems[0].videoWidth, 64,
    134       "sink is same width as source, modulo our cropping algorithm");
    135    is(videoElems[0].videoHeight, 64,
    136       "sink is same height as source, modulo our cropping algorithm");
    137    is(videoElems[1].videoWidth, 32,
    138       "sink is 1/2 width of source, modulo our cropping algorithm");
    139    is(videoElems[1].videoHeight, 32,
    140       "sink is 1/2 height of source, modulo our cropping algorithm");
    141 
    142    await statsReady;
    143    const senderStats = await sender.getStats();
    144    checkSenderStats(senderStats, 2);
    145    checkExpectedFields(senderStats);
    146    pedanticChecks(senderStats);
    147 
    148    emitter.stop();
    149    videoStream.getVideoTracks()[0].stop();
    150    offerer.close();
    151    answerer.close();
    152  }
    153 
    154  runNetworkTest(async () => {
    155    await pushPrefs(
    156        // 180Kbps was determined empirically, set well-higher than
    157        // the 80Kbps+overhead needed for the two simulcast streams.
    158        // 100Kbps was apparently too low.
    159        ['media.peerconnection.video.min_bitrate_estimate', 180*1000],
    160        ["media.webrtc.simulcast.vp9.enabled", true],
    161        ["media.webrtc.simulcast.av1.enabled", true],
    162        ["media.webrtc.codec.video.av1.enabled", true],
    163        ["media.navigator.video.disable_h264_baseline", false],
    164      );
    165 
    166    if (isAndroid) {
    167      await pushPrefs(
    168          // [TODO] re-enable HW decoder after bug 1526207 is fixed.
    169          ["media.navigator.mediadatadecoder_vpx_enabled", false],
    170          ["media.webrtc.hw.h264.enabled", false],
    171        );
    172    }
    173 
    174    const codecs = [
    175      {mimeType: "video/VP8"},
    176      {mimeType: "video/VP9"},
    177      {mimeType: "video/AV1"},
    178      {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42e01f.*packetization-mode=1/},
    179      {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42001f.*packetization-mode=1/},
    180    ];
    181 
    182    if (!isAndroid) {
    183      // Android uses only MediaDataEncoder for H264, and it does not support
    184      // packetization-mode=0.
    185      codecs.push(
    186        {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42e01f.*asymmetry-allowed=1$/},
    187        {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42001f.*asymmetry-allowed=1$/},
    188      );
    189    }
    190 
    191    for (const codec of codecs) {
    192      info(`Testing codec ${codec.mimeType}`)
    193      await doTest(codec);
    194    }
    195  });
    196 </script>
    197 </pre>
    198 </body>
    199 </html>