vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 1 | <html> |
| 2 | <head> |
| 3 | <title>Constraints and Statistics</title> |
hta@webrtc.org | db3f427 | 2013-03-05 15:23:40 +0000 | [diff] [blame] | 4 | <!-- Load the polyfill to switch-hit between Chrome and Firefox --> |
| 5 | <script src="../../base/adapter.js"></script> |
| 6 | |
hta@webrtc.org | 37bf584 | 2013-04-06 10:05:55 +0000 | [diff] [blame] | 7 | <style type="text/css"> |
| 8 | td { vertical-align: top; } |
| 9 | </style> |
| 10 | |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 11 | <script> |
| 12 | var mystream; |
| 13 | var pc1; |
| 14 | var pc2; |
hta@webrtc.org | 3ed599a | 2013-03-22 08:48:16 +0000 | [diff] [blame] | 15 | var bytesPrev = 0; |
| 16 | var timestampPrev = 0; |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 17 | |
| 18 | $ = function(id) { |
| 19 | return document.getElementById(id); |
| 20 | } |
| 21 | |
| 22 | function log(txt) { |
| 23 | console.log(txt); |
| 24 | } |
| 25 | |
| 26 | function openCamera() { |
| 27 | if (mystream) { |
| 28 | mystream.stop(); |
| 29 | } |
| 30 | navigator.webkitGetUserMedia(cameraConstraints(), gotStream, function() { |
| 31 | log("GetUserMedia failed"); |
| 32 | }); |
| 33 | } |
| 34 | |
| 35 | function gotStream(stream) { |
| 36 | log("GetUserMedia succeeded"); |
| 37 | mystream = stream; |
| 38 | $("local-video").src = webkitURL.createObjectURL(stream); |
| 39 | } |
| 40 | |
| 41 | function cameraConstraints() { |
| 42 | var constraints = {}; |
| 43 | constraints.audio = true; |
| 44 | constraints.video = { mandatory: {}, optional: [] }; |
| 45 | if ($("minwidth").value != "0") { |
| 46 | constraints.video.mandatory.minWidth = $("minwidth").value; |
| 47 | } |
| 48 | if ($("maxwidth").value != "0") { |
| 49 | constraints.video.mandatory.maxWidth = $("maxwidth").value; |
| 50 | } |
| 51 | if ($("minheight").value != "0") { |
| 52 | constraints.video.mandatory.minHeight = $("minheight").value; |
| 53 | } |
| 54 | if ($("maxheight").value != "0") { |
| 55 | constraints.video.mandatory.maxHeight = $("maxheight").value; |
| 56 | } |
| 57 | if ($("frameRate").value != "0") { |
| 58 | constraints.video.mandatory.minFrameRate = $("frameRate").value; |
| 59 | } |
| 60 | log('Camera constraints are ' + JSON.stringify(constraints)); |
| 61 | $("cameraConstraints").innerHTML = JSON.stringify(constraints, null, ' '); |
| 62 | return constraints; |
| 63 | } |
| 64 | |
| 65 | function streamConstraints() { |
| 66 | var constraints = { mandatory: {}, optional: [] }; |
| 67 | if ($("bandwidth").value != "0") { |
| 68 | constraints.optional[0] = { 'bandwidth' : $('bandwidth').value }; |
| 69 | } |
| 70 | log('Constraints are ' + JSON.stringify(constraints)); |
| 71 | $("addStreamConstraints").innerHTML = JSON.stringify(constraints, null, ' '); |
| 72 | return constraints; |
| 73 | } |
| 74 | |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 75 | function connect() { |
| 76 | pc1 = new webkitRTCPeerConnection(null); |
| 77 | pc2 = new webkitRTCPeerConnection(null); |
| 78 | pc1.addStream(mystream, streamConstraints()); |
| 79 | log('PC1 creating offer'); |
| 80 | pc1.onnegotiationeeded = function() { |
| 81 | log('Negotiation needed - PC1'); |
| 82 | } |
| 83 | pc2.onnegotiationeeded = function() { |
| 84 | log('Negotiation needed - PC2'); |
| 85 | } |
| 86 | pc1.onicecandidate = function(e) { |
| 87 | log('Candidate PC1'); |
| 88 | if (e.candidate) { |
| 89 | pc2.addIceCandidate(new RTCIceCandidate(e.candidate)); |
| 90 | } |
| 91 | } |
| 92 | pc2.onicecandidate = function(e) { |
| 93 | log('Candidate PC2'); |
| 94 | if (e.candidate) { |
| 95 | pc1.addIceCandidate(new RTCIceCandidate(e.candidate)); |
| 96 | } |
| 97 | } |
| 98 | pc2.onaddstream = function(e) { |
| 99 | log('PC2 got stream'); |
| 100 | $('remote-video').src = webkitURL.createObjectURL(e.stream); |
| 101 | log('Remote video is ' + $('remote-video').src); |
| 102 | } |
| 103 | pc1.createOffer(function(desc) { |
| 104 | log('PC1 offering'); |
| 105 | pc1.setLocalDescription(desc); |
| 106 | pc2.setRemoteDescription(desc); |
| 107 | pc2.createAnswer(function(desc2) { |
| 108 | log('PC2 answering'); |
| 109 | pc2.setLocalDescription(desc2); |
| 110 | pc1.setRemoteDescription(desc2); |
| 111 | }); |
| 112 | }); |
| 113 | } |
| 114 | |
hta@webrtc.org | cc39484 | 2013-08-21 17:00:54 +0000 | [diff] [blame] | 115 | // Augumentation of stats entries with utility functions. |
| 116 | // The augumented entry does what the stats entry does, but adds |
| 117 | // utility functions. |
| 118 | function AugumentedStatsResponse(response) { |
| 119 | this.response = response; |
| 120 | this.addressPairMap = []; |
| 121 | } |
| 122 | |
| 123 | AugumentedStatsResponse.prototype.collectAddressPairs = function(componentId) { |
| 124 | if (!this.addressPairMap[componentId]) { |
| 125 | this.addressPairMap[componentId] = []; |
| 126 | for (var i = 0; i < this.response.result().length; ++i) { |
| 127 | var res = this.response.result()[i]; |
| 128 | if (res.type == 'googCandidatePair' && |
| 129 | res.stat('googChannelId') == componentId) { |
| 130 | this.addressPairMap[componentId].push(res); |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | return this.addressPairMap[componentId]; |
| 135 | } |
| 136 | |
| 137 | AugumentedStatsResponse.prototype.result = function() { |
| 138 | return this.response.result(); |
| 139 | } |
| 140 | |
| 141 | // The indexed getter isn't easy to prototype. |
| 142 | AugumentedStatsResponse.prototype.get = function(key) { |
| 143 | return this.response[key]; |
| 144 | } |
| 145 | |
| 146 | |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 147 | // Display statistics |
| 148 | var statCollector = setInterval(function() { |
| 149 | var display = function(str) { |
| 150 | $('bitrate').innerHTML = str; |
| 151 | } |
| 152 | |
| 153 | display("No stream"); |
hta@webrtc.org | db3f427 | 2013-03-05 15:23:40 +0000 | [diff] [blame] | 154 | if (pc2 && pc2.getRemoteStreams()[0]) { |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 155 | if (pc2.getStats) { |
hta@webrtc.org | cc39484 | 2013-08-21 17:00:54 +0000 | [diff] [blame] | 156 | pc2.getStats(function(rawStats) { |
| 157 | stats = new AugumentedStatsResponse(rawStats); |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 158 | var statsString = ''; |
| 159 | var results = stats.result(); |
hta@webrtc.org | cc39484 | 2013-08-21 17:00:54 +0000 | [diff] [blame] | 160 | var videoFlowInfo = 'No bitrate stats'; |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 161 | for (var i = 0; i < results.length; ++i) { |
| 162 | var res = results[i]; |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 163 | statsString += '<h3>Report '; |
| 164 | statsString += i; |
| 165 | statsString += '</h3>'; |
hta@webrtc.org | ecfd328 | 2013-03-19 08:45:47 +0000 | [diff] [blame] | 166 | if (!res.local || res.local === res) { |
| 167 | statsString += dumpStats(res); |
hta@webrtc.org | 3ed599a | 2013-03-22 08:48:16 +0000 | [diff] [blame] | 168 | // The bandwidth info for video is in a type ssrc stats record |
| 169 | // with googFrameHeightReceived defined. |
| 170 | // Should check for mediatype = video, but this is not |
| 171 | // implemented yet. |
| 172 | if (res.type == 'ssrc' && res.stat('googFrameHeightReceived')) { |
hta@webrtc.org | cc39484 | 2013-08-21 17:00:54 +0000 | [diff] [blame] | 173 | // This is the video flow. |
| 174 | videoFlowInfo = extractVideoFlowInfo(res, stats); |
hta@webrtc.org | 3ed599a | 2013-03-22 08:48:16 +0000 | [diff] [blame] | 175 | } |
hta@webrtc.org | ecfd328 | 2013-03-19 08:45:47 +0000 | [diff] [blame] | 176 | } else { |
| 177 | // Pre-227.0.1445 (188719) browser |
| 178 | if (res.local) { |
| 179 | statsString += "<p>Local "; |
| 180 | statsString += dumpStats(res.local); |
| 181 | } |
| 182 | if (res.remote) { |
| 183 | statsString += "<p>Remote "; |
| 184 | statsString += dumpStats(res.remote); |
| 185 | } |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 186 | } |
| 187 | } |
hta@webrtc.org | 37bf584 | 2013-04-06 10:05:55 +0000 | [diff] [blame] | 188 | $('receiverstats').innerHTML = statsString; |
hta@webrtc.org | cc39484 | 2013-08-21 17:00:54 +0000 | [diff] [blame] | 189 | display(videoFlowInfo); |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 190 | }); |
hta@webrtc.org | 37bf584 | 2013-04-06 10:05:55 +0000 | [diff] [blame] | 191 | pc1.getStats(function(stats) { |
| 192 | var statsString = ''; |
| 193 | var results = stats.result(); |
| 194 | for (var i = 0; i < results.length; ++i) { |
| 195 | var res = results[i]; |
| 196 | statsString += '<h3>Report '; |
| 197 | statsString += i; |
| 198 | statsString += '</h3>'; |
| 199 | if (!res.local || res.local === res) { |
| 200 | statsString += dumpStats(res); |
| 201 | } |
| 202 | } |
| 203 | $('senderstats').innerHTML = statsString; |
| 204 | }); |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 205 | } else { |
| 206 | display('No stats function. Use at least Chrome 24.0.1285'); |
| 207 | } |
| 208 | } else { |
| 209 | log('Not connected yet'); |
| 210 | } |
| 211 | // Collect some stats from the video tags. |
| 212 | local_video = $('local-video'); |
| 213 | if (local_video) { |
| 214 | $('local-video-stats').innerHTML = local_video.videoWidth + |
| 215 | 'x' + local_video.videoHeight; |
| 216 | } |
| 217 | remote_video = $('remote-video'); |
| 218 | if (remote_video) { |
| 219 | $('remote-video-stats').innerHTML = remote_video.videoWidth + |
| 220 | 'x' + remote_video.videoHeight; |
| 221 | } |
| 222 | }, 1000); |
| 223 | |
hta@webrtc.org | cc39484 | 2013-08-21 17:00:54 +0000 | [diff] [blame] | 224 | function extractVideoFlowInfo(res, allStats) { |
| 225 | var description = ''; |
| 226 | var bytesNow = res.stat('bytesReceived'); |
| 227 | if (timestampPrev > 0) { |
| 228 | var bitRate = Math.round((bytesNow - bytesPrev) * 8 / |
| 229 | (res.timestamp - timestampPrev)); |
| 230 | description = bitRate + ' kbits/sec'; |
| 231 | } |
| 232 | timestampPrev = res.timestamp; |
| 233 | bytesPrev = bytesNow; |
| 234 | if (res.stat('transportId')) { |
| 235 | component = allStats.get(res.stat('transportId')); |
| 236 | if (component) { |
| 237 | addresses = allStats.collectAddressPairs(component.id); |
| 238 | if (addresses.length > 0) { |
| 239 | description += ' from IP '; |
| 240 | description += addresses[0].stat('googRemoteAddress'); |
| 241 | } else { |
| 242 | description += ' no address'; |
| 243 | } |
| 244 | } else { |
| 245 | description += ' No component stats'; |
| 246 | } |
| 247 | } else { |
| 248 | description += ' No component ID'; |
| 249 | } |
| 250 | return description; |
| 251 | } |
| 252 | |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 253 | // Dumping a stats variable as a string. |
| 254 | // might be named toString? |
| 255 | function dumpStats(obj) { |
| 256 | var statsString = 'Timestamp:'; |
| 257 | statsString += obj.timestamp; |
hta@webrtc.org | 3ed599a | 2013-03-22 08:48:16 +0000 | [diff] [blame] | 258 | if (obj.id) { |
hta@webrtc.org | 37bf584 | 2013-04-06 10:05:55 +0000 | [diff] [blame] | 259 | statsString += "<br>id "; |
hta@webrtc.org | 3ed599a | 2013-03-22 08:48:16 +0000 | [diff] [blame] | 260 | statsString += obj.id; |
| 261 | } |
| 262 | if (obj.type) { |
| 263 | statsString += " type "; |
| 264 | statsString += obj.type; |
| 265 | } |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 266 | if (obj.names) { |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 267 | names = obj.names(); |
| 268 | for (var i = 0; i < names.length; ++i) { |
| 269 | statsString += '<br>'; |
| 270 | statsString += names[i]; |
| 271 | statsString += ':'; |
| 272 | statsString += obj.stat(names[i]); |
| 273 | } |
| 274 | } else { |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 275 | if (obj.stat('audioOutputLevel')) { |
| 276 | statsString += "audioOutputLevel: "; |
| 277 | statsString += obj.stat('audioOutputLevel'); |
| 278 | statsString += "<br>"; |
| 279 | } |
| 280 | } |
| 281 | return statsString; |
| 282 | } |
| 283 | |
| 284 | |
| 285 | // Utility to show the value of a field in a span called name+Display |
| 286 | function showValue(name, value) { |
| 287 | $(name + 'Display').innerHTML = value; |
| 288 | } |
| 289 | </script> |
| 290 | </head> |
| 291 | <body> |
| 292 | <h1>Constraints and Statistics</h1> |
| 293 | This page is meant to give some hints on how one can use constraints and statistics in WebRTC applications. |
| 294 | <p> |
| 295 | The form to the left gives constraints you can set on the getUserMedia call. |
| 296 | When you hit "open", it will (re)open the camera with these constraints. |
| 297 | <p> |
| 298 | The left picture is the local preview. The right picture is the picture |
| 299 | after being passed through the PeerConnection (locally). |
| 300 | <p> |
| 301 | Underneath the picture you will see a running display of how many Kbits/sec |
| 302 | the video feed uses for transmission. |
| 303 | <hr> |
| 304 | <table> |
| 305 | <tr> |
| 306 | <td align="top"> |
| 307 | <h2>getUserMedia constraints</h2> |
| 308 | <table> |
| 309 | <tr><td><td>Min<td>Max |
| 310 | <tr><td>Horizontal |
| 311 | <td><input type="range" id="minwidth" min="0" max="1280" value="300" |
| 312 | onchange="showValue(this.id, this.value)"> |
| 313 | <td><input type="range" id="maxwidth" min="0" max="1280" value="640" |
| 314 | onchange="showValue(this.id, this.value)"> |
| 315 | <td><span id="minwidthDisplay">300</span>-<span id="maxwidthDisplay">640</span> |
| 316 | <tr><td>Vertical |
| 317 | <td><input type="range" id="minheight" min="0" max="1280" value="200" |
| 318 | onchange="showValue(this.id, this.value)"> |
| 319 | <td><input type="range" id="maxheight" min="0" max="1280" value="480" |
| 320 | onchange="showValue(this.id, this.value)"> |
| 321 | <td><span id="minheightDisplay">200</span>-<span id="maxheightDisplay">480</span> |
| 322 | <tr><td> |
| 323 | FrameRate |
| 324 | <td colspan=2><input type="range" id="frameRate" min="0" max="60" value="30" |
| 325 | onchange="showValue(this.id, this.value)"> |
| 326 | <td><span id="frameRateDisplay">30</span> |
| 327 | </table> |
| 328 | <input type="submit" name="capture" value="Capture!" onclick="openCamera()"> |
| 329 | </td> |
| 330 | <td align="top"> |
| 331 | <h2>addStream constraints</h2> |
| 332 | Maximum bitrate |
| 333 | <input type="range" id="bandwidth" min="0" max="2000" value="1000" |
| 334 | onchange="showValue(this.id, this.value)"> |
| 335 | <span id="bandwidthDisplay">1000</span> |
| 336 | <br> |
| 337 | <input type="submit" name="connect" value="Connect!" onclick="connect()"> |
| 338 | </td> |
| 339 | </tr> |
| 340 | <tr> |
| 341 | <td> |
vikasmarwaha@webrtc.org | da0f708 | 2013-03-11 16:58:07 +0000 | [diff] [blame] | 342 | <video id="local-video" autoplay width=400 muted="true"></video> |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 343 | </td> |
| 344 | <td> |
| 345 | <video id="remote-video" autoplay width=400></video> |
| 346 | </td> |
| 347 | <tr> |
| 348 | <td><span id="local-video-stats"></span> |
| 349 | <td><span id="remote-video-stats"></span> |
| 350 | <br> |
| 351 | <span id="bitrate">Bitrate unknown</span> |
| 352 | </td> |
| 353 | </tr> |
| 354 | <tr> |
| 355 | <td><pre><span id="cameraConstraints"></span></pre> |
| 356 | <td><pre><span id="addStreamConstraints"></span></pre> |
| 357 | </table> |
| 358 | <h2>Statistics report display</h2> |
hta@webrtc.org | 37bf584 | 2013-04-06 10:05:55 +0000 | [diff] [blame] | 359 | <table> |
| 360 | <tr> |
| 361 | <th>Sender side<th>Receiver side |
| 362 | <tr> |
| 363 | <td align="top"><div id="senderstats">Stats will appear here.</div> |
| 364 | <td align="top"><div id="receiverstats">Stats will appear here.</div> |
| 365 | </table> |
vikasmarwaha@webrtc.org | 98fce15 | 2013-02-27 23:22:10 +0000 | [diff] [blame] | 366 | </body> |
| 367 | </html> |