有人可以全面解释一下WebRTC stats API吗?

问题描述 投票:0回答:2

我正在完成视频通信研究生课程的 WebRTC 项目,它本质上是一个视频会议聊天室。连接到服务器的每个人都被添加到会议中。

我需要使用 WebRTC 中的 stats API 来显示每个 RTCPeerConnection 的一些相关性能统计数据(每秒丢失的数据包、抖动、重传等)。这有助于观察随着更多对等点添加到对话中的性能成本。

但是 API 似乎还没有完全充实。它显然经历了一些更新,并且与我见过的一些 W3C 规范不太匹配(尽管它可能已经过时,或者我只是不明白阅读规范的细微差别,两者都不会令我感到惊讶)。

我对 API 的调用与此类似,但解释数据并不简单。例如,当循环 RTCStatsReport::results()

 中的所有项目时,其中许多项目具有重复的名称和令人困惑的值。我似乎找不到任何有关其含义的信息。如果有人可以帮助我理解一些重要的内容或向我指出失落的黄金之城(例如适当的文档),我将不胜感激。

javascript webrtc
2个回答
18
投票
您感到困惑的根源可能是 Google Chrome 的

getStats()

 实现早于标准,并且尚未更新(您链接到的示例是特定于 Chrome 的,所以我假设您正在使用 Chrome)。

如果您尝试 Firefox,您会发现它实现了

getStats()

标准(但是它还不支持标准中的所有统计数据,总体统计数据比 Chrome 的旧 API 少)。

由于您没有指定浏览器,我将描述标准,并使用 Firefox 进行示例。您可能已经知道

getStats()

,但标准的可以让您过滤特定的 MediaStreamTrack,或者传入 
null
 来获取与连接关联的所有数据:

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

还有一个更新的 Promise 版本。

数据在

stats

 中返回,这是一个大雪球对象,每个记录都有唯一的 id。每条记录都有
以下基类:

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

其中

id

 是用于访问记录的属性名称的重复。 
here描述了派生类型。

您通常会枚举记录,直到找到感兴趣的

RTCStatsType,例如"inbound-rtp"

 看起来像这样:

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; };

有一个对应的

RTCOutboundRTPStreamStats

您还可以跟踪其他记录的交叉引用。任何以

Id

 结尾的成员都是外键,您可以使用它来查找另一条记录。例如,
mediaTrackId
 链接到 
RTCMediaStreamTrackStats,以获取此 RTP 数据所属的轨道。

一个特别奇怪的情况是 RTCP 数据,它存储在与上面相同的字典中,这意味着您必须检查

isRemote == false

 才能知道您正在查看 RTP 数据而不是 RTCP 数据。使用 
remoteId
 查找另一个(请注意,这是最近的名称更改,因此 Firefox 在此仍使用较旧的 
remoteId
)。出站 RTP 的关联 RTCP 统计信息存储在入站字典中,反之亦然(有意义)。

这是在 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>

要查看支持的内容,请执行

stats.forEach(stat => console.log(JSON.stringify(stat)))

 转储所有内容。很难阅读,但一切都在那里。

我相信很快就会计划为adapter.js 提供一个polyfill,以弥补Chrome 更新其实现之前的差距。

更新:我更新了示例以使用新的类地图语法,并将类型名称更改为包含破折号,以符合最新规范。


0
投票
实际上有几个统计数据块,涵盖入站/出站视频/音频流和常见连接参数。我们将其整合到一个易于使用的客户端库

webrtc-issue- detector中,用于解析 WebRTC 统计数据并收集 RTCPeerConnection 的性能统计数据。

参数很多,不同的场景需要使用不同的参数。

入站 RTP 流:

    每 N 秒收集一次所有入站流统计数据
  • jitter
    jitterBufferDelay
    jitterBufferEmittedCount
    currentRoundTripTime
    packetsLost
     的当前值与之前的值进行比较
  • 检查是否有平均 RTT、jitterBufferDelay、抖动值较高的迹象
  • 发送方

视频质量限制

    每 N 秒收集一次所有出站视频流统计信息
  • qualityLimitationReason
     的当前值与之前的值进行比较
  • 检查自上次检查以来是否发生任何限制 -
  • cpu
    bandwidth
    
    
还有许多其他

案例

© www.soinside.com 2019 - 2024. All rights reserved.