近期竞答直播火热,简直百花齐放,各大直播解决方案提供商为了快速抢占份额,提出各种降低延迟,同步答题卡与直播画面的方案。

其中,同步答题卡与直播画面的方案中,比较普遍的做法是在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组成的。

NAL

而NALU由头部和负荷组成。

NALU

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

RBSP

另外,关于RBSP还有一个概念叫(Encapsulation Byte Sequence Packets,RBSP,不清楚要怎么翻译)。这个EBSP是在RBSP的基础上,为了防止编码出来的比特流中有跟分隔NALU的StartCodePrefix(0x00000001)有冲突,导致分隔NALU失败。所以在连续出现两个字节为0时,强制插入一个0x03字节。在解码时,再讲0x03字节删除,还原回去。称为脱壳操作(有中文资料这么称呼,不清楚有没有这么个叫法)。

看到有大神用数学公式概括了以上几个概念的关系。此处start_code为annex-b格式中的分隔符,可能是三字节的0x000001或者四字节的0x00000001,实际来看后者较多,可以认为是下图一个小缺陷。

SODB-RBSP-NALU-bitstream
SODB-RBSP-NALU-bitstream

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的分隔符,有0x0000010x00000001,也就是前文提到RBSP时提到过的START CODES。为了为了防止NALU中本身带有0x000001或者0x00000001而导致的解析分隔失败,引入了0x03的防止竞争字节。这种防止竞争操作同样适用与avcC格式。

Annex-B格式中的SPS和PPS也以NALU的方式放在码流的首部,不像avcC需要将SPS和PPS格式化在extrdata中。

后续贴几张十六进制的图

新增flv实例分析2018年1月25日

AVCsequenceheader

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

NAL units

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