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 头部字段不是孤立的,它们共同决定接收端如何解析、何时播放、如何反馈。下面用表格梳理关键字段与实战含义。
| 字段 | 长度 | 含义 | 实战用途 |
|---|---|---|---|
| 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。排查思路如下:
- 先看序列号缺口:判断是网络丢包还是发送端未发。
- 再看时间戳增量:若增量忽大忽小,可能是采集抖动或编码器输出不稳定。
- 检查 PT 与 SDP 是否一致:若 PT 映射错误,解码器会选错。
- 检查 X 位与扩展头:若扩展头解析错误,反馈信息可能丢失,影响流控。
- 检查 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 与扩展头放在同一条数据流中理解,才能在真实网络环境中定位问题、优化体验。