美文网首页
(八)帧间编码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