uint8_t *dst, int * i_dst_stride,
int mvx,int mvy,
int i_width, int i_height )
{
int qpel_idx = ((mvy&3)<<2) + (mvx&3);
int offset = (mvy>>2)*i_src_stride + (mvx>>2);
uint8_t *src1 = src[hpel_ref0[qpel_idx]] + offset + ((mvy&3) == 3) * i_src_stride;
if( qpel_idx & 5 ) /* qpel interpolation needed */
{
uint8_t *src2 = src[hpel _ref1[qpel_idx]] + offset + ((mvx&3) == 3);
pixel_avg( dst, *i_dst_stride, src1, i_src_stride,
src2, i_src_stride, i_width, i_height );
return dst;
}
else
{
*i_dst_stride = i_src_stride;
return src1;
}
}
(1)int qpel_idx = ((mvy&3)<<2) + (mvx&3);
(mvx,mvy)最后一位是1/4精度的坐标,倒数第二位是1/2精度的坐标,倒数第三位是整像素精度坐标。
G a b c M x x x
d e f g x x x x
h i j k o x x x
n p q r x x x x
L x v x
x x x x
x x x x
x x x x
其中G是4*4块像素点,b、h、j是1/2内插点,其他是1/4内插点,M、L是相邻块的整像素点,o、v是相邻块1/2内差点。
int qpel_idx = ((mvy&3)<<2) + (mvx&3);
这里&3是保留最后两位,<<2是乘以4,qpel_idx 表示的意思就是取出运动矢量的分像素部分。
另外mvx、mvy会是负数,因为定义为int,所以都是正的表示,如 -1=11111111 11111111 11111111 11111111
-2=11111111 11111111 11111111 11111110
(2)int offset = (mvy>>2)*i_src_stride + (mvx>>2);
offset是补偿后的宏块相对当前宏块的整数偏移量。
(3)uint8_t *src1 = src[hpel_ref0[qpel_idx]] + offset + ((mvy&3) == 3) * i_src_stride;
在1/4像素内插以后(mvy&3) == 3,就意味着到快要从一行到下一行了,这时候需要乘以i_src_stride。
(4)qpel_idx & 5 ,5是0101, qpel_idx 在最后1位为1或者倒数第3位为1时,只有1/4内插的点才会qpel_idx & 5!=0。
(5)static const int hpel_ref0[16] = {0,1,1,1,0,1,1,1,2,3,3,3,0,1,1,1 };
static const int hpel_ref1[16] = {0,0,0,0,2,2,3,2,2,2,3,2,2,2,3,2};
数组元素共有四个取值:0,1,2,3。这四个值分别代表整数像素,水平1/2像素,垂直1/2像素,对角线1/2像素。
mvy : 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1
0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1
mvx : 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1
0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
hpel_ref0[16] : 0 1 1 1 0 1 1 1 2 3 3 3 0 1 1 1
hpel_ref1[16] : 0 0 0 0 2 2 3 2 2 2 3 2 2 2 3 2
这里 mvy、mvx是用二进制表示的,而不是十进制,竖着看。
mvy mvx hpel_ref0[16] hpel_ref1[16]
00 00 没有1/2像素的偏移也没有1/4像素的偏移 0 0
00 01 x方向1/4像素的偏移,需要b和G 1 0
00 10 x方向1/2像素的偏移,需要b 1 0
00 11 x方向1/4再1/2像素偏移,需要M和b 1 0
01 00 y方向1/4像素的偏移,需要G和h 0 2
01 01 x、y方向1/4像素的偏移,需要b和h 1 2
01 10 y方向1/4像素的偏移,x方向1/2像素的偏移,需要b和j 1 3
01 11 y方向1/4像素的偏移,x方向1/4再1/2像素的偏移,需要b和o 1 2
10 00 y方向有1/2像素的偏移,需要h 2 2
10 01 y方向1/2像素的偏移,x方向1/4像素的偏移,需要j和h 3 2
10 10 y方向1/2像素的偏移,x方向1/2像素的偏移,需要j 3 3
10 11 y方向1/2像素偏移,x方向1/4再1/2像素偏移,需要j和o 3 2
11 00 y方向1/4再1/2像素偏移,需要L和h 0 2
11 01 y方向1/4再1/2像素偏移,x方向1/4像素偏移,需要v和h 1 2
11 10 y方向1/4再1/2像素偏移,x方向1/2像素偏移,需要v和j 1 3
11 11 y方向1/4再1/2像素偏移,x方向1/4再1/2偏移,需要v和o 1 2
G a b c M x x x
d e f g x x x x
h i j k o x x x
n p q r x x x x
L x v x
x x x x
x x x x
x x x x
一套if...else语句将代码的走向分流,判断的条件就是‘qpel_idx & 5’是否为0,
即判断qpel_idx的第0位与第3位是否为1,也就是判断1/4像素在图1中的位置是否属于奇数行或奇数列。
首先来看else分支的语句。
如果想执行else分支,根据条件,必须是运动向量的1/4像素位置在图1中偶数行与偶数列的交叉位置,
即(0,0)、(2,0)、(0,2)、(2,2)这四个位置,这四个位置是1/2像素,
它们的值可以用6抽头滤波器直接计算的。而此时我们计算qpel_idx,分别为0,2,8,10,
索引到hpel_ref0数组中,对应的元素值也恰恰为0,1,2,3。可见,else部分是来处理整数像素及1/2像素的。
接下来分析if部分,揭开hpel_ref0与hpel_ref1的完整面纱。
if部分引入了src2,而且从pixel_avg()函数中可以看出,
通过src1与src2的均值才得到图1中奇数行和奇数列的那些1/4像素的值。
很自然地,我们将hpel_ref0与hpel_ref1数组中相同下标的元素对应起来,
结合H.264标准,图1中奇数行和奇数列的1/4像素由临近的1/2像素内插得到,
而四个奇数行和奇数列交叉位置处的1/4像素是由两个对角线1/2像素线性内插得到,得解。
例如,如果想计算坐标为(1,1)的1/4像素值,根据标准建议,pix(1,1)=(pix(0,2)+pix(2,0)+1)>>1,
此时,qpel_idx=5。hpel_ref0[5]=1, hpel_ref1[5]=2分别为(0,2)与(2,0)半像素位置的类别*/
细节上需要注意的是,在计算src1与src2时,都由三部分数值加和得到。前两部分很好理解,分别是基地址与偏移。
但是如果只有这两部分,代码就仅能处理不需要图1所示的范围之外的像素协助的情况,
当然,这样的像素占到多数,除去最下面一行和最右侧一列的1/4像素,
其余位置都可以只使用src1与src2的前两个部分完成后续的计算。
所以src1与src2的第三部分:((mvy&3) == 3) * i_src_stride或((mvx&3) == 3),
就是用来处理最下面一行和最右侧一列的1/4像素,
因为这些像素的插值要用到下方与右侧像素位置(可能是整数像素或1/2、1/4像素)的值。
例如,如果想计算坐标为(3,3)的1/4像素值,根据标准建议,pix(3,3)=(pix(2,4)+pix(4,2)+1)>>1,
这里的(2,4)与(4,2)分别是当前整数像素位置下方与右侧整数像素位置的(2,0)与(0,2)位置,
此时就需要通过src1与src2算式中的第三部分来索引。