2、RTP协议核心:RTP头部结构、SSRC与CSRC、RTP时间戳机制、Payload Type
各位同学,咱们今天来啃RTP协议的核心。说实话,RTP这玩意儿看着简单,但坑特别多。我当年第一次做音视频同步时,就被时间戳给坑惨了——画面和声音差了快两秒,排查了一整天才发现是时间戳基值没对齐。好,咱们从头捋一遍。
2.1 RTP固定头部结构:12字节的硬规矩
RTP头部,说白了就是12个字节的固定结构。不管你是传H.264还是传G.711,这12个字节一个都不能少。我习惯把这12字节拆成四个32位字来看,这样好记。
| 偏移(字节) | 位域 | 字段 | 长度(bit) | 说明 |
|---|---|---|---|---|
| 0 | 0-1 | V (Version) | 2 | 版本号,当前固定为2 |
| 0 | 2 | P (Padding) | 1 | 填充标志,1表示尾部有填充字节 |
| 0 | 3 | X (Extension) | 1 | 扩展头部标志 |
| 0 | 4-7 | CC (CSRC Count) | 4 | CSRC标识符数量 |
| 1 | 0-7 | M (Marker) | 1 | 标记位,视频帧边界常用 |
| 1 | 1-7 | PT (Payload Type) | 7 | 负载类型,标识编码格式 |
| 2-3 | 0-15 | Sequence Number | 16 | 序列号,每发一个包+1 |
| 4-7 | 0-31 | Timestamp | 32 | 时间戳,采样时刻 |
| 8-11 | 0-31 | SSRC | 32 | 同步源标识符 |
| 12-... | 0-31 | CSRC List | 32*CC | 贡献源列表,仅当CC>0时存在 |
核心要点:RTP头部最小12字节,最大可以扩展到12 + 4*15 = 72字节(当CC=15且X=1时)。但实际项目中,99%的情况只用12字节固定头。
这里有个细节——序列号是16位的,意味着它会循环。从0到65535,然后回到0。我在项目中遇到过接收端没处理好序列号回绕,导致丢包检测全乱套的情况。嗯,后面讲丢包重传时咱们再细聊。
2.2 SSRC与CSRC:谁在说话?
SSRC,全称是Synchronization Source,同步源。说白了,就是给每个RTP流发一个唯一的身份证号。32位随机数,理论上冲突概率很低,但也不是零。
我记得有一次做多方视频会议,两个终端随机出来的SSRC居然撞了。接收端直接疯掉,画面来回跳。后来怎么解决的?我们在SIP信令里加了个SSRC协商机制,强制服务端分配。
实战建议:SSRC冲突虽然概率低,但生产环境一定要做冲突检测。RFC 3550建议:收到同一SSRC但源传输地址不同的包时,必须进行冲突处理。最简单的做法是重新生成SSRC并发送RTCP BYE。
CSRC呢?Contributing Source,贡献源。这玩意儿在混音场景下特别有用。举个例子:一个MCU(多点控制单元)把三个人的音频混成一路RTP流,那这路流的SSRC是MCU自己的,但CSRC列表里会填上那三个人的SSRC。接收端看到CSRC列表,就知道这包音频是谁贡献的。
CSRC最多15个,因为CC字段只有4位。你想想看,15路混音,一般场景也够用了。
2.3 RTP时间戳机制:同步的灵魂
时间戳,这是RTP里最容易被误解的字段。很多人以为时间戳就是绝对时间,其实不是。RTP时间戳是一个单调递增的计数器,它的增量取决于采样率和帧间隔。
咱们直接看公式:
时间戳增量 = 采样率 / 帧率
举例:
- 音频:采样率 8000Hz,帧长 20ms → 增量 = 8000 * 0.02 = 160
- 视频:采样率 90000Hz,帧率 30fps → 增量 = 90000 / 30 = 3000
注意:视频的RTP时间戳采样率固定为90000Hz,这是历史原因——MPEG标准定的。音频则用实际采样率,比如8000、16000、44100、48000。千万别搞混!
我刚开始做流媒体时,犯过一个低级错误:把视频时间戳的增量算成了90000/25=3600,结果25fps的视频在接收端播放时一卡一卡的。后来才发现,我算的是理论值,但编码器实际输出帧率是29.97,增量应该是90000/29.97≈3003。差这3个计数,累积几秒就明显不同步了。
时间戳的初始值,RFC建议随机初始化。为什么?为了防止加密场景下的已知明文攻击。但实际项目中,很多实现直接设为0,也没出大问题。我个人习惯用随机值,毕竟安全无小事。
2.4 Payload Type:告诉对方怎么解码
PT字段,7位,取值范围0-127。其中0-96是静态分配的类型,96-127是动态类型,需要通过SDP协商。
| PT值 | 编码格式 | 采样率(Hz) | 声道数 |
|---|---|---|---|
| 0 | PCMU (G.711 μ-law) | 8000 | 1 |
| 3 | GSM | 8000 | 1 |
| 8 | PCMA (G.711 A-law) | 8000 | 1 |
| 10 | L16 (线性PCM 16位) | 44100 | 2 |
| 26 | JPEG (MJPEG) | 90000 | - |
| 31 | H.261 | 90000 | - |
| 96-127 | 动态类型(H.264/HEVC/Opus等) | 由SDP指定 | 由SDP指定 |
你看,H.264没有静态PT值,所以必须用动态类型。在SDP里这样写:
m=video 5004 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1;profile-level-id=42e01f
这里96就是动态PT,后面跟着编码名和时钟频率。fmtp行是编码参数,H.264的packetization-mode=1表示使用非交错模式,这是最常用的。
避坑指南:我曾经遇到一个设备,它把H.264的PT硬编码成99,但SDP里写的是96。结果接收端按96去查编码表,发现没注册,直接丢包。所以PT值一定要和SDP协商结果一致,别自己硬写。
2.5 知识体系总览
说了这么多,咱们用一张图把RTP核心串起来。这张图我画了好几次才满意,把头部结构、SSRC/CSRC、时间戳、PT的关系都理清了。
这张图把RTP核心的四个要素串起来了。你仔细看,头部字段是骨架,SSRC/CSRC是身份标识,时间戳是同步的命脉,PT是解码的钥匙。四者缺一不可。