RTP 之 H.264 封包和解包
根据课程视频整理:
159--4-RTP之H264封包和解包1-封包原理.mp4160--4-RTP之H264封包和解包2-封包原理.mp4目标:把 H.264 数据如何放进 RTP、什么时候单包发送、什么时候 FU-A 分片、接收端如何还原 NALU 这些基础流程讲清楚。
1. 基础概念
H.264 裸流由一个个 NALU 组成。每个 NALU 可以理解为一段独立的编码数据,常见类型包括 SPS、PPS、IDR 帧、非 IDR 帧等。
RTP 负责把这些 NALU 放进网络包里发送。问题在于:一个 NALU 的大小不固定,而网络包不能无限大。UDP 传输时尤其要考虑 MTU,如果 RTP payload 太大,底层 IP 分片会增加丢包和重传处理难度。

所以 H.264 over RTP 的核心判断很简单:
如果 NALU <= RTP payload 容量:
一个 NALU 放进一个 RTP 包,使用 Single NAL Unit
如果 NALU > RTP payload 容量:
一个 NALU 拆成多个 RTP 包,常用 FU-A

2. H.264 NALU Header
H.264 NALU 的第一个字节是 NALU Header:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+-+-+-+-+-+-+-+-+
字段含义:
| 字段 | 位数 | 含义 |
|---|---|---|
F | 1 bit | forbidden_zero_bit,正常应为 0 |
NRI | 2 bit | nal_ref_idc,表示参考优先级 |
Type | 5 bit | NALU 类型 |
常见 Type:
| Type | 含义 |
|---|---|
| 1 | 非 IDR 图像片 |
| 5 | IDR 图像片 |
| 6 | SEI |
| 7 | SPS |
| 8 | PPS |
| 24 | STAP-A,聚合包 |
| 28 | FU-A,分片包 |
课程重点主要落在 Single NAL Unit 和 FU-A,也就是最常见、最实用的两种打包方式。

3. Single NAL Unit 打包
当一个 NALU 小于等于 RTP payload 可承载大小时,可以直接把完整 NALU 放进 RTP payload。
结构如下:
RTP Header | NALU Header | NALU Data
这种方式简单,接收端拿到 RTP payload 后即可得到一个完整 NALU。

发送端流程:
读取一个 NALU
判断 NALU 长度
构造 RTP header
payload 直接拷贝完整 NALU
发送 RTP 包
sequence number 加 1
timestamp 按帧推进
接收端流程:
收到 RTP 包
检查 payload 第一个字节的 Type
如果 Type 在 1..23:
当前 payload 就是一个完整 NALU
补起始码 00 00 00 01
写入或交给解码器
4. FU-A 分片打包
当一个 NALU 太大,不能放进一个 RTP payload,就要拆成多个 RTP 包。课程重点讲的是 FU-A。
FU-A 的 RTP payload 结构:
RTP Header | FU Indicator | FU Header | Fragment Data
FU Indicator:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F|NRI| Type=28 |
+-+-+-+-+-+-+-+-+
FU Header:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+-+-+-+-+-+-+-+-+
字段含义:
| 字段 | 含义 |
|---|---|
S | Start,当前分片是否是这个 NALU 的第一个分片 |
E | End,当前分片是否是这个 NALU 的最后一个分片 |
R | Reserved,保留位,通常为 0 |
Type | 原始 NALU 的类型,例如 IDR 的 5 |

FU-A 的关键点是:分片后每个 RTP 包的 payload 不再直接以原始 NALU Header 开头,而是先放 FU Indicator 和 FU Header。接收端要用这两个字节还原原始 NALU Header。
原始 NALU Header 的还原方式:
restored_nalu_header = (FU Indicator 的 F/NRI) | (FU Header 的 Type)
例如原始 NALU 是 IDR,Type = 5。分片发送时:
FU Indicator Type = 28
FU Header Type = 5
接收端遇到 S=1 的首包时,先写起始码,再写还原出的 NALU Header,然后接上首片数据。
5. H.264 RTP 发送流程
发送端整体流程:
读取 H.264 文件或编码器输出
按起始码切出 NALU
去掉起始码,只保留 NALU Header + NALU Data
判断 NALU 长度
小包:Single NAL Unit
大包:FU-A 分片
构造 RTP header
设置 sequence number、timestamp、marker
发送到 socket

代码实现时,通常会有一个打包函数接收:
- RTP 输出缓冲区
- H.264 NALU 数据
- NALU 长度
- RTP payload 最大长度
- 序列号、时间戳、SSRC 等上下文

FU-A 发送时要特别处理三类包:
| 分片位置 | S | E | 处理 |
|---|---|---|---|
| 首片 | 1 | 0 | payload 写 FU Indicator + FU Header + 第一段数据 |
| 中间片 | 0 | 0 | payload 写 FU Indicator + FU Header + 中间数据 |
| 尾片 | 0 | 1 | payload 写 FU Indicator + FU Header + 最后一段数据,通常 marker 置 1 |

6. Timestamp、Sequence Number、Marker
RTP Header 里最容易写错的是这三个字段:
| 字段 | H.264 中的用法 |
|---|---|
sequence number | 每发送一个 RTP 包加 1,不管是不是同一个视频帧 |
timestamp | 同一帧的多个 RTP 分片使用同一个 timestamp |
marker | 一帧的最后一个 RTP 包通常置 1 |
视频时间戳使用视频时钟频率,常见为 90000Hz。如果帧率是 25fps,每帧 timestamp 增量通常是:
90000 / 25 = 3600
如果帧率是 30fps:
90000 / 30 = 3000

7. H.264 RTP 解包流程
接收端要根据 RTP payload 的第一个字节判断包类型:
nalu_type = payload[0] & 0x1f
解包流程:
收到 RTP 包
解析 RTP header,拿到 payload
读取 payload[0] 的 Type
如果 Type 在 1..23:
Single NAL Unit
写 start code + payload
如果 Type == 28:
FU-A
读取 FU Indicator 和 FU Header
如果 S == 1:
写 start code
还原 NALU Header
写 NALU Header
写当前 fragment data
如果 E == 1:
一个 NALU 结束
FU-A 解包时最重要的是不要把 FU Indicator 和 FU Header 当成原始 NALU 数据写进去。真正写入裸流的是还原后的 NALU Header 加每个分片的 Fragment Data。
8. 调试要点
开发或抓包时可以按下面几项检查:
- 小 NALU 是否走 Single NAL Unit。
- 大 NALU 是否走 FU-A。
- FU-A 首片
S=1,E=0,中间片S=0,E=0,尾片S=0,E=1。 - 同一个大 NALU 的所有分片 timestamp 是否相同。
- RTP sequence number 是否每包递增。
- 一帧最后一个 RTP 包 marker 是否置 1。
- 接收端还原出的 H.264 裸流是否带起始码。
- SPS/PPS 是否在解码器需要之前送达。

最后可以用一句话记住:
H.264 over RTP:小 NALU 直接放,大 NALU 用 FU-A 拆;解包时按 S/E 还原 NALU。
评论
...正在读取评论。