# 类型安全的单位 · Timestamp、TimeDelta、DataRate

> WebRTC 不用裸 int64 存时间和码率，而是用一整套类型安全的物理量：Timestamp、TimeDelta、DataRate、DataSize、Frequency。这一章读 units/ 目录，讲清这套 CRTP 单位体系怎么在编译期防止「毫秒当微秒用」的 bug，并顺带看它正在被移植到 Rust 的现状。

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

# 类型安全的单位 · Timestamp、TimeDelta、DataRate

[拥塞控制](https://daiw.org/manual/webrtc/congestion-control-overview)、[视频时序](https://daiw.org/manual/webrtc/video-timing)、[NetEq](https://daiw.org/manual/webrtc/neteq-overview)……WebRTC 到处在算时间和码率。一个朴素的写法是全用 `int64_t` 存毫秒、存 bps。但 WebRTC **偏不**——它用一整套**类型安全的单位**。这一章看 `units/` 目录，以及这么做的道理。

## 裸 int64 的陷阱

先说为什么不用裸整数。看这段“看起来没问题”的代码：

```cpp
int64_t timeout = 500;        // 我以为是毫秒
int64_t elapsed = GetElapsedMicros();   // 其实是微秒
if (elapsed > timeout) { ... }          // bug!毫秒和微秒直接比
```

<Callout type="warn">
  **用裸 `int64_t` 表示时间 / 码率，编译器**帮不了你**——毫秒当微秒、bps 当 kbps、时刻（某一刻）当时长（一段），这类单位混淆的 bug 编译期完全看不出来，只能等线上出诡异问题。** 这在[满是时间计算的实时系统](https://daiw.org/manual/webrtc/video-timing)里是高频雷区：一个 `1000` 到底是 1 秒、1 毫秒还是 1000 个包？函数签名 `Foo(int64_t t)` 完全没告诉你。WebRTC 代码量巨大、多人协作，这种“[全靠注释和约定](https://daiw.org/manual/webrtc/reading-the-source)”的做法迟早出错。**解法：让类型系统记住单位。**
</Callout>

## 一套物理量类型

WebRTC 在 `units/` 目录定义了一组**强类型物理量**，每种量一个类：

| 类型 | 表示 | 例子 |
| --- | --- | --- |
| `Timestamp` | **时刻**（某一个时间点） | 这个包到达的时刻 |
| `TimeDelta` | **时长**（两个时刻之差） | 超时 500ms |
| `DataRate` | **码率** | 目标带宽 2Mbps |
| `DataSize` | **数据量** | 一个包 1200 字节 |
| `Frequency` | **频率** | 采样率 48kHz |

它们都建在同一个 CRTP 基类上（真实源码 `units/timestamp.h:28`）：

```cpp
// 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;      // 取出微秒
};
```

<Callout type="info">
  **构造和读取都**必须带单位**：`Timestamp::Millis(500)` 而非 `500`，`t.ms()` 而非直接读整数——单位写在方法名里，编译器和读者都不会搞错。** 你**无法**把一个 `Timestamp` 直接当 `int64_t` 传给别处，也无法把 `DataRate` 和 `Timestamp` 相加（编译报错）。合法的运算遵循**物理意义**：

  - `TimeDelta = Timestamp - Timestamp`（两个时刻之差是时长）——但 `Timestamp + Timestamp` **非法**（两个时刻相加没意义）。
  - `DataRate = DataSize / TimeDelta`（数据量 ÷ 时长 = 码率）。
  - `DataSize = DataRate * TimeDelta`（码率 × 时长 = 数据量）。

  **类型系统强制这些量按物理规律组合**，“[毫秒当微秒](https://daiw.org/manual/webrtc/units)”这类 bug 直接编译不过。这就是[为什么 `rtc_base` 要造这套](https://daiw.org/manual/webrtc/rtc-base-overview)：[通用库没有](https://daiw.org/manual/webrtc/rtc-base-overview)，而实时通信离不开。你在[拥塞控制](https://daiw.org/manual/webrtc/rate-control)、[视频时序](https://daiw.org/manual/webrtc/video-timing)里看到的 `DataRate`、`Timestamp`，都是它。
</Callout>

## 一个现场细节：正在被移植到 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.cc
```

<Callout type="info">
  **WebRTC 正在**试验用 Rust 重写单位库**，并通过 FFI 桥（`*_ffi.rs`、`*_rs_bridge_unittest.cc`）让 C++ 和 Rust 版本互操作——这是从真实最新源码里才看得到的进行时。** 单位库是很好的 Rust 试点：逻辑纯粹（就是带类型的算术）、无平台依赖、被广泛使用却接口简单——适合作为“[把 Rust 引入大型 C++ 代码库](https://daiw.org/manual/webrtc/reading-the-source)”的先锋。这个细节也提醒我们：**WebRTC 是活的、在演进的代码**，教程讲的是[某个时刻的源码](https://daiw.org/manual/webrtc/reading-the-source)，而它一直在变。看到 `.rs` 文件不必惊讶——这是大型 C++ 项目渐进引入 Rust 的典型路径。
</Callout>

## 小结与下一章

WebRTC 不用裸 `int64_t` 表示时间 / 码率（那会让[毫秒当微秒、时刻当时长](https://daiw.org/manual/webrtc/units)这类 bug [编译期无法发现](https://daiw.org/manual/webrtc/reading-the-source)），而是在 `units/` 定义**强类型物理量**：**`Timestamp`**（时刻）、**`TimeDelta`**（时长）、**`DataRate`**、**`DataSize`**、**`Frequency`**——都基于 CRTP 的 `UnitBase`。构造 / 读取**必须带单位**（`Timestamp::Millis(500)`、`t.ms()`），运算遵循**物理意义**（`时长 = 时刻 - 时刻`、`码率 = 数据量 / 时长`），把单位错误挡在编译期。源码现场还能看到它**正被移植到 Rust**（`.rs` + FFI 桥），是[活代码演进](https://daiw.org/manual/webrtc/reading-the-source)的一个切片。

时间和码率之外，系统里流动最频繁的是**媒体数据本身**。下一章看 WebRTC 怎么让它“零拷贝”地传。👉 [零拷贝缓冲](https://daiw.org/manual/webrtc/buffers)。

## 参考资料

- [1] libwebrtc 源码.`api/units/timestamp.h`、`time_delta.h`、`data_rate.h`、`data_size.h`、`frequency.h`、`unit_base.h`。
- [2] libwebrtc 源码.`api/units/*.rs`、`*_ffi.rs`、`units_rs_bridge_unittest.cc`（Rust 移植与 FFI 桥）。
- [3] 关联[拥塞控制码率](https://daiw.org/manual/webrtc/rate-control)、[视频时序](https://daiw.org/manual/webrtc/video-timing)。
