RTP 之 H.264 封包和解包

根据课程视频整理:

  • 159--4-RTP之H264封包和解包1-封包原理.mp4
  • 160--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 NALU 通过 RTP/UDP 发送

所以 H.264 over RTP 的核心判断很简单:

如果 NALU <= RTP payload 容量:
    一个 NALU 放进一个 RTP 包,使用 Single NAL Unit
如果 NALU > RTP payload 容量:
    一个 NALU 拆成多个 RTP 包,常用 FU-A

MTU 与单个 NALU 打包

2. H.264 NALU Header

H.264 NALU 的第一个字节是 NALU Header:

  0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F|NRI|  Type   |
+-+-+-+-+-+-+-+-+

字段含义:

字段位数含义
F1 bitforbidden_zero_bit,正常应为 0
NRI2 bitnal_ref_idc,表示参考优先级
Type5 bitNALU 类型

常见 Type

Type含义
1非 IDR 图像片
5IDR 图像片
6SEI
7SPS
8PPS
24STAP-A,聚合包
28FU-A,分片包

课程重点主要落在 Single NAL UnitFU-A,也就是最常见、最实用的两种打包方式。

H.264 RTP 包类型与 FU-A

3. Single NAL Unit 打包

当一个 NALU 小于等于 RTP payload 可承载大小时,可以直接把完整 NALU 放进 RTP payload。

结构如下:

RTP Header | NALU Header | NALU Data

这种方式简单,接收端拿到 RTP payload 后即可得到一个完整 NALU。

Single NAL Unit 模式

发送端流程:

读取一个 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   |
+-+-+-+-+-+-+-+-+

字段含义:

字段含义
SStart,当前分片是否是这个 NALU 的第一个分片
EEnd,当前分片是否是这个 NALU 的最后一个分片
RReserved,保留位,通常为 0
Type原始 NALU 的类型,例如 IDR 的 5

FU-A 分片结构

FU-A 的关键点是:分片后每个 RTP 包的 payload 不再直接以原始 NALU Header 开头,而是先放 FU IndicatorFU 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

读取 NALU 与打包代码路径

代码实现时,通常会有一个打包函数接收:

H.264 RTP 打包代码流程

FU-A 发送时要特别处理三类包:

分片位置SE处理
首片10payload 写 FU Indicator + FU Header + 第一段数据
中间片00payload 写 FU Indicator + FU Header + 中间数据
尾片01payload 写 FU Indicator + FU Header + 最后一段数据,通常 marker 置 1

FU-A 起始、中间、结束分片代码

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

RTP Header 中的序列号和时间戳处理

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 IndicatorFU Header 当成原始 NALU 数据写进去。真正写入裸流的是还原后的 NALU Header 加每个分片的 Fragment Data。

8. 调试要点

开发或抓包时可以按下面几项检查:

H.264 RTP 打包 API 与发送路径

最后可以用一句话记住:

H.264 over RTP:小 NALU 直接放,大 NALU 用 FU-A 拆;解包时按 S/E 还原 NALU。

评论

...

正在读取评论。