1、RTP协议基础:RTP/RTCP协议栈概述、RTP在WebRTC中的角色、RTP数据包结构详解

各位好,我是老张。今天咱们正式进入《WebRTC中RTP协议的部署实践》的第一章。说实话,RTP这个协议看起来简单,但坑是真不少。我当年第一次做WebRTC项目时,就被RTP的时间戳和SSRC切换搞得焦头烂额。咱们今天先把地基打牢,后面实战才不会翻车。

1.1 RTP/RTCP协议栈概述

先看一张整体图,把RTP和RTCP在协议栈中的位置搞清楚。

核心认知:RTP(Real-time Transport Protocol)负责传输实时媒体数据,RTCP(RTP Control Protocol)负责监控传输质量。两者是伴生关系,缺一不可。

RTP/RTCP 协议栈分层结构 应用层(WebRTC / SIP / 流媒体) Opus / VP8 / H.264 编码数据 RTP 协议 媒体数据传输 端口:偶数(如 5004) RTCP 协议 质量反馈与控制 端口:奇数(如 5005) 传输层(UDP) 无连接、尽力而为、低延迟 网络层(IP) 路由、分片、寻址 💡 关键点: RTP 和 RTCP 共用同一 UDP 端口对(偶/奇),RTCP 报文间隔受带宽控制,默认不超过会话带宽的 5%。

这张图我画得比较细。你看,RTP和RTCP是并行的,都跑在UDP之上。为什么不用TCP?说白了,实时通信最怕延迟重传,UDP的"尽力而为"反而更适合。我做过一个测试,在丢包率5%的网络下,TCP的延迟能飙到800ms以上,而RTP配合FEC和NACK,延迟能控制在150ms以内。

1.2 RTP在WebRTC中的角色

WebRTC里,RTP不是可选项,是必选项。它承担了三个核心职责:

  • 媒体封装:把编码后的音频帧(Opus)和视频帧(VP8/VP9/H.264)切成RTP包,加上时间戳和序号。
  • 传输排序:接收端根据序号重组数据包,处理乱序到达的情况。
  • 丢包检测:通过序号间隙判断丢包,触发NACK请求重传。

我的经验:WebRTC内部对RTP做了不少扩展。比如RTP头部扩展(Header Extension)用于携带绝对时间戳、传输序列号等元数据。这些在标准RTP里没有,但WebRTC必须用。你抓包时看到RTP头长度超过12字节,别慌,那是扩展头。

举个例子,一个典型的WebRTC视频通话流程:

  1. 摄像头采集YUV帧 → 编码器压缩成VP8/VP9/H.264 NALU
  2. RTP打包器将NALU切片,每个切片加上RTP头(SSRC、序号、时间戳)
  3. 通过UDP Socket发送到对端
  4. 对端RTP解包器根据序号重组,时间戳用于音视频同步
  5. 解码器解码后渲染

你看,每一步都离不开RTP。没有RTP,WebRTC就是一堆裸编码数据,根本没法在网络上可靠传输。

1.3 RTP数据包结构详解

这是本章的重头戏。RTP固定头只有12字节,但每个字段都有讲究。咱们直接看结构:

偏移(字节) 字段 长度(位) 说明
0 V(版本) 2 当前为2,RFC 3550定义
0 P(填充) 1 为1时包尾有填充字节,用于对齐
0 X(扩展) 1 为1时头部后有扩展头
0 CC(CSRC计数) 4 CSRC标识符的数量,最多15个
1 M(标记) 1 视频帧结束、音频通话静音等标记
1 PT(负载类型) 7 标识编码格式,如96=H.264,111=Opus
2-3 序号(Sequence Number) 16 每发一个包+1,用于检测丢包和排序
4-7 时间戳(Timestamp) 32 采样时刻,音频8kHz/视频90kHz递增
8-11 SSRC(同步源标识符) 32 唯一标识一个媒体流,随机生成
12+ CSRC列表(可选) 32×CC 混音场景下标识原始源

⚠️ 避坑指南:我曾经在调试一个多路混音项目时,发现音频一直有杂音。查了两天才发现,CSRC列表里的SSRC写错了,导致接收端混音时引用了错误的源。记住:CSRC只在混音器(Mixer)场景下使用,点对点通话时CC必须为0。

咱们重点说几个容易出问题的字段:

1.3.1 序号(Sequence Number)

16位无符号整数,从随机初始值开始,每发一个包加1。接收端通过序号连续性判断丢包。举个例子:收到序号100、101、103,就知道102丢了,触发NACK请求重传。

这里有个坑:序号溢出。16位最大值65535,加1后归0。如果接收端没处理好回绕(Wrap-around),会把新包当成旧包丢弃。我见过一个开源项目因为这个bug导致视频每65秒卡顿一次。解决方案是用序列号差值比较,而不是直接比较大小。

1.3.2 时间戳(Timestamp)

32位,单位取决于采样时钟。音频通常用8kHz(即每125μs加1),视频用90kHz(每11.11μs加1)。时间戳用于:

  • 接收端播放同步(Jitter Buffer)
  • 音视频唇同步(Lip Sync)
  • 计算抖动(Jitter)

注意:时间戳的增量不是固定的。比如视频帧,如果编码器输出一帧,RTP包的时间戳相同(同一帧的多个切片),但下一帧的时间戳会跳变。增量 = 90000 / 帧率。30fps时,增量 = 3000。

关键公式:视频RTP时间戳增量 = 90000 / 帧率。例如:30fps → 3000,15fps → 6000。音频Opus 48kHz采样,每20ms一包,增量 = 48000 × 0.02 = 960。

1.3.3 SSRC(同步源标识符)

32位随机数,唯一标识一个RTP流。WebRTC中每个媒体轨道(Audio Track / Video Track)对应一个SSRC。SSRC冲突时,RTCP会发送BYE报文并重新协商。

我遇到过最头疼的问题:SSRC冲突导致视频花屏。两个客户端恰好生成了相同的SSRC,接收端把两个流混在一起了。后来我们在信令层加了SSRC校验,确保每个PeerConnection的SSRC全局唯一。

1.3.4 负载类型(PT)

7位,标准RTP定义了PT 0-127的映射,但WebRTC使用动态PT(96-127)。比如:

编码格式 典型PT值 说明
PCMU 0 标准G.711 μ-law
Opus 111 WebRTC默认音频编码
VP8 96 WebRTC默认视频编码
H.264 102 兼容场景常用

PT值在SDP协商中确定。你抓包时看到PT=111,就知道这是Opus音频包。

1.4 一个完整的RTP包抓包示例

咱们用Wireshark抓一个真实的RTP包,看看二进制长什么样:

Raw Hex Dump(以太网帧已剥离):
80 60 01 23 00 00 0E 10 12 34 56 78 [Payload...]

字段解析:
80 → V=2, P=0, X=0, CC=0
60 → M=0, PT=96(VP8)
01 23 → 序号 = 0x0123 = 291
00 00 0E 10 → 时间戳 = 0x00000E10 = 3600
12 34 56 78 → SSRC = 0x12345678

你看,12字节的固定头,信息量很大。实际项目中,你不需要手动解析这些字节,但理解每个字段的含义,对排查问题至关重要。

调试技巧:我习惯在WebRTC源码的RTP发送路径上加日志,打印每个包的序号和时间戳。配合Wireshark抓包对比,能快速定位是发送端问题还是网络问题。比如,发送端序号连续但接收端乱序,那就是网络抖动;发送端序号跳变,那就是发送端逻辑有bug。