H264基本概念(入门)
at 1年前 ca 音视频 pv 740 by touch
H264
编码总体思路
编码其实就是压缩,那么肯定是要去除冗余信息的,一般来说冗余信息要么是有重复多余的,可以直接丢弃或者换成另一种更省空间的方式来表达,要么是人感知不敏感,即使去掉一些信息,人也很难感知到。对于我么Android开发来说,最熟悉的压缩莫过于Bitmap的压缩了,常见2种,一是压缩分辨率,这个对应去除重复多余的信息,一种是质量压缩,对应去掉一些人感知不敏感的信息。那么视频也是有类似的冗余信息的:
空间冗余,即相邻的像素往往很相似。
时间冗余,即相邻的帧的内容往往很相似。
视觉冗余,即人眼感知不敏感的信息。
H264压缩技术正是针对以上冗余信息进行一一攻破,主要采用了以下几种方法对视频数据进行压缩。最主要的步骤包括:
帧内预测压缩:解决的是空域数据冗余问题。
帧间预测压缩(运动估计与补偿):解决的是时域数据冗余问题。
整数离散余弦变换(DCT),将空间上的相关性变为频域上无关的数据然后进行量化,解决视觉冗余问题。
熵编码:真正的编码环节,将前面3部处理得到的数据使用编码算法编码为最终的码流。
帧内预测
空间冗余
一幅图像中相邻像素的亮度和色度信息是比较接近的,并且亮度和色度信息也是逐渐变化的,不太会出现突变。也就是说,图像具有空间相关性。 利用这种相关性,视频压缩就可以去除空间冗余信息。
比如android开发中的渐变色,我们并不需要指定整个图像全部像素数据,而只是记录开头和结束以及中间变化的颜色,加上渐变位置以及渐变方向。 而视频也是利用了类似的方法去除冗余信息,叫做帧内预测,即帧内预测通过利用已经编码的相邻像素的值来预测待编码的像素值,最后达到减少空间冗余的目的。
具体预测方法
整体思路是利用一帧图像中已经编码部分来预测尚未编码部分图像,实际值和预测值之间的差别叫做残差。实际上真正编码的是残差数据,因为残差一般比较小,所以对残差编码比对实际数据编码会小很多。所谓物以类聚人以群分,分而治之的思想又发挥重要作用了。为了可以利用编码部分来预测尚未编码部分图像,所以需要根据具体情况对一帧图像划分为若干个部分,每个部分叫做块,其中某些块可以预测另外一些块。H264中对一帧图像划分为宏块的方式来分别进行帧内预测,宏块可以预测相邻的宏块,那么同个宏块的像素就使用一种预测模式。 那么何为宏块呢?
H264默认是使用16X16像素大小的区域作为一个宏块,其中亮度块为16x16,色度块为8x8,帧内预测中亮度块和色度块是分开独立进行预测的
H264对比较平坦的图像使用16X16大小的宏块。但为了更高的质量,在细节复杂的地方,还可以在16X16的宏块上更划分出更小的子块。 子块的大小可以是8X16、16X8、8X8、4X8、8X4、4X4,非常的灵活。
对于4*4的宏块,帧内预测模式总共有9个。其中有8种方向模式和一种DC模式
16*16和8*8的宏块预测模式一样,都是有4种帧内预测模式:
每一个宏块只能用一种预测模式,那如何选择呢?具体算法很复杂,大概思路就是对于每一个块或者子块,我们可以得到预测块,再用实际待编码的块减去预测块就可以得到残差块。然后在不同场景下根据不同的算法对残差块进行计算得到最优的预测模式。
帧间预测
时间冗余
在一个视频中,一般前后两帧图像往往变化比较小,这就是视频的时间相关性。而视频一般往往一秒会播放20-30帧,所以存在大量重复的图像数据,所以会有巨大的压缩空间。
视频编码中,就是通过在已经编码的帧里面找到一个块来预测待编码块的像素,从而达到减少时间冗余的目的,官方名称为:帧间预测。具体来说,就是在前面某一帧找到一个内容很接近的块,那么只要再加上运动矢量,就可以表示当前的块。
运动估计
帧间预测一个重要概念就是运动估计,就是寻找当前编码的块在已编码图像中的最佳对应块。如图,假设P为当前编码帧,Pr为参考帧,当前编码块为B,则运动估计要做的就是在Pr中寻找与B相减残差最小的块Br,Br就叫做B的最佳匹配块。
所以帧间预测这里的难点在于如何找到最佳的参考块来预测当前块,这里涉及很多复杂的运动搜索算法,主要有这两种算法:
全局搜索算法:该方法是把搜索区域内所有的像素块逐个与当前宏块进行比较,查找具有最小匹配误差的一个像素块为匹配块。这一方法的好处是可以找到最佳的匹配块,坏处是速度太慢。目前全局搜索算法极少使用。
快速搜索算法:该方法按照一定的数学规则进行匹配块的搜索。这一方法的好处是速度快,坏处是可能只能得到次最佳的匹配块。
GOP
前面已经介绍了帧内预测和帧间预测2种压缩技术,结合具体视频内容,为了得到更好的压缩率,我们可以对不同的帧使用不同的预测压缩方式。我们知道一个视频会有若干个场景,即在一个内容为相似的背景或者空间内有着相似的人物或者物体,而每个场景会由若干帧组成,而这些帧往往是强相关的,最适合进行帧间预测,这里一个场景的帧的组合,就叫做GOP(groupofpictures)。
2个视频场景GOP,根据场景内帧的强相关性和帧预测理论,所以可以对一个场景的首帧进行帧内预测去除空间冗余,然后后面的帧来参考首帧,再后面的帧再来参考前面已经预测出来的帧,为了更好地降低压缩率,还可以不止一个参考帧,比如一帧可以参考前后2帧,于是就出现了I帧,P帧和B帧。
经过压缩后的帧被人为划分为分为: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帧。
如下图所示:
每个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帧虽然提高了压缩率,但是也带来了编码延迟问题
(需要等后面一帧编码好才能编码)。
Buffer
由于帧间预测需要参考编码好的帧,所以需要缓存队列缓存编码好的再解码重建的帧 来给后续编码的帧作为参考帧。
那为什么不直接拿原始宏块而要专门重新解码编码好的宏块做为参考呢?关键点在于为了和解码流程保持一致的参考宏块,
因为编码出来的宏块和解码重建的宏块并非完全一致的,所以如果帧间预测在编码端和解码器端参考帧不一致,就会出错。
比如B帧需要前后参考帧,所以需要2个缓存队列:
这里由于B帧的引入,会导致一个现象,就是编码的帧顺序和播放的帧顺序会不一致:
时间戳DTS和PTS
所以也衍生了两个时间戳,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的先、后是相对的)。
变换DCT
首先说一下低频和高频,图像的低频是轮廓,高频是噪声和细节。
图像的频率:灰度值变化剧烈程度的指标,是灰度在平面空间上的梯度。
(1)什么是低频?
低频就是颜色缓慢地变化,也就是灰度缓慢地变化,就代表着那是连续渐变的一块区域,这部分就是低频.对于一幅图像来说,除去高频的就是低频了,也就是边缘以内的内容为低频,而边缘内的内容就是图像的大部分信息,即图像的大致概貌和轮廓,是图像的近似信息。
(2)什么是高频?
反过来,高频就是频率变化快.图像中什么时候灰度变化快?就是相邻区域之间灰度相差很大,这就是变化得快.图像中,一个影像与背景的边缘部位,通常会有明显的差别,也就是说变化那条边线那里,灰度变化很快,也即是变化频率高的部位.因此,图像边缘的灰度值变化快,就对应着频率高,即高频显示图像边缘。图像的细节处也是属于灰度值急剧变化的区域,正是因为灰度值的急剧变化,才会出现细节。
另外噪声(即噪点) 也是这样,在一个像素所在的位置,之所以是噪点,就是因为它与正常的点颜色不一样了,也就是说该像素点灰度值明显不一样了,也就是灰度有快速地变化了,所以是高频部分,因此有噪声在高频这么一说。
由于人眼的视觉敏感度是有限的,有的时候我们去除了一部分高频信息之后,人眼看上去感 觉区别并不大。这就是去除视觉冗余。 因此,我们可以先将图片通过 DCT(离散余弦变换) 变换到频域,然后再去除一些高频信息。这样我 们就可以减少信息量,从而达到压缩的目的。
最后效果为在变换后的块中,左上角的系数往往比较大,越靠近右下角的系数越小,这是就是因为高频系数 一般是比较小的缘故,而低频分量一般比较大。
量化
DCT变换并没有进行压缩,压缩需要后面一步的支持——量化。
最后效果为大部分系数都变为0了,这就是通过量化去除高频分量的结果,右下角部分基本都为0。
视频编码过程中,有一个重要步骤:量化,量化属于有损压缩过程。量化基本决定了视频的码率,视频的码率又从一定程度上决定了视频的质量。
量化值QP越大则量化的粒度越高,压缩率越大,码率更小,视频质量越低,呈现出来就是马赛克比较大,画面不细腻,画面比较模糊。
反之,压缩率低,码率大,质量高,画面细腻,细节丰富。
熵编码
视频编码中真正实现“压缩”的步骤,主要去除信息熵冗余。而前面说的去除空间、时间、视觉冗余,其实都是为这一步做准备的。
我们应该见过一道压缩字符串的编程题目,就是将 “aaaabbbccccc” 压缩成 “4a3b5c”,字符串由 13 个 字符压缩到 7 个字符,
这个叫做行程编码。熵编码中使用了类似行程编码的思想处理图像扫描出来的像 素数据,但是使用行程编码不一定能够压缩数据,如果刚才编程题的字符串是
“abcdabcdabcd” 的话, 那么编码之后就会是 “1a1b1c1d1a1b1c1d1a1b1c1d”。字符串的大小从 13 字符变成了 25 字符,还反 而变大了。
如何使得变小呢,答案就是尽量使得其为连续的字符,最好是连续的‘0’,因为0可以用最少 的位数存储(比如指数哥伦布编码,它就可以做到 0 只占用一个位。)。
注意到,前面的帧内帧间预测得到的残差以及后面的变换量化,都是将数据尽量转换为连续的0更的表现形式,然后再利用合理的编码算法去编码形成最终的码流。
重排序
为了使得码流有尽量多的‘0’,根据变换量化后的块靠近右下角的基本都是‘0’的特点,将用‘Zigzag’方式扫 描,使得得到的数字串有大量连续的‘0’。
编码算法
重排序得到的数字串最后会进行编码,进行最后的压缩形成最终的码流。编码一般分为定长编码和变长编码,定长编码常见的有UTF-16,ASCII等,变长编码常见有哈弗曼编码。一般来说,如果是出现频率高的短数据,用边长编码更省空间。而H264根据不同数据段的特点,码流参数部分划分为定长编码和哥伦布编码,视频内容部分使用的是内容自适应的(CABAC)算术编码。具体编码算法这里就不深入了, 不过核心思想都是利用了连续的‘0’尽可能去进行压缩。
版权声明
本文仅代表作者观点,不代表码农殇立场。
本文系作者授权码农殇发表,未经许可,不得转载。