【转】X264的帧间预测

来源:互联网 发布:java图片上传原理 编辑:程序博客网 时间:2024/05/16 18:47

 

【转】X264的帧间预测
 
 
1、x264学习笔记(9)--x26416x16运动搜索过程
函数实现是函数 static void x264_mb_analyse_inter_p16x16( x264_t *h, x264_mb_analysis_t *a )
1、大循环是参考帧的循环,从最近的一个参考帧开始搜索,一直到最远的一个参考帧;

2
、调用x264_mb_predict_mv_16x16函数,以上、右上、左块运动矢量的中值m.mvp作为候
选运动矢量。

3
、调用x264_mb_predict_mv_ref16x16函数,寻找其它候选运动矢量。这些候选者包括:
空间相邻的左、左上、上、右上块的MV;第0个参考帧中的当前块、右边块、下边快运动矢
量乘以时间差权重。

4
、调用x264_me_search_ref进行运动搜索。搜索时先从所有候选运动矢量中选出最佳的起
点,然后使用小钻石法、六边形法、UMH或者全搜索搜索出最佳的整像素位置。

5
x264_me_search_ref调用refine_subpel进行1/21/4运动搜索。两者都使用小钻石法


6
、搜索出最佳运动矢量后,如果当前是最近一个参考帧,而且最佳SA(T)D小与检测门限,
则尝试对其进行P_SKIP编码。

7
、保存搜索结果。 
 
 
2x264学习笔记(10)---分像素的运动估计总结 
得到分像素的值函数是下面两个函数,对照着
(1) 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;
/*src1和src2都分别指向的是1/2像素块,关键是这个hpel_ref0[qpel_idx]和hpel_ref1[qpel_idx],下面将详细介绍。
注意一点就是参考帧定义了uint8_t *p_fref[2][32][4+2]; /* last: lN, lH, lV, lHV, cU, cV */
这里面的 4+2 的这个2代表色度,而这个4分别代表整像素,在整像素水平右边的1/2像素,在整像素垂直下面的1/2像素和整像素右下角的1/2像素。1/2像素的值已经在前面函数里面插值存好了,只要调用就可以了,而如果要进行1/4像素估计,要临时插值。现在这个函数 get_ref 中,src[0]、src[1]、src[2]、src[3]这传进来的就是分别是 lN, lH, lV, lHV
*/
    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 );//1/4搜索时需要临时插值函数
        return dst;
    }
    else
    {
        *i_dst_stride = i_src_stride;
        return src1;
    }
}
按照 毕厚杰 的《新一代视频压缩编码标准——H.264/AVC》关于运动矢量那一节的介绍。看图6.22
那四个像素点,G为整像素点 b、h、i分别是lH, lV, lHV,也就是水平,垂直和对角线的值。
G  b
 h   i
对应为
src[0]  src[1]
src[2]  src[3]
现在看这两个数组
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};
也按像素的平面图画出来的话
src[hpel_ref0[qpel_idx]]
0 1 1 1
0 1 1 1
2 3 3 3
0 1 1 1
src[hpel_ref1[qpel_idx]]
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像素值,也就是毕厚杰书中的 G、b、h、I 。这里要注意src[hpel_ref0[qpel_idx]]最后一行的 0 1 1 1 和src[hpel_ref1[qpel_idx]]最右边一列0 2 2 2不是当前的整像素0的1/2像素,而分别是其下面和右边一个整像素的对应的1/2像素值,因为 ((mvy&3) == 3) * i_src_stride 和((mvx&3) == 3)。
为什么要这么来排,是因为要根据1/4像素是通过1/2像素线性插值的公式来的,具体看下面这个函数。
(2) static inline void pixel_avg( uint8_t *dst,  int i_dst_stride,
                              uint8_t *src1, int i_src1_stride,
                              uint8_t *src2, int i_src2_stride,
                              int i_width, int i_height )
