H.264流媒体协议格式中的Annex B格式和AVCC格式深度解析

时间:2021-01-17 07:21:30

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Romantic_Energy/article/details/50508332
本文需要读者对H.264流有一定的了解才可以理解2种格式差异。
    
    首先要理解的是没有标准的H.264基本流格式。文档中的确包含了一个Annex,特别是描述了一种可能的格式Annex B格式,但是这个并不是一个必须要求的格式。标准文档中指定了视频怎样编码成独立的包,但是这些包是怎样存储和传输的却是开放的。

一. Annex B
A.Network Abstraction Layer Units
    视频编码成的包叫做Network Abstraction Layer Units, 也简称为NALU、NAL,每个NALU包都可以被单独的解析和处理,每个NALU包的第一个字节包含了NALU类型,bit3-bit7包含的内容尤其重要(bit 0一定是off的,bit1-2指定了这个NALU是否被其他NALU引用)。
    NALU格式分为2类,VCL和non-VCL,总共有19种不同的NALU格式。

VCL, Video Coding Layer packets contain the actual visual information.  即视频编码后的数据
Non-VCL, contain metadata that may or may not be required to decode the video. 非视频数据,配置信息

一个单独的NALU包、或者甚至一个VCL NALU包都不意味着是一个独立的帧,一帧数据可以被分割成几个NALU,一个或多个NALU组成了一个Access Units(AU),AU包含了一个完整的帧。把帧分割成几个独立的NALU需要耗费许多CPU资源,所以分割帧数据并不经常使用。
    以下是所有定义了的NALU类型:

      Unspecified                                                    non-VCL
Coded slice of a non-IDR picture VCL
Coded slice data partition A VCL
Coded slice data partition B VCL
Coded slice data partition C VCL
Coded slice of an IDR picture VCL
Supplemental enhancement information (SEI) non-VCL
Sequence parameter set non-VCL
Picture parameter set non-VCL
Access unit delimiter non-VCL
End of sequence non-VCL
End of stream non-VCL
Filler data non-VCL
Sequence parameter set extension non-VCL
Prefix NAL unit non-VCL
Subset sequence parameter set non-VCL
Depth parameter set non-VCL
.. Reserved non-VCL
Coded slice of an auxiliary coded picture without partitioning non-VCL
Coded slice extension non-VCL
Coded slice extension for depth view components non-VCL
.. Reserved non-VCL
.. Unspecified non-VCL

有几种NALU格式的包包含了非常有用的信息。

    Sequence Parameter Set (SPS). This non-VCL NALU contains information required to configure the decoder such as profile, level, resolution, frame rate.
Picture Parameter Set (PPS). Similar to the SPS, this non-VCL contains information on entropy coding mode, slice groups, motion prediction and deblocking filters.
Instantaneous Decoder Refresh (IDR). This VCL NALU is a self contained image slice. That is, an IDR can be decoded and displayed without referencing any other NALU save SPS and PPS.
Access Unit Delimiter (AUD). An AUD is an optional NALU that can be use to delimit frames in an elementary stream. It is not required (unless otherwise stated by the container/protocol, like TS), and is often not included in order to save space, but it can be useful to finds the start of a frame without having to fully parse each NALU.

B. NALU Start Codes, NALU包开始码
    一个NALU包中的数据并不包含它的大小(长度)信息,因此不能简单的连接NALU包来建立一个流,因为你不知道一个包从哪里结束,另一个包从哪里开始。
    Annex B格式用开始码来解决这个问题,即给每个NALU加上前缀码:2个或者3个0x00,后面再加一个0x01, 如:0x000001或者0x00000001。
    4字节类型的开始码在在连续的数据传输中非常有用,因为用字节来对齐、分割流数据,比如:用连续的31个bit0后接一个bit1来分割流数据,是很容易的。
如果接下来的bit是0(因为每个NALU都以bit0开始),那么这就是一个NALU包数据的起始位置了。4字节类型的开始码通常只用于标识流中的随机访问点,
如SPS PPS AUD和IDR,然后其他地方都用3字节类型的开始码以减少数据量。

C. Emulation Prevention Bytes, 防竞争字节
    开始码能起作用是因为3字节的序列0x000000,0x000001,0x000002和0x000003(应该是所有的0x0000**)在non-VCL(原文是non-RBSP,译者修改)NALU包中是非法的,所以在构建ANLU包时,必须确保排除这些数值序列,这是由向每个这种类型的序列插入防竞争字节0x03实现的,那么插入防竞争字节后,0x000001变成了0x00000301。
    当解码的时候,查找和去除防竞争字节非常重要。因为防竞争字节可能出现在NALU包的任意位置,在文档中通常更方便的做法是假定它们已经被去除了,Raw Byte Sequence Payload原始字节序列负载 (RBSP)表示没有防竞争字节的数据序列(包)。

