H.264码流格式分析

2024-01-01 12:22:22

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。接下来对这两种码流格式进行介绍。

1?avcC

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的数据

}

2?annexB

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

3?NALU结构

NALU由两部分组成,NALU Header和扩展字节序列载荷EBSP(Encapsulated Byte Sequence Payload)。

NALU = NALU Header + EBSP

3.1 NALU Header

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

未指定

3.2 EBSP

EBSP实际上就是原始字节序列载荷RBSP(Raw Byte Sequence Payload)加上防竞争字节0x03。

3.3 RBSP

RBSP由两部分组成,SODB(String Of Data Bits)和RBSP尾部。其中SODB是最原始的编码数据

RBSP = SODB + RBSP尾部

3.4?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尾部。

4 SPS和PPS

最后再简单介绍一下序列参数集SPS(Sequence Paramater Set)和图像参数集PPS(Picture Paramater Set)。SPS里面含有一组编码视频序列的全局参数,而PPS里面含有一帧编码图像的参数。解码器需要使用SPS和PPS里的参数进行初始化,如果码流发生变化,需要获取新的SPS和PPS参数。

文章来源:https://blog.csdn.net/m0_51496461/article/details/135307330
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。