3. RTCP协议与同步:SR包、时间戳映射与带宽控制
上一章我们聊了RTP怎么把音视频数据打包扔到网络上。但光有RTP,你只能听到声音、看到画面,却没法保证它们是对齐的。为什么?因为RTP本身不携带绝对时间信息,它只告诉你「这是第几个包」,不告诉你「这个包对应什么时刻」。
这时候就需要RTCP登场了。RTCP的全称是RTP Control Protocol,说白了就是RTP的「管理通道」。它不传音视频数据,只传控制信息。而其中最重要的,就是SR包——Sender Report,发送端报告。
3.1 RTCP SR包结构解析
SR包是RTCP里最核心的报文之一。发送端(比如你的编码器)会定期发出SR包,告诉接收端:「我现在的系统时钟是多少,我发了多少个RTP包,总共多少字节。」
SR包的格式长这样:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| RC | PT=200 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of sender |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| NTP timestamp, most significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NTP timestamp, least significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's packet count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's octet count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Report block 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Report block 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| ... |
我来拆解一下关键字段:
- V=2:RTCP版本号,目前是2。
- PT=200:包类型,200表示这是SR包。
- SSRC:发送端的同步源标识符,用来区分不同的数据源。
- NTP timestamp:64位的NTP时间戳,表示发送这个SR包时的绝对时间。
- RTP timestamp:与NTP时间戳对应的RTP时间戳。
- sender's packet count:从开始到现在,这个SSRC总共发了多少个RTP包。
- sender's octet count:总共发了多少字节。
核心要点:SR包的关键在于它同时携带了NTP时间戳和RTP时间戳。这两个时间戳的映射关系,就是解决音画不同步的钥匙。
3.2 NTP时间戳与RTP时间戳的映射
NTP时间戳是绝对时间,单位是秒,精度可以到纳秒级。它来自系统时钟,比如你的电脑时间。RTP时间戳是相对时间,单位取决于采样率——音频通常是48000Hz,视频通常是90000Hz。
举个例子:
假设音频采样率是48000Hz。NTP时间戳显示当前是2024-01-01 12:00:00.000,对应的RTP时间戳是0。过了1秒,NTP时间戳变成12:00:01.000,RTP时间戳就应该是48000。
这个映射关系在SR包里是成对出现的。接收端拿到SR包后,就能算出:
RTP时间戳增量 = (当前NTP - 上次NTP) × 采样率
我在项目中遇到过一个问题:某个摄像头输出的RTP时间戳居然会回绕。它每2^32个采样点就归零一次。如果没有NTP时间戳做参考,你根本不知道它到底跑了多久。SR包里的NTP时间戳就是用来解决这个问题的——它给了你一个绝对的参考系。
实战技巧:在接收端维护一个映射表,记录每次收到的SR包中的(NTP, RTP)对。当需要把某个RTP时间戳转换成绝对时间时,用线性插值就能算出来。我一般会保留最近两个SR包的数据,这样即使网络有抖动,也能保持精度。
3.3 CNAME机制与会话管理
CNAME是Canonical Name的缩写,规范名称。每个参与RTP会话的端点都有一个CNAME,用来唯一标识这个端点。为什么不用SSRC?因为SSRC可能会变——比如端点重启后,SSRC可能重新分配。但CNAME是固定的。
CNAME的格式通常是 user@host 或者 host:port。比如:
alice@192.168.1.100
RTCP的SDES包(Source Description)就是用来传递CNAME的。它和SR包、RR包一起,构成了RTCP的完整控制体系。
CNAME的作用主要有三个:
- 关联多个流:同一个端点的音频流和视频流,虽然SSRC不同,但CNAME相同。接收端通过CNAME就能知道它们来自同一个设备。
- 会话管理:当端点离开或加入会话时,通过CNAME可以追踪状态。
- 调试排错:我在排查问题时,第一件事就是看CNAME是否一致。如果同一个设备的音频和视频CNAME不同,那肯定是配置出了问题。
避坑指南:我曾经遇到过一个案例,两个不同的设备居然用了相同的CNAME。结果接收端把它们的音视频流混在一起,画面和声音完全对不上。排查了半天才发现是CNAME冲突。所以,CNAME一定要保证全局唯一,建议用UUID或者MAC地址+进程ID的组合。
3.4 RTCP报告间隔与带宽控制
RTCP不能发得太频繁,否则会占用太多带宽。RFC 3550规定了一个算法,用来计算RTCP的发送间隔。基本原则是:RTCP的带宽占用不超过总会话带宽的5%。
计算公式大致是这样的:
interval = (avg_rtcp_size / rtcp_bw) × (number_of_participants)
其中:
avg_rtcp_size:平均每个RTCP包的大小rtcp_bw:分配给RTCP的带宽(总带宽的5%)number_of_participants:会话中的参与者数量
参与者越多,间隔越长。这是为了防止RTCP风暴。我见过一个极端案例:一个会议有200个人,RTCP间隔被拉长到30秒以上。结果SR包来得太慢,接收端的时间戳映射一直不准,音画不同步持续了十几秒才纠正过来。
经验之谈:对于实时性要求高的场景(比如直播、视频会议),我建议把RTCP带宽比例从5%提高到10%。虽然会多占一点带宽,但同步精度能提升不少。当然,前提是你的网络带宽足够。
下面这张图展示了RTCP SR包在音视频同步中的核心作用:
最后,关于RTCP的带宽控制,还有一个细节:RTCP包是分组的。SR包和RR包(Receiver Report)会交替发送。发送端发SR,接收端发RR。RR包里包含丢包率、抖动、延迟等信息,用来反馈网络质量。
我个人的习惯是,在接收端收到SR包后,立即更新本地的时间戳映射表。同时,如果连续3个RTCP间隔都没收到SR包,就触发一次主动同步请求——虽然RTCP没有专门的请求包,但可以通过发送RR包来触发发送端重新发送SR。
小技巧:在调试阶段,可以手动降低RTCP间隔,比如改成1秒一次。这样能更快地看到同步效果。但上线前一定要改回标准值,否则带宽会爆炸。