RTP & RTCP 协议精讲
一、RTP/RTCP 与 RTSP 的关系
上一篇讲的 RTSP 是"遥控器"——控制播/停/跳。它不搬运数据。搬运数据的是 RTP,监控传输质量的是 RTCP。
RTSP:遥控器(控制层) → "从第 30 秒开始播"
RTP :搬运工(数据层) → 把编码后的帧打包发出去
RTCP:质检员(反馈层) → "你丢了 3% 的包,抖动 12ms"
| RTSP | RTP | RTCP | |
|---|---|---|---|
| 功能 | 控制"播/停/跳" | 搬运媒体数据 | 反馈传输质量 |
| 传输层 | TCP | UDP(或 TCP 交错) | UDP(RTP 端口 +1) |
二、RTP 包格式
RTP 头部只有 12 字节固定头 + 载荷。极度精简,尽可能多的字节留给实际音视频数据。
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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 载荷 (Payload) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
逐字段:
| 字段 | 位数 | 含义 |
|---|---|---|
| V | 2 bit | 版本,固定为 2 |
| P | 1 bit | 填充标志 |
| X | 1 bit | 是否有头部扩展 |
| CC | 4 bit | CSRC 数量(混音场景) |
| M | 1 bit | 标记位。视频中通常标记一帧最后一个分片 |
| PT | 7 bit | 载荷类型(96=动态分配的 H.264 等) |
| Sequence Number | 16 bit | 每包递增 1,用于丢包检测和重排序 |
| Timestamp | 32 bit | 采样时刻,不是发送时刻(详解见下文) |
| SSRC | 32 bit | 同步源标识,随机生成,唯一标识一路流 |
三、RTP 三大核心机制(重点)
3.1 序列号——丢包检测
每发一个 RTP 包递增 1。接收方通过连续性检测丢包:
收到: 100, 101, 102, 105, 106
↑
103, 104 丢了,gap = 2
16 位会从 65535 回绕到 0,用 int16_t 差值即可正确处理。
3.2 时间戳——播放定时与同步(最重要)
时间戳标记的不是"发送时刻",而是载荷中第一个采样的时刻。 这是最容易误解的点。
单位取决于时钟频率,由编码格式决定:
| 编码 | 时钟频率 | 每帧递增 | 含义 |
|---|---|---|---|
| G.711 音频 | 8000 Hz | 160 | 160/8000 = 20ms 一帧 |
| AAC 音频 | 44100 Hz | 1024 | 1024/44100 ≈ 23.2ms |
| H.264 视频 | 90000 Hz | 3600 | 3600/90000 = 40ms(25fps) |
为什么视频用 90kHz? 因为 90000 能被所有常见帧率整除:24fps→3750, 25fps→3600, 30fps→3000, 60fps→1500。
关键规则:同一视频帧的所有 RTP 分片,时间戳相同。
一帧 4500 字节拆成 3 个 RTP 包(MTU 1400):
RTP(seq=200, ts=180000, M=0) ← 帧数据的第 1 段
RTP(seq=201, ts=180000, M=0) ← 帧数据的第 2 段
RTP(seq=202, ts=180000, M=1) ← 帧数据的第 3 段,M=1 标记帧结束
序列号递增,时间戳不变。接收方靠 M=1 或 FU-A 的 E 标志判断一帧组装完毕。
3.3 SSRC——标识唯一的媒体源
32 位随机标识符。一个摄像头的视频流一个 SSRC,音频流另一个 SSRC。
视频流: SSRC=0xAAAA, 所有视频包共享
音频流: SSRC=0xBBBB, 所有音频包共享
SSRC 可能因冲突而变,但 CNAME(在 RTCP SDES 中)永远不变,用来关联"换了 SSRC 但其实是同一个源"的流。
四、H.264 载荷打包(工程重点)
RTP 头部只解决了"从哪来、第几个、什么时刻"。编码数据如何塞进载荷,由载荷格式定义。H.264 最常见,有三种打包方式:
4.1 单 NAL 单元——小包直接放
SPS、PPS 这种小 NAL 直接放入载荷:
RTP Header (12 bytes) + NAL Unit (完整)
4.2 FU-A 分片——大包拆开
IDR 帧可能几十 KB,超过 MTU,需要拆分。FU-A(Fragmentation Unit type A)是最常用的打包方式。
RTP Header (12 bytes)
+ FU Indicator (1 byte): [F | NRI | Type=28]
+ FU Header (1 byte): [S | E | R | Type]
+ FU Payload (分片数据)
FU Header 中:
S=1: 这是首个分片
E=1: 这是最后分片
Type: 原始 NAL 类型(如 5=IDR)
完整示例: 一个 4500 字节的 IDR NAL(Type=5),拆成 4 个 RTP 包:
包 1 (seq=200, ts=180000, M=0):
FU Indicator: 0x7C (F=0, NRI=3, Type=28)
FU Header: 0x85 (S=1, E=0, Type=5) ← 首片
Payload: 第 1~1398 字节
包 2 (seq=201, ts=180000, M=0):
FU Indicator: 0x7C
FU Header: 0x05 (S=0, E=0, Type=5) ← 中间
Payload: 第 1399~2796 字节
包 3 (seq=202, ts=180000, M=0):
FU Indicator: 0x7C
FU Header: 0x05
Payload: 第 2797~4194 字节
包 4 (seq=203, ts=180000, M=1):
FU Indicator: 0x7C
FU Header: 0x45 (S=0, E=1, Type=5) ← 尾片,M=1 标记帧结束
Payload: 第 4195~4500 字节
组装: SSRC相同 + 时间戳相同 + 序列号连续 + 从S=1到E=1
→ 去掉 FU-A 头部,恢复原始 NAL
4.3 STAP-A 聚合——多个小包合并
SPS+PPS 常聚合在一个 RTP 包中发送:
RTP Header (12 bytes)
+ STAP-A Header (1 byte, Type=24)
+ NAL1 Size (2 bytes) + NAL1 Data ← 如 SPS
+ NAL2 Size (2 bytes) + NAL2 Data ← 如 PPS
五、RTCP 协议(重点)
5.1 基础
RTCP 与 RTP 成对出现,使用 RTP 端口 +1:
RTP: UDP 30000
RTCP: UDP 30001
五种包类型:
| PT | 类型 | 作用 |
|---|---|---|
| 200 | SR (Sender Report) | 发送统计 + NTP↔RTP 时间映射 |
| 201 | RR (Receiver Report) | 丢包、抖动、延迟统计 |
| 202 | SDES | 源描述(CNAME 等) |
| 203 | BYE | 通知离开 |
| 204 | APP | 应用自定义 |
5.2 SR——音视频同步的关键
发送方周期性发送 SR,建立 NTP 时间 ↔ RTP 时间戳 的映射。这是实现音视频同步的核心。
SR 关键字段:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NTP timestamp, most significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NTP timestamp, least significant word | ← 8 字节 NTP 时间
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP timestamp | ← 同一时刻的 RTP 时间戳
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's packet count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's octet count | ← 累计发送字节数
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
音视频同步过程:
摄像头同时发出视频 SR 和音频 SR:
视频 SR (SSRC=0xAAAA):
NTP = T₀ (如 2024-01-15 12:00:00.000)
RTP_TS = 3450012 (90kHz)
音频 SR (SSRC=0xBBBB):
NTP = T₀ (同一时刻)
RTP_TS = 98765 (8kHz)
接收方建立映射:
视频: T₀ ↔ 3450012
音频: T₀ ↔ 98765
后续收到视频包(ts=3453612):
时间 = T₀ + (3453612 - 3450012)/90000 = T₀ + 40ms
后续收到音频包(ts=99085):
时间 = T₀ + (99085 - 98765)/8000 = T₀ + 40ms
两者相同 → 同步播放 ✓
5.3 RR——传输质量反馈
接收方向发送方反馈,核心字段:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| fraction lost | cumulative number of packets lost |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| extended highest sequence number received |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| interarrival jitter |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| last SR (LSR) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| delay since last SR (DLSR) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
四个核心指标:
1. Fraction Lost(8 位)—— 本轮丢包率
fraction_lost = (期望包数 - 实际收到) / 期望包数 × 256
示例: 期望 100 包,收到 97 → fraction = 3/100×256 ≈ 8 → 丢包率约 3.1%
2. Cumulative Lost(24 位)—— 累计丢包总数
cumulative_lost = (最高序列号 - 初始序列号 + 1) - 实际收到包数
3. Interarrival Jitter(32 位)—— 网络抖动
RFC 3550 给出的指数加权移动平均算法:
int transit = arrival_time - rtp_timestamp;
int d = transit - last_transit; // 相邻两包的 transit 差值
last_transit = transit;
jitter = jitter + (abs(d) - jitter) / 16; // EWMA,除以 16 是定点运算
单位是 RTP 时钟 tick。视频 90kHz 下,jitter=4500 表示 50ms 抖动。
4. LSR + DLSR——计算往返延迟(RTT)
T₁: 发送方发出 SR (NTP=T₁)
T₂: 接收方收到 SR → LSR = T₁ 的中间 32 位
T₃: 接收方发出 RR → DLSR = T₃ - T₂
T₄: 发送方收到 RR
RTT = T₄ - LSR - DLSR
T₁ T₂ T₃ T₄
SR发出 → SR收到 → RR发出 → RR收到
◄─ 网络 ─► ◄── 网络 ──►
◄── DLSR ──►
◄──────── RTT = T₄ - LSR - DLSR ────────►
六、丢包恢复:NACK 与 FEC
RTP 本身"发了就忘",不提供重传。丢包恢复是上层的事。
NACK——请求重传
接收方告诉发送方"我没收到 seq=201",请求重传。代价是至少一个 RTT 的额外延迟。
接收方 → 发送方: RTCP NACK (PID=201) "请重传 seq 201"
发送方 → 接收方: RTP (seq=201) [重传]
FEC——前向纠错
发送方额外发送纠错包,接收方直接恢复,不需要请求重传。
XOR FEC (最简单):
原始包: P1, P2, P3
FEC 包: F = P1 ⊕ P2 ⊕ P3
如果 P2 丢失 → P2 = F ⊕ P1 ⊕ P3(即时恢复,0 额外延迟)
| NACK | FEC | |
|---|---|---|
| 原理 | 请求重传 | 冗余编码 |
| 额外带宽 | 仅丢失时消耗 | 固定开销(5%-20%) |
| 恢复延迟 | ≥ 1 RTT | 0(立即恢复) |
| 适用场景 | 延迟容忍、丢包率低 | 延迟敏感、视频会议 |
七、Jitter Buffer(抖动缓冲区)
网络到达间隔不均匀,但播放必须均匀。Jitter Buffer 吸收抖动,平滑输出。
到达(不均匀): 35ms, 48ms, 22ms, 55ms
↓ Jitter Buffer ↓
播放(均匀): 40ms, 40ms, 40ms, 40ms
自适应策略:目标延迟 ≈ 2 × 测量抖动值,限制在 [min_delay, max_delay] 范围内。
- 设太小 → 抖动没吸收完,播放卡顿
- 设太大 → 流畅但延迟高
典型值:实时会议 60-200ms,安防监控 200-500ms。
八、NAT 穿越与安全
NAT 问题
RTP 走 UDP,与 RTSP 的 TCP 控制通道分离。NAT 设备只看到 TCP 连接,不知道 UDP 端口映射。
最常用的工程方案:RTP over TCP 交错模式(RTSP interleaved),牺牲延迟换可用性。所有 RTP/RTCP 复用 RTSP 的 TCP 连接,封装格式:
'$' (0x24) | Channel (1B) | Length (2B, 大端) | RTP/RTCP 数据
Channel: 0=视频RTP, 1=视频RTCP, 2=音频RTP, 3=音频RTCP。
SRTP 加密
标准 RTP 明文传输。SRTP(RFC 3711)增加加密+认证:
┌──────────┬──────────────────┬──────────────┐
│ RTP 头部 │ AES-128 加密载荷 │ HMAC-SHA1 认证│
│ (明文) │ │ Tag (10B) │
└──────────┴──────────────────┴──────────────┘
WebRTC 使用 DTLS-SRTP 协商密钥。
九、关键数值速查
| 指标 | 值 |
|---|---|
| RTP 固定头部 | 12 字节 |
| RTP+UDP+IP 总开销 | 40 字节/包 |
| MTU | 1500 字节 |
| RTP 有效载荷上限 | ~1400 字节 |
| 视频时钟 | 90 kHz |
| RTCP 带宽上限 | 会话总带宽的 5% |
| RTCP 最小发送间隔 | 5 秒 |
| H.264 每帧 RTP 包数 | ceil(帧大小 / 1400) |
RTP 包大小实例(H.264, 25fps, 2Mbps):
每帧平均 = 2,000,000 / 8 / 25 = 10,000 字节
每帧 RTP 包数 = ceil(10000/1400) ≈ 8 包
每秒包数 = 200 pps
头部开销 = 200 × 12 = 2,400 B/s = 19.2 kbps(仅占码率 0.96%)
总结
RTSP → 控制"播什么"
RTP → 负责"怎么搬" → 12字节头 + 序号 + 时间戳 + SSRC
RTCP → 监控"搬得怎样" → SR(时间映射) + RR(丢包/抖动/RTT)
RTP 刻意轻量——不做重传、不做拥塞控制、不做流量管理。它只做一件事:把带序号和时间戳的小包发出去。复杂的事情全部留给上层(FEC/NACK/Jitter Buffer/拥塞控制)和搭档(RTCP 反馈)处理。
理解这套骨架,上层的自适应码率、丢包恢复、音视频同步就都有了立足点。