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/RTCP协议栈结构 应用层(H.264 / Opus / VP9) RTP / RTCP 层 UDP 层 IP 层 说明: RTP和RTCP共享同一对UDP端口(通常RTP为偶数,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。

我画个图帮你理解:

SSRC与CSRC关系示意图 发送端A SSRC=0x1234 发送端B SSRC=0x5678 发送端C SSRC=0x9ABC MCU(混音器) SSRC=0xDEF0 接收端 RTP包头部: SSRC=0xDEF0 CSRC=[0x1234, 0x5678, 0x9ABC]

看到没?接收端收到的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写死在代码里导致多路流冲突。嗯,基础不牢,地动山摇。