美文网首页
(八)帧间编码1:1/4像素生成

(八)帧间编码1:1/4像素生成

作者: 奔向火星005 | 来源:发表于2018-09-05 10:53 被阅读0次

    帧间编码原理与1/4像素生成的原理可参考《新一代压缩编码技术》第6章6.5小节相关内容。本文主要对源码做分析。

    为了提高帧间编码预测的精度,x264会对参考帧宏块生成1/4像素。如下图:


    1:4像素01.png

    如图,圈圈的是整像素,叉叉是半像素,黑点就是1/4像素。整像素是原来就有的,而半像素和1/4像素则需要通过内插法生成。

    半像素的生成可参看前面文章:(二)低分辨率图像生成函数x264_frame_init_lowres。

    现在讨论1/4像素生成。源码如下:

    static pixel *get_ref( pixel *dst,   intptr_t *i_dst_stride,
                           pixel *src[4], intptr_t i_src_stride,
                           int mvx, int mvy,
                           int i_width, int i_height, const x264_weight_t *weight )
    {
        //src[0],src[1],src[2],src[3]分别指向原点,水平,垂直,对角四个半像素内存块
        //mvx,mvy代表要生成的1/4像素的坐标
        int qpel_idx = ((mvy&3)<<2) + (mvx&3);
        int offset = (mvy>>2)*i_src_stride + (mvx>>2);
        pixel *src1 = src[x264_hpel_ref0[qpel_idx]] + offset + ((mvy&3) == 3) * i_src_stride;
    
        //qpel_idx & 5不等于0时,需要通过内插生成1/4像素
        if( qpel_idx & 5 ) /* qpel interpolation needed */
        {
            pixel *src2 = src[x264_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 );  //dst等于src1和src2的均值
            if( weight->weightfn )
                mc_weight( dst, *i_dst_stride, dst, *i_dst_stride, weight, i_width, i_height );
            return dst;
        }
        else if( weight->weightfn )
        {
            mc_weight( dst, *i_dst_stride, src1, i_src_stride, weight, i_width, i_height );
            return dst;
        }
        else
        {
            *i_dst_stride = i_src_stride;
            return src1;
        }
    }
    

    如源码注释所示,get_ref函数中的src是半像素,在前面文章中已说明如何生成。mvx,mvy是即将要生成的1/4像素的第一个点的坐标。下面举例说明生成的过程,先看下图:


    1:4像素02.png

    我们以第一个图中的左上角的像素点为例说明,每个方格代表一个像素,src[0][0],src[1][0],src[2][0],src[3[0]]是半像素在四个角上(注意,src是一个pixel指针的数组,src[i]实际上是一个指针,src[i][j]才是一个真正的像素值),方格上方的数字是qpel_idx的二进制值。如源码注释,当qpel_idx & 5 不为0时,则需要进行内插生成1/4像素。我们看到除了src[0] (qpel_idx==00),src[1] (qpel_idx==10),src[2] (qpel_idx==1000),src[3] (qpel_idx==1010)外,其余皆需要内插生成。也就是说,当要生成的1/4像素的第一个点正好与半像素坐标重合时,所有的点都不需要内插生成了,直接使用半像素点即可。
    那么问题来了,如不需要内插,应该使用半像素的哪些点呢,如要内插,又是以哪些半像素的点来内插呢?我们来看x264_hpel_ref0和x264_hpel_ref1这两个数组,如下:

    const uint8_t x264_hpel_ref0[16] =
     {
    0,1,1,1,
    0,1,1,1,
    2,3,3,3,
    0,1,1,1
    };
    
    const uint8_t x264_hpel_ref1[16] =
    {
    0,0,1,0,
    2,2,3,2,
    2,2,3,2,
    2,2,3,2
    };
    

    实际上,数组中的0,1,2,3分别代表src[0],src[1],src[2],src[3].举个例子,如mvx==0,mvy==0, 则src1 = src[x264_hpel_ref0[0]] + 0 + 0 = src[0],因qpel_idx==0,不需要内插,最终返回的是src1,也就是src[0];如mvx==1, mvy==1时,qpel_idx==5, x264_hpel_ref0[5]==1,src1==src[1],因qpel_idx&5不为0,则需要内插,src2==src[x264_hpel_ref1[5]]==src[2];如下图所示:


    1:4像素03.png

    pixel_avg函数是简单的求均值计算,源码如下:

    static inline void pixel_avg( pixel *dst,  intptr_t i_dst_stride,
                                  pixel *src1, intptr_t i_src1_stride,
                                  pixel *src2, intptr_t i_src2_stride, int i_width, int i_height )
    {
        for( int y = 0; y < i_height; y++ )
        {
            for( int 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:1/4像素生成

          本文链接:https://www.haomeiwen.com/subject/npaowftx.html