D. Example
    以下是一个完整的例子:

<pre><code>0x0000 |        0A AC
0x0010 | CA 3C
0x0020 | E8 8F
0x0030 | 4E 7F DF A5 8B EE A4 E9 B7 6A 6A
0x0040 | B9 0B 2E B5 0E E4 B8 A9
0x0050 | 5E FB E4 6A E9 E2
0x0060 | 9E 4D FF CE 7E B7 CE 2A E1 BE
0x0070 | BA F4 E6 9F DA D8 B1
0x0080 | F3 0C 0B 3C B3 9D BC EB C3 0E
0x0090 | CA EB 3F 3A FA B5 BF A8 6D
0x00A0 | C4 CE 9E 4E E6 4C C7 3E DE A1 0B
0x00B0 | D8 0B B6 B8 BC A9 EB FC 7A
0x00C0 | CA 6B B5 B7 D6 8C C5
0x00D0 | D8 A9 6F 4E DF E3 E2 B1 6B 6B
0x00E0 | BF 9F FB 2E A9 C4 A2 DF FA D9
0x00F0 | 1D 5A 1C D6 7C B6 6C
0x0100 | E6 4C AD BA C7 8E D0 F0 ED 8E F6
0x0110 | A6 A1 DB C8 AE E3 B6 E6 8D BC
0x0120 | A3 7F AF 4A 8A E2 7E 9F
0x0130 | DB D6 5E E1 4A C7 D6
0x0140 | FF C4 FD 9A D1 B2 FB DB A1 C2
0x0150 | F9 9B B2 3F 0C 8C DB A5 B2
0x0160 | 9D 7E 2F D0 E4 0A EF 2D
0x0170 | 3B 3B F5 2C B0 A6 F3 4B
0x0180 | 2F 1C 8F 3B C6 AA 1D D7 2A
0x0190 | 5E 2C D9 0A F5 F5 2C D1 FB 3F F0 4B E9
0x01A0 | DD CF C3 F3 D9 E5 4A
0x01B0 | 6C BC FE C1 9F E5 DB
0x01C0 | 5C 6B D8 D7 A8 A1 DC 9A
0x01D0 | 4F A5 C7 DE BE 6F AB
0x01E0 | 2E F3 D6 1A C2 D1 5D 6C DB 4A 7A E2 CB
0x01F0 | 0B B0 0B BE FC C0 BD 9D F5 F5 F8
0x0200 | A8 D6 B3 E9 BA E5 2C 7B F9 EA
0x0210 | 5A F9 A9 B1 6F 5B 1E 8D F4 CC
0x0220 | B2 6A A9 D0 B8 E2 E3 8B D1 C0 1C
0x0230 | CA B5 AC 3E F1 2C BD AB A8
0x0240 | A9 A4 8E 9C 8B DE F0 AE DB AF D6
0x0250 | F8 5E 9B B3 B3 B3 AC 6F A6 2F 3D
0x0260 | 3A CE FA 6C BB C5 4E F3</code>

这是一个完整的访问单元(AU),包括3个NALU包,如你所见,数据序列以开始码开始,后面接了一个SPS(SPS 以0x67开始),在SPS中,你可以看到有2个防竞争字节。没有这些字节那么非法的数据序列就会出现在这些位置。然后可以看到一个开始码后面接着一个PPS(PPS 以0x68开始),然后是一个最后的开始码,后面跟着一个IDR包。这是一个完整的H.264流,如果你把这些数据以16进制的方式保存到一个以.264为后缀名的文件中,可以把这些数据转换成以下图片:

Annex B格式通常用于实时的流格式,比如说传输流,通过无线传输的广播、DVD等。在这些格式中通常会周期性的重复SPS和PPS包,经常是在每一个关键帧之前,
因此据此建立解码器可以一个随机访问的点,这样就可以加入一个正在进行的流,及播放一个已经在传输的流。

二. AVCC
    另一个存储H.264流的方式是AVCC格式,在这种格式中,每一个NALU包都加上了一个指定其长度(NALU包大小)的前缀(in big endian format大端格式),这种格式的包非常容易解析,但是这种格式去掉了Annex B格式中的字节对齐特性,而且前缀可以是1、2或4字节,这让AVCC格式变得更复杂了,指定前缀字节数(1、2或4字节)的值保存在一个头部对象中(流开始的部分),这个头通常称为'extradata'或者'sequence header',它的基本格式如下:

