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>