1、H.264简介
MPEG(Moving Picture Experts Group)和 VCEG(Video Coding Experts Group)联合开发了一个比早期研发的MPEG 和 H.263 性能更好的视频压缩编码标准,这就是被命名 为 AVC(Advanced Video Coding),也被称为 ITU-T H.264 建议和 MPEG-4 的第 10 部分的标准,简称为 H.264/AVC 或 H.264。这个国际标准已经与 2003 年 3 月正式被 ITU-T 所通过并在国际上正式颁布。为适应高清视频压缩的需求,2004 年又增加了 FRExt 部分;为适应不同码率及质量的需求,2006 年又增加了可伸缩编码 SVC。
2、H.264编码格式
2.1 H.264原始码流结构
H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。他们的结构如下图所示。
其中每个NALU之间通过startcode(起始码)进行分隔,起始码分成两种:0x000001
(3Byte)或者0x00000001
(4Byte)。如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段。
2.2 NALU结构
NALU结构分为两层,如下图所示:
由上图我们知道: NALU = NALU Header + RBSP,下面分别介绍一下NALU Header 和 RBSP,介绍之前先上H.264官方协议文档里面的关于NALU语法构成,位于h264文档的7.3.1节:
NALU Header
通过上面我们也可以看到,NALU Header由三个句法元素组成,分别为:forbidden_zero_bit、nal_ref_idc和nal_unit_type,它们总共占据一个字节,也就是说,NALU Header,在整个NALU中,占据一个字节。
而且forbidden_zero_bit的值对应1个bit,nal_ref_idc的值对应2个bit,nal_unit_type的值对应5个bit,加起来刚好一个字节。下面分别来看看它们的语义:
-
forbidden_zero_bit:h264文档规定,这个值应该为0,当它不为0时,表示网络传输过程中,当前NALU中可能存在错误,解码器可以考虑不对这个NALU进行解码。
-
nal_ref_idc:取值0~3,代表当前这个NALU的重要性,取值越大,代表当前NALU越重要,就需要优先被保护。尤其是当前NALU为图像参数集(SPS)、序列参数集(PPS)或IDR图像时,或者为参考图像条带(片/Slice),或者为参考图像的条带数据分割时,nal_ref_idc值肯定不为0。而当NALU 类型,nal_unit_type为6、9、10、11、或12时,nal_ref_idc都为0。
-
nal_unit_type:它表示NALU Header后面的RBSP的数据结构的类型。下图为nal_unit_type所有可能的取值,和对应的语义。也可以参考H.264官方协议文档的7.4.1节。
可以看到,nal_unit_type的值为1-5时,表示RBSP里面包含的数据为条带(片/Slice)数据,所以值为1-5的NALU统称为VCL(视像编码层)单元,其他的NALU则称为非VCL NAL单元。
这里重点讲下nal_unit_type=5,即表示该RBSP的数据结构类型是IDR图像。
IDR图像:即时解码刷新图像,它是一个序列的第一个图像,H.264引入IDR图像是为了解码的重新同步。当解码器解码到IDR图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样一来,如果前一个序列发生重大错误,在这里就可以获得重新同步。IDR帧可以单独解码成一帧图像。
所以IDR图像之后的图像,永远不会引用IDR图像之前的图像来解码。并且IDR图像一定是I图像,而I图像不一定是IDR图像(H264里没有图像层,图像可以理解为帧、片或宏块)。
当nal_unit_type为7时,代表当前NALU为序列参数集(SPS),为8时为图像参数集(PPS)。这也是我们打开.h264文件后,遇到的前两个NALU,它们位于码流的最前面。
其中,SPS 主要包含的是图像的宽、高、YUV 格式和位深等基本信息;PPS 则主要包含熵编码类型、基础 QP 和 最大参考帧数量等基本编码信息。如果没有 SPS、PPS 里面的基础信息,之后的 I 帧、P 帧、B 帧就都没办法进行 解码。因此 SPS 和 PPS 是至关重要的。如果码流最前面没有这两个信息,放到播放器里面是播放不了的。
而且当nal_unit_type为14-31时,我们可以不用理睬,目前几乎用不到。
RBSP
上面我们说,NALU的组成部分为:
NALU = NALU Header + RBSP
其实严格来说,这个等式是不成立的,因为RBSP并不等于NALU刨去NALU Header。严格来说,NALU的组成部分应为:
NALU = NALU Header + EBSP
其中的EBSP为扩展字节序列载荷(Encapsulated Byte Sequence Payload),而RBSP为原始字节序列载荷(Raw Byte Sequence Payload)。那为什么我们上篇中,没有使用2式而使用了1式呢?那是因为,在h264的文档中,并没有EBSP这一名词出现,但是在h264的官方参考软件JM里,却使用了EBSP。
而在我们下面的分析中,我们会看到,使用EBSP是很易于理解的。
-
1、防止竞争
EBSP相较于RBSP,多了防止竞争的一个字节:0x03。
我们知道,NALU的起始码为0x000001或0x00000001,同时H264规定,当检测到0x000000时,也可以表示当前NALU的结束。那这样就会产生一个问题,就是如果在NALU的内部,出现了0x000001或0x000000时该怎么办?
所以H264就提出了“防止竞争”这样一种机制,当编码器编码完一个NAL时,应该检测NALU内部,是否出现如下左侧的四个序列。当检测到它们存在时,编码器就在最后一个字节前,插入一个新的字节:0x03。
0x000000 -> 0x00000300 0x000001 -> 0x00000301 0x000002 -> 0x00000302 0x000003 -> 0x00000303
图中0x000000和0x000001前面介绍了,0x000002是作为保留使用,而0x000003,则是为了防止NALU内部,原本就有序列为0x000003这样的数据。
这样一来,当我们拿到EBSP时,就需要检测EBSP内是否有序列:0x000003,如果有,则去掉其中的0x03。这样一来,我们就能得到原始字节序列载荷:RBSP。
-
2、RBSP和SODB
得到RBSP之后,我们迫切想做的,就是从RBSP中,提取出原始编码数据SODB(String Of Data Bits)。这就涉及到RBSP与SODB的关系:
RBSP = SODB + RBSP尾部
而且RBSP的尾部,在规定中有两种,我们分别介绍。
- 2.1 RBSP尾部
其中大多数类型的NALU,使用这种尾部。
图4 RBSP尾部语法(文档7.3.2.11)
其中:
rbsp_stop_one_bit 占1个比特位,值为1
rbsp_alignment_zero_bit 值为0,目的是为了进行字节对齐,占据若干比特位
所以RBSP就等于,SODB在它的最后一个字节的最后一个比特后,紧跟值为1的1个比特,然后增加若干比特的0,以补齐这个字节。
-
条带RBSP尾部
另一种尾部,就是当NALU类型为条带时,也即nal_unit_type等于1~5时,这时RBSP使用下面这种尾部:
图5 条带尾比特RBSP语法(文档7.3.2.10)
可以看到,rbsp_slice_trailing_bits()默认情况下,就是2.1介绍的第一种尾部。只是当entropy_coding_mode_flag值为1,也即当前采用的熵编码为CABAC,而且more_rbsp_trailing_data()返回为true,也即RBSP中有更多数据时,添加一个或多个0x0000。
所以我们拿到RBSP,只需要按照上述语法,去掉RBSP的尾部,就可以得到SODB。然后就可以对照对应类型的NALU的句法,解析出语法元素的值。
3、H.264原始码流简单解析
理论已经学习差不多了,接下来解析一段H.264码流。这里用到的是一代流媒体璀璨巨星雷神雷霄骅博士开发的工具:视音频编解码学习工程:H.264分析器,Respect!
放入一段H264的码流文件,点击开始即可,如下图所示:
从上面可以看到:视频流开头的第1帧和第2帧,是SPS和PPS信息,如果没有这两个信息,播放器就无法播放这个视频流。再接下来就是IDR帧,上面介绍过了。
现在点一下SPS信息看一下:
可以看到forbidden_zero_bit = 0
禁止位=0,代表该NALU没有错误;nal_ref_idc = 1
表示该NALU的重要性,前面提到如果当前NALU为图像参数集(SPS)、序列参数集(PPS)或IDR图像时,或者为参考图像条带(片/Slice),或者为参考图像的条带数据分割时,nal_ref_idc值肯定不为0;nal_unit_type=7
表示该NALU是SPS信息。下面还有一些数值,如图像的宽高参数:pic_width_in_mbs_minus1 : 61
, pic_height_in_map_units_minus1 : 34
,这里的值乘上16,就是实际的值,所以这个视频流的宽是61 x 16 = 986,高是34 x 16 = 544。
还有一些就不多介绍了,想继续深入可以参考官方协议文档第7.4.2.1节对照一下。
同样的点开PPS信息看一下,这里也差不多,感兴趣的可以参考官方协议文档第7.4.2.2节对照:
IDR_SLICE看一下:
SLICE看一下: