x264码率失真优化曲线的实现

h264编码 码率控制有一个重要的函数,J(cost) = D + lambda*R;(D一般为ssd,R为本宏块预计消耗的bits数目)

lambda为各种情况下的系数,该系数值和当前宏块qp值,具体看下面函数实现

static int rd_cost_mb( x264_t *h, int i_lambda2 )
{
    int b_transform_bak = h->mb.b_transform_8x8;
    int i_ssd;
    int i_bits;
    int type_bak = h->mb.i_type;

    x264_macroblock_encode( h );

    if( h->mb.b_deblock_rdo )
        x264_macroblock_deblock( h );

    i_ssd = ssd_mb( h );//计算当前宏块ssd,

    if( IS_SKIP( h->mb.i_type ) )
    {
        i_bits = (1 * i_lambda2 + 128) >> 8;// skip宏块只有一个bit
    }
    else if( h->param.b_cabac )
    {
        x264_cabac_t cabac_tmp;
        COPY_CABAC;
        macroblock_size_cabac( h, &cabac_tmp );
        i_bits = ( (uint64_t)cabac_tmp.f8_bits_encoded * i_lambda2 + 32768 ) >> 16;//计算lambda * R的结果
    }
    else
    {
        macroblock_size_cavlc( h );
        i_bits = ( (uint64_t)h->out.bs.i_bits_encoded * i_lambda2 + 128 ) >> 8;
    }

    h->mb.b_transform_8x8 = b_transform_bak;
    h->mb.i_type = type_bak;

    return X264_MIN( i_ssd + i_bits, COST_MAX );//J = lambda*R + D (ssd);//对应公式。
}

跟一下来看看,i_lambda值如何得到的。

static void mb_analyse_init_qp( x264_t *h, x264_mb_analysis_t *a, int qp )
{//宏块分析初始化
    int effective_chroma_qp = h->chroma_qp_table[SPEC_QP(qp)] + X264_MAX( qp - QP_MAX_SPEC, 0 );
    a->i_lambda = x264_lambda_tab[qp];
    a->i_lambda2 = x264_lambda2_tab[qp];//得到本宏块预计的lambda值,后面我们再分析下,宏块的初始qp如何得到的。 

    h->mb.b_trellis = h->param.analyse.i_trellis > 1 && a->i_mbrd;
    if( h->param.analyse.i_trellis )
    {
        h->mb.i_trellis_lambda2[0][0] = x264_trellis_lambda2_tab[0][qp];
        h->mb.i_trellis_lambda2[0][1] = x264_trellis_lambda2_tab[1][qp];
        h->mb.i_trellis_lambda2[1][0] = x264_trellis_lambda2_tab[0][effective_chroma_qp];
        h->mb.i_trellis_lambda2[1][1] = x264_trellis_lambda2_tab[1][effective_chroma_qp];
    }
    h->mb.i_psy_rd_lambda = a->i_lambda;
    /* Adjusting chroma lambda based on QP offset hurts PSNR but improves visual quality. */
    int chroma_offset_idx = X264_MIN( qp-effective_chroma_qp+12, MAX_CHROMA_LAMBDA_OFFSET );
    h->mb.i_chroma_lambda2_offset = h->param.analyse.b_psy ? x264_chroma_lambda2_offset_tab[chroma_offset_idx] : 256;

    if( qp > QP_MAX_SPEC )
    {
        h->nr_offset = h->nr_offset_emergency[qp-QP_MAX_SPEC-1];
        h->nr_residual_sum = h->nr_residual_sum_buf[1];
        h->nr_count = h->nr_count_buf[1];
        h->mb.b_noise_reduction = 1;
        qp = QP_MAX_SPEC; /* Out-of-spec QPs are just used for calculating lambda values. */
    }
    else
    {
        h->nr_offset = h->nr_offset_denoise;
        h->nr_residual_sum = h->nr_residual_sum_buf[0];
        h->nr_count = h->nr_count_buf[0];
        h->mb.b_noise_reduction = 0;
    }

    a->i_qp = h->mb.i_qp = qp;
    h->mb.i_chroma_qp = h->chroma_qp_table[qp];
}

