H264码流中的avcC和Annex-B
近期竞答直播火热,简直百花齐放,各大直播解决方案提供商为了快速抢占份额,提出各种降低延迟,同步答题卡与直播画面的方案。
其中,同步答题卡与直播画面的方案中,比较普遍的做法是在H264码流的SEI中增加相应的自定义信息,使得播放器在解码到对应的信息的时候弹出预先几秒下发的答题卡,并开始倒计时。
在这里,涉及到播放器解码H264码流的方式,播放器使用不同的解码方式可能导致SEI解析失败,甚至画面无法正常播放。
SEI是什么呢?
SEI是NALU type为6的NALU,全称为Supplemental enhancement
information,直译为补充增强信息,一般称为增强帧。此处涉及到比较基础的编码打包方面的知识,笔者也不太清楚,后续查阅资料搞明白之后与大家分享。今天先理下H264打包码流的两种格式:avcc与annex-B。
H264编码结构
要说H264的打包方式,首先要讲下H264的编码结构。
H264主要分为两层:编码层(Video Coding Layer,VCL)和网络抽象层(NetworkAbstraction Layer (NAL));前者定义了各种编码的算法,后者将前者编码的数据按照一定的方式进行打包存储或者传输。
而NAL单元(NALU)作为可以单独可以解码的结构,整个H264的码流可以理解为由多个NALU组成的。

而NALU由头部和负荷组成。

NALU的负荷由称为原始字节序列载荷(Raw Byte Sequence Payload,RBSP),是由一串数据比特串(String Of Data Bits,SODB)加上末尾的stop-bits(使得整个NALU长度是8的倍数)组成。

另外,关于RBSP还有一个概念叫(Encapsulation Byte Sequence Packets,RBSP,不清楚要怎么翻译)。这个EBSP是在RBSP的基础上,为了防止编码出来的比特流中有跟分隔NALU的StartCodePrefix(0x00000001
)有冲突,导致分隔NALU失败。所以在连续出现两个字节为0时,强制插入一个0x03
字节。在解码时,再讲0x03
字节删除,还原回去。称为脱壳操作(有中文资料这么称呼,不清楚有没有这么个叫法)。
看到有大神用数学公式概括了以上几个概念的关系。此处start_code为annex-b格式中的分隔符,可能是三字节的0x000001或者四字节的0x00000001,实际来看后者较多,可以认为是下图一个小缺陷。


NALU头部一个字节表明该NALU的类型,标准定义列表如下:
nal_unit_type | Content of NAL unit &RBSP syntax structure | C | NAL unit type class [Annex A] |
NAL unit type class [Annex G & H] |
NAL unittype class [Annex I] |
---|---|---|---|---|---|
0 | Unspecified | non-VCL | non-VCL | non-VCL | |
1 | Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( ) |
2, 3, 4 | VCL | VCL | VCL |
2 | Coded slice data partition A slice_data_partition_a_layer_rbsp( ) |
2 | VCL | not applicable | not applicable |
3 | Coded slice data partition B slice_data_partition_b_layer_rbsp( ) |
3 | VCL | not applicable | not applicable |
4 | Coded slice data partition C slice_data_partition_c_layer_rbsp( ) |
4 | VCL | not applicable | not applicable |
5 | Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( ) |
2, 3 | VCL | VCL | VCL |
6 | Supplemental enhancement information (SEI)sei_rbsp( ) |
5 | non-VCL | non-VCL | non-VCL |
7 | Sequence parameter set seq_parameter_set_rbsp( ) |
0 | non-VCL | non-VCL | non-VCL |
8 | Picture parameter set pic_parameter_set_rbsp( ) |
1 | non-VCL | non-VCL | non-VCL |
9 | Access unit delimiter access_unit_delimiter_rbsp( ) |
6 | non-VCL | non-VCL | non-VCL |
10 | End of sequence end_of_seq_rbsp( ) |
7 | non-VCL | non-VCL | non-VCL |
11 | End of stream end_of_stream_rbsp( ) |
8 | non-VCL | non-VCL | non-VCL |
12 | Filler data filler_data_rbsp( ) |
9 | non-VCL | non-VCL | non-VCL |
13 | Sequence parameter set extension seq_parameter_set_extension_rbsp( ) |
10 | non-VCL | non-VCL | non-VCL |
14 | Prefix NAL unit prefix_nal_unit_rbsp( ) |
2 | non-VCL | suffix dependent | suffix dependent |
15 | Subset sequence parameter set subset_seq_parameter_set_rbsp( ) |
0 | non-VCL | non-VCL | non-VCL |
16 | Depth parameter set depth_parameter_set_rbsp( ) |
11 | non-VCL | non-VCL | non-VCL |
17 – 18 | Reserved | non-VCL | non-VCL | non-VCL | |
19 | Coded slice of an auxiliary coded picture without partitioning slice_layer_without_partitioning_rbsp( ) |
2, 3, 4 | non-VCL | non-VCL | non-VCL |
20 | Coded slice extension slice_layer_extension_rbsp( ) |
2, 3, 4 | non-VCL | VCL | VCL |
21 | Coded slice extension for depth view component or a 3D-AVC texture view component slice_layer_extension_rbsp( ) |
2, 3, 4 | non-VCL | non-VCL | VCL |
22 – 23 | Reserved | non-VCL | non-VCL | VCL | |
24 – 31 | Unspecified | non-VCL | non-VCL | non-VCL |
基础知识讲得差不多了,下面进入正题,看下什么是avcc和annex打包方式呢。
以上已经提过,H264的码流有多个NALU组成,那边NALU是如何组成AVCC的呢,每个NALU又该如何去分隔呢,此处便涉及到了AVCC和annex-B两种格式。
avcC
avcC的码流打包方式是在每个NALU前面添加一个标记NALU长度的字段,该字段可能的长度为1字节或者2字节或者4字节,通常为了保证该字段可以足够描述NALU的长度,会设置为4个字节。
而该字节的长度可以通过解析extradata(也称为sequence header)的头信息来获取,也就是常说的avc头。以下为该头部信息的解析格式:
aligned(8) class AVCDecoderConfigurationRecord {
unsigned int(8) configurationVersion = 1;
unsigned int(8) AVCProfileIndication;
unsigned int(8) profile_compatibility;
unsigned int(8) AVCLevelIndication;
bit(6) reserved = ‘111111’b;
unsigned int(2) lengthSizeMinusOne;
bit(3) reserved = ‘111’b;
unsigned int(5) numOfSequenceParameterSets;
for (i=0; i< numOfSequenceParameterSets; i++) {
unsigned int(16) sequenceParameterSetLength ;
bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
}
unsigned int(8) numOfPictureParameterSets;
for (i=0; i< numOfPictureParameterSets; i++) {
unsigned int(16) pictureParameterSetLength;
bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
}
if( profile_idc == 100 || profile_idc == 110 ||
profile_idc == 122 || profile_idc == 144 )
{
bit(6) reserved = ‘111111’b;
unsigned int(2) chroma_format;
bit(5) reserved = ‘11111’b;
unsigned int(3) bit_depth_luma_minus8;
bit(5) reserved = ‘11111’b;
unsigned int(3) bit_depth_chroma_minus8;
unsigned int(8) numOfSequenceParameterSetExt;
for (i=0; i< numOfSequenceParameterSetExt; i++) {
unsigned int(16) sequenceParameterSetExtLength;
bit(8*sequenceParameterSetExtLength) sequenceParameterSetExtNALUnit;
}
}
}
其中第五个字节的最后两位unsigned int(2) lengthSizeMinusOne;
便是上述标记NALU长度的字段的长度。
在extrdata中还包含了SPS的个数,SPS的长度和SPS,以及PPS的个数,PPS的长度和PPS。对于解码器来说,上述参数都十分重要。
Annex-B
Annex-B是指H264的标准文档中的附录B中给的H264码流打包方式。相对于avcC来说,Annex-B打包方式没有固定长度的字段来表明NALU的长度,而是使用固定字符串来作为不同NALU的分隔符,有0x000001
和0x00000001
,也就是前文提到RBSP时提到过的START CODES
。为了为了防止NALU中本身带有0x000001
或者0x00000001
而导致的解析分隔失败,引入了0x03
的防止竞争字节。这种防止竞争操作同样适用与avcC格式。
Annex-B格式中的SPS和PPS也以NALU的方式放在码流的首部,不像avcC需要将SPS和PPS格式化在extrdata中。
后续贴几张十六进制的图
新增flv实例分析2018年1月25日
AVCsequenceheader

