概念#
- SODB: 数据比特串 -> 最原始的编码数据
- RBSP: 原始字节序列载荷 -> 在 SODB 的后面填加了结尾比特(RBSP trailing bits 一个 bit “1”)若干比特 “0”, 以便字节对齐。
- EBSP: 扩展字节序列载荷– > 在 RBSP 基础上填加了仿校验字节(0X03)它的原因是:在 NALU 加到 Annexb 上时,需要填加每组 NALU 之前的开始码 StartCodePrefix, 如果该 NALU 对应的 slice 为一帧的开始则用 4 位字节表示,ox00000001, 否则用 3 位字节表示 ox000001. 为了使 NALU 主体中不包括与开始码相冲突的,在编码时,每遇到两个字节连续为 0,就插入一个字节的 0x03。解码时将 0x03 去掉。 也称为脱壳操作。
H.264 的功能分为两层,视频编码层(VCL)和网络提取层(NAL)
VCL 数据即被压缩编码后的视频数据序列。在 VCL 数据要封装到 NAL 单元中之后,才可以用来传输或存储。
H.264 的编码视频序列包括一系列的 NAL 单元,每个 NAL 单元包含一个 RBSP,见表 1。编码片(包括数据分割片 IDR 片) 和序列 RBSP 结束符被定义为 VCL NAL 单元,其余为 NAL 单元。典型的 RBSP 单元序列如图 2 所示。
每个单元都按独立的 NAL 单元传送。单元的信息头(一个字节)定义了 RBSP 单元的类型,NAL 单元的其余部分为 RBSP 数据。
NAL 单元#
每个 NAL 单元是一个一定语法元素的可变长字节字符串,包括包含一个字节的头信息(用来表示数据类型),以及若干整数字节的负荷数据。一个 NAL 单元可以携带一个编码片、A/B/C 型数据分割或一个序列或图像参数集。
NALU 头由一个字节组成,它的语法如下:
NAL 单元按 RTP 序列号按序传送。其中,T 为负荷数据类型,占 5bit;R 为重要性指示位,占 2 个 bit;最后的 F 为禁止位,占 1bit。具体如下:
- NALU 类型位可以表示 NALU 的 32 种不同类型特征,类型 1~12 是 H.264 定义的,类型 24~31 是用于 H.264 以外的,RTP 负荷规范使用这其中的一些值来定义包聚合和分裂,其他值为 H.264 保留。
- 重要性指示位用于在重构过程中标记一个 NAL 单元的重要性,值越大,越重要。值为 0 表示这个 NAL 单元没有用于预测,因此可被解码器抛弃而不会有错误扩散;值高于 0 表示此 NAL 单元要用于无漂移重构,且值越高,对此 NAL 单元丢失的影响越大。
- 禁止位 编码中默认值为 0,当网络识别此单元中存在比特错误时,可将其设为 1,以便接收方丢掉该单元,主要用于适应不同种类的网络环境(比如有线无线相结合的环境)。
264 常见的帧头数据为:
00 00 00 01 67 (SPS)
00 00 00 01 68 (PPS)
00 00 00 01 65 (IDR 帧)
00 00 00 01 61 (P 帧)
上述的 **67,68,65,61,** 还有 41 等,都是该 NALU 的识别级别。
F:禁止为,0 表示正常,1 表示错误,一般都是 0
NRI:重要级别,11 表示非常重要。
TYPE:表示该 NALU 的类型是什么,
见下表,由此可知 7 为序列参数集 (SPS),8 为图像参数集 (PPS),5 代表 I 帧。1 代表非 I 帧。
由此可知,61 和 41 其实都是 P 帧(type 值为 1),只是重要级别不一样(它们的 NRI 一个是 11BIN,一个是 10BIN)
H264 (NAL 简介与 I 帧判断)#
我们还是接着看最上面图的码流对应的数据来层层分析,以 00 00 00 01 分割之后的下一个字节就是 NALU 类型,将其转为二进制数据后,
解读顺序为从左往右算,如下:
(1)第 1 位禁止位,值为 1 表示语法出错
(2)第 2~3 位为参考级别
(3)第 4~8 为是 nal 单元类型
例如上面 00000001 后有 67,68 以及 65
其中 0x67 的二进制码为:
0110 0111
4-8 为 00111,转为十进制 7,参考第二幅图:7 对应序列参数集 SPS
其中 0x68 的二进制码为:
0110 1000 4-8 为 01000,转为十进制 8,参考第二幅图:8 对应图像参数集 PPS
其中 0x65 的二进制码为:
011 00101
4-8 位为 00101,转为十进制 5,参考第二幅图:5 对应 IDR 图像中的片 (I 帧)
所以判断是否为 I 帧的算法为:
(NALU 类型 & 0001 1111) = 5 即 (NALU 类型 & 31) = 5 比如 0x65 & 31 = 5
RTP 打包发送 H264 之封包详解#
RFC3984 是 H.264 的 baseline 码流在 RTP 方式下传输的规范
H264 的码流结构
单个 NALU#
12 字节的 RTP 头后面的就是音视频数据,比较简单。一个封装单个 NAL 单元包到 RTP 的 NAL 单元流的 RTP 序号必须符合 NAL 单元的解码顺序。 对于 NALU 的长度小于 MTU 大小的包,一般采用单一 NAL 单元模式。对于一个原始的 H.264 NALU 单元常由[Start Code] [NALU Header] [NALU Payload]
三部分组成,其中 Start Code 用于标示这是一个 NALU 单元的开始,必须是 “00 00 00 01” 或 “00 00 01”, NALU 头仅一个字节,其后都是 NALU 单元内容.
打包时去除 “00 00 01” 或 “00 00 00 01” 的开始码,把其他数据封包的 RTP 包即可.
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
如有一个 H.264 的 NALU 是这样的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F …]
这是一个序列参数集 NAL 单元. [00 00 00 01] 是四个字节的开始码,67 是 NALU 头,42 开始的数据是 NALU 内容.
封装成 RTP 包将如下:
[RTP Header] [67 42 A0 1E 23 56 0E 2F]
即只要去掉 4 个字节的开始码就可以了.
包聚合#
当 NALU 的长度特别小时,可以把几个 NALU 单元封在一个 RTP 包中.
为了体现 / 应对有线网络和无线网络的 MTU 巨大差异,RTP 协议定义了包聚合策略:
- STAP-A:聚合的 NALU 时间戳都一样,无 DON(decoding order number);
- STAP-B:聚合的 NALU 时间戳都一样,有 DON;
- MTAP16:聚合的 NALU 时间戳不同,时间戳差值用 16 bit 记录;
- MTAP24:聚合的 NALU 时间戳不同,时间戳差值用 24 bit 记录;
- 包聚合时,RTP 的时间戳是所有 NALU 时间戳的最小值;
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| Type | |
+-+-+-+-+-+-+-+-+ |
| |
| one or more aggregation units |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 3. RTP payload format for aggregation packets
STAP-A 示例:
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 7. An example of an RTP packet including an STAP-A
containing two single-time aggregation units
FU-A 的分片格式#
数据比较大的 H264 视频包,被 RTP 分片发送。12 字节的 RTP 头后面跟随的就是 FU-A 分片: 而当 NALU 的长度超过 MTU 时,就必须对 NALU 单元进行分片封包。也称为 Fragmentation Units (FUs).
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14. RTP payload format for FU-A
FU indicator 有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
FU 指示字节的类型域 Type=28 表示 FU-A。。NRI 域的值必须根据分片 NAL 单元的 NRI 域的值设置。
FU header 的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S: 1 bit 当设置成 1 表明这是 NALU 的首个 fragmnet。当跟随的 FU 荷载不是分片 NAL 单元荷载的开始,开始位设为 0。
E: 1 bit 当设置成 1 表明是 NALU 的最后一个 fragment,即,荷载的最后字节也是分片 NAL 单元的最后一个字节。当跟随的 FU 荷载不是分片 NAL 单元的最后分片,结束位设置为 0。
R: 1 bit 保留位必须设置为 0,接收者必须忽略该位。
Type: 5 bits NAL 单元荷载类型定义见下表
单元类型以及荷载结构总结
.Type Packet Type name
---------------------------------------------------------
0 undefined -
1-23 NAL unit Single NAL unit packet per H.264
24 STAP-A Single-time aggregation packet
25 STAP-B Single-time aggregation packet
26 MTAP16 Multi-time aggregation packet
27 MTAP24 Multi-time aggregation packet
28 FU-A Fragmentation unit
29 FU-B Fragmentation unit
30-31 undefined
拆包和解包
拆包:当编码器在编码时需要将原有一个 NAL 按照 FU-A 进行分片,原有的 NAL 的单元头与分片后的 FU-A 的单元头有如下关系:
原始的 NAL 头的前三位为 FU indicator 的前三位,原始的 NAL 头的后五位为 FU header 的后五位,
FU indicator 与 FU header 的剩余位数根据实际情况决定。
** 解包:** 当接收端收到 FU-A 的分片数据,需要将所有的分片包组合还原成原始的 NAl 包时,FU-A 的单元头与还原后的 NAL 的关系如下:
还原后的 NAL 头的八位是由 FU indicator 的前三位加 FU header 的后五位组成,即:
nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f)