x264中的码控策略——CRF

一. 重点数据成员

    /* ABR stuff */
    int    last_satd;
    double last_rceq;
    double cplxr_sum;           /* sum of bits*qscale/rceq */
    double expected_bits_sum;   /* sum of qscale2bits after rceq, ratefactor, and overflow, only includes finished frames */
    int64_t filler_bits_sum;    /* sum in bits of finished frames' filler data */
    double wanted_bits_window;  /* target bitrate * window */
    double cbr_decay;
    double short_term_cplxsum;
    double short_term_cplxcount;
    double rate_factor_constant;
    double ip_offset;
    double pb_offset;

关键点:

1. x264中只对P帧计算qp,I/B帧的qp根据P帧的qp加减offset得到,offset分别是ip_offset,pb_offset,是配置的参数。

2. x264中计算qp时会以BBBP,这样的连续B帧和后面的一个P帧为单位,算出P帧的qp,再加权出前面的B帧的qp。

每帧开始编码前,会调用rate_estimate_qscale函数计算一帧的基础QP(qscale),此函数会:

1. 先检查当前帧是否B帧,如果是,B帧的qscale由前后参考帧的QP加权平均得到,不单独计算,详见后文。

2. 非B帧,即I/P帧时,会针对2paas编码和1paas的ABR分别处理,这里只看1paas的ABR,重点代码是

 rcc->last_satd = x264_rc_analyse_slice( h );
 rcc->short_term_cplxsum *= 0.5;
 rcc->short_term_cplxcount *= 0.5;
 rcc->short_term_cplxsum += rcc->last_satd / (CLIP_DURATION(h->fenc->f_duration) / BASE_FRAME_DURATION);
 rcc->short_term_cplxcount ++;
 rce.blurred_complexity = rcc->short_term_cplxsum / rcc->short_term_cplxcount;
 if( h->param.rc.i_rc_method == X264_RC_CRF )
 {
    q = get_qscale( h, &rce, rcc->rate_factor_constant, h->fenc->i_frame );
 }
  else
  {
     q = get_qscale( h, &rce, rcc->wanted_bits_window / rcc->cplxr_sum, h->fenc->i_frame );
}

第一行代码x264_rc_analyse_slice,是用1/4宽高的图像做简单的帧内预测/帧间预测,估计出编码的代码。当有lookahead过程时,此代码在lookahead中提前计算;没有lookahead时,临时计算。

重点就是最后两句代码,调用get_qscale计算qscale,区别在于CRF时传的参数是rate_factor_constant,ABR时,这个rate_factor是 rcc->wanted_bits_window / rcc->cplxr_sum

看看get_qscale函数,将原函数简化一下,去掉基本不会走到的逻辑:当0延迟编码时,不会使用mb_tree,通常也不会使用zone,简化后的函数关键代码为

static double get_qscale(x264_t *h, ratecontrol_entry_t *rce, double rate_factor, int frame_num)
{
    x264_ratecontrol_t *rcc= h->rc;
    double q = pow( rce->blurred_complexity, 1 - rcc->qcompress );
    // avoid NaN's in the rc_eq
    if( !isfinite(q) || rce->tex_bits + rce->mv_bits == 0 )
        q = rcc->last_qscale_for[rce->pict_type];//此代码通常也不会满足条件
    else
    {
        rcc->last_rceq = q;
        q /= rate_factor;
        rcc->last_qscale = q;
    }
    return q;
}

于是,这里计算出了几个值

1. last_rceq = pow( rce->blurred_complexity, 1 - rcc->qcompress );

2. last_qscale = q = pow( rce->blurred_complexity, 1 - rcc->qcompress )/rate_factor

意思是qscale=图像复杂度/rate_factor。

这里可以看到,每帧的qscale和两个值相关,blurred_complexity和rate_factor,当使用CRF时,rate_factor为设置的常数。

其中:qcompress在使用mb_tree时为1,不使用时为配置的编码参数f_qcompress,默认值为0.6

下面重点看blurred_complexity的计算过程:(blurred_complexity的实际值代表的是编码此帧的代价,使用帧内编码或帧间编码中的最小的代价)

在每帧开始编码前调用rate_estimate_qscale计算qp时,会更新

rcc->last_satd = x264_rc_analyse_slice( h );
rcc->short_term_cplxsum *= 0.5;
rcc->short_term_cplxcount *= 0.5;
rcc->short_term_cplxsum += rcc->last_satd / (CLIP_DURATION(h->fenc->f_duration) / BASE_FRAME_DURATION);
rcc->short_term_cplxcount ++;
rce.tex_bits = rcc->last_satd;
rce.blurred_complexity = rcc->short_term_cplxsum / rcc->short_term_cplxcount;

