最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Can someone comprehensively explain the WebRTC stats API? - Stack Overflow

programmeradmin2浏览0评论

I'm finishing a WebRTC project for a graduate course in video communications, it's essentially a video conference chat room. Everyone that connects to the server is added to the conference.

I need to use the stats API in WebRTC to show some relevant performance statistics for each RTCPeerConnection (packets lost per second, jitter, retransmission, etc). This helps to observe performance cost as more peers are added to the conversation.

However the API seems to not be fully fleshed out yet. It's apparently gone through some refreshes and doesn't quite match up to some W3C specs I've seen (though perhaps it's out of date or I just don't understand nuances of reading the spec, neither would surprise me).

My invocation of the API is similar to this one, but interpreting the data isn't straightforward. For instance when looping through all items in RTCStatsReport::results(), many of them have duplicate names and confusing values. I can't seem to find any information about their meaning. If anyone can assist me in understanding some of the important ones or point me to the lost city of gold (e.g. proper documentation), I'd be grateful.

I'm finishing a WebRTC project for a graduate course in video communications, it's essentially a video conference chat room. Everyone that connects to the server is added to the conference.

I need to use the stats API in WebRTC to show some relevant performance statistics for each RTCPeerConnection (packets lost per second, jitter, retransmission, etc). This helps to observe performance cost as more peers are added to the conversation.

However the API seems to not be fully fleshed out yet. It's apparently gone through some refreshes and doesn't quite match up to some W3C specs I've seen (though perhaps it's out of date or I just don't understand nuances of reading the spec, neither would surprise me).

My invocation of the API is similar to this one, but interpreting the data isn't straightforward. For instance when looping through all items in RTCStatsReport::results(), many of them have duplicate names and confusing values. I can't seem to find any information about their meaning. If anyone can assist me in understanding some of the important ones or point me to the lost city of gold (e.g. proper documentation), I'd be grateful.

Share Improve this question edited Jan 19, 2018 at 21:15 Mogsdad 45.7k21 gold badges162 silver badges285 bronze badges asked Apr 22, 2015 at 14:02 Joey CarsonJoey Carson 3,11310 gold badges38 silver badges62 bronze badges 4
  • 2 I feel your pain. I haven't found any docs on that topic too. I wrote a simple Wrapper around the get Stats API a while back, perhaps it helps you out a little, I just put it on GitHub. – wpp Commented Apr 23, 2015 at 7:45
  • Hi, the main problem is the lack of documentation on how to interpret the data. Looking over your implementation helps to piece some of this data together and make sense of it. I will definitely pull it into my project and I thank you for it. The README file is a pretty sparse, but the organization when i dump the data should be enough to understand the meaning of all data. Thanks again, this is the next best thing without documentation. – Joey Carson Commented Apr 23, 2015 at 15:11
  • You can also check out github.com/webrtc/apprtc/tree/master/src/web_app/js especially the files "infobox.js" and "stats.js". I haven't had a chance to update/expand the README sry. – wpp Commented Apr 23, 2015 at 15:51
  • For anyone from the future, this page shows API-by-API browser compatibility: webrtc-stats.callstats.io/verify – AndrewJC Commented Dec 12, 2019 at 23:15
Add a comment  | 

2 Answers 2

Reset to default 18

The source of your confusion is likely that Google Chrome's implementation of getStats() pre-dates the standard and has not been updated yet (the example you link to is Chrome-specific, so I presume you are using Chrome).

