x264宏块带权预测以及运动补偿

先解释下,运动补偿和带权预测

1 运动补偿 ,h264编码器为了让宏块的运动预测更加精确,通过差值的方法,将像素最大差值到1/4像素的方式,生成新的宏块,从而减少运动残差。

2 带权预测,h264在做宏块差值的时候,首先以帧为单位,计算当前编码帧和被参考帧的平均luma(亮度),chroma(色度)的平均值,然后计算平均值比值,将这个比值作为权重,先将参考帧的被参考块做一次比值乘法运算,让其更接近当前编码宏块。举例来说:参考宏块平均值255,编码宏块平均值130,先将参考宏块乘以130/255,然后再和编码宏块做差值运算,这样得到的结果更接近,更容易压缩。

(1) 运动补偿代码流程

...先是做了宏块分析...找到了参考帧,计算好了mv

void x264_macroblock_encode( x264_t *h )
{//按照编码的像素格式的不同,用不同的函数完成宏编码
    if( CHROMA444 )
        macroblock_encode_internal( h, 3, 0 );
    else if( CHROMA_FORMAT )
        macroblock_encode_internal( h, 1, 1 );
    else
        macroblock_encode_internal( h, 1, 0 );
}

static ALWAYS_INLINE void macroblock_encode_internal( x264_t *h, int plane_count, int chroma )
{
    int i_qp = h->mb.i_qp;
    int b_decimate = h->mb.b_dct_decimate;
    int b_force_no_skip = 0;
    int nz;
    h->mb.i_cbp_luma = 0;
    for( int p = 0; p < plane_count; p++ )
        h->mb.cache.non_zero_count[x264_scan8[LUMA_DC+p]] = 0;

    if( h->mb.i_type == I_PCM )// PCM encoder
    {
        /* if PCM is chosen, we need to store reconstructed frame data */
        for( int p = 0; p < plane_count; p++ )
            h->mc.copy[PIXEL_16x16]( h->mb.pic.p_fdec[p], FDEC_STRIDE, h->mb.pic.p_fenc[p], FENC_STRIDE, 16 );
        if( chroma )
        {
            int height = 16 >> CHROMA_V_SHIFT;
            h->mc.copy[PIXEL_8x8]  ( h->mb.pic.p_fdec[1], FDEC_STRIDE, h->mb.pic.p_fenc[1], FENC_STRIDE, height );
            h->mc.copy[PIXEL_8x8]  ( h->mb.pic.p_fdec[2], FDEC_STRIDE, h->mb.pic.p_fenc[2], FENC_STRIDE, height );
        }
        // 如果是PCM。则我们直接把fenc 存储到fdec重建帧中
        return;
    }

//上面PCM模式的忽略

for( int p = 0; p < plane_count; p++ )
                h->mc.mc_luma( h->mb.pic.p_fdec[p], FDEC_STRIDE,
                               &h->mb.pic.p_fref[0][0][p*4], h->mb.pic.i_stride[p],
                               mvx, mvy, 16, 16, &h->sh.weight[0][p] );// 计算 luma 亮度mc

//编码第一步,先把参考宏块拷贝到重建帧宏块对应的位置里面

static void mc_luma( 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 )
{
    int qpel_idx = ((mvy&3)<<2) + (mvx&3);//计算mv以1/4像素为单位的偏移,图中黑色部分是真实的像素,框框都是插值出来的半像素,1/4个像素大小


    int offset = (mvy>>2)*i_src_stride + (mvx>>2);
    pixel *src1 = src[x264_hpel_ref0[qpel_idx]] + offset + ((mvy&3) == 3) * i_src_stride;

    if( qpel_idx & 5 ) /* qpel interpolation needed  这里表示需要精确到1/4像素,因为前面做lookahead的时候已经做了1/2像素差值,这里要精确到1/4,所以需要再插值一次 */
    {
        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 );// 算 1/4 像素
        if( weight->weightfn )
            mc_weight( dst, i_dst_stride, dst, i_dst_stride, weight, i_width, i_height );
    }
    else if( weight->weightfn )
    {
        mc_weight( dst, i_dst_stride, src1, i_src_stride, weight, i_width, i_height );
    }
    else
    {// 复制像素数据
        mc_copy( src1, i_src_stride, dst, i_dst_stride, i_width, i_height );
    }
}

