X264码率控制二(vbv码率控制)

X264码率控制一(abr码率控制)

X264码率控制二(vbv码率控制)

X264码率控制三(Mb-Tree介绍)

一、VBV码率控制模型图

        将vbv buff比做水桶,编码后帧的bits比做水瓶的水;vbv的码率控制过程可以看做往水桶中加水以从水桶中用水的过程;vbv码率控制原理图如下:

        上图中可用水量buffer_fill_final初始量为水桶总容量vbv_buffer_size; 流入的流速固定为bitrate/fps,即平均每帧的目标bits; 当用水过多(enc_bits过大),导致流出水过大,使得可用水量低于下溢阈值线,则需要减少流出,用小瓶接水,即降低enc_bits; 当用水量过少(enc_bits过小),使得可用水量高于上溢阈值线,需要增加流出,用大瓶接水,即加大enc_bits; enc_bits的大小是通过调节qscale来调节qp从而影响enc_bits。

二、VBV码率控核心参数

        vbv码率控制的主要参数如下图。

        buffer_size可以看作是水桶的总容量; buffer_fill_final看作是可用水量; buffer_fill看作是未来一段时间内可用水量; buffer_rate可以看作是一次加水量,理想状态下一次加水和一次出水量是相等的。

1、vbv参数初始化

vbv主要参数的初始化在x264_ratecontrol_init_reconfigurable()函数中完成;

//代码有删减
void x264_ratecontrol_init_reconfigurable( x264_t *h, int b_init )
{
    x264_ratecontrol_t *rc = h->rc;
    if( !b_init && rc->b_2pass )
        return;
    rc->buffer_rate = vbv_max_bitrate / rc->fps;
    rc->vbv_max_rate = vbv_max_bitrate;
    rc->buffer_size = vbv_buffer_size;
    rc->single_frame_vbv = rc->buffer_rate * 1.1 > rc->buffer_size;//单帧码率控制的vbv,即vbv buff size只有一帧编码数据的大小

    rc->buffer_fill_final =
    rc->buffer_fill_final_min = rc->buffer_size * h->param.rc.f_vbv_buffer_init * h->sps->vui.i_time_scale;//这里乘上一个i_time_scale系数,方便后面码率控制按照帧时长来处理
    rc->b_vbv = 1;

}

        x264_ratecontrol_init_reconfigurable()函数在x264_ratecontrol_new()函数里调用。       

        h264的帧率计算的说明,下图中由(1)式可以推出(2)式,代码中多用(2)式等号右边来计算时长。

        

2、vbv码率控制过程

        在x264_ratecontrol_start()首先计算vbv buff的buffer_rate,即往水桶中注水的速率,也就是平均每帧的bits;代码如下:

