3. RTP协议核心概念

RTP(Real-time Transport Protocol)并不负责“把数据可靠送到”,它负责把实时媒体以可被接收端重建的方式封装、编号、定时与标识。理解 RTP 的关键,不是背字段,而是理解每个字段在“编解码—传输—流控—播放”链路中承担的角色:序列号解决丢包与乱序,时间戳解决播放节奏与抖动,SSRC 解决多流复用与冲突,PT 解决负载如何解码,扩展头解决协议演进与反馈携带。

一、RTP报文结构:固定头 + 可选扩展 + 负载

RTP 报文由三部分组成:固定 12 字节头部、可选的头部扩展(CSRC 列表与扩展字段)、以及负载(Payload)。固定头中前 12 字节是所有 RTP 包都必须具备的,后续内容按标志位决定是否存在。其结构可概括为:

  • V/P/X/CC:版本、填充、扩展、CSRC 计数,决定头部后续如何解析。
  • M/PT:标记位与负载类型,标记位常用于帧边界或重要事件,PT 决定解码器与时钟频率。
  • Sequence Number:16 位序列号,每发一个 RTP 包递增 1,用于丢包检测与重排。
  • Timestamp:32 位时间戳,按负载时钟频率递增,用于播放时刻与抖动计算。
  • SSRC:32 位同步源标识,标识一条媒体流。
  • CSRC[]:0~15 个贡献源标识,用于混音、转码等场景标注原始源。

下面这张图把 RTP 头部字段与“编解码/流控协同”中的用途对应起来,便于建立字段到行为的映射。

RTP 报文结构(固定头 12B + 可选扩展 + 负载) 固定头 12 字节 V | P | X | CC | M | PT | Sequence Number | Timestamp | SSRC CSRC 列表(0~15 个) 混音/转码时标注贡献源 扩展头(X=1 时存在) profile + length + 扩展数据 负载 Payload 编码帧数据(如 Opus、H.264、VP8) 字段到协同行为的映射 Sequence Number → 丢包/乱序检测 → 流控重传或 FEC 决策 Timestamp → 播放时刻/抖动计算 → 自适应码率与缓冲策略 PT → 解码器选择/时钟频率 → 编解码参数协商

二、头部字段含义:从位域到行为

RTP 头部字段不是孤立的,它们共同决定接收端如何解析、何时播放、如何反馈。下面用表格梳理关键字段与实战含义。

字段 长度 含义 实战用途
V 2 bit 版本号,通常为 2 解析兼容性判断
P 1 bit 填充标志 负载末尾可能有填充字节
X 1 bit 扩展头标志 决定是否存在扩展字段
CC 4 bit CSRC 计数 决定 CSRC 列表长度
M 1 bit 标记位 视频帧结束、音频静音等事件
PT 7 bit 负载类型 选择解码器与时钟频率
Sequence Number 16 bit 序列号 丢包、乱序、重复检测
Timestamp 32 bit 时间戳 播放节奏、抖动计算
SSRC 32 bit 同步源 标识一条媒体流
CSRC 32 bit × N 贡献源 混音/转码源标注

三、时间戳、序列号与 SSRC/CSRC:实时性的三根支柱

序列号是接收端判断“有没有丢、有没有乱、有没有重”的第一依据。它每包递增 1,16 位回绕。接收端通过当前序列号与期望序列号的差值,可以判断丢包、乱序或重复。流控系统常把序列号缺口作为重传、FEC 或降码率的触发条件。

时间戳不是“发送时刻”,而是“媒体采样时刻”。它按负载时钟频率递增,例如 Opus 常用 48000 Hz,视频常用 90000 Hz。接收端用时间戳计算抖动、决定播放时刻,并驱动音视频同步。时间戳的增量与帧时长相关,若时间戳异常,播放端会出现加速、减速或卡顿。

SSRC 标识一条媒体流,例如音频流和视频流各有一个 SSRC。当多路流复用同一传输通道时,SSRC 用于区分。若发生 SSRC 冲突,接收端需要重新映射或触发冲突解决。CSRC 则用于混音场景:混音器把多个原始源的 SSRC 放入 CSRC 列表,接收端可据此做语音活动检测、界面提示或统计。

关键理解:序列号管“包”,时间戳管“帧”,SSRC 管“流”,CSRC 管“源”。四者协同,才能让接收端在丢包、抖动、多流并发的环境下正确重建媒体。

四、负载类型与扩展头机制:编解码协商与协议演进

PT(Payload Type)是 7 位字段,静态 PT 有固定含义(如 0 为 PCMU、8 为 PCMA),动态 PT 则通过 SDP 协商。PT 直接决定解码器选择与时钟频率,因此编解码协商失败时,常见现象是“能收包但无法解码”或“声音异常”。在 WebRTC 中,PT 与 codec 的映射由 SDP 的 rtpmap 行确定。

扩展头机制由 X 位触发,结构为:16 位 profile、16 位 length、以及 length 个 32 位字的扩展数据。扩展头让 RTP 在不破坏固定头的前提下携带额外信息,例如:

  • 传输层反馈:如 abs-send-time、transport-cc 等。
  • 音频电平:如 audio-level 扩展。
  • 视频方向:如 video-orientation 扩展。
  • 帧标记:如 frame-marking 扩展。

扩展头是 RTP 能持续演进的关键。它把“新能力”放在可选区域,旧实现可忽略,新实现可解析,从而在编解码与流控协同中传递更细粒度的控制信息。

五、案例与排障:从抓包看字段

假设一次视频通话出现卡顿,抓包发现 RTP 序列号有缺口,时间戳增量不稳定,PT 为动态值 96。排查思路如下:

  1. 先看序列号缺口:判断是网络丢包还是发送端未发。
  2. 再看时间戳增量:若增量忽大忽小,可能是采集抖动或编码器输出不稳定。
  3. 检查 PT 与 SDP 是否一致:若 PT 映射错误,解码器会选错。
  4. 检查 X 位与扩展头:若扩展头解析错误,反馈信息可能丢失,影响流控。
  5. 检查 SSRC 冲突:多流场景下若 SSRC 重复,接收端可能混流。
# 伪代码:接收端基于序列号与时间戳的简单判断
expected_seq = last_seq + 1
if seq != expected_seq:
    if seq < expected_seq:
        # 乱序或重复
        handle_reorder_or_duplicate(seq)
    else:
        # 丢包
        loss_count = seq - expected_seq
        trigger_fec_or_nack(loss_count)

# 时间戳抖动计算
arrival = now()
transit = arrival - timestamp / clock_rate
jitter = jitter + (abs(transit - last_transit) - jitter) / 16

常见误区:把时间戳当作“发送时间”使用,会导致抖动计算错误;把序列号当作可靠传输序号,会误以为 RTP 能保证不丢包;忽略 CSRC 与 SSRC 的区别,会在混音场景中无法追溯原始源。

六、复盘:字段协同的检查清单

  • 序列号是否连续?缺口是否触发重传/FEC?
  • 时间戳增量是否符合帧时长?时钟频率是否与 PT 匹配?
  • SSRC 是否唯一?多流是否冲突?
  • CSRC 是否在混音场景正确填充?
  • PT 是否与 SDP 协商一致?动态 PT 是否映射正确?
  • X 位与扩展头长度是否匹配?扩展解析是否越界?

掌握 RTP 报文结构与字段含义,是后续编解码协同与流控策略设计的基础。只有把序列号、时间戳、SSRC/CSRC、PT 与扩展头放在同一条数据流中理解,才能在真实网络环境中定位问题、优化体验。