If you were to try Firefox, you would find that it implements getStats() to the standard (however it does not support all the stats in the standard yet, and fewer stats overall than Chrome's old API).

Since you didn't specify a browser, I'll describe the standard, and use Firefox to show an example. You probably know getStats() already, but the standard one lets you filter on, say, a specific MediaStreamTrack, or pass in null to get all the data associated with a connection:

var pc = new RTCPeerConnection(config)
...
pc.getStats(null, function(stats) { ...}, function(error) { ... });

There's a newer promise-version as well.

Data is returned in stats, a big snowball object with unique ids for each record. Each record has the following base class:

dictionary RTCStats {
    DOMHiResTimeStamp timestamp;
    RTCStatsType      type;
    DOMString         id;
};

where id is a repeat of the property name used to access the record. The derived types are described here.

You typically enumerate the records until you find an RTCStatsType of interest, e.g. "inbound-rtp" which looks like this:

dictionary RTCRTPStreamStats : RTCStats {
         DOMString     ssrc;
         DOMString     remoteId;
         boolean       isRemote = false;
         DOMString     mediaTrackId;
         DOMString     transportId;
         DOMString     codecId;
         unsigned long firCount;
         unsigned long pliCount;
         unsigned long nackCount;
         unsigned long sliCount;
};

dictionary RTCInboundRTPStreamStats : RTCRTPStreamStats {
         unsigned long      packetsReceived;
         unsigned long long bytesReceived;
         unsigned long      packetsLost;
         double             jitter;
         double             fractionLost;
};

There's a corresponding one for RTCOutboundRTPStreamStats.

You can also follow cross-references to other records. Any member ending with Id is a foreign-key you can use to look up another record. For instance, mediaTrackId links to RTCMediaStreamTrackStats for the track this RTP data belongs to.

A particularly squirrelly case is RTCP data, which is stored in the same dictionaries as above, which means you have to check isRemote == false to know you are looking at RTP data and not RTCP data. Use the remoteId to find the other one (Note that this is a recent name-change, so Firefox still uses an older remoteId here). The associated RTCP stats for outbound RTP is stored in an inbound dictionary, and vice versa (makes sense).

Here's an example that runs in Firefox:

var pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();

var add = (pc, can) => can && pc.addIceCandidate(can).catch(log);
pc1.onicecandidate = e => add(pc2, e.candidate);
pc2.onicecandidate = e => add(pc1, e.candidate);

pc2.oniceconnectionstatechange = () => update(statediv, pc2.iceConnectionState);
pc2.onaddstream = e => v2.srcObject = e.stream;

navigator.mediaDevices.getUserMedia({ video: true })
  .then(stream => pc1.addStream(v1.srcObject = stream))
  .then(() => pc1.createOffer())
  .then(offer => pc1.setLocalDescription(offer))
  .then(() => pc2.setRemoteDescription(pc1.localDescription))
  .then(() => pc2.createAnswer())
  .then(answer => pc2.setLocalDescription(answer))
  .then(() => pc1.setRemoteDescription(pc2.localDescription))
  .then(() => repeat(10, () => Promise.all([pc1.getStats(), pc2.getStats()])
    .then(([s1, s2]) => {
      var s = "";
      s1.forEach(stat => {
        if (stat.type == "outbound-rtp" && !stat.isRemote) {
          s += "<h4>Sender side</h4>" + dumpStats(stat);
        }
      });
      s2.forEach(stat => {
        if (stat.type == "inbound-rtp" && !stat.isRemote) {
          s += "<h4>Receiver side</h4>" + dumpStats(stat);
        }
      });
      update(statsdiv, "<small>"+ s +"</small>");
  })))
  .catch(failed);

function dumpStats(o) {
  var s = "";
  if (o.mozAvSyncDelay !== undefined || o.mozJitterBufferDelay !== undefined) {
    if (o.mozAvSyncDelay !== undefined) s += "A/V sync: " + o.mozAvSyncDelay + " ms";
    if (o.mozJitterBufferDelay !== undefined) {
      s += " Jitter buffer delay: " + o.mozJitterBufferDelay + " ms";
    }
    s += "<br>";
  }
  s += "Timestamp: "+ new Date(o.timestamp).toTimeString() +" Type: "+ o.type +"<br>";
  if (o.ssrc !== undefined) s += "SSRC: " + o.ssrc + " ";
  if (o.packetsReceived !== undefined) {
    s += "Recvd: " + o.packetsReceived + " packets";
    if (o.bytesReceived !== undefined) {
      s += " ("+ (o.bytesReceived/1024000).toFixed(2) +" MB)";
    }
    if (o.packetsLost !== undefined) s += " Lost: "+ o.packetsLost;
  } else if (o.packetsSent !== undefined) {
    s += "Sent: " + o.packetsSent + " packets";
    if (o.bytesSent !== undefined) s += " ("+ (o.bytesSent/1024000).toFixed(2) +" MB)";
  } else {
    s += "<br><br>";
  }
  s += "<br>";
  if (o.bitrateMean !== undefined) {
    s += " Avg. bitrate: "+ (o.bitrateMean/1000000).toFixed(2) +" Mbps";
    if (o.bitrateStdDev !== undefined) {
      s += " ("+ (o.bitrateStdDev/1000000).toFixed(2) +" StdDev)";
    }
    if (o.discardedPackets !== undefined) {
      s += " Discarded packts: "+ o.discardedPackets;
    }
  }
  s += "<br>";
  if (o.framerateMean !== undefined) {
    s += " Avg. framerate: "+ (o.framerateMean).toFixed(2) +" fps";
    if (o.framerateStdDev !== undefined) {
      s += " ("+ o.framerateStdDev.toFixed(2) +" StdDev)";
    }
  }
  if (o.droppedFrames !== undefined) s += " Dropped frames: "+ o.droppedFrames;
  if (o.jitter !== undefined) s += " Jitter: "+ o.jitter;
  return s;
}

var wait = ms => new Promise(r => setTimeout(r, ms));
var repeat = (ms, func) => new Promise(r => (setInterval(func, ms), wait(ms).then(r)));
var log = msg => div.innerHTML = div.innerHTML + msg +"<br>";
var update = (div, msg) => div.innerHTML = msg;
var failed = e => log(e.name +": "+ e.message +", line "+ e.lineNumber);
<table><tr><td>
  <video id="v1" width="124" height="75" autoplay></video><br>
  <video id="v2" width="124" height="75" autoplay></video><br>
  <div id="statediv"></div></td>
<td><div id="div"></div><br><div id="statsdiv"></div></td>
</tr></table>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

To see what's supported, do stats.forEach(stat => console.log(JSON.stringify(stat))) to dump everything. Hard to read but it's all there.

I believe a polyfill is planned shortly for adapter.js to bridge the gap until Chrome updates its implementation.

Update: I've updated the examples to use the new maplike syntax, and changed type-names to include dashes, to conform with the latest spec.

Actually there are several blocks of statistics that cover inbound/outbound video/audio streams and common connection params. We've put it together into one easy to use client-side library webrtc-issue-detector to parse WebRTC stats and collect performance statistics of RTCPeerConnection.

There are many parameters and you should use different sets of them for different scenarios.

Inbound RTP streams:

  • Collect all inbound streams stats every N seconds
  • Compare current values of jitter, jitterBufferDelay, jitterBufferEmittedCount, currentRoundTripTime, packetsLost with previous ones
  • Check if there are signs of high average RTT, jitterBufferDelay, jitter values

Video quality limitations on a sender side:

  • Collect all outbound video streams stats every N seconds
  • Compare current value of qualityLimitationReason with previous one
  • Check if there are any limitations happened since previous check - cpu or bandwidth

And many other cases

发布评论

评论列表(0)

  1. 暂无评论