WebRTC 音视频管道概览
做音画同步,你得先搞清楚音视频数据是怎么在 WebRTC 里流动的。说白了,就是音频和视频各自走了一条什么样的路,最后在 RTP 层怎么被打包、怎么带上时间戳。我刚开始接触 WebRTC 时,也踩过不少坑——比如音频采集的采样率没对齐,导致远端播放声音变调。所以这一章,咱们把管道从头到尾捋一遍。
音频采集与编码流程
音频从麦克风到网络,大致经过这么几步:
- 采集:浏览器通过
getUserMedia拿到麦克风数据,通常是 16kHz 或 48kHz 的 PCM 裸流。 - 预处理:回声消除(AEC)、降噪(NS)、自动增益控制(AGC)——这些都在 AudioProcessing 模块里完成。
- 编码:Opus 或 PCMU/PCMA 编码器把 PCM 数据压缩成编码帧。
- 封装成 RTP:每个编码帧被塞进 RTP 包,带上时间戳和序列号。
关键点:音频的 RTP 时间戳增量是固定的。比如 48kHz 采样率下,每 20ms 一帧,时间戳增量就是 960。这个值在接收端用来计算播放节奏。
我在项目中遇到过一个问题:某款 Android 手机采集的采样率是 16kHz,但编码器配置成了 48kHz,结果时间戳计算全乱套了。嗯,这里要注意——采集和编码的采样率必须一致。
视频采集与编码流程
视频管道比音频复杂一些,因为它涉及帧率控制、分辨率适配、码率调节。流程如下:
- 采集:摄像头输出 YUV 或 NV12 格式的原始帧。
- 预处理:缩放、旋转、裁剪——这些由 VideoAdapter 和 VideoFrameProcessor 处理。
- 编码:VP8、VP9、H264 或 AV1 编码器压缩视频帧。
- 封装成 RTP:一帧可能被拆成多个 RTP 包(特别是大尺寸帧),每个包携带相同的时间戳。
| 环节 | 常见问题 | 影响 |
|---|---|---|
| 采集 | 帧率不稳定(30fps 掉到 15fps) | 时间戳间隔不均匀,接收端播放卡顿 |
| 编码 | 编码器丢帧(CPU 过载) | RTP 时间戳出现跳变 |
| 封装 | NALU 分片顺序错乱 | 解码失败或花屏 |
我的习惯:在编码器输出回调里打日志,记录每一帧的采集时间、编码时间、RTP 时间戳。这样排查音画不同步时,能快速定位是采集端还是编码端的问题。
RTP 打包与时间戳机制
RTP 时间戳是音画同步的核心。音频和视频各自维护一个时间轴,接收端通过这两个时间轴的关系来对齐播放。
音频 RTP 时间戳
- 基于采样率递增。比如 48kHz 采样率,每 20ms 一包,时间戳加 960。
- 音频的 RTP 时间戳是连续的,不会跳变(除非有丢包或静音检测)。
视频 RTP 时间戳
- 基于 90kHz 时钟。为什么是 90kHz?因为它是 24fps、30fps、60fps 的公约数,方便计算。
- 每帧的时间戳 = 采集时刻的 90kHz 时钟值。比如 30fps 下,两帧间隔 3000(90kHz / 30)。
我曾经踩过的坑:视频编码器内部会重新打时间戳,导致 RTP 时间戳和实际采集时间对不上。后来我在采集端用 webrtc::Timestamp 记录原始时间,编码后再做一次映射,才把问题解决。
音视频管道整体流程
下面这张图展示了音频和视频从采集到 RTP 输出的完整路径。你可以看到,两条管道在 RTP 层汇合,但时间戳各自独立。
时间戳对齐的关键点
你想想看,音频和视频的时间戳是各自独立的,接收端怎么知道哪一帧音频对应哪一帧视频?答案是:通过 RTP 时间戳和 NTP 时间的映射。
- 发送端:在 RTCP SR(Sender Report)里,把 RTP 时间戳和 NTP 时间绑定在一起。
- 接收端:根据 SR 里的映射关系,把音频和视频的时间戳统一到同一个时间轴上。
核心公式:音画同步误差 = |(音频播放时间 - 音频参考时间) - (视频播放时间 - 视频参考时间)|。当这个误差超过 100ms 时,人眼就能感知到不同步。
我个人习惯在接收端维护一个缓冲区,音频和视频各自有一个播放队列。每次播放前,计算当前音频帧和视频帧的时间戳差值,如果超过阈值,就调整播放速度或丢帧。说白了,这就是音画同步的底层逻辑。
避坑指南
我曾经遇到的一个典型问题:某次线上会议,用户反馈声音和画面差了一秒多。排查后发现,视频编码器在 CPU 过载时自动跳帧,但 RTP 时间戳还是按原始帧率递增,导致接收端以为视频还在正常播放。解决方案是在编码器跳帧时,同步调整 RTP 时间戳的生成逻辑。
嗯,这里要注意:RTP 时间戳反映的是采集时间,而不是编码时间。如果编码延迟很大,时间戳和实际播放时间就会脱节。所以,我建议在采集端就打好时间戳,编码和打包过程中不要随意修改。