| OLD | NEW |
| (Empty) |
| 1 /** | |
| 2 * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. | |
| 3 * | |
| 4 * Use of this source code is governed by a BSD-style license | |
| 5 * that can be found in the LICENSE file in the root of the source | |
| 6 * tree. An additional intellectual property rights grant can be found | |
| 7 * in the file PATENTS. All contributing project authors may | |
| 8 * be found in the AUTHORS file in the root of the source tree. | |
| 9 */ | |
| 10 | |
| 11 // LoopbackTest establish a one way loopback call between 2 peer connections | |
| 12 // while continuously monitoring bandwidth stats. The idea is to use this as | |
| 13 // a base for other future tests and to keep track of more than just bandwidth | |
| 14 // stats. | |
| 15 // | |
| 16 // Usage: | |
| 17 // var test = new LoopbackTest(stream, callDurationMs, | |
| 18 // forceTurn, pcConstraints, | |
| 19 // maxVideoBitrateKbps); | |
| 20 // test.run(onDone); | |
| 21 // function onDone() { | |
| 22 // test.getResults(); // return stats recorded during the loopback test. | |
| 23 // } | |
| 24 // | |
| 25 function LoopbackTest( | |
| 26 stream, | |
| 27 callDurationMs, | |
| 28 forceTurn, | |
| 29 pcConstraints, | |
| 30 maxVideoBitrateKbps) { | |
| 31 | |
| 32 var pc1StatTracker; | |
| 33 var pc2StatTracker; | |
| 34 | |
| 35 // In order to study effect of network (e.g. wifi) on peer connection one can | |
| 36 // establish a loopback call and force it to go via a turn server. This way | |
| 37 // the call won't switch to local addresses. That is achieved by filtering out | |
| 38 // all non-relay ice candidades on both peers. | |
| 39 function constrainTurnCandidates(pc) { | |
| 40 var origAddIceCandidate = pc.addIceCandidate; | |
| 41 pc.addIceCandidate = function (candidate, successCallback, | |
| 42 failureCallback) { | |
| 43 if (forceTurn && candidate.candidate.indexOf("typ relay ") == -1) { | |
| 44 trace("Dropping non-turn candidate: " + candidate.candidate); | |
| 45 successCallback(); | |
| 46 return; | |
| 47 } else { | |
| 48 origAddIceCandidate.call(this, candidate, successCallback, | |
| 49 failureCallback); | |
| 50 } | |
| 51 } | |
| 52 } | |
| 53 | |
| 54 // FEC makes it hard to study bwe estimation since there seems to be a spike | |
| 55 // when it is enabled and disabled. Disable it for now. FEC issue tracked on: | |
| 56 // https://code.google.com/p/webrtc/issues/detail?id=3050 | |
| 57 function constrainOfferToRemoveFec(pc) { | |
| 58 var origCreateOffer = pc.createOffer; | |
| 59 pc.createOffer = function (successCallback, failureCallback, options) { | |
| 60 function filteredSuccessCallback(desc) { | |
| 61 desc.sdp = desc.sdp.replace(/(m=video 1 [^\r]+)(116 117)(\r\n)/g, | |
| 62 '$1\r\n'); | |
| 63 desc.sdp = desc.sdp.replace(/a=rtpmap:116 red\/90000\r\n/g, ''); | |
| 64 desc.sdp = desc.sdp.replace(/a=rtpmap:117 ulpfec\/90000\r\n/g, ''); | |
| 65 successCallback(desc); | |
| 66 } | |
| 67 origCreateOffer.call(this, filteredSuccessCallback, failureCallback, | |
| 68 options); | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 // Constraint max video bitrate by modifying the SDP when creating an answer. | |
| 73 function constrainBitrateAnswer(pc) { | |
| 74 var origCreateAnswer = pc.createAnswer; | |
| 75 pc.createAnswer = function (successCallback, failureCallback, options) { | |
| 76 function filteredSuccessCallback(desc) { | |
| 77 if (maxVideoBitrateKbps) { | |
| 78 desc.sdp = desc.sdp.replace( | |
| 79 /a=mid:video\r\n/g, | |
| 80 'a=mid:video\r\nb=AS:' + maxVideoBitrateKbps + '\r\n'); | |
| 81 } | |
| 82 successCallback(desc); | |
| 83 } | |
| 84 origCreateAnswer.call(this, filteredSuccessCallback, failureCallback, | |
| 85 options); | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 // Run the actual LoopbackTest. | |
| 90 this.run = function(doneCallback) { | |
| 91 if (forceTurn) requestTurn(start, fail); | |
| 92 else start(); | |
| 93 | |
| 94 function start(turnServer) { | |
| 95 var pcConfig = forceTurn ? { iceServers: [turnServer] } : null; | |
| 96 console.log(pcConfig); | |
| 97 var pc1 = new RTCPeerConnection(pcConfig, pcConstraints); | |
| 98 constrainTurnCandidates(pc1); | |
| 99 constrainOfferToRemoveFec(pc1); | |
| 100 pc1StatTracker = new StatTracker(pc1, 50); | |
| 101 pc1StatTracker.recordStat("EstimatedSendBitrate", | |
| 102 "bweforvideo", "googAvailableSendBandwidth"); | |
| 103 pc1StatTracker.recordStat("TransmitBitrate", | |
| 104 "bweforvideo", "googTransmitBitrate"); | |
| 105 pc1StatTracker.recordStat("TargetEncodeBitrate", | |
| 106 "bweforvideo", "googTargetEncBitrate"); | |
| 107 pc1StatTracker.recordStat("ActualEncodedBitrate", | |
| 108 "bweforvideo", "googActualEncBitrate"); | |
| 109 | |
| 110 var pc2 = new RTCPeerConnection(pcConfig, pcConstraints); | |
| 111 constrainTurnCandidates(pc2); | |
| 112 constrainBitrateAnswer(pc2); | |
| 113 pc2StatTracker = new StatTracker(pc2, 50); | |
| 114 pc2StatTracker.recordStat("REMB", | |
| 115 "bweforvideo", "googAvailableReceiveBandwidth"); | |
| 116 | |
| 117 pc1.addStream(stream); | |
| 118 var call = new Call(pc1, pc2); | |
| 119 | |
| 120 call.start(); | |
| 121 setTimeout(function () { | |
| 122 call.stop(); | |
| 123 pc1StatTracker.stop(); | |
| 124 pc2StatTracker.stop(); | |
| 125 success(); | |
| 126 }, callDurationMs); | |
| 127 } | |
| 128 | |
| 129 function success() { | |
| 130 trace("Success"); | |
| 131 doneCallback(); | |
| 132 } | |
| 133 | |
| 134 function fail(msg) { | |
| 135 trace("Fail: " + msg); | |
| 136 doneCallback(); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 // Returns a google visualization datatable with the recorded samples during | |
| 141 // the loopback test. | |
| 142 this.getResults = function () { | |
| 143 return mergeDataTable(pc1StatTracker.dataTable(), | |
| 144 pc2StatTracker.dataTable()); | |
| 145 } | |
| 146 | |
| 147 // Helper class to establish and manage a call between 2 peer connections. | |
| 148 // Usage: | |
| 149 // var c = new Call(pc1, pc2); | |
| 150 // c.start(); | |
| 151 // c.stop(); | |
| 152 // | |
| 153 function Call(pc1, pc2) { | |
| 154 pc1.onicecandidate = applyIceCandidate.bind(pc2); | |
| 155 pc2.onicecandidate = applyIceCandidate.bind(pc1); | |
| 156 | |
| 157 function applyIceCandidate(e) { | |
| 158 if (e.candidate) { | |
| 159 this.addIceCandidate(new RTCIceCandidate(e.candidate), | |
| 160 onAddIceCandidateSuccess, | |
| 161 onAddIceCandidateError); | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 function onAddIceCandidateSuccess() {} | |
| 166 function onAddIceCandidateError(error) { | |
| 167 trace("Failed to add Ice Candidate: " + error.toString()); | |
| 168 } | |
| 169 | |
| 170 this.start = function() { | |
| 171 pc1.createOffer(gotDescription1, onCreateSessionDescriptionError); | |
| 172 | |
| 173 function onCreateSessionDescriptionError(error) { | |
| 174 trace('Failed to create session description: ' + error.toString()); | |
| 175 } | |
| 176 | |
| 177 function gotDescription1(desc){ | |
| 178 trace("Offer: " + desc.sdp); | |
| 179 pc1.setLocalDescription(desc); | |
| 180 pc2.setRemoteDescription(desc); | |
| 181 // Since the "remote" side has no media stream we need | |
| 182 // to pass in the right constraints in order for it to | |
| 183 // accept the incoming offer of audio and video. | |
| 184 pc2.createAnswer(gotDescription2, onCreateSessionDescriptionError); | |
| 185 } | |
| 186 | |
| 187 function gotDescription2(desc){ | |
| 188 trace("Answer: " + desc.sdp); | |
| 189 pc2.setLocalDescription(desc); | |
| 190 pc1.setRemoteDescription(desc); | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 this.stop = function() { | |
| 195 pc1.close(); | |
| 196 pc2.close(); | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 // Request a turn server. This uses the same servers as apprtc. | |
| 201 function requestTurn(successCallback, failureCallback) { | |
| 202 var currentDomain = document.domain; | |
| 203 if (currentDomain.search('localhost') === -1 && | |
| 204 currentDomain.search('webrtc.googlecode.com') === -1) { | |
| 205 failureCallback("Domain not authorized for turn server: " + | |
| 206 currentDomain); | |
| 207 return; | |
| 208 } | |
| 209 | |
| 210 // Get a turn server from computeengineondemand.appspot.com. | |
| 211 var turnUrl = 'https://computeengineondemand.appspot.com/' + | |
| 212 'turn?username=156547625762562&key=4080218913'; | |
| 213 var xmlhttp = new XMLHttpRequest(); | |
| 214 xmlhttp.onreadystatechange = onTurnResult; | |
| 215 xmlhttp.open('GET', turnUrl, true); | |
| 216 xmlhttp.send(); | |
| 217 | |
| 218 function onTurnResult() { | |
| 219 if (this.readyState !== 4) { | |
| 220 return; | |
| 221 } | |
| 222 | |
| 223 if (this.status === 200) { | |
| 224 var turnServer = JSON.parse(xmlhttp.responseText); | |
| 225 // Create turnUris using the polyfill (adapter.js). | |
| 226 turnServer.uris = turnServer.uris.filter( | |
| 227 function (e) { return e.search('transport=udp') != -1; } | |
| 228 ); | |
| 229 var iceServers = createIceServers(turnServer.uris, | |
| 230 turnServer.username, | |
| 231 turnServer.password); | |
| 232 if (iceServers !== null) { | |
| 233 successCallback(iceServers); | |
| 234 return; | |
| 235 } | |
| 236 } | |
| 237 failureCallback("Failed to get a turn server."); | |
| 238 } | |
| 239 } | |
| 240 } | |
| OLD | NEW |