cc39484770
This CL demonstrates a couple of methods to extract more complex properties from the stats that are linked via stats IDs. RISK=P3 TESTED=manual test BUG= R=juberti@webrtc.org Review URL: https://webrtc-codereview.appspot.com/1667005 git-svn-id: http://webrtc.googlecode.com/svn/trunk@4584 4adac7df-926f-26a2-2b94-8c16560cd09d
368 lines
11 KiB
HTML
368 lines
11 KiB
HTML
<html>
|
|
<head>
|
|
<title>Constraints and Statistics</title>
|
|
<!-- Load the polyfill to switch-hit between Chrome and Firefox -->
|
|
<script src="../../base/adapter.js"></script>
|
|
|
|
<style type="text/css">
|
|
td { vertical-align: top; }
|
|
</style>
|
|
|
|
<script>
|
|
var mystream;
|
|
var pc1;
|
|
var pc2;
|
|
var bytesPrev = 0;
|
|
var timestampPrev = 0;
|
|
|
|
$ = function(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
function log(txt) {
|
|
console.log(txt);
|
|
}
|
|
|
|
function openCamera() {
|
|
if (mystream) {
|
|
mystream.stop();
|
|
}
|
|
navigator.webkitGetUserMedia(cameraConstraints(), gotStream, function() {
|
|
log("GetUserMedia failed");
|
|
});
|
|
}
|
|
|
|
function gotStream(stream) {
|
|
log("GetUserMedia succeeded");
|
|
mystream = stream;
|
|
$("local-video").src = webkitURL.createObjectURL(stream);
|
|
}
|
|
|
|
function cameraConstraints() {
|
|
var constraints = {};
|
|
constraints.audio = true;
|
|
constraints.video = { mandatory: {}, optional: [] };
|
|
if ($("minwidth").value != "0") {
|
|
constraints.video.mandatory.minWidth = $("minwidth").value;
|
|
}
|
|
if ($("maxwidth").value != "0") {
|
|
constraints.video.mandatory.maxWidth = $("maxwidth").value;
|
|
}
|
|
if ($("minheight").value != "0") {
|
|
constraints.video.mandatory.minHeight = $("minheight").value;
|
|
}
|
|
if ($("maxheight").value != "0") {
|
|
constraints.video.mandatory.maxHeight = $("maxheight").value;
|
|
}
|
|
if ($("frameRate").value != "0") {
|
|
constraints.video.mandatory.minFrameRate = $("frameRate").value;
|
|
}
|
|
log('Camera constraints are ' + JSON.stringify(constraints));
|
|
$("cameraConstraints").innerHTML = JSON.stringify(constraints, null, ' ');
|
|
return constraints;
|
|
}
|
|
|
|
function streamConstraints() {
|
|
var constraints = { mandatory: {}, optional: [] };
|
|
if ($("bandwidth").value != "0") {
|
|
constraints.optional[0] = { 'bandwidth' : $('bandwidth').value };
|
|
}
|
|
log('Constraints are ' + JSON.stringify(constraints));
|
|
$("addStreamConstraints").innerHTML = JSON.stringify(constraints, null, ' ');
|
|
return constraints;
|
|
}
|
|
|
|
function connect() {
|
|
pc1 = new webkitRTCPeerConnection(null);
|
|
pc2 = new webkitRTCPeerConnection(null);
|
|
pc1.addStream(mystream, streamConstraints());
|
|
log('PC1 creating offer');
|
|
pc1.onnegotiationeeded = function() {
|
|
log('Negotiation needed - PC1');
|
|
}
|
|
pc2.onnegotiationeeded = function() {
|
|
log('Negotiation needed - PC2');
|
|
}
|
|
pc1.onicecandidate = function(e) {
|
|
log('Candidate PC1');
|
|
if (e.candidate) {
|
|
pc2.addIceCandidate(new RTCIceCandidate(e.candidate));
|
|
}
|
|
}
|
|
pc2.onicecandidate = function(e) {
|
|
log('Candidate PC2');
|
|
if (e.candidate) {
|
|
pc1.addIceCandidate(new RTCIceCandidate(e.candidate));
|
|
}
|
|
}
|
|
pc2.onaddstream = function(e) {
|
|
log('PC2 got stream');
|
|
$('remote-video').src = webkitURL.createObjectURL(e.stream);
|
|
log('Remote video is ' + $('remote-video').src);
|
|
}
|
|
pc1.createOffer(function(desc) {
|
|
log('PC1 offering');
|
|
pc1.setLocalDescription(desc);
|
|
pc2.setRemoteDescription(desc);
|
|
pc2.createAnswer(function(desc2) {
|
|
log('PC2 answering');
|
|
pc2.setLocalDescription(desc2);
|
|
pc1.setRemoteDescription(desc2);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Augumentation of stats entries with utility functions.
|
|
// The augumented entry does what the stats entry does, but adds
|
|
// utility functions.
|
|
function AugumentedStatsResponse(response) {
|
|
this.response = response;
|
|
this.addressPairMap = [];
|
|
}
|
|
|
|
AugumentedStatsResponse.prototype.collectAddressPairs = function(componentId) {
|
|
if (!this.addressPairMap[componentId]) {
|
|
this.addressPairMap[componentId] = [];
|
|
for (var i = 0; i < this.response.result().length; ++i) {
|
|
var res = this.response.result()[i];
|
|
if (res.type == 'googCandidatePair' &&
|
|
res.stat('googChannelId') == componentId) {
|
|
this.addressPairMap[componentId].push(res);
|
|
}
|
|
}
|
|
}
|
|
return this.addressPairMap[componentId];
|
|
}
|
|
|
|
AugumentedStatsResponse.prototype.result = function() {
|
|
return this.response.result();
|
|
}
|
|
|
|
// The indexed getter isn't easy to prototype.
|
|
AugumentedStatsResponse.prototype.get = function(key) {
|
|
return this.response[key];
|
|
}
|
|
|
|
|
|
// Display statistics
|
|
var statCollector = setInterval(function() {
|
|
var display = function(str) {
|
|
$('bitrate').innerHTML = str;
|
|
}
|
|
|
|
display("No stream");
|
|
if (pc2 && pc2.getRemoteStreams()[0]) {
|
|
if (pc2.getStats) {
|
|
pc2.getStats(function(rawStats) {
|
|
stats = new AugumentedStatsResponse(rawStats);
|
|
var statsString = '';
|
|
var results = stats.result();
|
|
var videoFlowInfo = 'No bitrate stats';
|
|
for (var i = 0; i < results.length; ++i) {
|
|
var res = results[i];
|
|
statsString += '<h3>Report ';
|
|
statsString += i;
|
|
statsString += '</h3>';
|
|
if (!res.local || res.local === res) {
|
|
statsString += dumpStats(res);
|
|
// The bandwidth info for video is in a type ssrc stats record
|
|
// with googFrameHeightReceived defined.
|
|
// Should check for mediatype = video, but this is not
|
|
// implemented yet.
|
|
if (res.type == 'ssrc' && res.stat('googFrameHeightReceived')) {
|
|
// This is the video flow.
|
|
videoFlowInfo = extractVideoFlowInfo(res, stats);
|
|
}
|
|
} else {
|
|
// Pre-227.0.1445 (188719) browser
|
|
if (res.local) {
|
|
statsString += "<p>Local ";
|
|
statsString += dumpStats(res.local);
|
|
}
|
|
if (res.remote) {
|
|
statsString += "<p>Remote ";
|
|
statsString += dumpStats(res.remote);
|
|
}
|
|
}
|
|
}
|
|
$('receiverstats').innerHTML = statsString;
|
|
display(videoFlowInfo);
|
|
});
|
|
pc1.getStats(function(stats) {
|
|
var statsString = '';
|
|
var results = stats.result();
|
|
for (var i = 0; i < results.length; ++i) {
|
|
var res = results[i];
|
|
statsString += '<h3>Report ';
|
|
statsString += i;
|
|
statsString += '</h3>';
|
|
if (!res.local || res.local === res) {
|
|
statsString += dumpStats(res);
|
|
}
|
|
}
|
|
$('senderstats').innerHTML = statsString;
|
|
});
|
|
} else {
|
|
display('No stats function. Use at least Chrome 24.0.1285');
|
|
}
|
|
} else {
|
|
log('Not connected yet');
|
|
}
|
|
// Collect some stats from the video tags.
|
|
local_video = $('local-video');
|
|
if (local_video) {
|
|
$('local-video-stats').innerHTML = local_video.videoWidth +
|
|
'x' + local_video.videoHeight;
|
|
}
|
|
remote_video = $('remote-video');
|
|
if (remote_video) {
|
|
$('remote-video-stats').innerHTML = remote_video.videoWidth +
|
|
'x' + remote_video.videoHeight;
|
|
}
|
|
}, 1000);
|
|
|
|
function extractVideoFlowInfo(res, allStats) {
|
|
var description = '';
|
|
var bytesNow = res.stat('bytesReceived');
|
|
if (timestampPrev > 0) {
|
|
var bitRate = Math.round((bytesNow - bytesPrev) * 8 /
|
|
(res.timestamp - timestampPrev));
|
|
description = bitRate + ' kbits/sec';
|
|
}
|
|
timestampPrev = res.timestamp;
|
|
bytesPrev = bytesNow;
|
|
if (res.stat('transportId')) {
|
|
component = allStats.get(res.stat('transportId'));
|
|
if (component) {
|
|
addresses = allStats.collectAddressPairs(component.id);
|
|
if (addresses.length > 0) {
|
|
description += ' from IP ';
|
|
description += addresses[0].stat('googRemoteAddress');
|
|
} else {
|
|
description += ' no address';
|
|
}
|
|
} else {
|
|
description += ' No component stats';
|
|
}
|
|
} else {
|
|
description += ' No component ID';
|
|
}
|
|
return description;
|
|
}
|
|
|
|
// Dumping a stats variable as a string.
|
|
// might be named toString?
|
|
function dumpStats(obj) {
|
|
var statsString = 'Timestamp:';
|
|
statsString += obj.timestamp;
|
|
if (obj.id) {
|
|
statsString += "<br>id ";
|
|
statsString += obj.id;
|
|
}
|
|
if (obj.type) {
|
|
statsString += " type ";
|
|
statsString += obj.type;
|
|
}
|
|
if (obj.names) {
|
|
names = obj.names();
|
|
for (var i = 0; i < names.length; ++i) {
|
|
statsString += '<br>';
|
|
statsString += names[i];
|
|
statsString += ':';
|
|
statsString += obj.stat(names[i]);
|
|
}
|
|
} else {
|
|
if (obj.stat('audioOutputLevel')) {
|
|
statsString += "audioOutputLevel: ";
|
|
statsString += obj.stat('audioOutputLevel');
|
|
statsString += "<br>";
|
|
}
|
|
}
|
|
return statsString;
|
|
}
|
|
|
|
|
|
// Utility to show the value of a field in a span called name+Display
|
|
function showValue(name, value) {
|
|
$(name + 'Display').innerHTML = value;
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1>Constraints and Statistics</h1>
|
|
This page is meant to give some hints on how one can use constraints and statistics in WebRTC applications.
|
|
<p>
|
|
The form to the left gives constraints you can set on the getUserMedia call.
|
|
When you hit "open", it will (re)open the camera with these constraints.
|
|
<p>
|
|
The left picture is the local preview. The right picture is the picture
|
|
after being passed through the PeerConnection (locally).
|
|
<p>
|
|
Underneath the picture you will see a running display of how many Kbits/sec
|
|
the video feed uses for transmission.
|
|
<hr>
|
|
<table>
|
|
<tr>
|
|
<td align="top">
|
|
<h2>getUserMedia constraints</h2>
|
|
<table>
|
|
<tr><td><td>Min<td>Max
|
|
<tr><td>Horizontal
|
|
<td><input type="range" id="minwidth" min="0" max="1280" value="300"
|
|
onchange="showValue(this.id, this.value)">
|
|
<td><input type="range" id="maxwidth" min="0" max="1280" value="640"
|
|
onchange="showValue(this.id, this.value)">
|
|
<td><span id="minwidthDisplay">300</span>-<span id="maxwidthDisplay">640</span>
|
|
<tr><td>Vertical
|
|
<td><input type="range" id="minheight" min="0" max="1280" value="200"
|
|
onchange="showValue(this.id, this.value)">
|
|
<td><input type="range" id="maxheight" min="0" max="1280" value="480"
|
|
onchange="showValue(this.id, this.value)">
|
|
<td><span id="minheightDisplay">200</span>-<span id="maxheightDisplay">480</span>
|
|
<tr><td>
|
|
FrameRate
|
|
<td colspan=2><input type="range" id="frameRate" min="0" max="60" value="30"
|
|
onchange="showValue(this.id, this.value)">
|
|
<td><span id="frameRateDisplay">30</span>
|
|
</table>
|
|
<input type="submit" name="capture" value="Capture!" onclick="openCamera()">
|
|
</td>
|
|
<td align="top">
|
|
<h2>addStream constraints</h2>
|
|
Maximum bitrate
|
|
<input type="range" id="bandwidth" min="0" max="2000" value="1000"
|
|
onchange="showValue(this.id, this.value)">
|
|
<span id="bandwidthDisplay">1000</span>
|
|
<br>
|
|
<input type="submit" name="connect" value="Connect!" onclick="connect()">
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<video id="local-video" autoplay width=400 muted="true"></video>
|
|
</td>
|
|
<td>
|
|
<video id="remote-video" autoplay width=400></video>
|
|
</td>
|
|
<tr>
|
|
<td><span id="local-video-stats"></span>
|
|
<td><span id="remote-video-stats"></span>
|
|
<br>
|
|
<span id="bitrate">Bitrate unknown</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><pre><span id="cameraConstraints"></span></pre>
|
|
<td><pre><span id="addStreamConstraints"></span></pre>
|
|
</table>
|
|
<h2>Statistics report display</h2>
|
|
<table>
|
|
<tr>
|
|
<th>Sender side<th>Receiver side
|
|
<tr>
|
|
<td align="top"><div id="senderstats">Stats will appear here.</div>
|
|
<td align="top"><div id="receiverstats">Stats will appear here.</div>
|
|
</table>
|
|
</body>
|
|
</html>
|