4. 音画同步的参考模型:发送端同步模型、接收端同步模型、缓冲区管理与抖动消除
音画同步这件事,说白了就是让音频和视频在时间轴上对齐。但网络是个不靠谱的家伙,它会抖动、会延迟、甚至会丢包。所以我们需要一套模型来管理这一切。
我个人习惯把同步模型拆成三块来看:发送端怎么打时间戳、接收端怎么对齐播放、缓冲区怎么吸收抖动。这三块缺一不可。你想想看,如果发送端的时间戳就错了,接收端再怎么努力也白搭。
4.1 发送端同步模型
发送端的核心任务只有一个:给每一帧音频和视频打上准确的时间戳。这个时间戳不是本地时钟的随意读数,而是基于同一个参考时钟的。
在 WebRTC 里,这个参考时钟通常是 NTP 时间。音频和视频采集时,各自记录下当前的 NTP 时间,然后把这个时间戳塞进 RTP 包头的 timestamp 字段里。
关键点:音频和视频的时间戳必须来自同一个时钟源。如果音频用声卡时钟,视频用摄像头时钟,那这两个时钟的漂移率不同,时间戳就会越走越偏。
我在项目中遇到过一个问题:某款 Android 手机的音频驱动和视频驱动使用了不同的晶振,导致音频时间戳比视频时间戳每小时快 3 秒。用户开视频会议半小时后,口型就对不上了。后来我们强制让两个采集线程都从系统 NTP 获取时间戳,才解决了这个问题。
发送端的时间戳模型可以用下面这张图来表示:
4.2 接收端同步模型
接收端的工作就复杂多了。它要接收音频和视频的 RTP 包,从包里提取时间戳,然后决定什么时候播放。
接收端同步的核心是 SRTP 时间戳映射。音频和视频的 RTP 时间戳单位不同——音频通常是 48kHz 采样率,视频通常是 90kHz 时钟频率。所以接收端不能直接比较这两个数值,得先把它们映射到同一个时间轴上。
WebRTC 的做法是:利用 RTCP 的 Sender Report(SR)包。SR 包里同时包含了 RTP 时间戳和对应的 NTP 时间。接收端收到 SR 后,就能建立 RTP 时间戳到 NTP 时间的映射关系。
小技巧:我建议你在接收端维护一个映射表,记录最近收到的 SR 包信息。当音频和视频的 RTP 时间戳需要比较时,先查表转换成 NTP 时间,再做差值计算。这样能避免单位不一致的问题。
接收端同步的流程大致如下:
- 接收 RTP 包:分别从音频和视频流中接收数据包。
- 提取时间戳:从 RTP 包头拿到 timestamp 字段。
- 映射到 NTP 时间:利用 SR 包中的映射关系,把 RTP timestamp 转换成 NTP 时间。
- 计算差值:比较音频和视频的 NTP 时间差。
- 调整播放:如果差值超过阈值,就调整音频或视频的播放速度。
4.3 缓冲区管理与抖动消除
网络抖动是音画同步的头号敌人。什么叫抖动?就是数据包到达的时间间隔不均匀。有时候 10ms 来一个包,有时候 200ms 才来一个。如果不处理,播放端就会一卡一卡的。
解决抖动的方法就是加缓冲区。把收到的数据先存起来,等缓冲区积累到一定量后再开始播放。这样网络抖动就被缓冲区吸收了。
但缓冲区不是越大越好。缓冲区大了,延迟就大了。视频会议里延迟超过 300ms 用户就能感觉到,超过 500ms 对话就会变得很不自然。
| 缓冲区类型 | 作用 | 典型大小 | 风险 |
|---|---|---|---|
| 音频抖动缓冲区 (JitterBuffer) | 吸收音频包到达抖动 | 20-120ms | 过大导致延迟,过小导致丢音 |
| 视频抖动缓冲区 (JitterBuffer) | 吸收视频包到达抖动 | 30-200ms | 过大导致延迟,过小导致卡顿 |
| 解码后缓冲区 (DecodeBuffer) | 对齐音频和视频的播放时间 | 0-100ms | 用于音画同步微调 |
注意:我曾经在一个项目中把音频抖动缓冲区设成了 500ms,结果用户反馈说话有回声。为什么?因为缓冲区太大,本地播放的音频延迟了,用户听到自己的声音从对方那边传回来,形成了回声。后来我把缓冲区降到 80ms,问题就解决了。
抖动消除的算法有很多种,WebRTC 用的是 NetEQ(音频)和 JitterBuffer(视频)。NetEQ 会根据网络状态动态调整缓冲区大小,同时还能做丢包补偿。视频 JitterBuffer 则更关注帧的完整性,一帧没到齐不会送去解码。
缓冲区管理的核心逻辑可以用下面这张图来表示:
嗯,这里要注意一个细节:解码后缓冲区的作用。音频和视频解码后,它们的播放时间可能还是对不齐。为什么?因为解码本身也有延迟,而且音频和视频的解码延迟不一样。所以我们需要一个解码后缓冲区,让音频或视频等一等对方,直到时间戳对齐了再播放。
核心原则:抖动缓冲区解决的是网络抖动问题,解码后缓冲区解决的是音画同步问题。两个缓冲区各司其职,不要混为一谈。
我曾经在一个项目中看到有人把抖动缓冲区和解码后缓冲区合并了,结果网络一抖,音画同步也跟着乱。后来我强制拆成两个独立的缓冲区,各自做各自的事,问题就清晰多了。
最后总结一下这三个模型的关系:
- 发送端同步模型:保证时间戳准确,这是基础。
- 接收端同步模型:把时间戳映射到统一时间轴,计算差值。
- 缓冲区管理与抖动消除:吸收网络抖动,对齐播放时间。
这三层缺一不可。发送端时间戳错了,后面再怎么调也没用。接收端映射错了,差值计算就不准。缓冲区管理不好,播放就会卡顿或延迟。只有三层都做好,音画同步才能真正稳定。