RtcStatsCollector · 统计怎么跨线程汇总
getStats() 返回的那份报告,数据散落在不同对象、不同线程里。这一章读 pc/rtc_stats_collector.cc:它如何在 signaling 与 network 线程上分别采集「部分结果」再合并、如何用短缓存避免重复劳动,以及 RTCStatsReport 为什么是一堆类型化的统计对象。第 1 部分收尾。
RtcStatsCollector · 统计怎么跨线程汇总
第 1 部分的收尾,是一个「触及一切」的组件。你在浏览器里调 pc.getStats(),拿到一份琳琅满目的报告——每个编解码器的用量、每条流收发了多少包、丢了多少、抖动多大、选中的候选对是谁、往返时延多少……这份报告的数据,散落在整个 PeerConnection 的各个角落、各条线程上。把它们汇总成一份的,是 RtcStatsCollector。它是理解「WebRTC 可观测性」的起点(第 11 部分还会深入)。
入口:GetStatsReport
getStats() 落到 pc/rtc_stats_collector.cc 的 RTCStatsCollector::GetStatsReport(pc/rtc_stats_collector.h):
// pc/rtc_stats_collector.h(节选)
void GetStatsReport(scoped_refptr<RTCStatsCollectorCallback> callback);
// 还有按发送器 / 接收器筛选的重载:
void GetStatsReport(scoped_refptr<RtpSenderInternal> selector, ...);
void GetStatsReport(scoped_refptr<RtpReceiverInternal> selector, ...);它是异步的——你给个回调,报告采集好了再回调给你。为什么不能同步立即返回?因为数据不在一处、不在一条线程上。
难点:统计散落在多条线程
这是本章的核心。还记得三线程模型吗?PeerConnection 的状态被故意分散在不同线程上:
采集一份完整统计,必须「跑遍多条线程,各采一部分,再合并」——这正是 RtcStatsCollector 结构的由来。 它有一对方法(pc/rtc_stats_collector.h):
ProducePartialResultsOnSignalingThread(...)—— 在 signaling 线程上,采集会话 / 收发器 / 编解码器等部分结果。ProducePartialResultsOnNetworkThread(...)—— 在 network 线程上,采集传输 / 候选对 / 收发包等部分结果。
两条线程各产出一份「partial report」,最后合并成完整的 RTCStatsReport 再回调。这就是为什么 GetStatsReport 必须异步:它得在两条线程间往返(用任务派发),等两边都采完。你看,连「查个统计」这么简单的操作,在 WebRTC 里都因为线程模型而变成了一次精心编排的跨线程协作——这再次印证代理那章的教训:简单的调用,内部可能跳好几次线程。
缓存:别重复劳动
跨线程采集不便宜。如果应用高频调 getStats()(比如每秒画统计图),每次都跑一遍全流程就太浪费。RtcStatsCollector 因此加了短缓存——pc/rtc_stats_collector.h 提到结果在 cache_lifetime_ 毫秒内被视为新鲜:
[stats are] considered fresh for
cache_lifetime_ms.
在这个窗口内重复调用,直接返回缓存的报告,不再跨线程折腾;过期了才重新采集。一个「用短缓存换低开销」的经典权衡。
报告长什么样:一堆类型化统计
返回的 RTCStatsReport 不是一坨自由文本,而是一组类型化的统计对象——每个是一个 RTCStats 子类(stats/rtc_stats.h 的 RTCStats),对应 W3C getStats 规范里定义的一种统计:
| 统计对象(举例) | 描述什么 |
|---|---|
RTCOutboundRtpStreamStats | 某条发送流:发了多少包 / 字节、目标码率 |
RTCInboundRtpStreamStats | 某条接收流:收了多少、丢了多少、抖动 |
RTCIceCandidatePairStats | 选中的候选对:RTT、收发字节 |
RTCCodecStats | 用了哪个编解码器、payload type |
RTCTransportStats | 传输层:DTLS 状态、选中的候选对 |
每个对象有稳定的 id、类型、一堆成员。这份结构化报告,正是 WebRTC 排障的第一手资料——卡顿看 inbound 的丢包与抖动,建连慢看候选对的 RTT,码率上不去看 outbound 的目标码率。RtcStatsCollector 遍历整个 PeerConnection,把每个对象的状态,填进对应的 RTCStats。
小结与第 1 部分回顾
RtcStatsCollector(pc/rtc_stats_collector.cc)是 getStats() 的落地:它异步跨线程采集——ProducePartialResultsOnSignalingThread 采会话 / 收发器,ProducePartialResultsOnNetworkThread 采传输 / 候选对,再合并成一份 RTCStatsReport(一组类型化 RTCStats,对齐 W3C 规范);用 cache_lifetime_ 短缓存避免重复劳动。它触及每个下属对象,是 PeerConnection 可观测性的窗口。
第 1 部分「PeerConnection 与会话建立」到此完整:我们从工厂与共享上下文出发,认识了上帝对象的内部结构,打通了 SDP/JSEP → 协商驾驶舱 → 媒体取交集 → 收发器 → 传输落地与 BUNDLE → 数据通道 → 统计这条控制面主线。
控制面谈定后,真正的挑战才开始:怎么在两个可能藏在 NAT 后的端点之间,打通一条能通的路? 这是 WebRTC 最硬核的子系统之一。下一部分,我们进入 p2p/,拆解 ICE。👉 第 2 部分即将展开。
参考资料
- [1] libwebrtc 源码.
pc/rtc_stats_collector.h/.cc(GetStatsReport、ProducePartialResultsOn*Thread、cache_lifetime_)。 - [2] libwebrtc 源码.
stats/rtc_stats.h、stats/rtcstats_objects.h(RTCStats类型化统计)。 - [3] W3C「Identifiers for WebRTC's Statistics API」https://www.w3.org/TR/webrtc-stats/ —— 统计对象规范。