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,这个数据量显然在当前网络下是不现实的。
在 H.264/AVC 视频编码标准中,整个系统框架划分为如下两个层面:
因此平时每帧数据就是一个 NAL 单元。NAL 单元的实际格式如下:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NAL Header | EBSP | NAL Header | EBSP | ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
每个 NAL 单元都是由 1 字节 NAL header 和 若干整数字节的原始字节序列负荷(RBSP, Raw Byte Sequence Payload) 构成。
H264 结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的 yuv 数据组成。宏块作为 H264 编码的基本单位。
经过压缩后的帧分为:I帧,P帧 和 B帧:
两个 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)用于视频的显示序列。
H264 采用的核心压缩算法是帧内压缩和帧间压缩,帧内压缩是生成 I 帧的算法,帧间压缩是生成 B 帧和 P 帧的算法。
下面就简单描述下 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 种模式的预测。找出与原图最接近的一种预测模式。
再将我们之前得到的预测模式信息一起保存起来,这样我们就可以在解码时恢复原图了。效果如下:
经过帧内与帧间的压缩后,虽然数据有大幅减少,但还有优化的空间。
可以将残差数据做整数离散余弦变换,去掉数据的相关性,进一步压缩数据。如下图所示,左侧为原数据的宏块,右侧为计算出的残差数据的宏块。
上面的帧内压缩是属于有损压缩技术,也就是说图像被压缩后,无法完全复原。而熵编码压缩是一种无损压缩,其实现原理是使用新的编码来表示输入的数据,给高频的词一个短码,给低频词一个长码,从而达到压缩的效果。常用的熵编码有游程编码,哈夫曼编码和 CAVLC 编码等。
在 H.264/MPEG-4 AVC 中使用的熵编码算法是 CABAC(ContextAdaptive Binary Arithmatic Coding)。CABAC 在不同的上下文环境中使用不同的概率模型来编码。其编码过程大致是这样:首先,将欲编码的符号用二进制 bit 表示;然后对于每个 bit,编码器选择一个合适的概率模型,并通过相邻元素的信息来优化这个概率模型;最后,使用算术编码压缩数据。
MPEG-2 中使用的 VLC 也是这种算法,我们以 A-Z 作为例子,A属于高频数据,Z属于低频数据。看看它是如何做的。
CABAC 也是给高频数据短码,给低频数据长码。同时还会根据上下文相关性进行压缩,这种方式又比 VLC 高效很多。其效果如下:
从上面这张图中明显可以看出采用 CACBA 的无损压缩方案要比 VLC 高效的多。
以上就是整个编码流程,生成的数据就是压缩后的可传输的比特流。解码过程则与之相反。