4、网络传输抖动:RTP时间戳、jitter buffer、丢包重传
网络传输抖动,说白了就是数据包到达时间忽快忽慢。你想想看,发送端每隔 20ms 发一个包,接收端可能 5ms 就收到了一个,下一个却等了 50ms。这种不规律性,就是音画不同步的元凶之一。
我刚开始做实时音视频的时候,总觉得只要带宽够大就万事大吉。结果有一次线上会议,画面卡得像幻灯片,声音却提前了好几秒。查了半天才发现,不是带宽问题,是 jitter buffer 配置太激进,把正常到达的包也当成了抖动给丢掉了。嗯,从那以后我再也不敢小看网络抖动了。
4.1 RTP 时间戳:音频和视频的“心跳”
RTP 时间戳是每个媒体包的“出生证明”。它记录了采样时刻,而不是发送时刻。这一点非常关键。
音频的采样率通常是 8kHz、16kHz、48kHz。视频的时钟频率固定为 90kHz。为什么视频要用 90kHz?因为 90 能被 24、25、30、50、60 这些常见帧率整除,算起来方便。这是历史遗留的设计,但确实好用。
核心原则:音频和视频的 RTP 时间戳必须使用各自的时钟频率,不能混用。混用的后果就是音画不同步,而且很难排查。
举个例子,一段 48kHz 的音频,每帧 20ms,那么每帧的采样点数是 960。RTP 时间戳的增量就是 960。视频 30fps,每帧间隔约 33.33ms,时间戳增量是 3000(90kHz / 30)。
// 音频:48kHz, 20ms 帧长
// 每帧采样点数 = 48000 * 0.02 = 960
// RTP 时间戳增量 = 960
// 视频:30fps, 90kHz 时钟
// 每帧时间戳增量 = 90000 / 30 = 3000
我在项目中遇到过一个问题:某个编码器输出的视频时间戳偶尔会跳变,增量不是 3000 而是 6000。播放器以为丢了一帧,实际上只是时间戳写错了。这种问题最难定位,因为你得同时看抓包和日志才能发现。
4.2 Jitter Buffer:对抗网络抖动的“蓄水池”
jitter buffer 就像一个蓄水池。网络抖动大的时候,它多存一些水;网络平稳的时候,它放水快一些。但蓄水池的深度怎么定?太浅了,容易干涸(buffer underrun);太深了,延迟就上去了。
我个人习惯把 jitter buffer 分成两种:
- 静态 jitter buffer:固定深度,比如 100ms。实现简单,但适应性差。网络好的时候浪费延迟,网络差的时候容易卡顿。
- 自适应 jitter buffer:根据网络抖动动态调整深度。实现复杂,但效果更好。WebRTC 用的就是这种。
| 类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 静态 jitter buffer | 实现简单,CPU 开销低 | 延迟固定,适应性差 | 局域网、专线等稳定网络 |
| 自适应 jitter buffer | 动态调整,延迟和卡顿平衡好 | 实现复杂,需要算法调优 | 互联网、移动网络等不稳定场景 |
自适应 jitter buffer 的核心算法,说白了就是统计网络抖动的分布。WebRTC 用的是基于到达时间差的统计模型,计算出一个“目标延迟”。这个目标延迟会随着网络状况实时变化。
避坑指南:我曾经把 jitter buffer 的初始深度设得太小,结果用户一进房间就卡顿。后来改成先统计 500ms 的网络状况再开始播放,问题就解决了。记住,不要一上来就播放,先让 buffer 蓄点水。
4.3 丢包重传:丢了怎么办?
网络丢包是常态,不是异常。关键是怎么处理。常见的策略有三种:
- NACK(Negative ACKnowledgment):接收端发现丢包了,主动告诉发送端“我丢了第 X 个包,快重传”。这是最常用的方法。
- FEC(Forward Error Correction):发送端多传一些冗余数据,接收端即使丢了一部分也能恢复。适合丢包率不高的场景。
- PLC(Packet Loss Concealment):接收端自己猜丢失的数据。音频可以用波形相似性填补,视频可以用前一帧代替。
我建议优先用 NACK + FEC 的组合。NACK 保证可靠性,FEC 减少重传次数。但要注意,FEC 会增加带宽消耗。我曾经在一个带宽只有 500kbps 的链路上开了 20% 的 FEC 冗余,结果画面质量惨不忍睹。后来改成 5% 的 FEC + NACK 重传,效果好了很多。
注意:丢包重传不是万能的。如果 RTT(往返时延)超过 300ms,重传回来的包可能已经过了播放时间。这时候,与其等重传,不如直接丢帧或者用 PLC 填补。否则,音画不同步会更严重。
4.4 整体流程:从发送到播放
把上面三个概念串起来,就是一条完整的链路:
从发送端开始,媒体数据被打上 RTP 时间戳,经过网络传输到达接收端。接收端先进入 jitter buffer 排序,然后检测丢包。如果丢了,就触发 NACK 重传或者 FEC 恢复。最后根据时间戳对齐音频和视频,输出播放。
这个流程里,每一步都可能引入不同步。时间戳写错了,jitter buffer 排序乱了,重传回来的包太晚了——任何一个环节出问题,都会导致音画不同步。所以,排查问题的时候,一定要从端到端看,不要只盯着一个点。
总结一下:网络传输抖动是音画不同步的常见原因。RTP 时间戳是基础,jitter buffer 是核心,丢包重传是补充。三者配合好了,才能保证音画同步。我个人建议,调试的时候先看时间戳是否连续,再看 jitter buffer 的深度是否合理,最后才查丢包重传的延迟。这个顺序能帮你快速定位问题。