bits
version ( always 0x01 )
avc profile ( sps[][] )
avc compatibility ( sps[][] )
avc level ( sps[][] )
reserved ( all bits on )
NALULengthSi*usOne // 这个值是(前缀长度-1),值如果是3,那前缀就是4,因为4-1=3
reserved ( all bits on )
number of SPS NALUs (usually )
repeated once per SPS:
SPS size
variable SPS NALU data
number of PPS NALUs (usually )
repeated once per PPS
PPS size
variable PPS NALU data

使用上面的例子,那么AVCC extradata看起来像是这样的:

0x0000 |    0A FF E1      0A AC
0x0010 | CA 3C
0x0020 | E8 8F

你会发现SPS和PPS被存储在了非NALU包中(out of band带外),即独立于基本流数据。这些数据的存储和传输是文件容器的任务,超出了本文的范畴。
注意:虽然AVCC格式不使用起始码,防竞争字节还是有的。

另外,extradata中有一个命名比较容易让人困惑的变量NALULengthSi*usOne,这个变量告诉我们用几个字节来存储NALU的长度(前缀:1、2或4),如果NALULengthSi*usOne是0,那么每个NALU使用一个字节的前缀来指定长度,那么每个NALU包的最大长度是255字节,这个明显太小了,这种方式对于存储一个完整的关键帧来说太小了。使用2个字节的前缀来指定长度,那么每个NALU包的最大长度是64K字节,这个对于我们的例子来说是足够了,但是限制还是比较大;3字节是比较完美的,但是因为一些原因没有被广泛支持;因此,4字节长度的前缀是目前使用最多的方式,也是这里我们使用的方式:

0x0000 |          4E 7F  DF  A5 8B
0x0010 | EE A4 E9 B7 6A 6A B9 0B 2E
0x0020 | B5 0E E4 B8 A9 5E FB
0x0030 | E4 6A E9 E2 9E 4D FF CE 7E
0x0040 | B7 CE 2A E1 BE BA F4
0x0050 | E6 9F DA D8 B1 F3 0C 0B 3C B3
0x0060 | 9D BC EB C3 0E CA EB
0x0070 | 3F 3A FA B5 BF A8 6D C4 CE 9E
0x0080 | 4E E6 4C C7 3E DE A1 0B D8 0B B6 B8 BC
0x0090 | A9 EB FC 7A CA 6B B5
0x00A0 | B7 D6 8C C5 D8 A9 6F
0x00B0 | 4E DF E3 E2 B1 6B 6B BF 9F FB 2E A9
0x00C0 | C4 A2 DF FA D9 1D 5A
0x00D0 | 1C D6 7C B6 6C E6 4C AD BA
0x00E0 | C7 8E D0 F0 ED 8E F6 A6 A1
0x00F0 | DB C8 AE E3 B6 E6 8D BC A3 7F AF 4A 8A
0x0100 | E2 7E 9F DB D6 5E
0x0110 | E1 4A C7 D6 FF C4 FD 9A D1
0x0120 | B2 FB DB A1 C2 F9 9B B2
0x0130 | 3F 0C 8C DB A5 B2 9D 7E 2F D0
0x0140 | E4 0A EF 2D 3B 3B F5 2C
0x0150 | B0 A6 F3 4B 2F 1C 8F 3B
0x0160 | C6 AA 1D D7 2A 5E 2C D9 0A F5 F5
0x0170 | 2C D1 FB 3F F0 4B E9 DD CF C3
0x0180 | F3 D9 E5 4A 6C
0x0190 | BC FE C1 9F E5 DB 5C 6B D8
0x01A0 | D7 A8 A1 DC 9A 4F A5 C7 DE BE
0x01B0 | 6F AB 2E F3 D6 1A C2
0x01C0 | D1 5D 6C DB 4A 7A E2 CB 0B B0 0B BE
0x01D0 | FC C0 BD 9D F5 F5 F8 A8 D6 B3 E9
0x01E0 | BA E5 2C 7B F9 EA 5A F9 A9 B1 6F 5B
0x01F0 | 1E 8D F4 CC B2 6A A9 D0
0x0200 | B8 E2 E3 8B D1 C0 1C CA B5 AC 3E
0x0210 | F1 2C BD AB A8 A9 A4 8E 9C 8B DE
0x0220 | F0 AE DB AF D6 F8 5E 9B B3 B3
0x0230 | B3 AC 6F A6 2F 3D 3A CE FA
0x0240 | 6C BB C5 4E F3

AVCC格式的一个优点是在开始配置解码器的时候可以跳到流的中间播放,这种格式通常用于可以被随机访问的多媒体数据,如存储在硬盘的文件。
也因为这个特性,MP4、MKV通常用AVCC格式来存储