类型安全的单位 · Timestamp、TimeDelta、DataRate
WebRTC 不用裸 int64 存时间和码率,而是用一整套类型安全的物理量:Timestamp、TimeDelta、DataRate、DataSize、Frequency。这一章读 units/ 目录,讲清这套 CRTP 单位体系怎么在编译期防止「毫秒当微秒用」的 bug,并顺带看它正在被移植到 Rust 的现状。
类型安全的单位 · Timestamp、TimeDelta、DataRate
拥塞控制、视频时序、NetEq……WebRTC 到处在算时间和码率。一个朴素的写法是全用 int64_t 存毫秒、存 bps。但 WebRTC 偏不——它用一整套类型安全的单位。这一章看 units/ 目录,以及这么做的道理。
裸 int64 的陷阱
先说为什么不用裸整数。看这段“看起来没问题”的代码:
int64_t timeout = 500; // 我以为是毫秒
int64_t elapsed = GetElapsedMicros(); // 其实是微秒
if (elapsed > timeout) { ... } // bug!毫秒和微秒直接比用裸 int64_t 表示时间 / 码率,编译器帮不了你**——毫秒当微秒、bps 当 kbps、时刻(某一刻)当时长(一段),这类单位混淆的 bug 编译期完全看不出来,只能等线上出诡异问题。** 这在满是时间计算的实时系统里是高频雷区:一个 1000 到底是 1 秒、1 毫秒还是 1000 个包?函数签名 Foo(int64_t t) 完全没告诉你。WebRTC 代码量巨大、多人协作,这种“全靠注释和约定”的做法迟早出错。解法:让类型系统记住单位。
一套物理量类型
WebRTC 在 units/ 目录定义了一组强类型物理量,每种量一个类:
| 类型 | 表示 | 例子 |
|---|---|---|
Timestamp | 时刻(某一个时间点) | 这个包到达的时刻 |
TimeDelta | 时长(两个时刻之差) | 超时 500ms |
DataRate | 码率 | 目标带宽 2Mbps |
DataSize | 数据量 | 一个包 1200 字节 |
Frequency | 频率 | 采样率 48kHz |
它们都建在同一个 CRTP 基类上(真实源码 units/timestamp.h:28):
// units/timestamp.h(节选,真实源码)
class Timestamp final : public rtc_units_impl::UnitBase<Timestamp> {
public:
template <typename T> static constexpr Timestamp Seconds(T value);
template <typename T> static constexpr Timestamp Millis(T value);
template <typename T> static constexpr Timestamp Micros(T value);
constexpr int64_t ms() const; // 取出毫秒
constexpr int64_t us() const; // 取出微秒
};构造和读取都必须带单位**:Timestamp::Millis(500) 而非 500,t.ms() 而非直接读整数——单位写在方法名里,编译器和读者都不会搞错。** 你无法把一个 Timestamp 直接当 int64_t 传给别处,也无法把 DataRate 和 Timestamp 相加(编译报错)。合法的运算遵循物理意义:
TimeDelta = Timestamp - Timestamp(两个时刻之差是时长)——但Timestamp + Timestamp非法(两个时刻相加没意义)。DataRate = DataSize / TimeDelta(数据量 ÷ 时长 = 码率)。DataSize = DataRate * TimeDelta(码率 × 时长 = 数据量)。
类型系统强制这些量按物理规律组合,“毫秒当微秒”这类 bug 直接编译不过。这就是为什么 rtc_base 要造这套:通用库没有,而实时通信离不开。你在拥塞控制、视频时序里看到的 DataRate、Timestamp,都是它。
一个现场细节:正在被移植到 Rust
读 units/ 目录,你会发现一个很“当下”的细节——里面除了 .h/.cc,还躺着一批 .rs 文件:
units/timestamp.h units/timestamp.rs units/timestamp_ffi.rs
units/time_delta.h units/time_delta.rs units/time_delta_ffi.rs
units/demo_unit_interop.rs units/units_rs_bridge_unittest.ccWebRTC 正在试验用 Rust 重写单位库**,并通过 FFI 桥(*_ffi.rs、*_rs_bridge_unittest.cc)让 C++ 和 Rust 版本互操作——这是从真实最新源码里才看得到的进行时。** 单位库是很好的 Rust 试点:逻辑纯粹(就是带类型的算术)、无平台依赖、被广泛使用却接口简单——适合作为“把 Rust 引入大型 C++ 代码库”的先锋。这个细节也提醒我们:WebRTC 是活的、在演进的代码,教程讲的是某个时刻的源码,而它一直在变。看到 .rs 文件不必惊讶——这是大型 C++ 项目渐进引入 Rust 的典型路径。
小结与下一章
WebRTC 不用裸 int64_t 表示时间 / 码率(那会让毫秒当微秒、时刻当时长这类 bug 编译期无法发现),而是在 units/ 定义强类型物理量:Timestamp(时刻)、TimeDelta(时长)、DataRate、DataSize、Frequency——都基于 CRTP 的 UnitBase。构造 / 读取必须带单位(Timestamp::Millis(500)、t.ms()),运算遵循物理意义(时长 = 时刻 - 时刻、码率 = 数据量 / 时长),把单位错误挡在编译期。源码现场还能看到它正被移植到 Rust(.rs + FFI 桥),是活代码演进的一个切片。
时间和码率之外,系统里流动最频繁的是媒体数据本身。下一章看 WebRTC 怎么让它“零拷贝”地传。👉 零拷贝缓冲。