//1/4搜索时需要临时插值函数
    int x, y;
    for( y = 0; y < i_height; y++ )
    {
        for( x = 0; x < i_width; x++ )
        {
            dst[x] = ( src1[x] + src2[x] + 1 ) >> 1;   //利用相邻半像素和两个像素取平均插值
        }
        dst  += i_dst_stride;
        src1 += i_src1_stride;
        src2 += i_src2_stride;
    }
} 不过最后我有个疑问,那就是1/4插值后,应该原来的1/2 值保持不变的.但是分析发现,这个 b 、h、 i 这三个1/2像素中,h和i是不变的,不过 b会发生变化. 个人觉得 static const int hpel_ref1[16] = {0,0,0,0,2,2,3,2,2,2,3,2,2,2,3,2};如果改为 static const int hpel_ref1[16] = {0,0,1,0,2,2,3,2,2,2,3,2,2,2,3,2};则  b也不会发生变化. 所以这里打个问号?
 
3x264学习笔记(11)---关于运动矢量MV不传输的问题
    昨天看到H.264乐园群里面有人在讨论运动矢量MV不用传输的问题,就去看了下x264源代码,作个总结
 
编码端运动估计搜索得到的运动矢量MV是不需要传送的,需要传送的是MVDMVD即运动矢量MV(运动估计得到)和运动矢量的预测矢量MVP(预测得到)的差值。
MVD = MV - MVP
 
解码端:通过预测得到MVP,将传输过来的MVDMVP相加得到 MV = MVD + MVP,然后用这个MV去参考帧中获取预测象素值,最后把这个预测值和残差加一起, 作为重构像素值
 
x264中把这个过程放在了熵编码阶段,在这个函数里 x264_macroblock_write_cabac
 
MVD并保存下来以备传输的函数如下:
static inline void x264_cabac_mb_mvd( x264_t *h, x264_cabac_t *cb, int i_list, int idx, int width, int height )
{
    int mvp[2];
    int mdx, mdy;
 
    /* Calculate mvd */
    x264_mb_predict_mv( h, i_list, idx, width, mvp );  //预测MVP
    mdx = h->mb.cache.mv[i_list][x264_scan8[idx]][0] - mvp[0];  //计算MVD
    mdy = h->mb.cache.mv[i_list][x264_scan8[idx]][1] - mvp[1];
 
    /* encode */
    x264_cabac_mb_mvd_cpn( h, cb, i_list, idx, 0, mdx ); //编码
    x264_cabac_mb_mvd_cpn( h, cb, i_list, idx, 1, mdy );
 
    /* save value */
    x264_macroblock_cache_mvd( h, block_idx_x[idx], block_idx_y[idx], width, height, i_list, mdx, mdy ); // 保存MVD
}
 
4、firstime
MV预测过程详解(附图)
===========第一步:确定相邻块===========
      MV 预测以宏块分割(或亚宏块分割,如果宏块存在亚分割)为单位,同一个宏块分割(或亚宏块分割)内所有 4*4 块 MV 预测值相同。以每个宏块分割(或亚宏块分割)的左上角像素 pixel1 和右上角像素 pixel2 为参考点来确定相邻块则:
      pixel1 左侧相邻像素所在 4*4 块为当前宏块分割(或亚宏块分割)的相邻块 A
      pixel1 上方相邻像素所在 4*4 块为当前宏块分割(或亚宏块分割)的相邻块 B
      pixel2 右上对角线像素所在 4*4 块为当前宏块分割(或亚宏块分割)的相邻块 C
      pixel1 左上对角线像素所在 4*4 块为当前宏块分割(或亚宏块分割)的相邻块 D
