static uint8_t *get_ref( uint8_t *src[4], int i_src_stride,

                         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算式中的第三部分来索引。