- 00 00 09
- onMetadata结束位
- 00 00 02 F8
- previousTagSize
- 000002F8H=760D,代表前tag长度为760字节
- 09
- TagType
- 09H=09D,代表video(12H=18D,script;08H=08D,audio)
- 00 00 2D
- DataSize
- 00002DH=45D,代表实际的TagSize长度为45个字节
- 00 00 00
- 时间戳,单位毫秒
- AVC头时间戳为0
- 00
- 扩展时间戳,单位毫秒,作为基本时间戳的补位,补在基本时间戳的高位
- AVC头扩展时间戳为0
- 00 00 00
- StreamId,默认为0,作用不明
- 17
- 前四位为FrameType,此处为1,代表关键帧
- 1: keyframe (for AVC, a seekableframe)
- 2: inter frame(for AVC, a non -seekable frame)
- 3 : disposable inter frame(H.263only)
- 4 : generated keyframe(reserved forserver use only)
- 5 : video info / command frame
- 后四位为CodecID,此处为7,代表AVC编码
- 1: JPEG (currently unused)
- 2: Sorenson H.263
- 3 : Screen video
- 4 : On2 VP6
- 5 : On2 VP6 with alpha channel
- 6 : Screen video version 2
- 7 : AVC
- 由于上述的CodecID为7,所以下面为AVCVIDEOPACKET
- 00
- AVCPacketType,此处为0,代表AVCsequenceheader
- 0--AVC sequence header
- 1--AVC NALU
- 2--AVC end of sequence
- 00 00 00
- 由于此处是AVCVIDEOPACKET内容,解析为CompositionTime Offset,含义不明
- 接下来一串到下一个tag之前的PreviousTagSize都是AVC的内容,AVCDecoderConfigurationRecord。根据14496-15标准解析。
- 00 00 00 38
- 这个tag的大小。38H=56D,代表这个tag长度为56字节。
NAL units

- 27
- AVC的非inter frame
- 01
- AVC NALU数据
- 00 00 00
- 由于此处是AVCVIDEOPACKET内容,解析为CompositionTime Offset,含义不明
- 00 00 00 09
- 下一个NALU的长度为9字节
- 06
- NALU header为6,代表为NALU type为SEI
- 00 00 0F 61 1B 9B D9 39
- NAL中的实际内容,此项为SEI的内容。
- 00 00 02 23
- 下一个NALU的长度为223H=563D字节
- 41 9A ... 7E E0
- NALU的内容
- 其中第一个字节为nal header,包含以下三个内容
- forbidden_zero_bit
- nal_ref_idc
- nal_unit_type
- 此处41H=01 00 00 01B,
- 首位是0,符合要求,语法无错误
- 第二位到第三位是10,代表优先级是2
- 第四位到第八位为00001B=1D,表示该nal_unit_header=1,代表非IDR不可分区的slice
- 00 00 02 54
- 代表前面flv tag size是254H=596D字节。