2. RTP协议基础

要解决音画不同步,必须先看懂 RTP 报文本身。RTP(Real-time Transport Protocol)并不负责“保证送达”,它负责的是把媒体采样按时间关系封装成一个个可被接收端重排、播放和同步的数据包。换句话说,RTP 头部里的每一个字段,几乎都对应着音画同步问题的一个线索:序列号告诉你包有没有丢、有没有乱序;时间戳告诉你这段媒体应该在什么时刻播放;SSRC 告诉你这包属于哪一路媒体流;载荷类型告诉你这是音频还是视频、用什么编码。理解这些字段,是后续排查抖动、丢包、时间戳跳变和音视频漂移的基础。

一、RTP 报文结构:固定头 + 扩展头 + 载荷

RTP 报文由固定头部、可选的 CSRC 列表、可选的扩展头和载荷组成。固定头部最小 12 字节,是所有 RTP 包都必须具备的部分。其结构如下:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

固定头之后,如果 X=1,则存在扩展头;扩展头之后才是真正的媒体载荷。载荷可能是 Opus 音频帧、H.264 NAL 单元、VP8 分片等。RTP 本身不解释载荷内容,解释工作交给载荷类型和对应的编解码规范。

二、头部字段含义:每个字段都在回答一个同步问题

字段 长度 含义 与音画同步的关系
V(Version) 2 bit RTP 版本,通常为 2 版本不一致会导致解析失败
P(Padding) 1 bit 载荷末尾是否有填充字节 影响载荷边界计算
X(Extension) 1 bit 是否有扩展头 扩展头常携带传输时间信息
CC(CSRC Count) 4 bit CSRC 标识符数量 用于混音场景的源归属判断
M(Marker) 1 bit 标记重要事件,如视频帧结束 帮助识别帧边界,影响播放节奏
PT(Payload Type) 7 bit 载荷类型,标识编码格式 决定如何解码和时钟频率
Sequence Number 16 bit 序列号,每发一个包递增 1 检测丢包、乱序、重复包
Timestamp 32 bit 采样时刻,单位由时钟频率决定 音视频同步的核心依据
SSRC 32 bit 同步源标识,标识一路媒体流 区分音频流和视频流
CSRC 32 bit × N 贡献源列表,用于混音场景 追溯混音后的原始源

三、序列号、时间戳与 SSRC:同步的三根支柱

序列号是 16 位无符号整数,每发送一个 RTP 包递增 1。接收端通过序列号可以判断是否丢包、是否乱序、是否重复。例如收到 100、101、103,说明 102 丢失;收到 100、102、101,说明发生乱序。序列号本身不携带时间信息,但它决定了接收端能否把包按正确顺序交给抖动缓冲区和解码器。

时间戳是 32 位无符号整数,表示该包中第一个采样字节的采样时刻。它的单位不是秒,而是“采样时钟周期”。音频常用 8000 Hz、16000 Hz、48000 Hz,视频常用 90000 Hz。例如音频时钟频率为 48000 Hz,每包携带 20 ms 音频,则相邻包时间戳增量应为 960。时间戳是音画同步的核心:接收端用时间戳把音频和视频映射到同一条播放时间轴上。

SSRC 是 32 位同步源标识,用来区分同一会话中的不同媒体流。典型 WebRTC 会话中,音频流和视频流各有一个 SSRC。接收端通过 SSRC 把包归类到对应的流,再分别做抖动缓冲、解码和渲染。如果 SSRC 冲突或变化,接收端可能把两路流混在一起,导致播放异常。

CSRC 用于混音场景。当多个源经过混音器合并后,混音器会把自己的 SSRC 作为主 SSRC,把原始源的 SSRC 放入 CSRC 列表。这样接收端既能播放混音结果,又能知道声音来自哪些原始参与者。

核心记忆:序列号管“顺序”,时间戳管“时刻”,SSRC 管“哪一路”,PT 管“怎么解”,CSRC 管“混音来源”。音画不同步排查时,先看时间戳,再看序列号,最后确认 SSRC 和 PT 是否匹配。