//这是亮度参考宏块拷贝到重建宏块的过程

2 带权预测

mc_weight( dst, i_dst_stride, src1, i_src_stride, weight, i_width, i_height );

static void mc_weight( pixel *dst, intptr_t i_dst_stride, pixel *src, intptr_t i_src_stride,
                       const x264_weight_t *weight, int i_width, int i_height )
{
//加权预测,将参考块乘以个weight,然后再存入fdec 重建块中,然后再和编码块做差值,得到最终结果
    int offset = weight->i_offset << (BIT_DEPTH-8);
    int scale = weight->i_scale;//缩放系数
    int denom = weight->i_denom;
    if( denom >= 1 )
    {
        for( int y = 0; y < i_height; y++, dst += i_dst_stride, src += i_src_stride )
            for( int x = 0; x < i_width; x++ )
                opscale( x );
    }
    else
    {
        for( int y = 0; y < i_height; y++, dst += i_dst_stride, src += i_src_stride )
            for( int x = 0; x < i_width; x++ )
                opscale_noden( x );
    }
}

x264_encoder_encode()-->

weighted_pred_init()-->

void x264_weight_scale_plane( x264_t *h, pixel *dst, intptr_t i_dst_stride, pixel *src, intptr_t i_src_stride,
                              int i_width, int i_height, x264_weight_t *w )// 计算权重w的两个系数

{

    for( int i = 0; i < 3; i++ )//循环每一个plane 对应YUV
    {
        for( int j = 0; j < h->i_ref[0]; j++ )// 参考序列中的每一个参考帧都计算一次

       {

               x264_weight_scale_plane( h, dst, stride, src, stride, width, height, &h->sh.weight[j][0] );

               void x264_weight_scale_plane( x264_t *h, pixel *dst, intptr_t i_dst_stride, pixel *src, intptr_t i_src_stride,
                              int i_width, int i_height, x264_weight_t *w )
{
    /* Weight horizontal strips of height 16. This was found to be the optimal height
     * in terms of the cache loads. */
    while( i_height > 0 )
    {
        int x;
        for( x = 0; x < i_width-8; x += 16 )
            w->weightfn[16>>2]( dst+x, i_dst_stride, src+x, i_src_stride, w, X264_MIN( i_height, 16 ) );
        if( x < i_width )
            w->weightfn[ 8>>2]( dst+x, i_dst_stride, src+x, i_src_stride, w, X264_MIN( i_height, 16 ) );
        i_height -= 16;
        dst += 16 * i_dst_stride;
        src += 16 * i_src_stride;
    }
}

最终的weightfn用汇编实现的,具体代码后面再详细分析

        }

}

typedef struct x264_weight_t
{
    /* aligning the first member is a gcc hack to force the struct to be
     * 16 byte aligned, as well as force sizeof(struct) to be a multiple of 16 */
    ALIGNED_16( int16_t cachea[8] );
    int16_t cacheb[8];
    int32_t i_denom;
    int32_t i_scale;
    int32_t i_offset;
    weight_fn_t *weightfn;
} ALIGNED_16( x264_weight_t );

带权预测运算过程

#define opscale(x) dst[x] = x264_clip_pixel( ((src[x] * scale + (1<<(denom - 1))) >> denom) + offset ) 这个计算公式暂时还没理解
#define opscale_noden(x) dst[x] = x264_clip_pixel( src[x] * scale + offset )

这里大致介绍了这两个东西的基本含义和流程,详细公式含义后续再研究。

 

 

 

 

 

 

 


 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值