参考:http://blog.csdn.net/ivy_reny/article/details/47144121
http://blog.sina.com.cn/s/blog_4ad7c2540101me90.html
从这一篇开始,我们详细的讲解H.264/AVC 比特码流的句法和语义,可以说,能够学习并掌握H.264的句法和语义,是能彻底掌握并应用H.264的关键。在前面几篇文章中,我们只是从理论层面,介绍了H.264、片、宏块、帧内预测、帧间预测。而在句法和语义中,我们就可以拿到H.264编码之后的裸流,用理论加实践的方式,一步步探索H.264的编解码实现过程。
而在这篇文章里呢,我们就先从宏观的角度,来看看使用H.264编码之后,得到的裸流的分层结构是什么样的。
1. H264句法元素层次结构
(1)以往标准句法元素的分层结构
在H.264以往标准句法中,句法元素被组织成六个层次:序列(sequence)层、图像组(gop)层、图像(frame/field-picture)层、片/条带(slice)层、宏块(macroblock)层、子块(sub-block)层。

上图就是在之前标准中的分层结构,可以看到句法元素同画面的划分一样,被组织成了有层次的结构,这种结构有助于更高效的节省码流。但是这样的结构,有几个很大的缺点:
(1) 在这样的结构中,每一层的头部和它的数据部分形成管理与被管理的强依赖关系,头部的句法元素是该层数据的核心,而一旦头部丢失,数据部分的信息几乎不可能再被正确解码出来,尤其在序列层及图像层。
(2)在序列层及图像层,因为数据量过大,不可能将所有的句法元素一次传输,这时假如头部所在的分组丢失,那么该层其他的数据,即使能正确接收也无法解码。
(3)图像层内的各个片之间,经常会携带相同的数据,造成码流的浪费。
所以在此基础上,H.264取消了图像层和序列层,取而代之的,将原本属于图像层和序列层的大部分句法元素,抽取出来形成图像参数集和序列参数集,其余的部分,则放入片层。
(2)H.264的句法元素的分层结构
下图为H.264图像参数集和序列参数集,与片中句法元素的关系:
H.264参数集与片中句法元素的关系
从图中可以看到,同一个序列参数集可以被多个序列中的图像参数集引用,同一个图像参数集也可以被多个图像引用。所以我们在打开H.264码流文件时会看到,序列参数集和图像参数集位于码流的最前面。如果编码器认为需要更新参数集时,会发送新的参数集。
在这种引用关系中,被引用方在时间上必须先被发送,所以在H.264建议中,参数集和参数集外部的句法元素,分别处于不同的信道中传输。
除了在参数上的改进,H.264在片层以下的句法元素上的结构,和之前的标准类似。而且因为取消了图像层,片成为携带图像像素数据的,最上层的数据单位。并且每个片必须携带所属的图像的编号、大小等信息,这些信息在同一个图像的每个片中必须是一致的。
以上就是H.264的句法元素的分层结构,它分别为序列、图像、片、宏块、子宏块这5个层次。
- Sequence:序列起始码后的序列头中包含了图像尺寸,宽高比,图像速率等信息。
- GOP:序列层下是图像组层,一个图像组由相互间有预测和生成关系的一组I、P、B图像构成,但头一帧图像总是I帧。
- frame:图像组层下是图像,分为I、P、B三类。PIC头中包含了图像编码的类型和时间参考信息。
- slice:多个宏块的组合。
- MB:宏块大小为1616 , 对于亮度宏块可以分为4个88的亮度色块。
图像组结构示意图:
H264一个图像序列的组成:SPS+PPS+SEI+一个I帧+若干个P帧。SPS、PPS、SEI、一个I帧、一个P帧都 可以称为一个NALU。
2. H.264数据元素
2.1. 序列(Sequence)
序列起始码后的序列头中包含了图像尺寸,宽高比,图像速率等信息。
2.2. 图像组(Gop)
参照一段时间内图像的统计结果表明,在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内。
所以对于一段变化不大图像画面,我们可以先编码出一个完整的图像帧A,随后的B帧就不编码全部图像,只写入与A帧的差别,这样B帧的大小就只有完整帧的1/10或更小!B帧之后的C帧如果变化不大,我们可以继续以参考B的方式编码C帧,这样循环下去。这段图像我们称为一个图像组Gop(图像组就是有相同特点的一段数据),当某个图像与之前的图像变化很大,无法参考前面的帧来生成,那我们就结束上一个Gop,开始下一个Gop,也就是对这个图像生成一个完整帧A1,随后的图像就参考A1生成,只写入与A1的差别内容。
在H264协议里定义了三种帧,完整编码的帧叫I帧,参考之前的I帧生成的只包含差异部分编码的帧叫P帧,还有一种参考前后的帧编码的帧叫B帧。
H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。
在H264中图像以Gop为单位进行组织,一个Gop是一段图像编码后的数据流, 以I帧开始,到下一个I帧结束。
一个Gop的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的Gop。这样,如果前一个Gop出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。
一个Gop就是一段内容差异不太大的图像编码后生成的一串数据流。当运动变化比较少时,一个Gop可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个I帧,然后一直P帧、B帧了。当运动变化多时,可能一个Gop就比较短了,比如就包含一个I帧和3、4个P帧。
2.3. 图像(picture)、帧(frame)、场(field)
视频压缩中,一幅图像(picture)可以分成一帧(frame)或两场(field)。
(1)帧内编码帧/I 帧/关键帧
Intra codedframes/ Intra coded slices/ I-frames/ Key frames
在I帧中,所有宏块都采用帧内预测的方式,因此解码时仅用I帧的数据就可重构完整图像,不需要参考其他画面而生成。I帧可以用来快进快退,作为随机访问的参考点。I帧用来作为P帧和B帧的参考帧,其质量直接影响到其后各帧的质量。I帧描述了图像背景和运动主体的详情,可以帮助场景切换时重置画面质量。当场景进行切换时,可以切换I帧从而更加高效地压缩P帧和B帧(当然这要求编码器要有场景切换检测功能)。由于I帧仅进行帧内预测,没有进行运动估计等帧间预测,因此I帧的码率比较高。
在H.264中规定了两种类型的I帧:普通I帧(normal Iframes)和IDR帧(InstantaneousDecoding Refresh, 即时解码刷新)。
IDR帧实质也是I帧,使用帧内预测。IDR帧的作用是立即刷新,会导致DPB(Decoded Picture Buffer参考帧列表)清空,而I帧不会。所以IDR帧承担了随机访问功能,一个新的IDR帧开始,可以重新算一个新的Gop开始编码,播放器永远可以从一个IDR帧播放,因为在它之后没有任何帧引用之前的帧。如果一个视频中没有IDR帧,这个视频是不能随机访问的。所有位于IDR帧后的B帧和P帧都不能参考IDR帧以前的帧,而普通I帧后的B帧和P帧仍然可以参考I帧之前的其他帧。IDR帧阻断了误差的积累,而I帧并没有阻断误差的积累。
I帧特点:
1)它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
2)解码时仅用I帧的数据就可重构完整图像;
3)I帧描述了图像背景和运动主体的详情;
4)I帧不需要参考其他画面而生成;
5)I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
6)I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧;
7)I帧不需要考虑运动矢量;
8)I帧所占数据的信息量比较大。
I 帧编码的基本流程:
① 进行帧内预测,决定所采用的帧内预测模式。
② 像素值减去预测值,得到残差。
③ 对残差进行变换和量化。
④ 变长编码和算术编码。
⑤ 重构图像并滤波,得到的图像作为其它帧的参考帧。
(2)预测帧/P帧/前向预测编码帧
P帧属于前向预测的帧间编码,仅参考前面的最靠近它的I帧或者P帧进行帧间预测。编码端在参考帧中找到P帧某点的预测值以及运动矢量,相减获取预测差值,将预测差值和运动矢量进行编码后传输。解码端根据运动矢量从I帧中找出P帧某点的预测值并与预测差值相加得到P帧某点样值,从而可得到完整的P帧。在以前的标准中(如MPEG-2),P帧解码时只使用前一帧作为参考,在H.264中,解码时可以使用多帧已解码的图像作为参考。P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧。由于P帧是参考帧,它可能造成解码错误的扩散。由于是差值传送,P帧的压缩比较高。在H.264基本配置中仅仅存在I帧和P帧,不存在B帧。
P帧特点:
1)P帧是I帧后面相隔1~2帧的编码帧;
2)P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
3)解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
4)P帧属于前向预测的帧间编码,它只参考前面最靠近它的I帧或P帧;
5)P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧;
6)由于P帧是参考帧,它可能造成解码错误的扩散;
7)由于是差值传送,P帧的压缩比较高。
(3)B帧/双向预测编码帧
B帧以前面的I或P帧和后面的P帧为参考帧进行预测,编码找出B帧某点的预测值和两个运动矢量,并取预测差值和运动矢量传送。解码端根据运动矢量在两个参考帧中找出预测值并与差值求和,得到B帧某点样值,从而可得到完整的B帧。在以前的标准中(如MPEG-2),B帧不作为参考帧,可以使用较低的码率、降低图像质量而不会影响后续图像,解码时使用两帧图像作为参考。在H.264中,B帧可以作为参考帧,解码时可以使用一幅、两幅或多幅图像作为参考,图像帧的传输顺序和显示顺序是不同的。B帧压缩比最高,因为它只反映两参考帧间运动主体的变化情况,预测比较准确,但是解码时CPU会比较累。
B帧特点:
1)B帧是由前面的I或P帧和后面的P帧来进行预测的;
2)B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
3)B帧是双向预测编码帧;
4)B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确;
5)B帧不是参考帧,不会造成解码错误的扩散。
P 帧和 B 帧编码的基本流程:
① 进行运动估计,计算采用帧间编码模式的率失真函数(节)值。P帧只参考前面的帧,B帧可参考后面的帧。
② 进行帧内预测,选取率失真函数值最小的帧内模式与帧间模式比较,确定采用哪种编码模式。
③ 计算实际值和预测值的差值。
④ 对残差进行变换和量化。
⑤ 熵编码,如果是帧间编码模式,编码运动矢量。
注:I、B、P各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I帧的压缩率是7(跟JPG差不多),P帧是20,B帧可以达到50。可见使用B帧能节省大量空间,节省出来的空间可以用来保存多一些I帧,这样在相同码率下,可以提供更好的画质。
2.4. 片/条带(slice)
在H.264中,一幅图像可以编码为一个或多个片(slice),每个slice由宏块组成,一个宏块由一个16×16亮度像素和附加的一个8×8 Cb和一个8×8 Cr彩色像素块组成。宏块是H.264编码的基本单位,可以采用不同的编码类型。slice共有5种类型。**
slice的目的是为了限制误码的扩散和传输,使编码片相互间保持独立。
**一个slice编码之后被打包进一个NALU,NALU除了容纳slice还可以容纳其它数据,如序列参数集SPS、PPS、SEI等。
类型 | 描述 | 支持的框架 |
---|---|---|
I frames/slices (Intra) | I宏块 | 全部 |
P frames/slices (Predicted) | I宏块、P宏块 | 全部 |
B frames/slices (Bi-Predicted) | I宏块、B宏块 | 扩展和主 |
SI-frames/slices (switching I) | SI宏块(一种特殊的帧内编码宏块),用于不同编码流之间的切换 | 扩展 |
SP-frames/slices (switching P) | I宏块、P宏块,用于不同编码流之间的切换 | 扩展 |
以下是片的句法结构:片头规定了片的类型、属于哪个图像、有关的参考图像等;片的数据包含了一系列宏块和不编码数据。
2.5. 宏块(MB)
宏块(Macro Block)是H.264编码的基本单位,一个编码图像首先要划分成多个块(4x4 像素)才能进行处理,显然宏块应该是整数个块组成,通常宏块大小为16x16个像素。
宏块分为I、P、B宏块:
- I宏块(帧内预测宏块)只能利用当前片中已解码的像素作为参考进行帧内预测;
- P宏块(帧间预测宏块)可以利用前面已解码的图像作为参考图像进行帧内预测;
- B宏块(帧间双向预测宏块)则是利用前后向的参考图形进行帧内预测
关于H.264的协议文档:http://www.itu.int/rec/T-REC-H.264-200503-S/en