四、载荷类型与时钟频率:同步计算的分母

载荷类型 PT 决定了解码方式和时钟频率。常见静态 PT 包括 PCMU(0,8000 Hz)、PCMA(8,8000 Hz)、G.722(9,8000 Hz)等。动态 PT 通常通过 SDP 协商,例如 Opus 常用 111,时钟频率 48000 Hz;H.264 常用 96~127 中的某个值,时钟频率 90000 Hz。时钟频率决定了时间戳增量的含义:同样增加 48000,在 48000 Hz 音频中代表 1 秒,在 90000 Hz 视频中只代表约 0.533 秒。因此,音视频同步计算必须使用各自的时钟频率,不能直接比较时间戳数值。

音频:PT=111 (Opus), clock=48000 Hz, 20ms/包
时间戳增量 = 48000 × 0.020 = 960

视频:PT=96 (H.264), clock=90000 Hz, 33.3ms/帧
时间戳增量 = 90000 × 0.0333 ≈ 3000

五、案例场景:一次典型的音画不同步排查

假设用户反馈“视频比音频慢半拍”。抓包后先看 RTP 头部:音频 SSRC 为 0x1A2B3C4D,视频 SSRC 为 0x5E6F7A8B,两者 PT 分别为 111 和 96,说明流区分正常。继续观察时间戳:音频时间戳每包增加 960,视频时间戳每帧增加 3000,增量符合 20 ms 和 33.3 ms 的预期,说明发送端节奏正常。再看序列号:视频序列号出现 100、101、104、105,说明中间丢了 102、103 两个包。视频丢包后,解码器等待后续帧或做错误隐藏,导致视频渲染时刻被推迟,而音频继续按时间戳播放,最终表现为视频滞后。此时解决方向不是调整时间戳,而是先处理视频丢包和抖动缓冲策略。

排查顺序建议:先确认 SSRC 是否稳定,再检查 PT 与时钟频率是否匹配,然后看序列号丢包/乱序,最后对比音视频时间戳映射到同一时间轴后的偏差。不要一上来就改时间戳,很多“不同步”其实是丢包或抖动造成的播放延迟。

六、常见误区与复盘

  • 误区一:时间戳就是毫秒。时间戳单位是采样周期,必须除以时钟频率才能换算成秒。
  • 误区二:序列号连续就没有丢包。序列号连续只能说明 RTP 层没丢,网络抖动仍可能导致到达时间不均匀。
  • 误区三:音频和视频时间戳可以直接相减。两者时钟频率不同,必须先换算到同一时间单位。
  • 误区四:SSRC 不重要。SSRC 变化会导致接收端重建流状态,可能引发播放中断或同步重置。
  • 误区五:CSRC 只和混音有关,和同步无关。在混音场景中,CSRC 能帮助定位是哪个原始源的时间戳异常。

掌握 RTP 报文结构和头部字段,是后续理解 RTCP 发送者报告、NTP 时间戳映射、抖动缓冲和音画同步算法的基础。只有先能准确读出每个字段的含义,才能在真实抓包中快速定位问题,而不是凭感觉猜测。

RTP 报文结构与音画同步关键字段 固定头 12 字节 + CSRC 列表 + 扩展头 + 载荷 RTP 固定头部(12 字节) V 2bit P 1bit X 1bit CC 4bit M 1bit PT 7bit Sequence Number 16bit Timestamp 32bit SSRC 32bit 同步源标识 CSRC 列表 32bit × CC 扩展头(X=1 时存在) 可选,常携带传输时间信息 载荷 Payload:Opus / H.264 / VP8 等编码数据 序列号 检测丢包 / 乱序 / 重复 决定包顺序 时间戳 采样时刻 / 时钟频率 音画同步核心依据 SSRC 区分音频流 / 视频流 标识一路媒体源 音频流示例 PT=111 (Opus) · clock=48000 Hz 20ms/包 → 时间戳增量 960 视频流示例 PT=96 (H.264) · clock=90000 Hz 33.3ms/帧 → 时间戳增量 ≈3000