这里重点:

1. last_satd是对每帧进行预分析得到的satd,代表每帧的复杂度

2. short_term_cplxsum是将之前的帧的复杂度之和与当前帧的复杂度各计算一半的权重的加权和

3. blurred_complexity是将加权和再除以加权帧个数后,得到的评估当前帧(短期帧序列)的复杂度

4. 所以qscale=复杂度/rate_factor

另外,从rate_estimate_qscale函数中可以看到,x264不会为B帧单独计算qp,而使用B帧的两个参数帧(非B帧)的平均QP值(f_qp_avg_rc)来计算。计算分几种条件,见注释

if( pict_type == SLICE_TYPE_B )
    {
        /* B-frames don't have independent ratecontrol, but rather get the
         * average QP of the two adjacent P-frames + an offset */

        int i0 = IS_X264_TYPE_I(h->fref_nearest[0]->i_type);
        int i1 = IS_X264_TYPE_I(h->fref_nearest[1]->i_type);
        int dt0 = abs(h->fenc->i_poc - h->fref_nearest[0]->i_poc);
        int dt1 = abs(h->fenc->i_poc - h->fref_nearest[1]->i_poc);
        float q0 = h->fref_nearest[0]->f_qp_avg_rc;
        float q1 = h->fref_nearest[1]->f_qp_avg_rc;

        if( h->fref_nearest[0]->i_type == X264_TYPE_BREF )
            q0 -= rcc->pb_offset/2;
        if( h->fref_nearest[1]->i_type == X264_TYPE_BREF )
            q1 -= rcc->pb_offset/2;

        if( i0 && i1 )//两个参考帧都为I帧
            q = (q0 + q1) / 2 + rcc->ip_offset;//将I帧的QP换成对应的P帧的QP需要+ip_offset
        else if( i0 )//只有i0为I帧,则用q1的,即另一个P帧参考帧的qp,此时不需要再+ip_offset
            q = q1;
        else if( i1 )//同理用另一个P帧的
            q = q0;
        else//两个参考帧都为P帧,则按时域距离加权
            q = (q0*dt1 + q1*dt0) / (dt0 + dt1);
        //上面仅是转换成了对应P帧的qp,因为当前帧为B帧,还要再转换为对应B帧的qp,加qp_offset
        if( h->fenc->b_kept_as_ref )//当前帧编码为被参考的B帧,则只加一半的pb_offset,即质量设定在非参考的B帧和P帧之前
            q += rcc->pb_offset/2;
        else
            q += rcc->pb_offset;

        rcc->qp_novbv = q;
        q = qp2qscale( q );//将QP换算为qscale

        return q;
    }

下面是函数分析一帧代码的过程,见注释,

关键代码是:cost = frames[b]->i_cost_est[b-p0][p1-b];

在lookahead中,已经预先分析了每一帧从不同的帧进行预测时的代价,保存在i_cost_est中,二维数组中保存的是每一帧分别从前面和后面的不同帧进行预测时的代价。每帧缩小为1/4后放在frames队列中,每次会处理一个小GOP,即一个I或P帧加上后面连续的B帧,示意图为

I   B   B  B  P  B  B  B  P  B  B  B  P B B B

每个I或P加上后面的3个B帧即为小GOP,当前处理的小GOP为  B B B P, X为I或P帧。

所以,如果当前帧为I帧,就是第0个位置,所以b=p0=p1。 注,I帧在小GOP的最前面

如果当前帧为P帖,是在小GOP的最后面,即 X B B B P中的P,此时,X为前向参考帧,即p0=0,当前要编码的帧是P,即b = i_bframes+1,即3+1=4。因为只做前向参考,不后面参考,所以要让p1-b=0,所以p1 = b。

如果当前帧为B帧,前向参考的p0=0,b帧即当前帧的位置由当前帧的poc减去前向参考帧的poc计算,除2是因为h264中每一帧的poc会增加2.  p1也是由poc位置计算而来

