音视频基础(二)下:编码之h264解析①
上篇我们分析了压缩编码的两种类型:无损压缩和有损压缩。并且介绍了使用ffmpeg
将PCM编码成AAC的步骤以及可配置项。上篇文章由于篇幅以及能力问题,没有详细介绍AAC解码的过程。
本篇将介绍H264这一编码格式的具体压缩编码过程,以及如何解码h264格式为y4m(即原始yuv数据)。
1 帧内压缩
帧内压缩,实际上就是图像压缩。H264对于图像压缩的步骤如下:
1.1 划分宏块和子块
宏块(MacroBlock)是一个像素组,在h264中为一个 16 * 16(或者 8 * 8 )的像素组。
划分好宏块之后,会计算宏块中的像素值,如果宏块中像素值差别较大,会划分子块。
子块的大小非常灵活,可以是(4,8,16) * (4,8,16)的任意结果,子块的作用就是让一个块中的像素值相对统一
如下面这个宏块,可以进一步划分为这样的子块:
1.2 帧内预测&残差计算
图片中像素的分布一般是有规律的,我们甚至可以用几种模式大致的匹配像素分布的样子,这就是帧内预测的原理。
对于h264来说,它有三种预测方式:
预测方式
1.对细节比较多的图片,也就是划分了子块的图片,进行 4*4的亮度块预测
预测实际上就是根据周围的亮度信息预测这个块中的亮度信息
2.对比较平坦的图片,也就是只有宏块的图片,进行 16 * 16的亮度快预测:
3.对于色度,预测方法同16*16亮度块预测中的4种,只不过块大小是8 * 8
预测模式编码
如果把所有块都一个一个进行预测,很明显计算量比较大,所以h264可以进行预测模式编码,简单的来说就是 根据周边块的预测模式决定当前块的预测模式
这样就可以减少预测计算量,同时使预测结果相对一致。
残差计算
很明显,虽然h264有众多的预测模式,但是现实中的图片肯定与预测模式多少有所差别:
所以我们还需要计算 残差:原始图像和预测图像的差别,这样我们就得到了残差图(residual picture)。在存储时,我们将残差图和预测模式信息存储起来,这样解码时就可还原成原图了。
Prediction Mode Info + Residual picture = Compression ≈ Original Picture
1.3 离散余弦变换(DCT)
我们得到了残差图,但是残差图实际上也是很大的一张图片,我们现在的压缩比还不是很高。
所以我们可以用离散余弦变换对残差图进行进一步处理。
DCT实际上是原始变换信号是实偶函数的离散傅里叶变换(DFT),作用就是在保存较好的频域能量聚集度的同时尽可能压缩图片,本文不做过多介绍,有兴趣请查看详解离散余弦变换(DCT)
处理结果如下:
1.4 量化
H.264采用标量量化技术,它将每个图像样点编码映射成较小的数值。一般标量量化器的原理为:
其中,y 为输入样本点编码,QP 为量化步长,FQ 为 y 的量化值,round()为取整函数(其输出为与输入实数最近的整数)
量化步长 QP 决定量化器的编码压缩率及图像精度。如果 QP 比较大,则量化值 FQ 动态范围较小,其相应的编码长度较小,数据压缩率高但会损失较多的图像细节信息;
简单的来说,量化就是进一步的压缩图片,虽然代价是进一步丢失图像细节信息
1.5 熵编码
前面量化得到的数据还可以进一步进行压缩,因为前面的压缩实际上都是 针对图片这一特质进行压缩的,无论是预测模式还是DCT变换,都是针对诸如亮度分布等信息进行压缩,我们还可以基于 数据自身的统计特征 再进行一次压缩,这就是 熵编码。
实际上就是在信息熵极限范围内,想办法提高信息熵。由于没有突破信息熵极限,所以不会市镇,因而是无损压缩。
H264中使用的熵编码是算数编码,具体来说可能是
上下文自适应的变长编码(Context-based Adaptive Variable-Length Coding,CAVLC)
或者
上下文自适应的二进制算术编码(Context-based Adaptive Binary Arithmetic Coding,CABAC)
这类编码可以说是前文提到的哈夫曼编码的升级,这里同样不做具体介绍,有兴趣可以参考算术编码
在熵编码结束后,我们终于成功实现了帧内压缩!
我们可以简单的概括一下: 帧内压缩 = 帧内预测 + 变换 + 量化 + 熵编码
实际上帧间压缩只是换了预测的过程,后面的不变。这里为了行文完整把后面三步提到帧内压缩中介绍,下面帧间压缩不再重复
2 帧间压缩
2.1 分组
一般来说,视频的帧数都大于物体运动变化的速度。比方说在一个台球赛的30帧的视频中,很可能1秒钟内只有白球在每帧进行缓慢移动。其他都不变。
这意味着我们实际上可以在一定时间内只保留一个帧的数据,其他的帧根据这个帧进行计算,我们可以将这个时间范围内的帧以及其他计算结果称为一个 GOP(Group Of Pictures)。那么怎么确定GOP的范围呢?H264的算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组
在这样一组帧中,经过编码后,我们只保留第一帧的完整数据,其它帧都通过参考上一帧计算出来。这个完整帧被称为 I帧,而其他帧则是预测编码帧,不存储完整信息,需要根据别的帧计算。
2.2 定义帧
我们可以将GOP中的帧具体分为三种: I、P、B
I帧
I帧不参考其他图像帧,具有完整信息,同时是其他帧的参考帧,所以其质量直接影响后面的P和B帧的质量。在h264中,一个GOP只有一个I帧,它是GOP的第一帧,也可以称为IDR(Instantaneous Decoder Refresh)
I帧的编码就是上文所说的帧内编码,不过要加上重构图像并滤波这一步
P帧
P帧不存储完整信息,而是从I帧或者前面的P帧中找出P帧中某点的预测值和运动矢量,取预测差值和运动矢量。
P 帧编码的基本流程:
- 进行运动估计,计算采用帧间编码模式的率失真函数值。P 帧只参考前面的帧;
- 进行帧内预测,选取率失真函数值最小的帧内模式与帧间模式比较,确定采用哪种编码模式;
- 计算实际值和预测值的差值;
- 对残差进行变换和量化;
- 若编码,如果是帧间编码模式,编码运动矢量。
因为P帧是通过预测的方式获得的,并且也可以作为P或者B帧的参考帧,所以可能导致错误的传递,造成最后较为严重的失真。
B帧
B帧同样不存储完整信息。与P帧的不同是:B帧既需要之前的图像帧(I 帧或 P 帧),也需要后来的图像帧(P 帧),采用运动预测的方式进行帧间双向预测编码。B代表着Bi-Directional,也就是双向的。
B帧的编码流程与P帧类似,只不过需要同时参考后面的帧。
2.3 预测帧
在2.2中我们提到了:P帧和B帧都是通过运动估计等方法存储信息的,下面我将介绍运动估计、运动补偿、率失真函数等概念。
运动估计
在分组这部分就已经介绍过:一系列帧中可能只有一小部分变化,或者说运动。运动估计就是去计算这部分运动,或者准确的说 运动矢量
H264编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了。下面这幅图就是搜索后的台球移动的位置。
通过上图中台球位置相差,就可以计算出台图运行的方向和距离。H264依次把每一帧中球移动的距离和方向都记录下来就成了下面的样子。
运动补偿
简单的说:没有运动的部分就是补偿数据。在上图中就是绿色的台球桌。我们将补偿数据压缩保存,再存储运动矢量,需要解码时就可以将二者组合回复原图:
率失真函数
有损压缩算法,性能由编码输出的比特率和失真共同决定。编码的目的就是在保证一定视频质量的条件下尽量减少编码比特率,或在一定编码比特率限制条件下尽量地减小编码失真。
率失真函数就是平衡输出比特率和失真两者,让它们处于可接受的平衡。由于两者成反比,所以它是一个反比例函数
所谓的”进行帧内预测,选取率失真函数值最小的帧内模式与帧间模式比较,确定采用哪种编码模式”,就是指对P帧也进行1中所说的帧内压缩,与P帧的基于参考帧的帧间预测比较,看看哪个模式率失真值越小就选择哪个。
下篇内容
本来打算在这篇中一起将H264的具体码流格式等介绍了,但是文章已经比较长了,再添加内容重点就不清晰了。所以这部分会放在下张一起讲。这样下章就会有比较充足的时间实现js解析h264的方法。