# RtcStatsCollector · 统计怎么跨线程汇总

> getStats（） 返回的那份报告，数据散落在不同对象、不同线程里。这一章读 pc/rtc_stats_collector.cc：它如何在 signaling 与 network 线程上分别采集「部分结果」再合并、如何用短缓存避免重复劳动，以及 RTCStatsReport 为什么是一堆类型化的统计对象。第 1 部分收尾。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/stats-collector
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# RtcStatsCollector · 统计怎么跨线程汇总

第 1 部分的收尾，是一个「触及一切」的组件。你在浏览器里调 `pc.getStats()`，拿到一份琳琅满目的报告——每个编解码器的用量、每条流收发了多少包、丢了多少、抖动多大、选中的候选对是谁、往返时延多少……这份报告的数据，**散落在整个 PeerConnection 的各个角落、各条线程上**。把它们**汇总成一份**的，是 [`RtcStatsCollector`](https://daiw.org/manual/webrtc/peer-connection-object)。它是理解「WebRTC 可观测性」的起点（[第 11 部分](https://daiw.org/manual/webrtc/source-map)还会深入）。

## 入口：GetStatsReport

`getStats()` 落到 `pc/rtc_stats_collector.cc` 的 `RTCStatsCollector::GetStatsReport`（`pc/rtc_stats_collector.h`）：

```cpp
// pc/rtc_stats_collector.h(节选)
void GetStatsReport(scoped_refptr<RTCStatsCollectorCallback> callback);
// 还有按发送器 / 接收器筛选的重载:
void GetStatsReport(scoped_refptr<RtpSenderInternal> selector, ...);
void GetStatsReport(scoped_refptr<RtpReceiverInternal> selector, ...);
```

它是**异步**的——你给个回调，报告采集好了再回调给你。为什么不能同步立即返回？因为数据不在一处、不在一条线程上。

## 难点：统计散落在多条线程

这是本章的核心。还记得[三线程模型](https://daiw.org/manual/webrtc/threading-model)吗？PeerConnection 的状态被**故意分散**在不同线程上：

- **signaling 线程**：PeerConnection 的会话状态、[收发器](https://daiw.org/manual/webrtc/transceivers)、数据通道。
- **network 线程**：[传输](https://daiw.org/manual/webrtc/jsep-transport-controller)、ICE 候选对、DTLS 状态、收发包计数。

<Callout type="info">
  **采集一份完整统计，必须「跑遍多条线程，各采一部分，再合并」——这正是 `RtcStatsCollector` 结构的由来。** 它有一对方法（`pc/rtc_stats_collector.h`）：

  - `ProducePartialResultsOnSignalingThread(...)` —— 在 signaling 线程上，采集会话 / 收发器 / 编解码器等**部分结果**。
  - `ProducePartialResultsOnNetworkThread(...)` —— 在 network 线程上，采集传输 / 候选对 / 收发包等**部分结果**。

  两条线程各产出一份「partial report」，最后**合并**成完整的 `RTCStatsReport` 再回调。这就是为什么 `GetStatsReport` 必须异步：它得**在两条线程间往返**（用[任务派发](https://daiw.org/manual/webrtc/threading-model)），等两边都采完。你看，连「查个统计」这么简单的操作，在 WebRTC 里都因为[线程模型](https://daiw.org/manual/webrtc/threading-model)而变成了一次精心编排的跨线程协作——这再次印证[代理那章](https://daiw.org/manual/webrtc/proxy-pattern)的教训：**简单的调用，内部可能跳好几次线程。**
</Callout>

## 缓存：别重复劳动

跨线程采集不便宜。如果应用高频调 `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 规范](https://www.w3.org/TR/webrtc-stats/)里定义的一种统计：

| 统计对象（举例） | 描述什么 |
| --- | --- |
| `RTCOutboundRtpStreamStats` | 某条**发送**流：发了多少包 / 字节、目标码率 | 
| `RTCInboundRtpStreamStats` | 某条**接收**流：收了多少、丢了多少、抖动 |
| `RTCIceCandidatePairStats` | 选中的[候选对](https://daiw.org/manual/webrtc/source-map)：RTT、收发字节 |
| `RTCCodecStats` | 用了哪个编解码器、payload type |
| `RTCTransportStats` | [传输](https://daiw.org/manual/webrtc/jsep-transport-controller)层：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 与会话建立」到此完整**：我们从[工厂与共享上下文](https://daiw.org/manual/webrtc/factory-and-context)出发，认识了[上帝对象的内部结构](https://daiw.org/manual/webrtc/peer-connection-object)，打通了 [SDP/JSEP](https://daiw.org/manual/webrtc/sdp-and-jsep) → [协商驾驶舱](https://daiw.org/manual/webrtc/offer-answer-handler) → [媒体取交集](https://daiw.org/manual/webrtc/media-session) → [收发器](https://daiw.org/manual/webrtc/transceivers) → [传输落地与 BUNDLE](https://daiw.org/manual/webrtc/jsep-transport-controller) → [数据通道](https://daiw.org/manual/webrtc/data-channel-controller) → 统计这条控制面主线。

控制面谈定后，真正的挑战才开始：**怎么在两个可能藏在 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/ —— 统计对象规范。
