时间戳基础:系统时钟、NTP 与 RTP 时间戳
做音画同步,说白了就是跟时间打交道。你连时间都拿不准,后面的缓冲区管理、丢帧补偿全是白搭。这一章我们就把时间戳的底子打牢。
系统时钟:CLOCK_REALTIME vs CLOCK_MONOTONIC
Linux 下有两个最常用的时钟类型,我当年刚入行时傻傻分不清,结果线上服务出了大问题——NTP 一调整,所有音视频时间戳全乱套了。
| 特性 | CLOCK_REALTIME | CLOCK_MONOTONIC |
|---|---|---|
| 基准点 | 1970-01-01 UTC(墙上时钟) | 系统启动时(从 0 开始) |
| 受 NTP 影响 | 会跳变(向前/向后调整) | 不受影响,只会微调频率 |
| 适用场景 | 日志时间戳、用户可见时间 | 音视频同步、性能测量 |
| 是否可暂停 | 系统挂起时暂停 | 系统挂起时暂停 |
我曾经踩过的坑: 用 CLOCK_REALTIME 做音视频时间戳基准,半夜 NTP 同步把时钟往回拨了 200ms,结果音频缓冲区直接溢出,用户听到一阵刺耳的爆音。从那以后,我所有音视频时间戳一律改用 CLOCK_MONOTONIC。
代码里怎么选?很简单:
// 错误示范:用 REALTIME 做音视频同步
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts); // 别这么干!
// 正确做法:用 MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t now_ns = ts.tv_sec * 1000000000ULL + ts.tv_nsec;
嗯,这里要注意一点:CLOCK_MONOTONIC 在多核 CPU 上可能存在微小的偏移(几十纳秒级别),但对音视频同步来说完全够用。
NTP 时间同步原理
你想想看,两台设备之间的时钟偏差怎么解决?靠 NTP。它的核心思想很简单:客户端发一个请求,记下本地时间 T1;服务器收到后记下 T2,再回复时记下 T3;客户端收到回复时记下 T4。
NTP 核心公式:
- 往返延迟 = (T4 - T1) - (T3 - T2)
- 时钟偏移 = ((T2 - T1) + (T3 - T4)) / 2
我个人习惯把 NTP 的精度分成三个等级:
- 局域网内: 精度可达 0.1ms 以内,适合音视频同步
- 互联网环境: 通常 1-10ms,勉强够用
- 移动网络: 抖动大,10-50ms 甚至更差,需要额外滤波
做音视频传输时,我建议在接收端维护一个 NTP 时间戳到本地 MONOTONIC 时钟的映射表。这样即使网络有抖动,也能通过插值算出准确的播放时间。
小技巧: 如果 NTP 不可用(比如纯内网环境),可以用 RTCP 的 SR(Sender Report)包里的 NTP 时间戳来做时钟同步。我在一个直播项目中就这么干过,效果还不错。
RTP 时间戳与序列号的关系
很多新手会把 RTP 时间戳和序列号搞混。我直接说结论:时间戳管播放节奏,序列号管丢包检测。
| 属性 | RTP 时间戳 (Timestamp) | RTP 序列号 (Sequence Number) |
|---|---|---|
| 增量依据 | 采样率(如 48kHz 音频每帧 960 样本) | 每个 RTP 包 +1 |
| 是否单调递增 | 不一定(VAD 静音时可能暂停) | 必须单调递增 |
| 主要用途 | 计算播放时间、音画同步 | 检测丢包、排序 |
| 初始值 | 随机值(防攻击) | 随机值(防攻击) |
举个例子你就明白了。假设音频采样率 48kHz,每帧 20ms(960 个样本):
// 音频参数
采样率: 48000 Hz
帧长: 20 ms
每帧样本数: 960
// 时间戳增量
timestamp_increment = 960 // 每帧 +960
// 序列号增量
seq_increment = 1 // 每包 +1
// 如果收到两包:
// 包1: timestamp=1000, seq=5000
// 包2: timestamp=1960, seq=5001
// 说明:包2比包1晚20ms,中间没有丢包
// 如果收到:
// 包1: timestamp=1000, seq=5000
// 包3: timestamp=2920, seq=5003
// 说明:丢了 seq=5001 和 5002 两包,但时间戳跳了 1920(2帧)
// 需要做丢包隐藏处理
注意: 时间戳的初始值是随机的,你不能拿两个不同流的绝对时间戳做比较。要比较也得先做时钟同步,或者用 RTCP 里的 NTP 时间戳做映射。
我见过最离谱的 bug 是什么?有人用序列号算播放时间,结果网络重传导致序列号乱序,播放器直接卡死。记住:序列号只管顺序和丢包,时间戳才管时间。
本章知识体系
下面这张图把三个核心概念串起来了,你看完应该能明白它们各自的位置:
这张图里,系统时钟是地基,NTP 是校准工具,RTP 时间戳是上层应用。三者缺一不可。
我的建议: 写代码时先把 MONOTONIC 时钟的获取封装成一个函数,所有时间戳计算都走它。NTP 同步单独一个线程跑,每 30 秒校准一次。RTP 时间戳只用来算播放时间,别跟序列号混着用。