//接下来看看SSD如何得到的。

static inline int ssd_mb( x264_t *h )
{
    int i_ssd = ssd_plane( h, PIXEL_16x16, 0, 0, 0 );
    if( CHROMA_FORMAT )
    {
        int chroma_size = h->luma2chroma_pixel[PIXEL_16x16];
        int chroma_ssd = ssd_plane( h, chroma_size, 1, 0, 0 ) + ssd_plane( h, chroma_size, 2, 0, 0 );
        i_ssd += ((uint64_t)chroma_ssd * h->mb.i_chroma_lambda2_offset + 128) >> 8;
    }
    return i_ssd;
}//计算得到本宏块的SSD

static inline int ssd_plane( x264_t *h, int size, int p, int x, int y )
{//size 块大小,x ,y 块的坐标位置  p通道号,0亮度 1 U 2 V
    int satd = 0;
    pixel *fdec = h->mb.pic.p_fdec[p] + x + y*FDEC_STRIDE;
    pixel *fenc = h->mb.pic.p_fenc[p] + x + y*FENC_STRIDE;
    if( p == 0 && h->mb.i_psy_rd )//如果开了亮度,并且开了psy_rd
    {
        /* If the plane is smaller than 8x8, we can't do an SA8D; this probably isn't a big problem. */
        if( size <= PIXEL_8x8 )
        {
            uint64_t fdec_acs = h->pixf.hadamard_ac[size]( fdec, FDEC_STRIDE );
            uint64_t fenc_acs = cached_hadamard( h, size, x, y );//已经缓存了的哈德曼区块
            satd = abs((int32_t)fdec_acs - (int32_t)fenc_acs)
                 + abs((int32_t)(fdec_acs>>32) - (int32_t)(fenc_acs>>32));
            satd >>= 1;
        }
        else
        {//lambda为码率 失真 权重比
            int dc = h->pixf.sad[size]( fdec, FDEC_STRIDE, (pixel*)x264_zero, 0 ) >> 1;
            satd = abs(h->pixf.satd[size]( fdec, FDEC_STRIDE, (pixel*)x264_zero, 0 ) 
                - dc - cached_satd( h, size, x, y ));//已经缓存了satd的量
        }
        satd = (satd * h->mb.i_psy_rd * h->mb.i_psy_rd_lambda + 128) >> 8;//psy 这里用到了psy-rd系数,这里可以看出来,

//这里psy-rd弱化了satd 对ssd最终结果的影响,psy-rd 越小,计算出来的satd越小,
    }
    return h->pixf.ssd[size](fenc, FENC_STRIDE, fdec, FDEC_STRIDE) + satd;
}

以上公式展开就是:

cost = SSD + SATD*psy + lambda*R;

rdo算法试图找到一个COST最小的,lambda是固定值,这个公式的意义是,如果psy很大,SATD权重大,psy大的时候,这个

宏块越不容易被选中为参考块,因为cost也大。这个时候cost回偏向选择SATD小的,这种情况是在意失真的,为什么说这里是在意失真,cost要选择SATD小的块,qp值是一定的,这样SATD小直接就被选择了,而忽略了码率R的影响,会导致一个结果,就是这个块码率可能会很大,这样一来别的块就不够码率。 反之,如果psy很小,satd对最终cost计算结果影响就小,这个时候cost偏向于选择R码率小的宏块。但是这样会导致失真大,因为码率小,satd如何,这里没考虑,会压缩得更厉害。

 

实践证明,在码率充足的时候,psy设置比较大是没问题,范围0-10,默认是1。 码率不足的时候,最好设置0-1 ,这样能把码率节约下来。

 

上一篇:Objc中2维指针作为输出参数时由ARC及@autoreleasepool引发的血案


下一篇:c – 我正在创建开源GPL H264编码lib / app(基于x264)我是否需要为许可付费?