帧间预测是采用基于块的运动补偿从一个或多个先前编码的图像帧中产生一个预测模型的。H.264与早起标准的主要不同之处在于支持不同的块尺寸(从16×16到4×4)以及支持精细子像素精度的运动矢量(亮度成分是1/4像素精度)
每个宏块(16×16)的亮度分量可以按四种方式划分,即按一个16×16块,或两个16×8块,或两个8×16块,或者4个8×8块的划分进行运动补偿。如果选择8×8模式,宏块中的4个8×8子宏块可以用另一种方式进一步划分,或者作为一个8×8块,或作为两个8×4块,或作为两个4×8块,或者作为四个4×4块。
每个分块或者子宏块都产生一个单独的运动矢量。每个运动矢量均需要编码和传输,同时分块模式信息需要进行编码并放在压缩比特流中。
每个色度块按照与亮度分量同样的分块方式进行划分。
编码每个分块的运动矢量需要大量比特位。由于相邻块的运动矢量高度相关,所以每个块的运动矢量都是从邻近的先前编码块中进行预测得到的。当前运动矢量与预测运动矢量MVp的差值MVD被编码和传输。
MVp的预测规则如下:
假设E是当前宏块、子宏块或子宏块分块,A是E左边的分块或子分块,B是E上边的分块或子分块,C是E右上的分块或子分块。如果E左边的分块数大于1,则最上边的分块被选为A。如果E上边的分块数大于1,则最左边的分块被选为B。
1.除了16×8和8×16两种分块尺寸的其余传输块,MVp是分块A、B、C的运动矢量的中值(不是平均值)
2.对于16×8分块,上边16×8分块的MVp是从B预测得到的,下边16×8分块的MVp是从A预测得到的。
3.对于8×16分块,左边8×16分块的MVp是从A预测得到的,右边8×16分块的MVp是从C预测得到的。
4.对于skip宏块,产生一个16×16块的MVp,和第1种情况一样。MVp的形成规则相应修改。
如果得不到一个或多个先前传输块的话(如,它在当前条带之外),则MVp的形成原则相应修改。
——————————————————————————————————————–
Yeah! 又可以看实例了:
这里对foreman_part_qcif.yuv的第二帧中地址为40的宏块(白色框框住,图贴在文章开头)进行分析,关键代码还是在encode_one_macroblock_high中,由于该帧是P帧,所以会进行帧间预测。其中最重要的函数为BlockMotionSearch,该函数为所有大小的分块完成运动搜索的过程,得到最优的MV。
1.进行skip模式,实现函数FindSkipModeMotionVector,该函数只是从周围块的MV来预测当前宏块的MVp,获得MVp的函数GetMotionVectorPredictorNormal。由于skip宏块是没有MVD的,它把MVp作为运动矢量并得到运动补偿宏块。实例中获得的MVp为(-17, 3)
2.16×16模式,也需要先获得MVp(-17, 3),于是将(-16, 4)定为搜索中心(最近的整数像素),在一定的搜索范围(32)之内进行整像素搜索,需要搜索的位置有(32*2+1)*(32*2+1)=4225个。对于每个位置,都要计算一个motion cost(block_sad+mv_cost),最终找到使cost最小(2311)的MV(-16, 8)。然后在该点周围9个点(包括该点)再进行半像素搜索,找到一个cost最小(3639)的MV(-16, 6),这里采用的误差度量不再是SAD(Sum of Absolute Difference),而是SATD(Sum of Absolute Transformed Difference),所以与之前的2311没可比性,这个配置文件里面可以配置,只不过默认的配置是整像素采用SAD,半像素和四分之一像素采用SATD。进行完半像素搜索后,再在cost最小的半象素点周围的9个点进行四分之一像素搜索,最后找到一个cost值最小(3523)的MV(-17, 7)。
3.16×8模式,要对上下两个16×8块进行运动搜索,先是上面的块,MVp为(-17, 5),于是从(-16, 4)开始进行整像素搜索,得到SAD最小的是(-24, 4),半像素搜索(-26, 2),四分之一像素搜索(-27,2),最小cost为1262。然后是下面的块,类似得到最小cost(2020)的MV(-15, 5)。两个cost的和为1262+2020=3282。
4.8×16模式,和16×8的区别就是现在的分块为左右两个,分别得到左块和右块的MV为(-32, 3)和(-15, 5),cost为761+1825=2586。
5.8×8模式,如上面所说,如果选择8×8模式,四个8×8子宏块可以用另外四种方式进行划分,所以需要对4个子宏块进行运动估计和模式选择。需要用RDO技术来选择。
先是第一个8×8子宏块。SMB8x8模式,cost最小(483)的MV(-30, 2),rdcost = 1827.9051343856379;8×4模式,上块和下块最优MV分别为(-32, 2)和(-32, 3),cost为323+146=469,rdcost = 1839.9845446142001;4×8模式rdcost = 2071.8257241570759;4×4模式rdcost = 1906.9051343856379。选择rdcost最小的模式,也就是8×8模式,见图中高亮宏块的左上角8×8块。
第二个8×8子宏块。8X8 rdcost = 2192.8257241570759;8×4 rdcost = 2337.8735125141839; 4×8 rdcost = 1486.6669036999515,左块和右块MV分别为(-21, 5)、(-15, 3),SATD cost = 413;4×4 rdcost = 1927.9051343856379。选择rdcost最小的模式,也就是4×8模式模式,见图中高亮宏块的右上角8×8块。
第三块和第四块采用类似的方法,分别选择了SMB8x8模式和SMB4x4模式。见图中高亮宏块的左下和右上8×8块。
——————————————————————————————————————–
所有帧间预测模式都做完了,下面就是要通过各个模式的rdcost来选择最佳模式了。
skip模式,48032.539705114279
P16x16模式,13120.288152342357
P16x8模式,12449.192575628142
P8x16模式,10033.668325899660
P8x8模式,8257.1286207853791
即使是P帧,也还是要做帧内预测的,下面是帧内模式的rdcost
I16x16模式,16021.447683899336
I4x4模式,13026.446972799482
I_PCM模式,105585.41572855003
当然是选rdcost最小的咯,也就是P8x8,而四个8×8子宏块的分块情况也在之前选择好了。
———————————————————————————————————————
之后还是把用Elecard StreamEye工具得到的该宏块的信息贴出来研究下:
position : 7×3 (112×48)
mb_addr : 40
size (in bits) : 146
mb_type : 4 宏块类型,见标准文档表7-13,4表示的宏块类型名称为P_8x8ref0
pmode : 3 预测模式,8×8
mb_type : Inter(P_8x8ref0)
slice_number : 0
transform_8x8 : 0
field\frame : frame
cbp bits : 0 1100 0 00 0 00
: 0000 00 00
: 0011
: 0001
quant_param : 28 QP
pmode : Part_8x8 预测模式名称
sub_pmode : SubPart_8x8 SubPart_4x8 子宏块的分块方式
: SubPart_8x8 SubPart_4x4
sub_pdir : Pred_L0 Pred_L0 ?
: Pred_L0 Pred_L0
mvL0 : MV,与之前分析的一致
-30, 2, 0| -30, 2, 0| -21, 5, 0| -15, 3, 0
-30, 2, 0| -30, 2, 0| -21, 5, 0| -15, 3, 0
-33, 3, 0| -33, 3, 0| -26, 2, 0| -10, 3, 0
-33, 3, 0| -33, 3, 0| -26, 2, 0| -20, 43, 0
———————————————————————————————————————
下面分析码流,相关函数writeMBLayerPSlice
先写入的是一个mb_skip_run语法元素,表示之前有多少个skip宏块,这里为0,因为之前的宏块不是skip空块
再写入MBType,表示宏块类型
然后是4个8×8子宏块的模式
之后写入的是运动矢量信息
再然后写入cbp和DQuant
最后写如的是亮度和色度的残差
该实例中,mb_skip_run占1bit,MBType占5bits,子宏块的模式占10bits,运动矢量信息占82bits,cbp占9bits,DQuant占1bit,亮度残差占38bits,色度残差为空,一共1+5+10+82+9+1+38=146bits,与上面一致,忽忽
其中运动矢量信息的编码函数为write_pslice_motion_info_to_NAL,编码每个块的最佳MV和MVp的差值MVD。