3、RTP时间戳机制:采样率与时间戳增量、RTP时间与NTP时间映射、时间戳回绕处理
时间戳是RTP协议里最容易被低估的字段。很多人觉得它就是个单调递增的计数器,没什么好讲的。但我在实际项目中踩过不少坑——音画不同步、播放器卡死、回绕导致的计算错误,追根溯源都是时间戳处理不当。
说白了,RTP时间戳是接收端重建媒体时序的唯一依据。你发送端怎么打时间戳,接收端就怎么播放。如果这里出了问题,后面再好的流控算法也救不回来。
3.1 采样率与时间戳增量
RTP时间戳的单位不是毫秒,而是采样周期。每个媒体格式都有自己的采样率,时间戳每次增加的值就是「一个包携带的采样数」。
举个例子:
- PCM音频,采样率8000Hz,每20ms发一包,每包160个样本。时间戳增量就是160。
- H.264视频,时钟频率90000Hz,每33ms发一帧(30fps),时间戳增量就是 90000 / 30 = 3000。
为什么视频要用90000Hz?我刚开始做RTP时也觉得奇怪。后来查了标准才知道,这是为了兼容MPEG的时序精度。90000能被大多数帧率整除(24、25、30、50、60),算起来方便。
| 媒体类型 | 典型采样率 | 包间隔 | 时间戳增量 |
|---|---|---|---|
| G.711 音频 | 8000 Hz | 20 ms | 160 |
| Opus 音频 | 48000 Hz | 20 ms | 960 |
| H.264 视频 | 90000 Hz | 33 ms (30fps) | 3000 |
| H.265 视频 | 90000 Hz | 40 ms (25fps) | 3600 |
关键原则:同一媒体流的所有包,时间戳增量必须一致。如果发送端因为编码器变速导致包内样本数变化,必须在PT(Payload Type)中明确标识,否则接收端无法正确计算播放时长。
3.2 RTP时间与NTP时间映射
RTP时间戳是相对值,它只告诉接收端「这个包和上个包之间隔了多少个采样」。但接收端需要知道「这个包对应的绝对时间是什么」,才能做音画同步。
这就是RTCP SR(Sender Report)的作用。SR包里同时携带了两个时间:
- NTP时间戳:发送端当前的绝对时间,64位,单位是秒的2^32分之一。
- RTP时间戳:与NTP时间对应的RTP时间戳值。
有了这对映射关系,接收端就能算出任意RTP时间戳对应的绝对时间。
我记得有一次做多路摄像头同步,三路视频流各自独立发送,播放端总是对不齐。排查了半天,发现其中一路的RTCP SR里NTP和RTP的对应关系是错的——发送端在构造SR时用了不同的采样时刻。后来统一在编码器输出帧的时刻同时打上NTP和RTP戳,问题才解决。
我的习惯:在发送端维护一个「时间映射表」,每生成一个SR包就记录一次映射关系。接收端收到SR后,用线性插值计算中间任意RTP时间戳对应的NTP时间。公式很简单:
ntp_time = ntp_ref + (rtp_ts - rtp_ref) / sample_rate
注意单位换算,NTP的高32位是整数秒,低32位是小数秒。
3.3 时间戳回绕处理
RTP时间戳是32位无符号整数。对于90000Hz的视频,大约13小时就会回绕一次。对于48000Hz的音频,大约25小时。你以为系统不会跑这么久?我见过7×24小时的监控系统,回绕是必然发生的。
回绕带来的问题很直接:接收端无法通过简单比较时间戳大小来判断包的先后顺序。比如当前时间戳是0xFFFF0000,下一个包是0x00001000,按数值看后者更小,但实际后者更新。
处理回绕的标准做法是使用序列号来判断顺序,时间戳只用来计算相对间隔。但有些场景下你确实需要比较时间戳的大小,比如计算抖动时。
我曾经踩过的坑:在计算网络抖动时,直接拿两个时间戳相减。回绕发生后,差值变成了一个巨大的负数,抖动值瞬间爆表,触发流控误判。后来我改用带回绕补偿的差值计算:
int32_t diff = (int32_t)(ts2 - ts1);
if (diff < 0) {
diff += 0x100000000; // 补偿回绕
}
注意这里用int32_t来接收差值,利用C语言的溢出特性自动处理回绕。但前提是两个时间戳的间隔不能超过2^31个采样周期,否则无法区分是正向回绕还是反向回绕。
更稳妥的做法是维护一个扩展时间戳:在32位RTP时间戳之外,本地维护一个64位的高位计数器。每次收到新包时,如果RTP时间戳比上一个包小(且差值超过阈值),就认为发生了回绕,高位加1。这样你始终有一个单调递增的64位时间戳可用。
嗯,这里要注意:扩展时间戳的高位计数器只在本地维护,不随RTP包传输。接收端和发送端各自维护自己的扩展时间戳,它们之间没有同步关系。你只需要保证单个接收端内部的时间戳单调递增就够了。
避坑指南:如果你在做音画同步,千万别直接用RTP时间戳做跨流比较。音频和视频的采样率不同,时间戳增量也不同。正确的做法是:先把两路流的RTP时间戳通过RTCP SR映射到NTP时间,再用NTP时间做对齐。我曾经见过有人直接拿音频和视频的RTP时间戳做差值,结果音画不同步得一塌糊涂。
总结一下时间戳处理的三个核心点:采样率决定增量单位,RTCP SR提供绝对时间映射,回绕处理保证长时间运行的稳定性。这三个点搞清楚了,RTP时间戳这块基本就不会出大问题。