1 H264 简介
H.264,同时也是 MPEG-4 第十部分,是由 ITU-T 视频编码专家组(VCEG)和 ISO/IEC 动态图像专家组(MPEG)联合组成的联合视频组(JVT,Joint Video Team)提出的高度压缩数字视频编解码器标准。这个标准通常被称之为 H.264/AVC(或者 AVC/H.264 或者 H.264/MPEG-4 AVC 或 MPEG-4/H.264 AVC)。是一种面向块,基于运动补偿的视频编码标准。
视频编码其本质就是将数据压缩,主要是去除冗余信息(包括空间上的冗余信息和时间上的冗余信息),从而实现数据量的压缩。
- 空间冗余: 在同一图像(帧)内,相近像素之间的差别很小(甚至是相同的),所以就可以用一个特定大小的像素矩阵来表示相邻的像素。
- 时间冗余: 视频中连续的图像(帧)之间,其中发生变化的像素占整张图像像素的比例极其微小,所以就可以用其中一帧来表示相邻的帧来减少带宽消耗。
- 编码冗余: 不同像素出现的概率不同,所以就可以为出现概率高的像素分配尽量少的字节,对出现概率低的像素分配尽量多的字节。
- 视觉冗余:人眼对很多像素颜色不敏感,所以就可以丢弃这些冗余的信息而并不影响人眼观看的效果。
- 知识冗余:有许多图像的理解与某些基础知识有相当大的相关性。例如,人脸的图像有固定的结构,嘴的上方有鼻子,鼻子的上方有眼睛,鼻子位于正面图像的中线上等等。这类规律性的结构可由先验知识和背景知识得到,我们称此类冗余为知识冗余。根据已有知识,对某些图像中所包含的物体,可以构造其基本模型,并创建对应各种特征的图像库,进而图像的存储只需要保存一些特征参数,从而可以大大减少数据量。
用一个简单的例子来说明编码的必要性:
当你此刻显示器正在播放一个视频,分辨率是1280*720,帧率是25,那么一秒所产生正常的数据大小为:
1280*720(位像素)*25(帧) / 8(1字节8位)(转换结果:B) / 1024(转换结果:KB) / 1024
(转换结果:MB) = 2.74MB
那么90分钟的电影就要14.8GB,这个数据量显然在当前网络下是不现实的。
2 H264 功能结构
在 H.264/AVC 视频编码标准中,整个系统框架划分为如下两个层面:
- 视频编码层(Video Coding Layer,VCL):VCL 数据即被压缩编码后的视频数据序列,负责有效表示视频数据的内容,主要包括帧内预测,帧间预测、变换量化、熵编码等压缩单元。
- 网络抽象层(Network Abstraction Layer,NAL):负责将 VCL 数据封装到 NAL 单元中,并提供头信息,以保证数据适合各种信道和存储介质上的传输。
如图所示:
因此平时每帧数据就是一个 NAL 单元。NAL 单元的实际格式如下:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NAL Header | EBSP | NAL Header | EBSP | ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
每个 NAL 单元都是由 1 字节 NAL header 和 若干整数字节的原始字节序列负荷(RBSP, Raw Byte Sequence Payload) 构成。
3 H264 帧类型
H264 结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的 yuv 数据组成。宏块作为 H264 编码的基本单位。
经过压缩后的帧分为:I帧,P帧 和 B帧:
- I 帧 :关键帧,采用帧内压缩技术。你可以理解为这一帧画面的完整保留,解码时只需要本帧数据就可以完成(因为包含完整画面)。
- P 帧 :向前参考帧,在压缩时,只参考前面已经处理的帧。采用帧间压缩技术。P 帧表示的是这一帧跟之前的一个关键帧(或 P 帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P 帧没有完整画面数据,只有与前一帧的画面差别的数据)。
- B 帧 :双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用帧间压缩技术。B 帧记录的是本帧与前后帧的差别,换言之,要解码 B 帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B 帧压缩率高,但是解码时 CPU 工作量会比较大。
两个 I 帧之间是一个图像序列,即 GOP,在一个图像序列中只有一个I帧。
如下图所示:
当运动变化比较少时,一个序列可以很长,因为运动变化少就代表图像画面的内容变动很小,所以就可以编一个 I 帧,然后一直 P 帧、B 帧了。当运动变化多时,可能一个序列就比较短了。
还有一个概念是,IDR帧:
一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是 I 帧图像。H.264 引入 IDR 图像是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR 图像之后的图像永远不会使用 IDR 之前的图像的数据来解码。IDR 图像一定是 I 图像,但 I 图像不一定是 IDR 图像。一个序列中可以有很多的I图像,I 图像之后的图像可以引用 I 图像之间的图像做运动参考。
还有一点注意的,对于 IDR 帧来说,在 IDR 帧之后的所有帧都不能引用任何 IDR 帧之前的帧的内容,与此相反,对于普通的 I 帧来说,位于其之后的 B帧 和 P 帧可以引用位于普通 I 帧之前的 I 帧。从随机存取的视频流中,播放器永远可以从一个 IDR 帧播放,因为在它之后没有任何帧引用之前的帧。但是,不能在一个没有 IDR 帧的视频中从任意点开始播放,因为后面的帧总是会引用前面的帧。
I、B、P 各帧是根据压缩算法的需要,是人为定义的,它们都是实实在在的物理帧。一般来说,I 帧的压缩率是7(跟JPG差不多),P 帧是20,B 帧可以达到50。可见使用 B 帧能节省大量空间,节省出来的空间可以用来保存多一些 I 帧,这样在相同码率下,可以提供更好的画质。
正因为有 B 帧这样的双向预测帧的存在,某一帧的解码序列和实际的显示序列是不一样的。如下图所示:
DTS: (Decode Time Stamp) 用于视频的解码序列
PTS: (Presentation Time Stamp)用于视频的显示序列。
4 H264 编码原理
H264 采用的核心压缩算法是帧内压缩和帧间压缩,帧内压缩是生成 I 帧的算法,帧间压缩是生成 B 帧和 P 帧的算法。
- 帧内(Intraframe)压缩:也称为空间压缩(Spatialcompression)。当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,所以可以独立的解码、显示。帧内压缩一般达不到很高的压缩,跟编码 JPEG 差不多。
- 帧间(Interframe)压缩:也称为时间压缩(Temporalcompression),它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的。帧差值(Framedifferencing)算法是一种典型的时间压缩法,相邻几帧的数据有很大的相关性,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量。
下面就简单描述下 H264 压缩数据的过程。通过摄像头采集到的视频帧(按每秒 30 帧算),被送到 H264 编码器的缓冲区中。编码器先要为每一幅图片划分宏块。
以下面这张图为例:
划分宏块
H264 默认是使用 16X16 大小的区域作为一个宏块,也可以划分成 8X8 大小。
划分好宏块后,计算宏块的象素值。
以此类推,计算一幅图像中每个宏块的像素值,所有宏块都处理完后如下面的样子。
划分子块
H264 对比较平坦的图像使用 16X16 大小的宏块。但为了更高的压缩率,还可以在 16X16 的宏块上更划分出更小的子块。子块的大小可以是 8X16、 16X8、 8X8、 4X8、 8X4、 4X4非常的灵活。
上幅图中,红框内的 16X16 宏块中大部分是蓝色背景,而三只鹰的部分图像被划在了该宏块内,为了更好的处理三只鹰的部分图像,H264就在 16X16 的宏块内又划分出了多个子块。
这样再经过帧内压缩,可以得到更高效的数据。下图是分别使用 mpeg-2 和 H264 对上面宏块进行压缩后的结果。其中左半部分为 MPEG-2 子块划分后压缩的结果,右半部分为 H264 的子块划压缩后的结果,可以看出 H264 的划分方法更具优势。
宏块划分好后,就可以对 H264 编码器缓存中的所有图片进行分组了。
帧分组
对于视频数据主要有两类数据冗余,一类是时间上的数据冗余,另一类是空间上的数据冗余。其中时间上的数据冗余是最大的。为什么这么说呢?假设摄像头每秒抓取30帧,这30帧的数据大部分情况下都是相关联的。也有可能不止30帧的的数据,可能几十帧,上百帧的数据都是关联特别密切的。
对于这些关联特别密切的帧,其实我们只需要保存一帧的数据,其它帧都可以通过这一帧再按某种规则预测出来,所以说视频数据在时间上的冗余是最多的。
为了达到相关帧通过预测的方法来压缩数据,就需要将视频帧进行分组。那么如何判定某些帧关系密切,可以划为一组呢?我们来看一下例子,下面是捕获的一组运动的台球的视频帧,台球从右上角滚到了左下角。
H264 编码器会按顺序,每次取出两幅相邻的帧进行宏块比较,计算两帧的相似度。如下图:
通过宏块扫描与宏块搜索可以发现这两个帧的关联度是非常高的。进而发现这一组帧的关联度都是非常高的。因此,上面这几帧就可以划分为一组。其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。
在这样一组帧中,经过编码后,我们只保留第一帖的完整数据,其它帧都通过参考上一帧计算出来。我们称第一帧为 IDR/I 帧,其它帧我们称为 P/B 帧,这样编码后的数据帧组我们称为 GOP。
运动估计与补偿
在 H264 编码器中将帧分组后,就要计算帧组内物体的运动矢量了。还以上面运动的台球视频帧为例,我们来看一下它是如何计算运动矢量的。
H264 编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了。下面这幅图就是搜索后的台球移动的位置。
通过上图中台球位置相差,就可以计算出台球运动的方向和距离。H264 依次把每一帧中球移动的距离和方向都记录下来就成了下面的样子。
运动矢量计算出来后,将相同部分(也就是绿色部分)减去,就得到了补偿数据。我们最终只需要将补偿数据进行压缩保存,以后在解码时就可以恢复原图了。压缩补偿后的数据只需要记录很少的一点数据。如下所示:
我们把运动矢量与补偿称为帧间压缩技术,它解决的是视频帧在时间上的数据冗余。除了帧间压缩,帧内也要进行数据压缩,帧内数据压缩解决的是空间上的数据冗余。下面我们就来介绍一下帧内压缩技术。
帧内预测
人眼对图象都有一个识别度,对低频的亮度很敏感,对高频的亮度不太敏感。所以基于一些研究,可以将一幅图像中人眼不敏感的数据去除掉。这样就提出了帧内预测技术。
H264 的帧内压缩与 JPEG 很相似。一幅图像被划分好宏块后,对每个宏块可以进行 9 种模式的预测。找出与原图最接近的一种预测模式。
下面这幅图是对整幅图中的每个宏块进行预测的过程。
帧内预测后的图像与原始图像的对比如下:
然后,将原始图像与帧内预测后的图像相减得残差值。
再将我们之前得到的预测模式信息一起保存起来,这样我们就可以在解码时恢复原图了。效果如下:
经过帧内与帧间的压缩后,虽然数据有大幅减少,但还有优化的空间。
对残差数据做 DCT 变换
可以将残差数据做整数离散余弦变换,去掉数据的相关性,进一步压缩数据。如下图所示,左侧为原数据的宏块,右侧为计算出的残差数据的宏块。
将残差数据宏块数字化后如下图所示:
将残差数据宏块进行 DCT 转换。
去掉相关联的数据后,我们可以看出数据被进一步压缩了。
做完 DCT 变换后,还不够,还要进行熵编码。
熵编码(CABAC)
上面的帧内压缩是属于有损压缩技术,也就是说图像被压缩后,无法完全复原。而熵编码压缩是一种无损压缩,其实现原理是使用新的编码来表示输入的数据,给高频的词一个短码,给低频词一个长码,从而达到压缩的效果。常用的熵编码有游程编码,哈夫曼编码和 CAVLC 编码等。
在 H.264/MPEG-4 AVC 中使用的熵编码算法是 CABAC(ContextAdaptive Binary Arithmatic Coding)。CABAC 在不同的上下文环境中使用不同的概率模型来编码。其编码过程大致是这样:首先,将欲编码的符号用二进制 bit 表示;然后对于每个 bit,编码器选择一个合适的概率模型,并通过相邻元素的信息来优化这个概率模型;最后,使用算术编码压缩数据。
MPEG-2 中使用的 VLC 也是这种算法,我们以 A-Z 作为例子,A属于高频数据,Z属于低频数据。看看它是如何做的。
CABAC 也是给高频数据短码,给低频数据长码。同时还会根据上下文相关性进行压缩,这种方式又比 VLC 高效很多。其效果如下:
现在将 A-Z 换成视频帧,它就成了下面的样子。
从上面这张图中明显可以看出采用 CACBA 的无损压缩方案要比 VLC 高效的多。
以上就是整个编码流程,生成的数据就是压缩后的可传输的比特流。解码过程则与之相反。