H.264是一种编码方式,H.264裸流(编码后的数据)是由一个个NALU组成。H.264分为VCL(Video Coding Layer)和NAL(Network Abstract Layer)两个部分。VCL简单来说就是对数据进行编码,NAL简单来说就是对编码后的数据进行打包。
接下来简单介绍一下NALU的形成。在H.264编码过程中,一帧图像会被分为一个或多个片(Slice),一个片可以被分为多个宏块,而一个宏块又可以被分为多个子块。一个编码后的Slice就是一个NALU。
H.264有两种码流格式,avcC和annexB。接下来对这两种码流格式进行介绍。
avcC的NALU前面是NALU的长度,大小可以是1、2或4字节,具体大小在extradata(sequence header)中定义。
extradata中包含NALU Length所占字节的大小以及SPS和PPS。
Field | No. of bits | Meaning |
version | 8 | 值为0x01 |
avc profile | 8 | sps[0][1](第一个sps的第一个字节) |
avc compatibility | 8 | sps[0][2](第一个sps的第二个字节) |
avc level | 8 | sps[0][3](第一个sps的第三个字节) |
reserved | 6 | 保留字段 |
NALULengthSizeMinusOne | 2 | NALU Length所占字节的大小减1,值为3表示NALU Length所占字节的大小为4 |
reserved | 3 | 保留字段 |
number of SPS NALUs | 5 | SPS NALU的数量,通常为1 |
for (int i = 0; i < number of SPS NALUs; i++) { | ||
SPS size | 16 | SPS NALU data的长度 |
SPS NALU data | SPS NALU的数据 | |
} | ||
number of PPS NALUs | 8 | PPS NALU的数量,通常为1 |
for (int i = 0; i < number of PPS NALUs; i++) { | ||
PPS size | 16 | PPS NALU data的长度 |
PPS NALU data | PPS NALU的数据 | |
} |
annexB的NALU前面是起始码,可以是4字节的0x00 00 00 01,也可以是3字节的0x00 00 01。
与avcC不同的是,annexB的SPS和PPS存放在NALU中,并且在I帧之前。
2.1 防竞争字节
仅仅只是引入起始码来分隔NALU是不行的,因为原始码流中也可能存在0x00 00 00 01或者0x00 00 01,这会导致误分割的问题,因此引入了防竞争字节。
防竞争字节就是在下面四种情况出现时,在最后一个字节前插入0x03。
0x00 00 00 ? ?==> ? ?0x00 00 03 00
0x00 00 01 ? ?==> ? ?0x00 00 03 01
0x00 00 02 ? ?==> ? ?0x00 00 03 02
0x00 00 03 ? ?==> ? ?0x00 00 03 03
NALU由两部分组成,NALU Header和扩展字节序列载荷EBSP(Encapsulated Byte Sequence Payload)。
NALU = NALU Header + EBSP
NALU Header的长度为1个字节,表示当前NALU的重要性以及负载数据的类型。其结构如下表所示。
Field | No. of bits | Meaning |
forbidden_zero_bit | 1 | 固定为0 |
nal_ref_idc | 2 | 表示当前NALU的重要程度,值越大表示越重要 |
nal_unit_type | 5 | NALU的类型 |
NALU的类型如下表所示。
nal_unit_type | NAL 单元和 RBSP 语法结构的内容 | C |
0 | 未指定 | |
1 | 一个非 IDR 图像的编码条带 slice_layer_without_partitioning_rbsp( ) | 2, 3, 4 |
2 | 编码条带数据分割块 A slice_data_partition_a_layer_rbsp( ) | 2 |
3 | 编码条带数据分割块 B slice_data_partition_b_layer_rbsp( ) | 3 |
4 | 编码条带数据分割块 C slice_data_partition_c_layer_rbsp( ) | 4 |
5 | IDR 图像的编码条带 slice_layer_without_partitioning_rbsp( ) | 2, 3 |
6 | 辅助增强信息 (SEI) sei_rbsp( ) | 5 |
7 | 序列参数集 seq_parameter_set_rbsp( ) | 0 |
8 | 图像参数集 pic_parameter_set_rbsp( ) | 1 |
9 | 访问单元分隔符 access_unit_delimiter_rbsp( ) | 6 |
10 | 序列结尾 end_of_seq_rbsp( ) | 7 |
11 | 流结尾 end_of_stream_rbsp( ) | 8 |
12 | 填充数据 filler_data_rbsp( ) | 9 |
13 | 序列参数集扩展 seq_parameter_set_extension_rbsp( ) | 10 |
14…18 | 保留 | |
19 | 未分割的辅助编码图像的编码条带 slice_layer_without_partitioning_rbsp( ) | 2, 3, 4 |
20…23 | 保留 | |
24…31 | 未指定 |
EBSP实际上就是原始字节序列载荷RBSP(Raw Byte Sequence Payload)加上防竞争字节0x03。
RBSP由两部分组成,SODB(String Of Data Bits)和RBSP尾部。其中SODB是最原始的编码数据。
RBSP = SODB + RBSP尾部
RBSP尾部有两种结构,其中大多数NALU使用下面这种RBSP尾部结构。
rbsp_trailing_bits( ) { | C | 描述符 |
rbsp_stop_one_bit /* equal to 1 */ | 全部 | f(1) |
while( !byte_aligned( ) ) | ||
rbsp_alignment_zero_bit /* equal to 0 */ | 全部 | f(1) |
} |
其中rbsp_stop_one_bit = 1,rbsp_alignment_zero_bit = 0。这种RBSP尾部结构是在SODB的最后一个字节的下一个字节的起始处插入1个值为1的1bit,然后插入若干个0以进行对齐。
另一种RBSP尾部是条带RBSP尾部,当NALU的nal_unit_type为1~5时使用这种RBSP尾部。
rbsp_slice_trailing_bits( ) { | C | 描述符 |
rbsp_trailing_bits( ) | 全部 | |
if( entropy_coding_mode_flag ) | ||
while( more_rbsp_trailing_data( ) ) | ||
cabac_zero_word /* equal to 0x0000 */ | 全部 | f(16) |
} |
可以看出,只有当entropy_coding_mode_flag值为1(也就是采用CABAC熵编码),并且more_rbsp_trailing_data( )的返回值为true(也就是RBSP中有更多数据)时才使用第二种RBSP尾部。其结构是在第一种RBSP尾部的后面继续添加一个或多个0x0000。否则使用第一种RBSP尾部。
最后再简单介绍一下序列参数集SPS(Sequence Paramater Set)和图像参数集PPS(Picture Paramater Set)。SPS里面含有一组编码视频序列的全局参数,而PPS里面含有一帧编码图像的参数。解码器需要使用SPS和PPS里的参数进行初始化,如果码流发生变化,需要获取新的SPS和PPS参数。