//代码有删减。
void x264_ratecontrol_start( x264_t *h, int i_force_qp, int overhead )
{
        if( rc->b_vbv )
    {
        memset( h->fdec->i_row_bits, 0, h->mb.i_mb_height * sizeof(int) );
        memset( h->fdec->f_row_qp, 0, h->mb.i_mb_height * sizeof(float) );
        memset( h->fdec->f_row_qscale, 0, h->mb.i_mb_height * sizeof(float) );
        rc->row_pred = rc->row_preds[h->sh.i_type];
        rc->buffer_rate = h->fenc->i_cpb_duration * rc->vbv_max_rate * h->sps->vui.i_num_units_in_tick / h->sps->vui.i_time_scale;
        update_vbv_plan( h, overhead );//更新vbv

}

update_vbv_plan()主要是用于计算buffer_fill,帧级多线程的时候使用预测的码率来更新buffer_fill,代码如下:

//代码有删减
static void update_vbv_plan( x264_t *h, int overhead )
{
    rcc->buffer_fill = h->thread[0]->rc->buffer_fill_final_min / h->sps->vui.i_time_scale;
//因为buffer_fill_final_min乘上了i_time_scale这里除上i_time_scale;
    if( h->i_thread_frames > 1 )//帧级多线程编码
    { 
        int j = rcc - h->thread[0]->rc;
        for( int i = 1; i < h->i_thread_frames; i++ )
        {
            x264_t *t = h->thread[ (j+i)%h->i_thread_frames ];
            double bits = t->rc->frame_size_planned;
            if( !t->b_thread_active )//b_thread_active为1表示线程编码未完成,0表示线程没有编码                	continue;
            bits = X264_MAX(bits, t->rc->frame_size_estimated);//没有编码完成的使用预测的bits
            rcc->buffer_fill -= bits; //相当于水桶出水
            rcc->buffer_fill = X264_MAX( rcc->buffer_fill, 0 );
            rcc->buffer_fill += t->rc->buffer_rate;//相当于水桶加水
            rcc->buffer_fill = X264_MIN( rcc->buffer_fill, rcc->buffer_size );
        }
    }
    rcc->buffer_fill -= overhead;//减去帧头占用的bits(如pps,sps,slice header占用的bits)

}

然后在vbv_pass1()函数中完成vbv的码率调节,如果lookahead开启则会使用未来的一些帧来调整当前帧的qscale;在lookahaead中会预测未来若干帧的帧类型和未来帧的satd值,就可以使用predict_size()函数来预测未来帧的编码bits,根据未来帧的编码bits对vbv buff的影响来调整当前帧的qscale。

//代码有删减
static double vbv_pass1( x264_t *h, int pict_type, double q )
{
    double fenc_cpb_duration = (double)h->fenc->i_cpb_duration * h->sps->vui.i_num_units_in_tick / h->sps->vui.i_time_scale;//计算当前帧的时长
    if( h->param.rc.i_lookahead )//lookahead 开启
    {
        //iterations 限制最大循环次数,terminate 调整失败容错处理
        for(int iterations = 0; iterations < 1000 && terminate != 3; iterations++ )
        {
            double cur_bits = predict_size( &rcc->pred[h->sh.i_type], q, rcc->last_satd );
            double buffer_fill_cur = rcc->buffer_fill - cur_bits;//出水之后可用水量的更新,
            double last_duration = fenc_cpb_duration;
            //根据当前帧qscale计算I/P/B帧的qscale
            frame_q[0] = h->sh.i_type == SLICE_TYPE_I ? q * h->param.rc.f_ip_factor : q;//P帧qscale
            frame_q[1] = frame_q[0] * h->param.rc.f_pb_factor;//B帧qscale
            frame_q[2] = frame_q[0] / h->param.rc.f_ip_factor;  //I帧qscale
                //根据lookahead里面计算的信息来预估未来帧对vbv buff的消耗
            for( int j = 0; buffer_fill_cur >= 0 && buffer_fill_cur <= rcc->buffer_size; j++ )
            {
                buffer_fill_cur += rcc->vbv_max_rate * last_duration;//根据帧的时长计算该帧对vbvbuff的补充,可以看做是水桶的加水
                int i_type = h->fenc->i_planned_type[j];//lookahead中对未来帧的帧类型预测
                int i_satd = h->fenc->i_planned_satd[j];//lookahead中对未来帧的satd预测
                if( i_type == X264_TYPE_AUTO )//帧类型未确定,也受lookahead的深度影响
                        break;
                    i_type = IS_X264_TYPE_I( i_type ) ? SLICE_TYPE_I : IS_X264_TYPE_B( i_type ) ? SLICE_TYPE_B : SLICE_TYPE_P;
                    cur_bits = predict_size( &rcc->pred[i_type], frame_q[i_type], i_satd );//根据satd计算编码预测bits  
                    buffer_fill_cur -= cur_bits;//出水之后可用水量的更新
                    last_duration = h->fenc->f_planned_cpb_duration[j]; //更新帧时长
            }
            //到这buffer_fill_cur就表示当前帧以及未来帧一起对vbv消耗后可用的水量,
            //target_fill 最大不超过buffer_size 的50%,
            //X264_MIN()的作用主要是历史帧编码压不住导致码率高上去,可用的buffer_fill 很低,且未来帧补充来的也比较低(如视频的末尾)
            //可能导致buffer_fill_cur会一直低于buffer_size 的50%
            target_fill = X264_MIN( rcc->buffer_fill + total_duration * rcc->vbv_max_rate * 0.5, rcc->buffer_size * 0.5 );//下溢的阈值
            if( buffer_fill_cur < target_fill )//预测出的可用水量低于目标可用水量的阈值,则需要降低当前帧的bits,即降低出水量
            {
                q *= 1.01;//增大qscale
                terminate |= 1;
                continue;//继续用增大后的qsacle来预测剩余可用水量是否在阈值范围
            }
            target_fill = x264_clip3f( rcc->buffer_fill - total_duration * rcc->vbv_max_rate * 0.5, rcc->buffer_size * 0.8, rcc->buffer_size );//上溢的阈值
            //b_vbv_min_rate 当vbv的最大码率不高于目标码率才会为1,不上溢防止码率高于目标码率
            f( rcc->b_vbv_min_rate && buffer_fill_cur > target_fill )//预测出的可用水量高于目标可用水量的阈值,则需要提高当前帧的bits,即增大出水量
            {
                q /= 1.01;//降低qscale,来降低qp
                terminate |= 2;
                continue;
            }
        }
    }
    esle lookahead 关闭
    {         
        //p帧或连续的I帧且剩余可用输出的大小不足则降低当前帧的大小
        if( ( pict_type == SLICE_TYPE_P ||
            ( pict_type == SLICE_TYPE_I && rcc->last_non_b_pict_type == SLICE_TYPE_I ) ) &&//连续出现两个I帧
            rcc->buffer_fill/rcc->buffer_size < 0.5 )//可用于输出的大小不足总buff的一半
        {
            q /= x264_clip3f( 2.0*rcc->buffer_fill/rcc->buffer_size, 0.5, 1.0 );//加大qscale来增大qp,来降低编码码率
        }
        double bits = predict_size( &rcc->pred[h->sh.i_type], q, rcc->last_satd );//根据satd预测帧编码的大小
        //vbvbuffsize比较小的时候运行当前编码帧占用完剩余可用的大小,否只允许占用不大于剩余可用大小的50%,
        double max_fill_factor = h->param.rc.i_vbv_buffer_size >= 5*h->param.rc.i_vbv_max_bitrate / rcc->fps ? 2 : 1;    
        double min_fill_factor = rcc->single_frame_vbv ? 1 : 2;
        //防止下溢的判断
        if( bits > rcc->buffer_fill/max_fill_factor )//预测编码的bit比剩余可用大小还要大则,增大qscale来增大qp,进而降低编码bits    
        {   
            double qf = x264_clip3f( rcc->buffer_fill/(max_fill_factor*bits), 0.2, 1.0 );
            q /= qf;//增大qscale
            bits *= qf;
        }
        //防止上溢的判断
        if( bits < rcc->buffer_rate/min_fill_factor )//预测编码bites比加水的量的一半还要低则降低qscale来降低qp,进而提升编码bits
        {
            double qf = x264_clip3f( bits*min_fill_factor/rcc->buffer_rate, 0.001, 1.0 );
            q *= qf;
        }
    }

   //防溢出处理完毕后
    if( h->sh.i_type == SLICE_TYPE_P && !rcc->single_frame_vbv )
    {
        int nb = rcc->bframes;
        double bits = predict_size( &rcc->pred[h->sh.i_type], q, rcc->last_satd );//预测当前编码帧的编码bits
        double pbbits = bits;
        double bbits = predict_size( rcc->pred_b_from_p, q * h->param.rc.f_pb_factor, rcc->last_satd );//预测b帧的bits
        double space;
        double bframe_cpb_duration = 0;//mingop中所有B帧的时长
        double minigop_cpb_duration;//mingop中所有帧的时长
        for( int i = 0; i < nb; i++ )//统计mingop中B帧的总时长
            bframe_cpb_duration += h->fenc->f_planned_cpb_duration[i];

        if( bbits * nb > bframe_cpb_duration * rcc->vbv_max_rate )//B帧总的预测bits大于B帧总的补充buff(总的进水量)
        {
            nb = 0;
            bframe_cpb_duration = 0;
        }
        pbbits += nb * bbits;//P帧和所有B帧的bits和

        minigop_cpb_duration = bframe_cpb_duration + fenc_cpb_duration;
        space = rcc->buffer_fill + minigop_cpb_duration*rcc->vbv_max_rate - rcc->buffer_size;//当前剩余可用buff加上mingop编码完成后新补充的buff减去vbv大小得到溢出大小(上溢),即mingop全部编码完成后溢出的大小
        if( pbbits < space )//mingop全部编码完成后溢出的大小比mingop预测出的编码bits要大则说明当前当前mingop的bits太小了
        {
            q *= X264_MAX( pbbits / space, bits / (0.5 * rcc->buffer_size) );//降低qscale来降低qp,提升bits
        }
        q = X264_MAX( q0/2, q );
    }

}

3、vbv的参数更新

        vbv参数在update_vbv( x264_t *h, int bits )函数里面更新;vbv参数更新代码如下:

//代码有删减
static int update_vbv( x264_t *h, int bits )
{
    uint64_t buffer_diff = (uint64_t)bits * h->sps->vui.i_time_scale;//bits为实际编码的大小
    rct->buffer_fill_final -= buffer_diff;//减去实际编码的大小,可以看作是出水
    rct->buffer_fill_final_min -= buffer_diff;

    //下面的代码等于价于buffer_diff = (uint64_t)bitrate *h->sps->vui.i_time_scale* (h->sps->vui.i_num_units_in_tick * h->fenc->i_cpb_duration/h->sps->vui.i_time_scale)
    //即buffer_diff = bitrate *i_time_scale*单帧时长
    buffer_diff = (uint64_t)bitrate * h->sps->vui.i_num_units_in_tick * h->fenc->i_cpb_duration;

    rct->buffer_fill_final += buffer_diff;//剩余可用buff的大小,可以看作是加水
    rct->buffer_fill_final_min += buffer_diff;

}

三、vbv行级(宏块)码率控制

1、vbv行级码率控制预测过程的主要参数

2、vbv行级(宏块)码率控制原理

        每编码完一行,计算前面编码完所有行的总的bits,使用前一行qpm预测剩余未编码行的所有bits,将已经编码完行的实际bits和剩余未编码行预测的bits进行累加得到总的bits(预测帧的编码bits),然后和帧级码控预测出来的帧bit做比较,如果比帧级预测的bits大则提升qpm,如果小则降低qpm。

3、vbv行级(宏块)码率控制代码

在int x264_ratecontrol_mb( x264_t *h, int bits )函数里进行vbv的行级(宏块)码率控制,主要代码代码如下:

h->fdec->i_row_bits[y] += bits;//累加一行中每一个宏块的编码bits
rc->qpa_aq += h->mb.i_qp;
if( h->mb.i_mb_x != h->mb.i_mb_width - 1 )//只有一行宏块编码完成才在做行级码率控制
     return 0;

//更新行级编码bits预测的参数
update_predictor( &rc->row_pred[0], qscale, h->fdec->i_row_satd[y], h->fdec->i_row_bits[y] );
//如果是P帧或者B帧,且当前行的qp比第一个前向参考帧中对应行的qp低(当前行很可能采用帧内压缩) 
if( h->sh.i_type != SLICE_TYPE_I && rc->qpm < h->fref[0][0]->f_row_qp[y] )//更新使用帧内satd的预测参数
        update_predictor( &rc->row_pred[1], qscale, h->fdec->i_row_satds[0][0][y], h->fdec->i_row_bits[y] );

float step_size = 0.5f;//qpm调整的步长
float slice_size_planned = h->param.b_sliced_threads ? rc->slice_size_planned : rc->frame_size_planned;//多slice下其他slice的预测bits或者帧的预测bits
float bits_so_far = row_bits_so_far( h, y );//计算已经完成编码行的总bits 

//vbv剩余可用缓存的大小
float buffer_left_planned = rc->buffer_fill - rc->frame_size_planned;
//当前线程(帧)从剩余可用缓存中分配到的可用缓存大小,作为b1的弹性值,即b1和rame_size_planned的差异在rc_tol内都不做处理,超过才处理
float rc_tol = buffer_left_planned / h->param.i_threads * rc->rate_tolerance;
//predict_row_size_to_end()用于预测剩余未编码行的总bits,b1就是根据已经编码和未编码的行计算出来的帧的bits
float b1 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;
//vbv下溢判断和处理
 while( rc->qpm < qp_max 
          && ((b1 > rc->frame_size_planned + rc_tol) ||b1和帧级预测出的bits差异大于弹性阈值rc_tol了
          (b1 > rc->frame_size_planned && rc->qpm < rc->qp_novbv) ||//b1比帧级预测出的bits且qpm的值比帧级qp还低
           (b1 > rc->buffer_fill - buffer_left_planned * 0.5f)) )//vbv下溢了
{
       rc->qpm += step_size;//增大qp
       b1 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;//根据新的qpm重新计算当前帧的编码bits
}
float b_max = b1 + ((rc->buffer_fill - rc->buffer_size + rc->buffer_rate) * 0.90f - b1) * trust_coeff;
rc->qpm -= step_size;
float b2 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;//根据新的qpm重新计算当前帧编码的bits
//vbv上溢判断和处理
while( rc->qpm > qp_min && rc->qpm < prev_row_qp
               && (rc->qpm > h->fdec->f_row_qp[0] || rc->single_frame_vbv)
               && (b2 < max_frame_size)
               && ((b2 < rc->frame_size_planned * 0.8f) || (b2 < b_max)) )
{
            b1 = b2;
            rc->qpm -= step_size;//降低qp
            b2 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;//根据新的qpm重新计算当前帧编码的bits

}
rc->frame_size_estimated = b1 - size_of_other_slices;//将当前已经编码和剩余未编码bits的总和更新到frame_size_estimated

4、vbv行级(宏块)码率控制bits的计算函数

//统计0到y行的所有bits
static int row_bits_so_far( x264_t *h, int y )
{
    int bits = 0;
   //如果是多slice的话计算当前slice的已经编码的bits
    for( int i = h->i_threadslice_start; i <= y; i++ )
        bits += h->fdec->i_row_bits[i];
    return bits;
}
//预测从y行到最后一行所有行的bits和
static float predict_row_size_to_end( x264_t *h, int y, float qp )
{
    float qscale = qp2qscale( qp );
    float bits = 0;
    //如果是多slice的话只计算当前slice的未编码的bits
    for( int i = y+1; i < h->i_threadslice_end; i++ )
        bits += predict_row_size( h, i, qscale );//预测当前行的bits
    return bits;
}
static float predict_row_size( x264_t *h, int y, float qscale )
{
    //预测当前行的bits
    float pred_s = predict_size( &rc->row_pred[0], qscale, h->fdec->i_row_satd[y] );//当前帧为I帧的时候i_row_satd为帧内代价,P/B帧则为帧间代价
    if( h->sh.i_type == SLICE_TYPE_I || qscale >= h->fref[0][0]->f_row_qscale[y] )
    //P帧且qscale比参考帧的qscale大则大概率是帧间块,只需要帧间代价来计算bits
    {
        if( h->sh.i_type == SLICE_TYPE_P
            && h->fref[0][0]->i_type == h->fdec->i_type
            && h->fref[0][0]->f_row_qscale[y] > 0
            && h->fref[0][0]->i_row_satd[y] > 0
            && (abs(h->fref[0][0]->i_row_satd[y] - h->fdec->i_row_satd[y]) < h->fdec->i_row_satd[y]/2))
        {
            float pred_t = h->fref[0][0]->i_row_bits[y] * h->fdec->i_row_satd[y] / h->fref[0][0]->i_row_satd[y]
                         * h->fref[0][0]->f_row_qscale[y] / qscale;
            return (pred_s + pred_t) * 0.5f;
        }
        return pred_s;
    }
    else //当前QP比ref中的qp小则大概率是帧内压缩,需要加上根据帧内代价预测出的bits
    {
        float pred_intra = predict_size( &rc->row_pred[1], qscale, h->fdec->i_row_satds[0][0][y] );//i_row_satds[0][0]是帧内的statd代价
        /* Sum: better to overestimate than underestimate by using only one of the two predictors. */
        return pred_intra + pred_s;
    }
}

5、vbv行级(宏块)satd的介绍

        和帧级一样行级的bits估计也是需要使用satd的,行级satd的参数如下:

int     *i_row_satds[X264_BFRAME_MAX+2][X264_BFRAME_MAX+2];
int     *i_row_satd;

        i_row_satds是存储的是当前帧在lookahead阶段与前向、后向参考帧计算的satd代价,存储的是每一行的代价。 i_row_satd是当前帧与某一参考帧的代价,存储的是每一行的代价。 i_row_satds[0][0]存储的是帧内的satd代价,其他是帧间的代价。 当为I帧则i_row_satd=i_row_satds[0][0]; 当为P/B帧则i_row_satd是i_row_satds[b-p0][p1-b]的satd的代价。satd的获取在 x264_rc_analyse_slice( x264_t *h )函数中实现:

int x264_rc_analyse_slice( x264_t *h )
{
    int p0 = 0, p1, b;
    if( IS_X264_TYPE_I(h->fenc->i_type) )
        p1 = b = 0;
    else if( h->fenc->i_type == X264_TYPE_P )
        p1 = b = h->fenc->i_bframes + 1;
    else //B
    {
        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;
    }

    h->fenc->i_row_satd = h->fenc->i_row_satds[b-p0][p1-b];//I帧的时候b=p0=p1=0
    h->fdec->i_row_satd = h->fdec->i_row_satds[b-p0][p1-b];
    
   //将fenc的结果拷贝到fedc中后面码率控制使用的是fdec中的结果
    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) );
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值