int x264_rc_analyse_slice( x264_t *h )
{
    int p0 = 0, p1, b;//这里b,p0,p1分别指三种类型的帧的位置。
    int cost;
    x264_emms();

    if( IS_X264_TYPE_I(h->fenc->i_type) )//如果当前帧为I帧,所以p0/p1/b都为0
        p1 = b = 0;
    else if( h->fenc->i_type == X264_TYPE_P )//当前帧为p帧时,p1指向下一个p帧的位置,中间留出了bframes个空位
        p1 = b = h->fenc->i_bframes + 1;
    else //B帧时,由i_poc算出当前编码的B帧的位置,再算出p1应该在的位置
    {
        p1 = (h->fref_nearest[1]->i_poc - h->fref_nearest[0]->i_poc)/2;
        b  = (h->fenc->i_poc - h->fref_nearest[0]->i_poc)/2;
    }
    /* We don't need to assign p0/p1 since we are not performing any real analysis here. */
    x264_frame_t **frames = &h->fenc - b;

    /* cost should have been already calculated by x264_slicetype_decide */
    cost = frames[b]->i_cost_est[b-p0][p1-b];
    assert( cost >= 0 );

    if( h->param.rc.b_mb_tree && !h->param.rc.b_stat_read )
    {
        cost = slicetype_frame_cost_recalculate( h, frames, p0, p1, b );
        if( b && h->param.rc.i_vbv_buffer_size )
            slicetype_frame_cost_recalculate( h, frames, b, b, b );
    }
    /* In AQ, use the weighted score instead. */
    else if( h->param.rc.i_aq_mode )
        cost = frames[b]->i_cost_est_aq[b-p0][p1-b];

    h->fenc->i_row_satd = h->fenc->i_row_satds[b-p0][p1-b];
    h->fdec->i_row_satd = h->fdec->i_row_satds[b-p0][p1-b];
    h->fdec->i_satd = cost;
    memcpy( h->fdec->i_row_satd, h->fenc->i_row_satd, h->mb.i_mb_height * sizeof(int) );
    if( !IS_X264_TYPE_I(h->fenc->i_type) )
        memcpy( h->fdec->i_row_satds[0][0], h->fenc->i_row_satds[0][0], h->mb.i_mb_height * sizeof(int) );

    if( h->param.b_intra_refresh && h->param.rc.i_vbv_buffer_size && h->fenc->i_type == X264_TYPE_P )
    {
        int ip_factor = 256 * h->param.rc.f_ip_factor; /* fix8 */
        for( int y = 0; y < h->mb.i_mb_height; y++ )
        {
            int mb_xy = y * h->mb.i_mb_stride + h->fdec->i_pir_start_col;
            for( int x = h->fdec->i_pir_start_col; x <= h->fdec->i_pir_end_col; x++, mb_xy++ )
            {
                int intra_cost = (h->fenc->i_intra_cost[mb_xy] * ip_factor + 128) >> 8;
                int inter_cost = h->fenc->lowres_costs[b-p0][p1-b][mb_xy] & LOWRES_COST_MASK;
                int diff = intra_cost - inter_cost;
                if( h->param.rc.i_aq_mode )
                    h->fdec->i_row_satd[y] += (diff * frames[b]->i_inv_qscale_factor[mb_xy] + 128) >> 8;
                else
                    h->fdec->i_row_satd[y] += diff;
                cost += diff;
            }
        }
    }

    return cost;
}

下面这部分在CRF中不会使用,所以暂时不用看

其中,初始化时

 rc->cplxr_sum = .01 * pow( 7.0e5, rc->qcompress ) * pow( h->mb.i_mb_count, 0.5 );

每帧编码完后,更新为:

        if( h->sh.i_type != SLICE_TYPE_B )
            rc->cplxr_sum += bits * qp2qscale( rc->qpa_rc ) / rc->last_rceq;
        else
        {
            /* Depends on the fact that B-frame's QP is an offset from the following P-frame's.
             * Not perfectly accurate with B-refs, but good enough. */
            rc->cplxr_sum += bits * qp2qscale( rc->qpa_rc ) / (rc->last_rceq * h->param.rc.f_pb_factor);
        }
        rc->cplxr_sum *= rc->cbr_decay;

 rc->last_rceq * h->param.rc.f_pb_factor   这句代码可理解为将B帧的统计值转换为P帧的统计值,因为P/B帧有一个QP的比例关系

则上面只需要理解:rc->cplxr_sum += bits * qp2qscale( rc->qpa_rc ) / rc->last_rceq;

其中,当前帧的cplxr就可当成是bits * qp2qscale( rc->qpa_rc ) / rc->last_rceq,即bits*qscale/last_rceq

下面是CRF计算会用到的,要仔细看

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值