图片附件: MV预测示意图.JPG (2006-9-29 11:14 AM, 85.25 K)
      以最复杂的 8*8 宏块分割类型为例(此时只存在亚宏块分割),分析如下:
      假设图中黑色框表示宏块、每个绿色框表示一个 4*4 块、每个红色框表示一个 8*8 块。当前宏块的宏块分割模式为 8*8(如图中红色线),其亚宏块分割模式分别为:第一个 8*8 块为 8*8,第二个 8*8 块为 4*4(如图中蓝色线),第三个 8*8 块为 4*8(如图中蓝色线),第四个 8*8 块为 8*4(如图中蓝色线)。则按照上述方法来确定相邻块的方法如下:
      第一个预测对象为第一个 8*8 块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 7 号 4*4 块,B 为 2 号 4*4 块,C 为 4 号 4*4 块,D 为 1 号 4*4 块。9、14、15 与 8 具有相同 MV 预测值
      第二个预测对象为第二个 8*8 块的第一个 4*4 块,即 10 号块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 9 号4*4块,B 为 4 号4*4块,C 为 5 号 4*4 块, D 为 3 号 4*4 块
      第三个预测对象为第二个 8*8 块的第二个 4*4 块,即 11 号块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 10 号4*4块,B 为 5 号4*4块,C 为 6 号 4*4 块,D 为4 号 4*4 块
      第四个预测对象为第二个 8*8 块的第三个 4*4 块,即 16 号块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 15 号4*4块,B 为 10 号4*4块,C 为 11 号 4*4 块,D 为 9 号 4*4 块
      第五个预测对象为第二个 8*8 块的第四个 4*4 块,即 17 号块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 16 号4*4块,B 为 11 号4*4块,C 为 12 号 4*4 块,D 为 10 号 4*4 块
      第六个预测对象为第三个 8*8 块的第一个 4*8 块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 19 号 4*4 块,B 为 14 号 4*4 块,C 为 15 号 4*4 块,D 为 13 号 4*4 块。26 与 20 具有相同 MV 预测值
      第七个预测对象为第三个 8*8 块的第二个 4*8 块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 20 号 4*4 块,B 为 15 号 4*4 块,C 为 16 号 4*4 块,D 为 14 号 4*4 块。27 与 21 具有相同 MV 预测值
      第八个预测对象为第四个 8*8 块的第一个 8*4 块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 21 号 4*4 块,B 为 16 号 4*4 块,C 为 18 号 4*4 块,D 为 15 号 4*4 块。23 与 22 具有相同 MV 预测值
     第九个预测对象为第四个 8*8 块的第二个 8*4 块,以其左上角像素 pixel1 和右上角像素 pixel2 为参考点,则:A 为 27 号 4*4 块,B 为 22 号 4*4 块,C 为 24 号 4*4 块,D 为 21 号 4*4 块。29 与 28 具有相同 MV 预测值
===========第二步:确定 A、B、C 的可用性===========
根据 A、B、C 所在宏块是否存在或者是否允许参与预测来判断。如果 C 不可用,采用 D 代替 C
===========第三步:预测 MV ===========
1、如果 A、B、C 三个参考块中只有一个与当前预测对象为同一参考帧,则选取该参考块的 MV 作为最终 MV 预测值
2、当前宏块是否为 8*16 或者 16*8 分割:
(1)、如果当前宏块为 8*16 分割类型:
          对于左边 8*16 分割,如果 A 与当前分割为同一参考帧,则采用 A 的 MV 为该分割的最终 MV 预测值
          对于右边 8*16 分割,如果 C 与当前分割为同一参考帧,则采用 C 的 MV 为该分割的最终 MV 预测值
(2)、如果当前宏块为 16*8 分割类型:
          对于上边 16*8 分割,如果 B 与当前分割为同一参考帧,则采用 B 的 MV 为该分割的最终 MV 预测值
          对于下边 16*8 分割,如果 A 与当前分割为同一参考帧,则采用 A 的 MV 为该分割的最终 MV 预测值
3、其余情况并且 B、C 中有一个可用或者两者都可用,则采用中值预测(取 A、B、C 三者中MV的中值为最终 MV 预测值)
4、其余情况并且 B、C 皆不可用,则采用 A 的 MV 为最终 MV 预测值
【注】:1、宏块分割时的相邻块确定方法与第一步所述过程雷同:16*16 相当于 8*8,8*16、16*8 分别相当于 4*8、8*4
            2、对于不可用的相邻块,其 MV 仍然可能参与 MV 预测,但其值为 0。例如:A 不可用,B、C 可用,则最终可能仍然是在 A、B、C 中取中值,但此时 A 的 MV 为 0;
            3、对于不可用的相邻块,其参考帧索引被设置为 -1,即必然与当前预测对象非同一参考帧;
            4、可以验证:同时满足第三步的第一、第二两种情况时,按第一种情况计算 MV 预测值与按第二种情况计算 MV 预测值等效;
            5、该预测过程即为标准 8.4.1.3 小节的内容,在 JM86 中对应的代码为 SetMotionVectorPredictor 函数;
            6、MBAFF 情况下的相邻块均指对应位置(co-locate)块。