test_peerConnection_simulcastOffer_screenshare.html (6916B)
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: "1692873", 16 title: "Screensharing peer connection with Simulcast offer", 17 visible: true 18 }); 19 20 async function hasH264() { 21 const pc = new RTCPeerConnection(); 22 const offer = await pc.createOffer({offerToReceiveVideo: true}); 23 return offer.sdp.match(/a=rtpmap:[1-9][0-9]* H264/); 24 } 25 26 async function doTest(codec) { 27 const recvCodecs = RTCRtpReceiver.getCapabilities("video")?.codecs; 28 isnot(recvCodecs, null, "Expected recv capabilities"); 29 isnot(recvCodecs.length, 0, "Expected some recv codecs"); 30 if (!recvCodecs || !recvCodecs.length) { 31 return; 32 } 33 34 const filteredRecvCodecs = recvCodecs.filter(recvCodec => { 35 if (recvCodec.mimeType != codec.mimeType) { 36 return false; 37 } 38 if (codec.sdpFmtpLineRegex && !recvCodec.sdpFmtpLine.match(codec.sdpFmtpLineRegex)) { 39 return false; 40 } 41 return true; 42 }); 43 is( 44 filteredRecvCodecs.length, 45 1, 46 `Should match a single recv codec\nOriginal recv codecs:\n${JSON.stringify( 47 recvCodecs, 48 null, 49 2 50 )}\nFiltered recv codecs:\n${JSON.stringify( 51 filteredRecvCodecs, 52 null, 53 2 54 )}\nRequired codec: ${JSON.stringify(codec)}` 55 ); 56 if (!filteredRecvCodecs.length) { 57 return; 58 59 } 60 const [recvCodec] = filteredRecvCodecs; 61 62 const offerer = new RTCPeerConnection(); 63 const answerer = new RTCPeerConnection(); 64 65 const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed); 66 offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback()); 67 answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback()); 68 69 const metadataToBeLoaded = []; 70 answerer.ontrack = e => { 71 metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track)); 72 }; 73 74 // One send transceiver, that will be used to send both simulcast streams 75 SpecialPowers.wrap(document).notifyUserGestureActivation(); 76 const videoStream = await navigator.mediaDevices.getDisplayMedia({video: {width: {max: 640}}}); 77 const sendEncodings = [ 78 { rid: '0' }, 79 { rid: '1', maxBitrate: 100000, scaleResolutionDownBy: 2 }, 80 { rid: '2', maxBitrate: 40000, scaleResolutionDownBy: 2 } 81 ]; 82 offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings}); 83 84 const [sender] = offerer.getSenders(); 85 86 const offer = await offerer.createOffer(); 87 88 const mungedOffer = ridToMid(offer); 89 info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`); 90 91 await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer}); 92 await offerer.setLocalDescription(offer); 93 94 const recvTransceivers = answerer.getTransceivers(); 95 const rids = recvTransceivers.map(({mid}) => mid); 96 is(rids.length, 3, 'Should have 3 mids in offer'); 97 isnot(rids[0], '', 'First mid should be non-empty'); 98 isnot(rids[1], '', 'Second mid should be non-empty'); 99 isnot(rids[2], '', 'Third mid should be non-empty'); 100 101 for (const transceiver of recvTransceivers) { 102 transceiver.setCodecPreferences([recvCodec]); 103 } 104 105 const answer = await answerer.createAnswer(); 106 107 let mungedAnswer = midToRid(answer); 108 info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`); 109 await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer}); 110 await answerer.setLocalDescription(answer); 111 112 is(metadataToBeLoaded.length, 3, 'Offerer should have gotten 3 ontrack events'); 113 info('Waiting for 3 loadedmetadata events'); 114 const videoElems = await Promise.all(metadataToBeLoaded); 115 116 const statsReady = 117 Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]); 118 119 const {width} = videoStream.getVideoTracks()[0].getSettings(); 120 const {height} = videoStream.getVideoTracks()[0].getSettings(); 121 is(videoElems[0].videoWidth, width, 122 "sink is same width as source, modulo our cropping algorithm"); 123 is(videoElems[0].videoHeight, height, 124 "sink is same height as source, modulo our cropping algorithm"); 125 is(videoElems[1].videoWidth, Math.trunc(width / 2), 126 "sink is 1/2 width of source, modulo our cropping algorithm"); 127 is(videoElems[1].videoHeight, Math.trunc(height / 2), 128 "sink is 1/2 height of source, modulo our cropping algorithm"); 129 is(videoElems[2].videoWidth, Math.trunc(width / 2), 130 "sink is 1/2 width of source, modulo our cropping algorithm"); 131 is(videoElems[2].videoHeight, Math.trunc(height / 2), 132 "sink is 1/2 height of source, modulo our cropping algorithm"); 133 134 await statsReady; 135 const senderStats = await sender.getStats(); 136 checkSendCodecsMimeType(senderStats, codec.mimeType, recvCodec.sdpFmtpLine); 137 checkSenderStats(senderStats, 3); 138 checkExpectedFields(senderStats); 139 pedanticChecks(senderStats); 140 141 videoStream.getVideoTracks()[0].stop(); 142 offerer.close(); 143 answerer.close(); 144 for (const elem of videoElems) { 145 elem.remove(); 146 } 147 } 148 149 runNetworkTest(async () => { 150 await pushPrefs( 151 // 400Kbps was determined empirically, set well-higher than 152 // the 140Kbps+overhead needed for the two restricted simulcast streams. 153 ['media.peerconnection.video.min_bitrate_estimate', 400*1000], 154 ["media.navigator.permission.disabled", true], 155 ["media.peerconnection.video.lock_scaling", true], 156 ["media.webrtc.simulcast.vp9.enabled", true], 157 ["media.webrtc.simulcast.av1.enabled", true], 158 ["media.webrtc.codec.video.av1.enabled", true], 159 ["media.navigator.video.disable_h264_baseline", false], 160 ); 161 162 const codecs = [ 163 {mimeType: "video/VP8"}, 164 {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42e01f.*packetization-mode=1/}, 165 {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42e01f.*asymmetry-allowed=1$/}, 166 {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42001f.*packetization-mode=1/}, 167 {mimeType: "video/H264", sdpFmtpLineRegex: /profile-level-id=42001f.*asymmetry-allowed=1$/}, 168 {mimeType: "video/VP9"}, 169 {mimeType: "video/AV1"}, 170 ]; 171 172 for (const codec of codecs) { 173 info(`Testing codec ${codec.mimeType}`) 174 await doTest(codec); 175 } 176 }); 177 </script> 178 </pre> 179 </body> 180 </html>