码率控制的代码流程图
码率控制的关键的结构体参数介绍图
一、x264码率控制模型
1、P帧下的QP和QSCALE计算公式
2、P帧下的QP和QSCALE计算代码
static inline float qp2qscale( float qp )
{
return 0.85f * powf( 2.0f, ( qp - (12.0f + QP_BD_OFFSET) ) / 6.0f );
}
static inline float qscale2qp( float qscale )
{
return (12.0f + QP_BD_OFFSET) + 6.0f * log2f( qscale/0.85f );
}
x264先根据输入视频图像复杂度和目标码率计算qpscale,在根据qpscale来计算出帧级的qp;每一帧的qscale的计算都是按照P帧的类型计算的,然后根据实际的帧类型,来调整,比如I帧的qscale计算会通过f_ip_factor参数来调整,B帧通过f_pb_factor来调整。
二、图像复杂度计算
1、复杂度计算公式
2、复杂度计算的代码
复杂度的计算代码在rate_estimate_qscale()函数中,如下:
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;
复杂度是历史帧的stad的值的平均。cplxsum乘上0.5以及cplxcount 乘上0.5都是历史帧的复杂度的权重为0.5,当前帧的复杂度权重为0.5;当前帧的复杂度保存在blurred_complexity 中以便后面qscale的计算。
三、根据复杂度计算qscale
1、qscale的计算公式
参数解释:
qcompress:由用户配置的参数默认0.6,取值范围(0,1);
blurred_complexity:历史图像的平均模糊复杂度;
wanted_bits_window:编码帧总的目标码率大小;
cplxr_sum:历史帧bits*qscale/rceq的累加和;
avgrc_qscale:根据 未经过aq所有宏块的平均QP 计算而来;
2、qscale的计算代码
qscale的计算代码在函数get_qscale(x264_t *h, ratecontrol_entry_t *rce, double rate_factor, int frame_num)中;
//代码有删减
static double get_qscale(x264_t *h, ratecontrol_entry_t *rce, double rate_factor, int frame_num)
{
double q;
if( h->param.rc.b_mb_tree )//mb_tree码率控制开启
{
double timescale = (double)h->sps->vui.i_num_units_in_tick / h->sps->vui.i_time_scale;
q = pow( BASE_FRAME_DURATION / CLIP_DURATION(rce->i_duration * timescale), 1 - h->param.rc.f_qcompress );
}
else
q = pow( rce->blurred_complexity, 1 - rcc->qcompress );//根据复杂度估算出一个qscale
{
rcc->last_rceq = q;
q /= rate_factor;//将qscale除上rate_factor,rate_factor=wanted_bits_window / cplxr_sum
rcc->last_qscale = q;
}
return q;
}
rate_factor的计算是在rate_estimate_qscale()函数中,代码如下:
q = get_qscale( h, &rce, rcc->wanted_bits_window / rcc->cplxr_sum, h->fenc->i_frame );
其中rate_factor=rcc->wanted_bits_window / rcc->cplxr_sum;
wanted_bits_window:编码帧总的目标码率大小;cplxr_sum:历史帧bits*qscale/rceq的累加和;这两个参数是在x264_ratecontrol_end()函数中更新的,代码如下:
//代码有删减
//bits是编码后的实际bits
int x264_ratecontrol_end( x264_t *h, int bits, int *filler )
{
if( rc->b_abr )
{
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);//B帧cplxr_sum的计算需要乘上f_pb_factor)
}
rc->cplxr_sum *= rc->cbr_decay;
rc->wanted_bits_window += h->fenc->f_duration * rc->bitrate;//累加当前帧的目标码率
rc->wanted_bits_window *= rc->cbr_decay;
}
}
代码中的rc->qpa_rc 是当前帧中所有mb的qp的平均值,也即avgrc_qscale=qp2qscale( rc->qpa_rc )代码如下,
rc->qpa_rc += rc->qpm * h->mb.i_mb_width; rc->qpa_rc /= h->mb.i_mb_count;
四、qscale的码率溢出处理
1、qscale的码率溢出计算;
实际编码出来的predicted_bits 比目标编码wanted_bits的要大,则overflow大于1,即溢出,溢出处理就是增加qscale来提高qp进而降低编码bits; 实际编码出来的predicted_bits 比目标编码wanted_bits的要小,则overflow小于1,也即溢出,溢出处理就是减小qscale来减小qp进而提升编码bits。abr_buffer是用来控制溢出的容忍度,由外部api参数rate_tolerance和帧率来控制,abr_buffer越小溢出系数overflow和1.0的差异越大(比1大的越多,或者比1.0小的越多),溢出处理也越明显。
2、qscale的码率溢出计算代码:
//代码有删减
static float rate_estimate_qscale( x264_t *h )
{
int64_t total_bits = 8*(h->stat.i_frame_size[SLICE_TYPE_I]
+ h->stat.i_frame_size[SLICE_TYPE_P]
+ h->stat.i_frame_size[SLICE_TYPE_B])
- rcc->filler_bits_sum;//计算已经编码完整帧总的bits数
double abr_buffer = 2 * rcc->rate_tolerance * rcc->bitrate;
double predicted_bits = total_bits;
int i_frame_done = h->i_frame;//已经编码的帧数
double time_done = i_frame_done / rcc->fps;//根据已经编码的帧数计算出已经编码的时长
wanted_bits = time_done * rcc->bitrate;//计算已经编码的帧的bits大小
if( wanted_bits > 0 )
{
abr_buffer *= X264_MAX( 1, sqrt( time_done ) );
overflow = x264_clip3f( 1.0 + (predicted_bits - wanted_bits) / abr_buffer, .5, 2 );
q *= overflow;
}
}
五、图像编码bits的预估
1、帧级编码bits的预测公式
由上面的公式可以看出图像编码bits和satd成正比,即satd越大bits越大 图像bits和qscale成反比,即qscale越大bits越小(qscale越大QP也越大)。
2、帧级编码bits的预测代码
typedef struct
{
float coeff_min;
float coeff;
float count;
float decay;
float offset;
} predictor_t;
static float predict_size( predictor_t *p, float q, float var )
{
return (p->coeff*var + p->offset) / (q*p->count);
}
帧级编码bits的预测函数predict_size()的调用代码如下:
x264_encoder_encode()
-->x264_ratecontrol_start()
-->rate_estimate_qscale()
-->predict_size( &rcc->pred[h->sh.i_type], q, rcc->last_satd )//I/P 帧
-->predict_size( rcc->pred_b_from_p, q, h->fref[1][h->i_ref[1]-1]->i_satd );//B帧编码大小预测,根据最近的的参考P帧的satd来预测
3、coeff、offset参数更新
当编码完成后实际的编码bits和qscale、satd都是已知的则可以更新coeff、offset ;
satd=last_satd采用的是lows->statd qscale=qp2qscale( rcc->qpa_rc )//qpa_rc未经过aq所有宏块的平均QP;
4、coeff、offset参数更新的代码
//bits为实际编码后的bits,var为satd,q为qscale
static void update_predictor( predictor_t *p, float q, float var, float bits )
{
float range = 1.5;
if( var < 10 )
return;
float old_coeff = p->coeff / p->count;
float old_offset = p->offset / p->count;
float new_coeff = X264_MAX( (bits*q - old_offset) / var, p->coeff_min );
float new_coeff_clipped = x264_clip3f( new_coeff, old_coeff/range, old_coeff*range );
float new_offset = bits*q - new_coeff_clipped * var;
if( new_offset >= 0 )
new_coeff = new_coeff_clipped;
else
new_offset = 0;
p->count *= p->decay;
p->coeff *= p->decay;
p->offset *= p->decay;
p->count ++;
p->coeff += new_coeff;
p->offset += new_offset;
}
当前帧编码完成后,会调用update_predictor()函数来更新预测参数;调用代码流程如下:
六、 帧级多线程编码对abr码率控制流程
1、帧级多线程码率控制介绍
帧级多线程开启时候相临连续的几个帧同时编码,导致后面的帧(帧序号大的帧)在启动编码的时候前面的帧可能没有编码完成,则在码率控制过程需要用到的编码码率则为使用predict_size()函数预测出的码率。如下图示例,第三个P帧编码启动时候I帧和第一个P帧都编码完成了,第二个P帧还么编码完成,则第三个P帧的预测时候第二个P帧的编码bits使用的是预测出来的bits。
2、帧级多线程编码的代码
代码如下:
//代码有删减
static float rate_estimate_qscale( x264_t *h )
{
int64_t total_bits = 8*(h->stat.i_frame_size[SLICE_TYPE_I]
+ h->stat.i_frame_size[SLICE_TYPE_P]
+ h->stat.i_frame_size[SLICE_TYPE_B])
- rcc->filler_bits_sum;
double predicted_bits = total_bits;
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);
predicted_bits += bits;
}
}
}