1. RTP协议基础回顾:RTP/RTCP协议栈结构、RTP头部格式详解、Payload Type与编解码器映射、SSRC与CSRC机制
各位同学,咱们今天正式开讲。第一节课,我打算带大家把RTP协议的老底翻一翻。你可能会说:“RTP嘛,不就是个传输音视频数据的协议吗?”嗯,话是没错,但WebRTC里对RTP的用法,跟传统流媒体还真不太一样。我当年刚接触WebRTC时,就因为在SSRC冲突问题上栽了个跟头,排查了整整两天……后来才发现,是对基础理解不够透。
所以这节课,咱们把地基打牢。后面29章,你才能盖得起高楼。
1.1 RTP/RTCP协议栈结构
先看整体架构。RTP全称是Real-time Transport Protocol,实时传输协议。它跑在UDP上面,专门用来传音视频数据。RTCP是它的控制兄弟,负责传质量反馈、统计信息。
我习惯把这一层关系画成下面这张图,你一看就明白:
说白了,RTP就是负责把摄像头采集到的每一帧画面、麦克风录到的每一段声音,切成一个个小包,打上时间戳和序号,扔到网络里。RTCP则在旁边默默统计:丢包率多少?延迟多大?然后告诉发送方“你该调整码率了”。
这里有个细节我提醒你注意:RTP和RTCP共用一对UDP端口,RTP用偶数端口,RTCP用下一个奇数端口。比如你用端口10000传RTP,那RTCP就是10001。这是RFC 3550规定的,WebRTC里也严格遵循。
核心记忆点:RTP传数据,RTCP传控制。两者缺一不可。没有RTCP,发送方就是个瞎子,不知道网络状况如何。
1.2 RTP头部格式详解
接下来咱们拆开RTP包,看看它的头部长什么样。我当年第一次看RFC 3550里的头部格式图,说实话,有点懵。但后来自己抓包分析了几次,就全通了。
标准的RTP头部固定12字节,结构如下:
| 偏移(字节) | 字段 | 长度(bit) | 说明 |
|---|---|---|---|
| 0 | V(版本) | 2 | 当前为2,RFC 3550定义 |
| 0 | P(填充) | 1 | 为1时尾部有填充字节 |
| 0 | X(扩展) | 1 | 为1时头部后有扩展头 |
| 0 | CC(CSRC计数) | 4 | 表示CSRC标识符数量 |
| 1 | M(标记) | 1 | 视频帧边界标记 |
| 1 | PT(负载类型) | 7 | 标识编解码器类型 |
| 2-3 | 序列号 | 16 | 每发一个包+1,用于检测丢包和排序 |
| 4-7 | 时间戳 | 32 | 采样时刻,用于播放同步 |
| 8-11 | SSRC | 32 | 同步源标识符,唯一标识一个流 |
| 12-...(可选) | CSRC列表 | 32×CC | 贡献源列表,混音时使用 |
嗯,这里我要重点说几个字段,因为WebRTC里经常跟它们打交道:
- 序列号(Sequence Number):每发一个RTP包就加1。接收方靠它判断有没有丢包。我遇到过一个问题:发送方序列号从0开始,但接收方期望从随机值开始,结果前几个包全被当成乱序包丢弃了……后来发现是没做序列号初始化同步。
- 时间戳(Timestamp):注意,它不是绝对时间,而是采样时钟的计数。比如音频用48kHz采样率,那每增加1就代表1/48000秒。视频则常用90kHz时钟。
- M标记位(Marker):视频里,一帧可能拆成多个RTP包。最后一个包会把M位置1,告诉接收方“这一帧齐了”。
我的小技巧:调试RTP问题时,我习惯先用Wireshark抓包,然后过滤 rtp,直接看序列号和时间戳的走势。如果序列号出现大的跳变,基本就是丢包了。如果时间戳长时间不变,那编码器可能卡住了。
1.3 Payload Type与编解码器映射
PT字段只有7个bit,取值范围0-127。其中0-96是静态分配,RFC 3551里写死了。比如:
- PT=0:PCMU(G.711 μ-law)
- PT=8:PCMA(G.711 A-law)
- PT=111:G.722
但WebRTC里用的编解码器,比如VP8、VP9、H.264、Opus,它们的PT值都是动态分配的。什么意思呢?就是在SDP协商阶段,双方商量好:你用PT=96代表VP8,我用PT=100代表Opus。
举个例子,SDP里会这样写:
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
a=rtpmap:96 VP8/90000
a=rtpmap:97 VP9/90000
a=rtpmap:98 H264/90000
这里96、97、98就是动态PT值,分别映射到VP8、VP9、H.264。时钟频率都是90000Hz,这是视频的标准值。
注意避坑:我曾经遇到一个互操作问题:Chrome发出来的视频流PT=96,但接收方是旧版Firefox,它硬编码认为PT=96是H.263。结果解码全乱码。后来发现是SDP协商时Firefox没正确解析rtpmap。所以,永远不要假设对端能正确理解动态PT,一定要在SDP里明确声明。
音频也一样,Opus通常用PT=111:
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=rtpmap:111 opus/48000/2
48000是采样率,2是声道数(立体声)。
1.4 SSRC与CSRC机制
最后咱们聊聊SSRC和CSRC。这两个概念,说白了就是“谁发的这个包”。
SSRC(同步源):每个RTP流都有一个唯一的32位SSRC标识符。比如你开了一个WebRTC通话,你的摄像头视频流有一个SSRC,你的麦克风音频流有另一个SSRC。接收方靠SSRC区分不同的流。
SSRC是随机生成的,但冲突概率虽然低,不是零。RFC 3550里专门有一节讲SSRC冲突检测和解决。WebRTC里也实现了这个机制:如果检测到冲突,就换一个新的SSRC,并发送RTCP BYE包通知对端。
CSRC(贡献源):这个在混音场景下用。比如一个会议服务器把3个人的音频混成一路RTP流,那SSRC是服务器的,CSRC列表里放那3个人的原始SSRC。接收方看到CSRC列表,就知道这包音频是混了哪几个人的声音。
我画了张图帮你理解:
在WebRTC里,SSRC的分配通常由SFU(选择性转发单元)或MCU(混音单元)来管理。如果是P2P通话,那就简单了,每个媒体轨道一个SSRC。
一句话总结:SSRC标识“这个包属于哪个流”,CSRC标识“这个流是由哪些源混合而成的”。WebRTC里,SSRC冲突是灾难性的,会导致音视频串流。所以框架内部有完善的冲突检测机制,你一般不用操心,但排查问题时得知道有这回事。
好了,这一章的基础内容就到这里。RTP头部那12个字节,你最好能背下来——不是死记硬背,而是理解每个字段为什么存在。后面讲NACK、FEC、带宽估计时,你会反复用到这些知识。