H.264 压缩与编解码原理
- H.264 压缩与编解码原理
- H.264 简介
- 视频编码的总体思路
- H.264 压缩技术
- 帧内预测压缩
- 什么是空间冗余?
- 具体预测方法
- 帧间预测压缩
- 什么是时间冗余?
- 具体预测方法:运动估计
- 概念:Group of Pictures(GOP)
- 概念:Decoded Picture Buffer
- 整数离散余弦变换(Discrete Cosine Transform,DCT)
- 什么是低频?
- 什么是高频?
- DCT 的理论依据
- 量化
- CABAC压缩
- 二值化
- 上下文建模
- 二进制算术编码
- 实例:H.264 压缩过程
- H.264 编解码
- H.264 的码流分层结构
- 片(Slice)
- 宏块(Macroblock)
- NALU——H.264 原始码流的基本组成单位
- NALU header 字段解析
- 补充:H.264 封装模式
- H.264 的码流六层结构
- AnnexB 三层结构
- H.264 编码器框图
- H.264 解码器框图
- 解析 H.264 文件的 C 语言代码
- 参考
H.264 压缩与编解码原理
H.264 简介
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)而明确的说明它两方面的开发者。
H.264标准各主要部分有Access Unit delimiter(访问单元分割符),SEI(附加增强信息),primary coded picture(基本图像编码),Redundant Coded Picture(冗余图像编码)。还有Instantaneous Decoding Refresh(IDR,即时解码刷新)、Hypothetical Reference Decoder(HRD,假想参考解码)、Hypothetical Stream Scheduler(HSS,假想码流调度器)。
视频编码的总体思路
编码其实就是压缩,就是要去除冗余信息。
通常有2种方法:一是压缩分辨率,去除重复多余的信息;二是质量压缩,去掉一些人感知不敏感的信息。
视频的冗余信息:
- 空间冗余,即相邻的像素往往很相似。
- 时间冗余,即相邻的帧的内容往往很相似。
- 视觉冗余,即人眼感知不敏感的信息。
H.264 压缩技术正是针对以上冗余信息进行一一攻破。
H.264 压缩技术
- 帧内预测压缩:解决的是空域数据冗余问题。
什么是空域数据,就是这幅图⾥数据在宽⾼空间内包含了很多颜⾊,光亮。⼈的⾁眼很难察觉的数据。 对于这些数据,我们可以认作冗余。
- 帧间预测压缩:解决的是时域数据冗余问题。
视频在⼀段时间内的画面没有较⼤的变化,我们针对这⼀时间内的相同的数据压缩掉。这叫时域数据压缩。
- 整数离散余弦变换(DCT):将空间上的相关性变为频域上无关的数据然后进行量化,解决的是视觉冗余问题。
这个⽐较抽象。这个跟数学是紧密联系在⼀起的。如果对傅⾥叶变换理解的⽐较好的。对这个会理解的⽐较快。如果对傅⾥叶变换不了解的。可能有稍许困难。傅⾥叶变换可以把⼀个复杂波形图变换成许多的正弦波。只是他们之间的频率不⼀样。以及振幅也不⼀样。如果它们在频率上没有⼀致性那么我们就可以对他进⾏压缩处理。
- CABAC压缩:一种熵编码,属于⽆损压缩。这是真正的编码环节,将前面3步处理得到的数据使用编码算法编码为最终的码流。
经过压缩后的帧分为:I帧,P帧和B帧。
- I帧:关键帧,采用帧内压缩技术。
- P帧:向前参考帧,在压缩时,只参考前面已经处理的帧。采用帧间压缩技术。
- B帧:双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用帧间压缩技术。
除了I/P/B帧外,还有图像序列GOP。
GOP:两个I帧之间是一个图像序列,在一个图像序列中只有一个I帧。如下图所示:
下面来详解介绍这些压缩技术的原理。
帧内预测压缩
什么是空间冗余?
一幅图像中相邻像素的亮度和色度信息是比较接近的,并且亮度和色度信息也是逐渐变化的,不太会出现突变。也就是说,图像具有空间相关性。 利用这种相关性,视频压缩就可以去除空间冗余信息。
比如渐变色,我们并不需要指定整个图像全部像素数据,而只是记录开头和结束以及中间变化的颜色,加上渐变位置以及渐变方向。 而视频也是利用了类似的方法去除冗余信息,叫做帧内预测,即帧内预测通过利用已经编码的相邻像素的值来预测待编码的像素值,最后达到减少空间冗余的目的。
具体预测方法
整体思路是利用一帧图像中已经编码部分来预测尚未编码部分图像,实际值和预测值之间的差别叫做残差。实际上真正编码的是残差数据,因为残差一般比较小,所以对残差编码比对实际数据编码会小很多。
所谓物以类聚人以群分,分而治之的思想又发挥重要作用了。
为了可以利用编码部分来预测尚未编码部分图像,所以需要根据具体情况对一帧图像划分为若干个部分,每个部分叫做块,其中某些块可以预测另外一些块。
H264中对一帧图像划分为宏块的方式来分别进行帧内预测,宏块可以预测相邻的宏块,那么同个宏块的像素就使用一种预测模式。
那么何为宏块呢?
H.264默认是使用16x16像素大小的区域作为一个宏块,其中亮度块为16x16,色度块为8x8,帧内预测中亮度块和色度块是分开独立进行预测的。
H264对比较平坦的图像使用16x16大小的宏块。但为了更高的质量,在细节复杂的地方,还可以在16x16的宏块上更划分出更小的子块。 子块的大小可以是8x16、16x8、8x8、4x8、8x4、4x4,非常的灵活。
对于4x4的宏块,帧内预测模式总共有9个。其中有8种方向模式和一种DC模式:
16x16和8x8的宏块预测模式一样,都是有4种帧内预测模式:
每一个宏块只能用一种预测模式,那如何选择呢?
具体算法很复杂,大概思路就是对于每一个块或者子块,我们可以得到预测块,再用实际待编码的块减去预测块就可以得到残差块。然后在不同场景下根据不同的算法对残差块进行计算得到最优的预测模式。
帧间预测压缩
什么是时间冗余?
在一个视频中,一般前后两帧图像往往变化比较小,这就是视频的时间相关性。而视频一般往往一秒会播放20-30帧,所以存在大量重复的图像数据,所以会有巨大的压缩空间。
视频编码中,就是通过在已经编码的帧里面找到一个块来预测待编码块的像素,从而达到减少时间冗余的目的,官方名称为:帧间预测。具体来说,就是在前面某一帧找到一个内容很接近的块,那么只要再加上运动矢量,就可以表示当前的块。
具体预测方法:运动估计
帧间预测一个重要概念就是运动估计,就是寻找当前编码的块在已编码图像中的最佳对应块。如图,假设P为当前编码帧,Pr为参考帧,当前编码块为B,则运动估计要做的就是在Pr中寻找与B相减残差最小的块Br,Br就叫做B的最佳匹配块。
所以帧间预测这里的难点在于如何找到最佳的参考块来预测当前块,这里涉及很多复杂的运动搜索算法,主要有这两种算法:
- 全局搜索算法:该方法是把搜索区域内所有的像素块逐个与当前宏块进行比较,查找具有最小匹配误差的一个像素块为匹配块。全搜索算法的优点是可以从预测帧中选择出最准确的预测块,为全局最优结果,预测精度很⾼。但是这种算法每确定⼀个块的预测块都需要对预测帧中的所有块做运算,算法复杂度⾼,搜索范围太⼴,⼤⼤增加了视频压缩时间。目前全局搜索算法极少使用。
- 快速搜索算法:该方法按照一定的数学规则进行匹配块的搜索。包括:三步搜索算法、菱形搜索算法等。这一方法的好处是速度快,坏处是可能只能得到次最佳的匹配块。
- 三步搜索算法是快速搜索算法中较为常⽤的算法。它的计算规模相⽐于全搜索算法,是后者的 1/10 左右。此算法的思想如下:⾸先选定开始进⾏搜索的中⼼坐标,然后确定进⾏搜索的范围,搜索步⻓初始值设定为最⼤搜索范围的⼀半。在以起始点 为中⼼的正⽅形上对待预测块计算匹配误差,如果在中⼼点处,计算的 SAD 值最⼩,则认为待预测块没有运动,预测块所处的位置即为中⼼点;若在正⽅形上的其余 8个点上计算的 SAD 最⼩,则以最⼩点所处位置为中⼼重新确定正⽅形,此时搜索步⻓为上⼀次的⼆分之⼀,将第三次搜索得出的结果作为最终结果。此种搜索算法的计算复杂度较⼩,但是容易由于计算出的点是局部最优,⽽不是全局最优导致匹配误差较⼤。
- 菱形搜索算法为使⽤度最⾼的搜索算法,可以选⽤⼤菱形搜索算法(LDSP)或者⼩菱形搜索算法(SDSP)。
⼤菱形搜索算法中采⽤ 9 个点进⾏搜索,中⼼点和按照菱形顶点确定的 8 个像素 点,⼩菱形搜索算法中使⽤ 5 个点进⾏搜索,中⼼点以及垂直和⽔平⽅向上的 4 个像素点。具体算法思想是,⾸先和三步搜索算法相同,确定搜索的中⼼点和搜索步⻓,然后加⼊以此为中⼼的菱形 8 个顶点(⼤菱形搜索算法)或者 5个顶点(⼩菱形搜索算法),对这些点计算 SAD 值,从中得出 SAD 值最⼩的点,如果最⼩值的点为中⼼点则判定没有运动,算法结束,继续计算该待预测块的运动⽮量和运动残差,如果最⼩值点位于菱形顶点,则继续以此点为中⼼点,搜索步⻓和初始搜索步⻓相同,确定出新的菱形顶点,并继续计算,直到最⼩值点位于菱形中⼼点则算法结束。采⽤此算法时,搜索点数最⼩为 13,但是不断⽤菱形搜索算法持续计算,可能会导致搜索点数很多,⽽且选⽤了固定⻓度的搜索步⻓,很有可能使算法陷⼊局部最优,⽽不是全局最优甚⾄有可能导致计算发⽣错误。
概念:Group of Pictures(GOP)
前面已经介绍了帧内预测和帧间预测2种压缩技术,结合具体视频内容,为了得到更好的压缩率,我们可以对不同的帧使用不同的预测压缩方式。我们知道一个视频会有若干个场景,即在一个内容为相似的背景或者空间内有着相似的人物或者物体,而每个场景会由若干帧组成,而这些帧往往是强相关的,最适合进行帧间预测,这里一个场景的帧的组合,就叫做GOP。
根据场景内帧的强相关性和帧预测理论,可以对一个场景的首帧进行帧内预测去除空间冗余,然后后面的帧来参考首帧,再后面的帧再来参考前面已经预测出来的帧,为了更好地降低压缩率,还可以不止一个参考帧,比如一帧可以参考前后2帧,于是就出现了I帧,P帧和B帧。
-
I帧:关键帧,采用帧内压缩技术。
-
P帧:向前参考帧,在压缩时,只参考前面已经处理的帧。采用帧间压缩和帧内技术。
-
B帧:双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用帧间和帧内压缩技术。
由于P、B帧同时包含帧间帧内预测,所以宏块同样也有划分I、P、B宏块,I宏块用来做帧内预测,P宏块用来做向前参考帧的预测,B宏块用来做双向参考帧预测。I帧仅包含I宏块,P帧包含P宏块和I宏块,B帧包含B宏块和I宏块。
那么GOP究竟是什么呢?GOP是一个图像序列,一般可以理解为一个场景的若干个帧,比如一段电影片段在主角在公园里,因为整体画面差别不大,所以可以放入一个GOP中,接下来切到主角在室内了,那么此时就重新开始另一个gop了。在一个图像序列中只有一个I帧,且I帧位于序列开头。
如下图所示:
每个GOP首帧就是I帧,它采用帧内预测,是一个全帧压缩编码帧,描述了图像背景和运动主体的详情,不需要考虑运动矢量,解码时仅用I帧的数据就可重构完整图像,是P帧和B帧的参考帧。
后面的帧会使用帧间预测技术参考I帧或之后编码出来的P帧,P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,P帧没有完整画面数据,只有与前一帧的画面差别的数据。P帧是以I帧或前面的P帧为参考帧,在参考帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运行矢量从I帧找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。
B帧是双向差别帧,和P帧的主要区别在于它是参考前后2帧,也就是B帧记录的是本帧与前后帧的差别。B帧以前面的I或P帧和后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。不过正是由于需要参考后面的P帧,所以B帧虽然提高了压缩率,但是也带来了编码延迟问题(需要等后面一帧编码好才能编码)。
需要说明的是,通过提⾼GOP值来提⾼图像质量是有限度的,在遇到场景切换的情况时,H.264编码器会⾃动强制插⼊⼀个I帧,此时实际的GOP值被缩短了。另⼀⽅⾯,在⼀个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量⽐较差时,会影响到⼀个GOP中后续P、B帧的图像质量,直到下⼀个GOP开始才有可能得以恢复,所以GOP值也不宜设置过⼤。同时,由于P、B帧的复杂度⼤于I帧,所以过多的P、B帧会影响编码效率,使编码效率降低。另外,过⻓的GOP还会影响Seek操作的响应速度,由于P、B帧是由前⾯的I或P帧预测得到的,所以Seek操作需要直接定位,解码某⼀个P或B帧时,需要先解码得到本GOP内的I帧及之前的N个预测帧才可以,GOP值越⻓,需要解码的预测帧就越多,seek响应的时间也越⻓。
概念:Decoded Picture Buffer
由于帧间预测需要参考编码好的帧,所以需要缓存队列缓存编码好的再解码重建的帧 来给后续编码的帧作为参考帧。
那为什么不直接拿原始宏块而要专门重新解码编码好的宏块做为参考呢?
关键点在于为了和解码流程保持一致的参考宏块,因为编码出来的宏块和解码重建的宏块并非完全一致的,所以如果帧间预测在编码端和解码器端参考帧不一致,就会出错。
比如B帧需要前后参考帧,所以需要2个缓存队列:
这里由于B帧的引入,会导致一个现象,就是编码的帧顺序和播放的帧顺序会不一致:
所以也衍生了两个时间戳,DTS(Decoding Time Stamp)和 PTS(Presentation Time Stamp)。顾名思义,前者是解码的时间,后者是显示的时间。这在后面的代码开发是一种很需要注意的点,不然用错出问题了都不知道什么原因。
PTS是真正录制和播放的时间戳,而DTS是解码的时间戳。
对于普通的无B-frame视频(H264Baseline或者VP8),PTS/DTS应该是相等的,因为没有延迟编码。而对于有B-frame的视频,I-frame的PTS依然等于DTS,P-frame的PTS>DTS,B-frame的PTS<DTS。
可以简单地这样理解:
-
若视频没有B-frame,则I和P都是解码后即刻显示。
-
若视频含有B-frame,则I是解码后即刻显示,P是先解码后显示,B是后解码先显示。(B和P的先、后是相对的)。
整数离散余弦变换(Discrete Cosine Transform,DCT)
首先说一下低频和高频,图像的低频是轮廓,高频是噪声和细节。
图像的频率:灰度值变化剧烈程度的指标,是灰度在平面空间上的梯度。
什么是低频?
低频就是颜色缓慢地变化,也就是灰度缓慢地变化,就代表着那是连续渐变的一块区域,这部分就是低频。
对于一幅图像来说,除去高频的就是低频了,也就是边缘以内的内容为低频,而边缘内的内容就是图像的大部分信息,即图像的大致概貌和轮廓,是图像的近似信息。
什么是高频?
反过来,高频就是频率变化快。
图像中什么时候灰度变化快?就是相邻区域之间灰度相差很大,这就是变化得快。
图像中,一个影像与背景的边缘部位,通常会有明显的差别,也就是说那里灰度变化很快,也即是变化频率高的部位。因此,图像边缘的灰度值变化快,就对应着频率高,即高频显示图像边缘。图像的细节处也是属于灰度值急剧变化的区域,正是因为灰度值的急剧变化,才会出现细节。
另外噪声(即噪点) 也是这样,在一个像素所在的位置,之所以是噪点,就是因为它与正常的点颜色不一样了,也就是说该像素点灰度值明显不一样了,也就是灰度有快速地变化了,所以是高频部分,因此有噪声在高频这么一说。
DCT 的理论依据
由于人眼的视觉敏感度是有限的,有的时候我们去除了一部分高频信息之后,人眼看上去感觉区别并不大。这就是视觉冗余。
因此,我们可以先将图片通过 DCT 变换到频域,然后再去除一些高频信息。这样我 们就可以减少信息量,从而达到压缩的目的。
最后效果为在变换后的块中,左上角的系数往往比较大,越靠近右下角的系数越小,这是就是因为高频系数一般比较小的缘故,而低频分量一般比较大。
量化
DCT变换并没有进行压缩,压缩需要后面一步的支持——量化。
视频编码过程中,有一个重要步骤:量化,量化属于有损压缩过程。
量化基本决定了视频的码率,视频的码率又从一定程度上决定了视频的质量。量化值QP越大则量化的粒度越高,压缩率越大,码率更小,视频质量越低,呈现出来就是马赛克比较大,画面不细腻,画面比较模糊。反之,压缩率低,码率大,质量高,画面细腻,细节丰富。
最后效果为大部分系数都变为0了,这就是通过量化去除高频分量的结果,右下角部分基本都为0。
CABAC压缩
视频编码中真正实现“压缩”的步骤,主要去除信息熵冗余。而前面说的去除空间、时间、视觉冗余,其实都是为这一步做准备的。
CABAC压缩属于无损压缩,编码的目的是从概率的角度再做一次压缩,编码的过程主要分为二值化,上下文建模,二进制算术编码。
无损压缩技术大家最熟悉的可能就是哈夫曼编码了,给高频的词一个短码,给低频词一个长码从而达到数据压缩的目的。MPEG-2中使用的VLC就是这种算法。
CABAC也是给高频数据短码,给低频数据长码。同时还会根据上下文相关性进行压缩,这种方式又比VLC高效很多。
二值化
在图像处理的世界中,所谓二值化就是将像素点的值根据一定的算法,将像素分别修改为0,或255,即获取图像的灰度图,或者通俗些讲就是图像的黑白图。而此处的“二值化”可以暂且理解为,将数值二进制化的一个过程,当然不是简单的将十进制转换为二进制。经过二值化之后,CABAC 就已经把待编码的语法元素按照一定的规则转换为只用“0”和“1”的二进制流,称为比特流。
上下文建模
待编码数据具有上下文相关性,利用已编码数据提供的上下文信息,为待编码的数据选择合适的概率模型,这就是上下文建模。通过对上下文模型的构建,基本概率模型能够适应随视频图像而改变的统计特性,降低数据之间的冗余度,并减少运算开支。
H.264/AVC 标准将一个 Slice 可能出现的数据划分为 399 个上下文模型,每个模型均有自己的上下文序号,命名为 CtxIdx,每个不同的字符依据对应的上下文模型,来索引自身的概率查找表。即收到字符后,先找到字符对应的上下文模型的序号 CtxIdx,然后根据 CtxIdx 找到其对应的概率查找表。 详细的步骤如下:
-
确定当前的字符对应的上下文模型的区间,H264 标准中的表9-1描述了相应的对应关系。
-
按照不同的法则,在第1步中得到的区间中最终确定的上下文模型个的CtxIdx。
二进制算术编码
通过上下文建模找到的概率模型的概率估计方法构成了一个自适应二进制算术编码器。概率估计是在前一次上下文建模阶段更新后的概率估计。在对每个二进制数值编码过后,概率估计的值相应的也会根据刚刚编码的二进制符号进行调整。
二进制算术编码是算术编码的特殊情况,其原理与一般算术编码一样。不同的是,二进制算术编码序列只有“0”和“1”两种符号,所涉及的概率也只有P(0)和P(1)。
实例:H.264 压缩过程
通过摄像头采集到的视频帧(按每秒 30 帧算),被送到 H.264 编码器的缓冲区中。
第一步:划分宏块。
划分好宏块后,计算宏块的象素值。
以此类推,计算一幅图像中每个宏块的像素值,所有宏块都处理完后如下面的样子。
第二步:划分子块。
对比较平坦的图像使用 16x16 大小的宏块。但为了更高的压缩率,还可以在 16x16 的宏块上更划分出更小的子块。子块的大小可以是 8x16、 16x8、 8x8、 4x8、 8x4、 4x4非常的灵活。
这样再经过帧内压缩,可以得到更高效的数据。下图是分别使用MPEG-2和H.264对上面宏块进行压缩后的结果。其中左半部分为MPEG-2子块划分后压缩的结果,右半部分为H.264的子块划压缩后的结果,可以看出H.264的划分方法更具优势。
宏块划分好后,就可以对H.264编码器缓存中的所有图片进行分组了。
第三步:帧分组。
对于视频数据主要有两类数据冗余,一类是时间上的数据冗余,另一类是空间上的数据冗余。其中时间上的数据冗余是最大的。下面我们就先来说说视频数据时间上的冗余问题。
为什么说时间上的冗余是最大的呢?
假设摄像头每秒抓取30帧,这30帧的数据大部分情况下都是相关联的。也有可能不止30帧的的数据,可能几十帧,上百帧的数据都是关联特别密切的。
对于这些关联特别密切的帧,其实我们只需要保存一帧的数据,其它帧都可以通过这一帧再按某种规则预测出来,所以说视频数据在时间上的冗余是最多的。
为了达到相关帧通过预测的方法来压缩数据,就需要将视频帧进行分组。那么如何判定某些帧关系密切,可以划为一组呢?我们来看一下例子,下面是捕获的一组运动的台球的视频帧,台球从右上角滚到了左下角。
H.264编码器会按顺序,每次取出两幅相邻的帧进行宏块比较,计算两帧的相似度。如下图:
通过宏块扫描与宏块搜索可以发现这两个帧的关联度是非常高的。进而发现这一组帧的关联度都是非常高的。因此,上面这几帧就可以划分为一组。
其算法是:在相邻几幅图像画面中,一般有差别的像素只有10%以内的点,亮度差值变化不超过2%,而色度差值的变化只有1%以内,我们认为这样的图可以分到一组。
在这样一组帧中,经过编码后,我们只保留第一帖的完整数据,其它帧都通过参考上一帧计算出来。我们称第一帧为 I 帧,其它帧我们称为 P/B 帧,这样编码后的数据帧组我们称为 GOP。
第四步:运动估计与补偿。
将帧分组后,就要计算帧组内物体的运动矢量了。还以上面运动的台球视频帧为例,我们来看一下它是如何计算运动矢量的。
H.264编码器首先按顺序从缓冲区头部取出两帧视频数据,然后进行宏块扫描。当发现其中一幅图片中有物体时,就在另一幅图的邻近位置(搜索窗口中)进行搜索。如果此时在另一幅图中找到该物体,那么就可以计算出物体的运动矢量了。下面这幅图就是搜索后的台球移动的位置。
通过上图中台球位置相差,就可以计算出台图运行的方向和距离。H.264编码器依次把每一帧中球移动的距离和方向都记录下来就成了下面的样子。
运动矢量计算出来后,将相同部分(也就是绿色部分)减去,就得到了补偿数据。我们最终只需要将补偿数据进行压缩保存,以后在解码时就可以恢复原图了。压缩补偿后的数据只需要记录很少的一点数据。如下所示:
我们把运动矢量与补偿称为帧间压缩技术,它解决的是视频帧在时间上的数据冗余。除了帧间压缩,帧内也要进行数据压缩,帧内数据压缩解决的是空间上的数据冗余。下面我们就来介绍一下帧内压缩技术。
第五步:帧内压缩。
人眼对图象都有一个识别度,对低频的亮度很敏感,对高频的亮度不太敏感。所以基于一些研究,可以将一幅图像中人眼不敏感的数据去除掉。这样就提出了帧内预测技术。
H.264的帧内压缩与JPEG很相似。一幅图像被划分好宏块后,对每个宏块可以进行 9 种模式的预测。找出与原图最接近的一种预测模式。
下面这幅图是对整幅图中的每个宏块进行预测的过程。
帧内预测后的图像与原始图像的对比如下:
然后,将原始图像与帧内预测后的图像相减,得到残差值。
再将我们之前得到的预测模式信息一起保存起来,这样我们就可以在解码时恢复原图了。效果如下:
经过帧内与帧间的压缩后,虽然数据有大幅减少,但还有优化的空间。
第六步:对残差数据做DCT。
将残差数据做整数离散余弦变换,去掉数据的相关性,进一步压缩数据。
如下图所示,左侧为原数据的宏块,右侧为计算出的残差数据的宏块。
将残差数据宏块数字化后如下图所示:
将残差数据宏块进行 DCT 转换,去掉相关联的数据后,我们可以看出数据被进一步压缩了:
第七步:CABAC压缩。
上面的帧内压缩是属于有损压缩技术。也就是说图像被压缩后,无法完全复原。而CABAC属于无损压缩技术。
无损压缩技术大家最熟悉的可能就是哈夫曼编码了,给高频的词一个短码,给低频词一个长码从而达到数据压缩的目的。MPEG-2中使用的VLC就是这种算法,我们以 A-Z 作为例子,A属于高频数据,Z属于低频数据。看看它是如何做的。
CABAC也是给高频数据短码,给低频数据长码。同时还会根据上下文相关性进行压缩,这种方式又比VLC高效很多。其效果如下:
现在将 A-Z 换成视频帧,它就成了下面的样子。
从上面这张图中明显可以看出采用 CACBA 的无损压缩方案要比 VLC 高效的多。
H.264 编解码
H.264 的码流结构,相⽐于之前的视频标准,虽然都采⽤了基于块匹配的混合编码⽅式,但在层次划分上有着较⼤的改进。
从 H.261 到 H.263 视频编码标准中,将码流的结构划分为四个层次,按照从上到下的顺序依次为:图像层(picture layer)、块组层(GOB layer)、宏块层(macroblock layer)和块层(block layer)。
H.264 的码流分层结构
H.264的码流分层结构:序列(GOP)、图片(Picture)、片(Slice)、宏块(Macroblock)、子块(Subblock)。
H.264将视频分为连续的帧进⾏传输,在连续的帧之间使⽤I帧、P帧和B帧。同时对于帧内⽽⾔,将图像分块为片、宏块和字块进⾏分片传输;通过这个过程实现对视频⽂件的压缩包装。
片(Slice)
一帧图片经过 H.264 编码器之后,就被编码为一个或多个片(slice),而装载着这些片(slice)的载体,就是 NALU 了,我们可以来看看 NALU 跟片的关系(slice)。
片(slice)的概念不同与帧(frame),帧(frame)是用作描述一张图片的,一帧(frame)对应一张图片,而片(slice),是 H.264 中提出的新概念,是通过编码图片后切分通过高效的方式整合出来的概念,一张图片至少有一个或多个片(slice)。
上图中可以看出,片(slice)都是又 NALU 装载并进行网络传输的,但是这并不代表 NALU 内就一定是切片,这是充分不必要条件,因为 NALU 还有可能装载着其他用作描述视频的信息。
为什么要切片?
片的主要作用是用作宏块(Macroblock)的载体。片之所以被创造出来,主要目的是为限制误码的扩散和传输。
如何限制误码的扩散和传输?
每个片(slice)都应该是互相独立被传输的,某片的预测(片内预测和片间预测)不能以其它片中的宏块(Macroblock)为参考图像。
那么片(slice)的具体结构,我们用一张图来直观说明吧:
我们可以理解为一 张帧/图片可以包含一个或多个分片,而每一个分片包含整数个宏块,即每片至少一个宏块,最多时每片包 整个图像的宏块。
上图结构中,我们不难看出,每个分片也包含着头和数据两部分:
- 分片头中包含着分片类型、分片中的宏块类型、分片帧的数量、分片属于那个图像以及对应的帧的设置和参数等信息。
- 分片数据中则是宏块,这里就是我们要找的存储像素数据的地方。
宏块(Macroblock)
宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。
组成部分:一个宏块由一个 16×16 亮度像素和附加的一个8×8 Cb和一个 8×8 Cr 彩色像素块组成。每个图象中,若干宏块被排列成片的形式。
从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。
NALU——H.264 原始码流的基本组成单位
从层次的划分上可以看出没有针对⽹络传输部分进⾏层级的定义和专⻔设定,H.264 则针对视频数据经由⽹络传输的需求,从功能上划分为两层,分别是视频编码层(VCL)以及⽹络抽象层(NAL)。
- VCL(Video Coding Layer):视频编码层。包括核心压缩引擎和块,宏块和片的语法级别定义,设计目标是尽可能地独立于网络进行高效的编码,对视频原始数据进行压缩。
- NAL(Network Abstraction Layer): 网络抽象层。在以太网每个包大小是 1500 字节,而一帧往往会大于这个值,所以就需要用于按照一定格式,对 VCL 视像编码层输出的数据拆成多个包传输,并提供包头(header)等信息,以在不同速率的网络上传输或进行存储,所有的拆包和组包都是 NAL 层去处理的。覆盖了所有片级以上的语法级别。
NALU 是H.264原始码流的基本组成单位。
VCL 数据传输或者存储之前,会被映射或封装进一个 NALU 中。
NALU = 一组对应于视频编码的NALU头部信息 + 一个原始字节序列负荷(RBSP,Raw Byte Sequence Payload)。
H.264码流第一个NALU是SPS,第二个NALU是PPS。
- SPS(Sequence Parameter Sets): 序列参数集,保存了⼀组编码视频序列(Coded Video Sequence)的全局参数,包括有多少帧等。
- PPS(Picture Parameter Sets): 图像参数集,对应的是⼀个序列中某⼀幅图像或者某⼏幅图像的参数。
解码的时候必须获取到 SPS 和 PPS 的信息,才能对后面的数据进行解码。
NALU header 字段解析
NALU header 固定占 1 字节。
-
T 为负荷数据类型(nal_unit_type),占5 bit。表示这个NALU单元的类型,1~12由H.264使⽤,24~31由H.264以外的应用使用。
-
R 为重要性指示位(nal_ref_idc),占 2 bit。指示这个NALU的重要性,取值越⼤,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的片,或是序列参数集,或是图像参数集这些重要的单位时,本元素必须⼤于0。解码器在解码处理不过来的时候,可以丢掉重要性为 0 的NALU。
-
F 为禁⽌位(forbidden_zero_bit),占1bit。在 H.264 规范中规定了这⼀位必须初始为 0,当网络发现NAL单元有比特错误时可设置该比特为1,以便接收方纠错或丢掉该单元。
H.264标准指出,当数据流是储存在介质上时,在每个NALU 前添加起始码:0x000001(3 bit) 或 0x00000001(4 bit),⽤来指示⼀个 NALU 的起始和终⽌位置。
在这样的机制下,在码流中检测起始码,作为⼀个NALU得起始标识,当检测到下⼀个起始码时,当前NALU结束。
如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。
实例:
00 00 00 01 为起始符,67 即 nal_unit_type。
0x67的二进制是 0110 0111
则 forbidden_zero_bit(1bit) = 0;
nal_ref_idc(2bit) = 3;
nal_unit_type(5bit) = 7,即 SPS 类型。
后面的 NALU 也用这种方法分析,这里不多讲了。
补充:H.264 封装模式
H.264有两种封装:
- ⼀种是 AnnexB 模式,传统模式,有startcode,SPS和PPS是在ES中。
- ⼀种是 MP4 模式,⼀般mp4、mkv都是MP4模式,没有startcode,SPS和PPS以及其它信息被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度。
很多解码器只⽀持annexb这种模式,因此需要将mp4做转换。在FFmpeg中,可以用 名叫 h264_mp4toannexb 的滤镜(Filter)做转换:
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
AVBSFContext *bsf_ctx = NULL;
// 2 初始化过滤器上下⽂
av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
// 3 添加解码器属性
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx>streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);
H.264 的码流六层结构
AnnexB 三层结构
第一层:AnnexB 格式:起始码 + NALU
第二层:NALU:NALU Header + NALU主体(Slice)
第三层:Slice
H.264 编码器框图
H.264 解码器框图
由于 H.264 中加⼊了视频流在网络传输的⽀持,解码器从后侧输⼊的数据包格式为 NAL,码流⽅向从右⾄左。
解码器在⽹络抽象层 NAL 中获取到压缩后的码流,然后从码流中提取⼀帧数据,经过熵编码、重新排序、反量化和反变换后作为加法器的输⼊,其中包含着重建图像所需要的残差数据和运动⽮量等进⾏解码操作所必需的信息。接下来,解码器通过在码流中解码所获得的头部信息创建出⼀个预测块 P,将之前求得的残差信息 D 和预测块 P 求和,求得图像块数据 uF’n,然后经过去块滤波,SAO 等操作获得重建图像的解码块 F。
解析 H.264 文件的 C 语言代码
详见于:最简单的 H.264 视频码流解析程序
参考
- https://baike.baidu.com/item/H.264/1022230?fr=ge_ala
- https://blog.csdn.net/qq_40342400/article/details/129621544
- https://www.kancloud.cn/machh03/webrtc-ffmpeg/2133725
- https://blog.csdn.net/yinshipin007/article/details/129245688
- https://cloud.tencent.com/developer/article/1608800
- https://www.jianshu.com/p/c256f4f741de
- https://moonfdd.blog.csdn.net/article/details/130425228
- https://blog.csdn.net/leixiaohua1020/article/details/50534369
- https://blog.csdn.net/leixiaohua1020/article/details/11845625
- https://zhuanlan.zhihu.com/p/472940444
- https://www.youtube.com/watch?v=PmoEsPWEdOA&list=PLjzeV7-_WfduKiS8T_3uncqoBOQ_OydW6&index=3