1. RTP协议基础回顾:RTP/RTCP协议栈、RTP头部格式、Payload Type、SSRC与CSRC
各位同学,咱们今天先不急着讲流控和编解码协同。万丈高楼平地起,RTP协议栈就是这栋楼的地基。我见过不少工程师,上来就调拥塞控制算法,结果抓包一看,连SSRC都配错了——那真是白忙活一场。
所以第一章,咱们老老实实把RTP/RTCP协议栈、头部格式、Payload Type、SSRC和CSRC这几个核心概念捋一遍。嗯,都是干货,我尽量讲得接地气些。
1.1 RTP/RTCP协议栈:一对孪生兄弟
RTP全称是Real-time Transport Protocol,实时传输协议。它跑在UDP上面,专门负责传音视频数据。但RTP自己不管质量——丢包了、延迟了、抖动大了,它一概不闻不问。
那谁管?RTCP(RTP Control Protocol)。
说白了,RTP是送货的司机,RTCP是调度员。司机只管把货送到,调度员才关心路况好不好、要不要换条路。
核心关系:RTP负责媒体数据的实时传输,RTCP负责传输质量监控和会话控制。两者缺一不可。
我刚开始做流媒体时,有个项目只开了RTP端口,没开RTCP。结果线上跑了一个月,用户投诉卡顿,我们连基本的丢包率都拿不到。后来加上RTCP,五分钟就定位到是某个运营商节点在丢包。你说这教训值不值?
来看一下典型的协议栈结构:
你想想看,为什么RTP要选UDP而不是TCP?因为实时性。TCP丢包重传那一套,在音视频通话里根本等不起。RTP允许丢包,但要求低延迟——这是设计哲学的根本差异。
1.2 RTP头部格式:12字节的固定头
RTP头部固定12字节。不多不少,刚好够用。我当年背这个头部结构背得滚瓜烂熟,后来调bug时发现,很多问题看一眼头部就能定位。
| 偏移(字节) | 字段 | 长度(bit) | 说明 |
|---|---|---|---|
| 0 | V(版本) | 2 | 当前版本为2 |
| 0 | P(填充) | 1 | 1表示尾部有填充字节 |
| 0 | X(扩展) | 1 | 1表示头部后有扩展头 |
| 0 | CC(CSRC计数) | 4 | CSRC标识符的数量 |
| 1 | M(标记) | 1 | 应用自定义标记位 |
| 1 | PT(Payload Type) | 7 | 标识负载类型 |
| 2-3 | 序列号 | 16 | 每发送一个包递增1 |
| 4-7 | 时间戳 | 32 | 采样时刻,单位由负载类型决定 |
| 8-11 | SSRC | 32 | 同步源标识符 |
| 12+ | CSRC列表 | 32×CC | 贡献源标识符列表 |
小技巧:序列号用来检测丢包和排序,时间戳用来计算抖动和播放同步。这两个字段是RTP头部里最核心的调试信息。我抓包时第一眼看的就是序列号有没有断层。
这里有个细节很多人会忽略——时间戳的单位不是毫秒,而是由采样率决定的。比如音频用8000Hz采样,那时间戳每增加1就代表125微秒。视频用90000Hz,每增加1代表约11微秒。为什么视频用90000Hz?因为要兼容多种帧率,90000是常见帧率(24、25、30、50、60)的最小公倍数。
1.3 Payload Type:告诉接收端这是什么数据
Payload Type(PT)占7位,取值范围0-127。其中0-95是静态分配的类型,96-127是动态类型,由SDP协商决定。
我举个例子:
| PT值 | 编码格式 | 采样率(Hz) | 类型 |
|---|---|---|---|
| 0 | PCMU | 8000 | 静态 |
| 3 | GSM | 8000 | 静态 |
| 8 | PCMA | 8000 | 静态 |
| 96 | H.264 | 90000 | 动态 |
| 97 | Opus | 48000 | 动态 |
| 98 | VP9 | 90000 | 动态 |
注意:动态PT值(96-127)必须在SDP中明确映射。我曾经遇到一个兼容性问题:A端用PT=96表示H.264,B端用PT=96表示VP9,结果画面花屏了半小时才定位到问题。所以,PT映射一定要在信令阶段确认清楚。
为什么要有静态和动态之分?静态类型是为了保证最基本的互通——你总得让两个设备在不协商的情况下也能传个G.711语音吧?动态类型则给了编解码器扩展的空间,H.265、AV1这些新编码都可以通过动态PT来支持。
1.4 SSRC与CSRC:谁在说话?
SSRC(Synchronization Source)是同步源标识符,32位随机数。每个RTP流有且只有一个SSRC。你想想看,一个会议里有三个人同时说话,接收端怎么知道哪段数据是谁的?靠SSRC区分。
CSRC(Contributing Source)是贡献源标识符。它出现在混音场景中。比如MCU(多点控制单元)把三个人的音频混成一路,那这路RTP包的SSRC是MCU自己的,CSRC列表里则填上三个原始参与者的SSRC。
我画个图帮你理解:
看到没?接收端收到的RTP包,SSRC是MCU的,但CSRC列表告诉它:这包数据其实是A、B、C三个人混出来的。这样接收端就知道该给谁显示谁在说话了。
实战经验:SSRC冲突是个坑。两个终端随机到了同一个SSRC值,概率虽然低(1/2^32),但在大规模部署中确实发生过。RTCP的SDES报文里有个CNAME字段,可以用来做冲突检测和恢复。我建议你在实现时一定要处理SSRC冲突,别指望那32位随机数万无一失。
1.5 小结:地基打牢了再盖楼
好了,这一章的内容就这些。RTP头部12字节,PT告诉你怎么解码,SSRC告诉你是谁发的,CSRC告诉你是谁贡献的。RTCP在旁边默默记着丢包率、抖动、延迟这些指标。
这些概念看起来简单,但实际项目中出问题往往就出在这些基础点上。我见过有人把PT=0当成PCMA来解(其实是PCMU),也见过SSRC写死在代码里导致多路流冲突。嗯,基础不牢,地动山摇。