【x264】码率控制模块的简单分析—编码主流程

2024-06-07 17:28

本文主要是介绍【x264】码率控制模块的简单分析—编码主流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【x264】码率控制(rate control)模块的简单分析—编码主流程

  • 1. 码率控制概述
    • 1.1 比特分配
    • 1.2 率失真优化(RDO)
  • 2.码率控制中比特分配的实现
    • 2.1 码率控制器的创建(x264_ratecontrol_new)
      • 2.1.1 码控模块的重新配置(x264_ratecontrol_init_reconfigurable)
    • 2.2 帧级别码率控制
      • 2.2.1 计算一帧的AQ信息(x264_adaptive_quant_frame)
      • 2.2.2 计算一帧的mbtree信息(x264_lookahead_get_frames —> ... —> macroblock_tree)
    • 2.2.3 码控模块启动(x264_ratecontrol_start)
      • 2.2.3.1 估计qscale(rate_estimate_qscale)
    • 2.3 获取帧级qp(x264_ratecontrol_qp)
    • 2.4 获取当前mb的qp(x264_ratecontrol_mb_qp)
    • 2.5 x264_ratecontrol_mb
  • 3.小结

参数分析:
【x264】x264编码器参数配置

流程分析:
【x264】x264编码主流程简单分析
【x264】编码核心函数(x264_encoder_encode)的简单分析
【x264】分析模块(analyse)的简单分析—帧内预测
【x264】分析模块(analyse)的简单分析—帧间预测
【x264】码率控制模块的简单分析—宏块级码控工具Mbtree和AQ
【x264】码率控制模块的简单分析—帧级码控策略

1. 码率控制概述

编码器是一个由许多简单或者复杂的编码工具组成的一个系统,这个系统的任务是实现高效的视频或图像压缩,评价的指标是编码之后的质量和码率,分别用PSNR和Bitrate描述。在对这样一个系统进行开发或者使用时,应该考虑使用何种工具来满足当前的任务需求,这样就引出了一个重要的问题,使用何种方法,能够将这么多的工具串联起来,以一个较低的编码码率来获得较高的编码质量。因为这种方法是一种编码控制算法,且目标是较低的编码码率,所以这种方法又可以称为码率控制(Rate Control, RC)

我理解码率控制可以分为两个部分:比特分配(Bit Allocation)和率失真优化(Rate Distortion Optimization,RDO)。比特分配用于整体控制,为当前帧或者当前行确定使用的比特数量,依据r- λ \lambda λ模型来动态的调整 λ \lambda λ,从而调控分配的比特数量。RDO用于预测模块,通过综合考量编码损失和编码码率,为当前mb选择一个最佳的编码模式

1.1 比特分配

在x264中, λ \lambda λ用qscale这个变量来描述,因此比特分配的核心目标变为确定一个当前最优的qscale值,确定的方式由不同的配置参数决定,例如帧级码控模式(CRF和ABR)、mb级码控工具(mbtree和AQ)和VBV,这在其他文中记录过。基于x264当中的r- λ \lambda λ模型,通过qscale来预测消耗的比特数,从而动态调整未来帧可能会使用的比特数量。在x264中,r- λ \lambda λ模型可以简单描述为
r = c ∗ c o m p l e x q s c a l e r = \frac{c*complex}{qscale} r=qscaleccomplex
其中,c为系数,并且在编码过程中动态更新,qscale就是 λ \lambda λ。比特分配通过结合r- λ \lambda λ模型以及所配置的参数,实现精确地比特控制。qscale和qp的对应关系是
λ = 0.85 ∗ 2 Q − 12 6 \lambda = 0.85 * 2^{\frac{Q-12}{6}} λ=0.8526Q12

1.2 率失真优化(RDO)

RDO是编码器当中的一个核心概念,通过给定一个目标的码率,来作为衡量各个工具使用的标准,例如选择哪种帧内预测模式,选择哪种帧间预测模式,使用何种宏块划分类型等等。为了在编码质量和编码码率之间做出一个平衡,提出了一种基于拉格朗日算子的数学模型:
J M o d e ( S k , I k ∣ Q , λ M o d e ) = D R e c ( S k , I k ∣ Q ) + λ ∗ R R e c ( S k , I k ∣ Q ) J_{Mode}(S_k, I_k|Q, \lambda_{Mode}) = D_{Rec}(S_k, I_k|Q) +\lambda * R_{Rec}(S_k, I_k|Q) JMode(Sk,IkQ,λMode)=DRec(Sk,IkQ)+λRRec(Sk,IkQ)

其中, S k S_k Sk表示当前的宏块, I k I_k Ik表示宏块的编码模式, Q Q Q为量化参数, λ M o d e \lambda_{Mode} λMode是这个编码模式对应的量化参数, D R e c D_{Rec} DRec是编码后的失真度, R R e c R_{Rec} RRec是编码后的比特率。从公式中看出,码率控制将失真度和比特率结合起来,使用一个权重系数进行调控,组成了拉格朗日代价函数。通过使用拉格朗日代价函数,能够确定当前编码模式是否是最佳的。

这里还有一个问题,使用拉格朗日代价函数能够确定一个模式的好坏,那编码过程中使用的一系列编码工具,所对应的一系列模式,怎么从整个系统的角度上,去选择一组模式,使得整体的编码质量最好,同时码率最小呢?解决这个问题的方法是,基于拉格朗日代价函数,让所有的模式都选取最优的模式,这样选择的一组模式能够在保证一定的码率下,获得不错的编码质量。不过这样选择出来的一组模式可能不是最优的,有可能是次优的,但考虑到操作的简便性,实际应用时仍然使用这种方法。用公式可以描述如下:
min ⁡ I ∑ k = 1 K J ( S k , I ∣ λ ) = ∑ k = 1 K min ⁡ I J ( S k , I k ∣ λ ) \min_{I}\sum_{k=1}^{K}J(S_{k},I|\lambda)=\sum_{k=1}^{K}\min_{I}J(S_{k},I_{k}|\lambda) Imink=1KJ(Sk,Iλ)=k=1KIminJ(Sk,Ikλ)
即要分别对每一个宏块选择最优的编码模式,就能够得到整体最优的编码模式

为了选择一个最优的编码模式,需要计算当前模式所带来的失真度。失真度由宏块的原始像素和重建像素决定,计算的方式有SSD、SAD和SATD,这三者的计算方式为:
S S D = 1 m n ∑ j = 0 m − 1 ∑ i = 0 n − 1 ∣ f ( x , y ) − g ( x , y ) ∣ 2 SSD = \frac{1}{mn}\sum_{j = 0}^{m-1} \sum_{i = 0}^{n-1}|f(x,y) - g(x,y)|^2 SSD=mn1j=0m1i=0n1f(x,y)g(x,y)2
S A D = 1 m n ∑ j = 0 m − 1 ∑ i = 0 n − 1 ∣ f ( x , y ) − g ( x , y ) ∣ SAD = \frac{1}{mn}\sum_{j = 0}^{m-1} \sum_{i = 0}^{n-1}|f(x,y) - g(x,y)| SAD=mn1j=0m1i=0n1f(x,y)g(x,y)
S A T D = ∑ K ∑ K H X H SATD = \sum_{K} \sum_{K}HXH SATD=KKHXH
率失真优化RDO具体地实现在帧间预测模块和帧内预测模块两文中记录

2.码率控制中比特分配的实现

在x264当中,码控的比特分配分为帧级和mb级,因此实现的方式也分为不同的层级。按照编码流程来看,码控的主要实现流程为:

  1. 码率控制器的创建(x264_ratecontrol_new)
    (1)码率控制器的重新配置(x264_ratecontrol_init_configurable)
  2. 帧级码控
    (2.1)计算一帧的AQ信息(x264_adaptive_quant_frame)
    (2.2)计算一帧的mbtree信息(x264_lookahead_get_frames —> … —> macroblock_tree)
    (2.3)帧级码控启动(x264_ratecontrol_start)
      (2.3.1)CRF模式和ABR模式,基于目前使用的实际比特更新1帧的qscale(rate_estimate_qscale)
      (2.3.2)2pass,基于目前使用的实际比特更新1帧的qscale(rate_estimate_qscale)
      (2.3.3)CQP模式,直接计算qscale
      (2.3.4)更新pqp(accum_p_qp_update)
  3. 获取帧级qp(x264_ratecontrol_qp)
  4. 获取当前mb的qp,进行qp的调整(x264_ratecontrol_mb_qp)
  5. 宏块级码率控制(x264_ratecontrol_mb)

2.1 码率控制器的创建(x264_ratecontrol_new)

本函数用于创建码率控制器,函数的定义位于ratecontrol.c,且在编码器打开时(x264_encoder_open)调用,其主要的工作流程为:

  1. 码控模块的重新配置(x264_ratecontrol_init_reconfigurable)
  2. ABR模式参数的配置
  3. 初始化一些码控参数
  4. 读取状态文件并且初始化2pass算法
  5. 打开输出文件
int x264_ratecontrol_new( x264_t *h )
{x264_ratecontrol_t *rc;x264_emms();CHECKED_MALLOCZERO( h->rc, h->param.i_threads * sizeof(x264_ratecontrol_t) );rc = h->rc;rc->b_abr = h->param.rc.i_rc_method != X264_RC_CQP && !h->param.rc.b_stat_read; // 检查是否使用abr模式// 2pass模式是两遍编码模式,第一遍时会将一些编码信息存储下来,第二遍时以这些信息作为参考进行更加精细的编码// 目标:在给定第一遍编码(如ABR模式)信息之后,尝试在总比特数恒定的情况下选择一系列mb的QP,提升编码质量// 步骤:// (1)编码前,选择前后帧分配的相对比特数,此时并不关注编码总比特数,经验公式为complexity * 0.6//		其中复杂度定义为帧在恒定QP下的比特大小(从第一次通过估计)// (2)缩放前面计算的结果来填充请求的总尺寸,可以选择加上VBV的限制,由于帧大小预测器和VBV中的非线性,这是一个迭代过程// (3)开始编码,在每帧编码之后,更新未来的qp来补偿之前的预测误差,如果第二帧始终偏离预测大小(通常因为使用了比第一帧更慢的压缩工具)// 		那么将所有未来帧的qscales乘以误差的倒数。此外,还有一个短期补偿,以防止在开始时(当没有足够的全局补偿数据时)和//		接近结束时(当全局没有时间做出反应时)偏离期望的大小太远rc->b_2pass = h->param.rc.i_rc_method == X264_RC_ABR && h->param.rc.b_stat_read; // 检查是否使用2pass模式(没有深入研究)/* FIXME: use integers */if( h->param.i_fps_num > 0 && h->param.i_fps_den > 0 )rc->fps = (float) h->param.i_fps_num / h->param.i_fps_den;elserc->fps = 25.0;if( h->param.rc.b_mb_tree ) // 检查是否使用mb_tree{h->param.rc.f_pb_factor = 1;rc->qcompress = 1;}elserc->qcompress = h->param.rc.f_qcompress;rc->bitrate = h->param.rc.i_bitrate * (h->param.i_avcintra_class ? 1024. : 1000.);rc->rate_tolerance = h->param.rc.f_rate_tolerance;rc->nmb = h->mb.i_mb_count;rc->last_non_b_pict_type = -1;rc->cbr_decay = 1.0;if( h->param.rc.i_rc_method != X264_RC_ABR && h->param.rc.b_stat_read ){x264_log( h, X264_LOG_ERROR, "CRF/CQP is incompatible with 2pass.\n" );return -1;}// ----- 1.码控模块的重新配置 ----- //x264_ratecontrol_init_reconfigurable( h, 1 );if( h->param.i_nal_hrd ){uint64_t denom = (uint64_t)h->sps->vui.hrd.i_bit_rate_unscaled * h->sps->vui.i_time_scale;uint64_t num = 90000;x264_reduce_fraction64( &num, &denom );rc->hrd_multiply_denom = 90000 / num;double bits_required = log2( num )+ log2( h->sps->vui.i_time_scale )+ log2( h->sps->vui.hrd.i_cpb_size_unscaled );if( bits_required >= 63 ){x264_log( h, X264_LOG_ERROR, "HRD with very large timescale and bufsize not supported\n" );return -1;}}if( rc->rate_tolerance < 0.01 ){x264_log( h, X264_LOG_WARNING, "bitrate tolerance too small, using .01\n" );rc->rate_tolerance = 0.01;}h->mb.b_variable_qp = rc->b_vbv || h->param.rc.i_aq_mode;// ---- 2.ABR模式参数的配置 ----- //if( rc->b_abr ){/* FIXME ABR_INIT_QP is actually used only in CRF */
#define ABR_INIT_QP (( h->param.rc.i_rc_method == X264_RC_CRF ? h->param.rc.f_rf_constant : 24 ) + QP_BD_OFFSET)rc->accum_p_norm = .01;rc->accum_p_qp = ABR_INIT_QP * rc->accum_p_norm;/* estimated ratio that produces a reasonable QP for the first I-frame */rc->cplxr_sum = .01 * pow( 7.0e5, rc->qcompress ) * pow( h->mb.i_mb_count, 0.5 );rc->wanted_bits_window = 1.0 * rc->bitrate / rc->fps;rc->last_non_b_pict_type = SLICE_TYPE_I;}// ----- 3.初始化一些码控参数 ----- //rc->ip_offset = 6.0 * log2f( h->param.rc.f_ip_factor );rc->pb_offset = 6.0 * log2f( h->param.rc.f_pb_factor );rc->qp_constant[SLICE_TYPE_P] = h->param.rc.i_qp_constant;rc->qp_constant[SLICE_TYPE_I] = x264_clip3( h->param.rc.i_qp_constant - rc->ip_offset + 0.5, 0, QP_MAX );rc->qp_constant[SLICE_TYPE_B] = x264_clip3( h->param.rc.i_qp_constant + rc->pb_offset + 0.5, 0, QP_MAX );h->mb.ip_offset = rc->ip_offset + 0.5;rc->lstep = pow( 2, h->param.rc.i_qp_step / 6.0 );rc->last_qscale = qp2qscale( 26 + QP_BD_OFFSET );int num_preds = h->param.b_sliced_threads * h->param.i_threads + 1;CHECKED_MALLOC( rc->pred, 5 * sizeof(predictor_t) * num_preds );CHECKED_MALLOC( rc->pred_b_from_p, sizeof(predictor_t) );static const float pred_coeff_table[3] = { 1.0, 1.0, 1.5 };for( int i = 0; i < 3; i++ ){rc->last_qscale_for[i] = qp2qscale( ABR_INIT_QP );rc->lmin[i] = qp2qscale( h->param.rc.i_qp_min );rc->lmax[i] = qp2qscale( h->param.rc.i_qp_max );for( int j = 0; j < num_preds; j++ ){rc->pred[i+j*5].coeff_min = pred_coeff_table[i] / 2;rc->pred[i+j*5].coeff = pred_coeff_table[i];rc->pred[i+j*5].count = 1.0;rc->pred[i+j*5].decay = 0.5;rc->pred[i+j*5].offset = 0.0;}for( int j = 0; j < 2; j++ ){rc->row_preds[i][j].coeff_min = .25 / 4;rc->row_preds[i][j].coeff = .25;rc->row_preds[i][j].count = 1.0;rc->row_preds[i][j].decay = 0.5;rc->row_preds[i][j].offset = 0.0;}}rc->pred_b_from_p->coeff_min = 0.5 / 2;rc->pred_b_from_p->coeff = 0.5;rc->pred_b_from_p->count = 1.0;rc->pred_b_from_p->decay = 0.5;rc->pred_b_from_p->offset = 0.0;if( parse_zones( h ) < 0 ){x264_log( h, X264_LOG_ERROR, "failed to parse zones\n" );return -1;}// ---- 4.读取状态文件并且初始化2pass算法 ----- ///* Load stat file and init 2pass algo */if( h->param.rc.b_stat_read ){char *p, *stats_in, *stats_buf;/* read 1st pass stats */assert( h->param.rc.psz_stat_in );stats_buf = stats_in = x264_slurp_file( h->param.rc.psz_stat_in );if( !stats_buf ){x264_log( h, X264_LOG_ERROR, "ratecontrol_init: can't open stats file\n" );return -1;}if( h->param.rc.b_mb_tree ){char *mbtree_stats_in = strcat_filename( h->param.rc.psz_stat_in, ".mbtree" );if( !mbtree_stats_in )return -1;rc->p_mbtree_stat_file_in = x264_fopen( mbtree_stats_in, "rb" );x264_free( mbtree_stats_in );if( !rc->p_mbtree_stat_file_in ){x264_log( h, X264_LOG_ERROR, "ratecontrol_init: can't open mbtree stats file\n" );return -1;}}/* check whether 1st pass options were compatible with current options */if( strncmp( stats_buf, "#options:", 9 ) ){x264_log( h, X264_LOG_ERROR, "options list in stats file not valid\n" );return -1;}float res_factor, res_factor_bits;{int i, j;uint32_t k, l;char *opts = stats_buf;stats_in = strchr( stats_buf, '\n' );if( !stats_in )return -1;*stats_in = '\0';stats_in++;if( sscanf( opts, "#options: %dx%d", &i, &j ) != 2 ){x264_log( h, X264_LOG_ERROR, "resolution specified in stats file not valid\n" );return -1;}else if( h->param.rc.b_mb_tree ){rc->mbtree.srcdim[0] = i;rc->mbtree.srcdim[1] = j;}res_factor = (float)h->param.i_width * h->param.i_height / (i*j);/* Change in bits relative to resolution isn't quite linear on typical sources,* so we'll at least try to roughly approximate this effect. */res_factor_bits = powf( res_factor, 0.7 );if( !( p = strstr( opts, "timebase=" ) ) || sscanf( p, "timebase=%u/%u", &k, &l ) != 2 ){x264_log( h, X264_LOG_ERROR, "timebase specified in stats file not valid\n" );return -1;}if( k != h->param.i_timebase_num || l != h->param.i_timebase_den ){x264_log( h, X264_LOG_ERROR, "timebase mismatch with 1st pass (%u/%u vs %u/%u)\n",h->param.i_timebase_num, h->param.i_timebase_den, k, l );return -1;}CMP_OPT_FIRST_PASS( "bitdepth", BIT_DEPTH );CMP_OPT_FIRST_PASS( "weightp", X264_MAX( 0, h->param.analyse.i_weighted_pred ) );CMP_OPT_FIRST_PASS( "bframes", h->param.i_bframe );CMP_OPT_FIRST_PASS( "b_pyramid", h->param.i_bframe_pyramid );CMP_OPT_FIRST_PASS( "intra_refresh", h->param.b_intra_refresh );CMP_OPT_FIRST_PASS( "open_gop", h->param.b_open_gop );CMP_OPT_FIRST_PASS( "bluray_compat", h->param.b_bluray_compat );CMP_OPT_FIRST_PASS( "mbtree", h->param.rc.b_mb_tree );if( (p = strstr( opts, "interlaced=" )) ){char *current = h->param.b_interlaced ? h->param.b_tff ? "tff" : "bff" : h->param.b_fake_interlaced ? "fake" : "0";char buf[5];sscanf( p, "interlaced=%4s", buf );if( strcmp( current, buf ) ){x264_log( h, X264_LOG_ERROR, "different interlaced setting than first pass (%s vs %s)\n", current, buf );return -1;}}if( (p = strstr( opts, "keyint=" )) ){p += 7;char buf[13] = "infinite ";if( h->param.i_keyint_max != X264_KEYINT_MAX_INFINITE )sprintf( buf, "%d ", h->param.i_keyint_max );if( strncmp( p, buf, strlen(buf) ) ){x264_log( h, X264_LOG_ERROR, "different keyint setting than first pass (%.*s vs %.*s)\n",strlen(buf)-1, buf, strcspn(p, " "), p );return -1;}}if( strstr( opts, "qp=0" ) && h->param.rc.i_rc_method == X264_RC_ABR )x264_log( h, X264_LOG_WARNING, "1st pass was lossless, bitrate prediction will be inaccurate\n" );if( !strstr( opts, "direct=3" ) && h->param.analyse.i_direct_mv_pred == X264_DIRECT_PRED_AUTO ){x264_log( h, X264_LOG_WARNING, "direct=auto not used on the first pass\n" );h->mb.b_direct_auto_write = 1;}if( ( p = strstr( opts, "b_adapt=" ) ) && sscanf( p, "b_adapt=%d", &i ) && i >= X264_B_ADAPT_NONE && i <= X264_B_ADAPT_TRELLIS )h->param.i_bframe_adaptive = i;else if( h->param.i_bframe ){x264_log( h, X264_LOG_ERROR, "b_adapt method specified in stats file not valid\n" );return -1;}if( (h->param.rc.b_mb_tree || h->param.rc.i_vbv_buffer_size) && ( p = strstr( opts, "rc_lookahead=" ) ) && sscanf( p, "rc_lookahead=%d", &i ) )h->param.rc.i_lookahead = i;}/* find number of pics */p = stats_in;int num_entries;for( num_entries = -1; p; num_entries++ )p = strchr( p + 1, ';' );if( !num_entries ){x264_log( h, X264_LOG_ERROR, "empty stats file\n" );return -1;}rc->num_entries = num_entries;if( h->param.i_frame_total < rc->num_entries && h->param.i_frame_total > 0 ){x264_log( h, X264_LOG_WARNING, "2nd pass has fewer frames than 1st pass (%d vs %d)\n",h->param.i_frame_total, rc->num_entries );}if( h->param.i_frame_total > rc->num_entries ){x264_log( h, X264_LOG_ERROR, "2nd pass has more frames than 1st pass (%d vs %d)\n",h->param.i_frame_total, rc->num_entries );return -1;}CHECKED_MALLOCZERO( rc->entry, rc->num_entries * sizeof(ratecontrol_entry_t) );CHECKED_MALLOC( rc->entry_out, rc->num_entries * sizeof(ratecontrol_entry_t*) );/* init all to skipped p frames */for( int i = 0; i < rc->num_entries; i++ ){ratecontrol_entry_t *rce = &rc->entry[i];rce->pict_type = SLICE_TYPE_P;rce->qscale = rce->new_qscale = qp2qscale( 20 + QP_BD_OFFSET );rce->misc_bits = rc->nmb + 10;rce->new_qp = 0;rc->entry_out[i] = rce;}/* read stats */p = stats_in;double total_qp_aq = 0;for( int i = 0; i < rc->num_entries; i++ ){ratecontrol_entry_t *rce;int frame_number = 0;int frame_out_number = 0;char pict_type = 0;int e;char *next;float qp_rc, qp_aq;int ref;next= strchr(p, ';');if( next )*next++ = 0; //sscanf is unbelievably slow on long stringse = sscanf( p, " in:%d out:%d ", &frame_number, &frame_out_number );if( frame_number < 0 || frame_number >= rc->num_entries ){x264_log( h, X264_LOG_ERROR, "bad frame number (%d) at stats line %d\n", frame_number, i );return -1;}if( frame_out_number < 0 || frame_out_number >= rc->num_entries ){x264_log( h, X264_LOG_ERROR, "bad frame output number (%d) at stats line %d\n", frame_out_number, i );return -1;}rce = &rc->entry[frame_number];rc->entry_out[frame_out_number] = rce;rce->direct_mode = 0;e += sscanf( p, " in:%*d out:%*d type:%c dur:%"SCNd64" cpbdur:%"SCNd64" q:%f aq:%f tex:%d mv:%d misc:%d imb:%d pmb:%d smb:%d d:%c",&pict_type, &rce->i_duration, &rce->i_cpb_duration, &qp_rc, &qp_aq, &rce->tex_bits,&rce->mv_bits, &rce->misc_bits, &rce->i_count, &rce->p_count,&rce->s_count, &rce->direct_mode );rce->tex_bits  *= res_factor_bits;rce->mv_bits   *= res_factor_bits;rce->misc_bits *= res_factor_bits;rce->i_count   *= res_factor;rce->p_count   *= res_factor;rce->s_count   *= res_factor;p = strstr( p, "ref:" );if( !p )goto parse_error;p += 4;for( ref = 0; ref < 16; ref++ ){if( sscanf( p, " %d", &rce->refcount[ref] ) != 1 )break;p = strchr( p+1, ' ' );if( !p )goto parse_error;}rce->refs = ref;/* find weights */rce->i_weight_denom[0] = rce->i_weight_denom[1] = -1;char *w = strchr( p, 'w' );if( w ){int count = sscanf( w, "w:%hd,%hd,%hd,%hd,%hd,%hd,%hd,%hd",&rce->i_weight_denom[0], &rce->weight[0][0], &rce->weight[0][1],&rce->i_weight_denom[1], &rce->weight[1][0], &rce->weight[1][1],&rce->weight[2][0], &rce->weight[2][1] );if( count == 3 )rce->i_weight_denom[1] = -1;else if( count != 8 )rce->i_weight_denom[0] = rce->i_weight_denom[1] = -1;}if( pict_type != 'b' )rce->kept_as_ref = 1;switch( pict_type ){case 'I':rce->frame_type = X264_TYPE_IDR;rce->pict_type  = SLICE_TYPE_I;break;case 'i':rce->frame_type = X264_TYPE_I;rce->pict_type  = SLICE_TYPE_I;break;case 'P':rce->frame_type = X264_TYPE_P;rce->pict_type  = SLICE_TYPE_P;break;case 'B':rce->frame_type = X264_TYPE_BREF;rce->pict_type  = SLICE_TYPE_B;break;case 'b':rce->frame_type = X264_TYPE_B;rce->pict_type  = SLICE_TYPE_B;break;default:  e = -1; break;}if( e < 14 ){
parse_error:x264_log( h, X264_LOG_ERROR, "statistics are damaged at line %d, parser out=%d\n", i, e );return -1;}rce->qscale = qp2qscale( qp_rc );total_qp_aq += qp_aq;p = next;}if( !h->param.b_stitchable )h->pps->i_pic_init_qp = SPEC_QP( (int)(total_qp_aq / rc->num_entries + 0.5) );x264_free( stats_buf );if( h->param.rc.i_rc_method == X264_RC_ABR ){if( init_pass2( h ) < 0 )return -1;} /* else we're using constant quant, so no need to run the bitrate allocation */}// ----- 5.打开输出文件 ----- ///* Open output file *//* If input and output files are the same, output to a temp file* and move it to the real name only when it's complete */if( h->param.rc.b_stat_write ){char *p;rc->psz_stat_file_tmpname = strcat_filename( h->param.rc.psz_stat_out, ".temp" );if( !rc->psz_stat_file_tmpname )return -1;rc->p_stat_file_out = x264_fopen( rc->psz_stat_file_tmpname, "wb" );if( rc->p_stat_file_out == NULL ){x264_log( h, X264_LOG_ERROR, "ratecontrol_init: can't open stats file\n" );return -1;}p = x264_param2string( &h->param, 1 );if( p )fprintf( rc->p_stat_file_out, "#options: %s\n", p );x264_free( p );if( h->param.rc.b_mb_tree && !h->param.rc.b_stat_read ){rc->psz_mbtree_stat_file_tmpname = strcat_filename( h->param.rc.psz_stat_out, ".mbtree.temp" );rc->psz_mbtree_stat_file_name = strcat_filename( h->param.rc.psz_stat_out, ".mbtree" );if( !rc->psz_mbtree_stat_file_tmpname || !rc->psz_mbtree_stat_file_name )return -1;rc->p_mbtree_stat_file_out = x264_fopen( rc->psz_mbtree_stat_file_tmpname, "wb" );if( rc->p_mbtree_stat_file_out == NULL ){x264_log( h, X264_LOG_ERROR, "ratecontrol_init: can't open mbtree stats file\n" );return -1;}}}if( h->param.rc.b_mb_tree && (h->param.rc.b_stat_read || h->param.rc.b_stat_write) ){if( !h->param.rc.b_stat_read ){rc->mbtree.srcdim[0] = h->param.i_width;rc->mbtree.srcdim[1] = h->param.i_height;}if( macroblock_tree_rescale_init( h, rc ) < 0 )return -1;}for( int i = 0; i<h->param.i_threads; i++ ){h->thread[i]->rc = rc+i;if( i ){rc[i] = rc[0];h->thread[i]->param = h->param;h->thread[i]->mb.b_variable_qp = h->mb.b_variable_qp;h->thread[i]->mb.ip_offset = h->mb.ip_offset;}}return 0;
fail:return -1;
}

2.1.1 码控模块的重新配置(x264_ratecontrol_init_reconfigurable)

该函数进行码控模块的重新配置,定义位于ratecontrol.c中,主要的工作流程如下:

  1. 如果使用了crf模式,计算rate_factor_constant因子
  2. 如果使用了vbv机制,进行vbv参数的初始化
void x264_ratecontrol_init_reconfigurable( x264_t *h, int b_init )
{x264_ratecontrol_t *rc = h->rc;if( !b_init && rc->b_2pass )return;// ----- 1.如果使用了crf模式,计算rate_factor_constant因子 ----- //if( h->param.rc.i_rc_method == X264_RC_CRF ){/* Arbitrary rescaling to make CRF somewhat similar to QP.* Try to compensate for MB-tree's effects as well. */double base_cplx = h->mb.i_mb_count * (h->param.i_bframe ? 120 : 80);// mb_tree默认为1,f_qcompress默认为0.6double mbtree_offset = h->param.rc.b_mb_tree ? (1.0-h->param.rc.f_qcompress)*13.5 : 0;// #define QP_BD_OFFSET (6*(BIT_DEPTH-8)),如果使用的是8bit,则为0rc->rate_factor_constant = pow( base_cplx, 1 - rc->qcompress )/ qp2qscale( h->param.rc.f_rf_constant + mbtree_offset + QP_BD_OFFSET );}// ----- 2.如果使用了vbv机制,进行vbv参数的初始化 ----- //// VBV用于控制码率上下限,防止编码过程中出现上溢或者下溢的情况if( h->param.rc.i_vbv_max_bitrate > 0 && h->param.rc.i_vbv_buffer_size > 0 ){/* We don't support changing the ABR bitrate right now,so if the stream starts as CBR, keep it CBR. *//*b_vbv_min_rate表示1pass ABR模式下,设置的水池最大的输入码率小于目标码率rc->b_vbv_min_rate = !rc->b_2pass                                        // 不是2pass&& h->param.rc.i_rc_method == X264_RC_ABR                  // 使用ABR模式&& h->param.rc.i_vbv_max_bitrate <= h->param.rc.i_bitrate; // 最大的码率小于目标码率如果设置了vbv最小的码率,则将输入的码率赋值给vbv最大的码率,此时变成了CBR模式*/if( rc->b_vbv_min_rate )h->param.rc.i_vbv_max_bitrate = h->param.rc.i_bitrate;// vbv_buffer_size为整个水池的容量,如果小于输入帧的大小,则将容量配置为输入帧的大小if( h->param.rc.i_vbv_buffer_size < (int)(h->param.rc.i_vbv_max_bitrate / rc->fps) ){h->param.rc.i_vbv_buffer_size = h->param.rc.i_vbv_max_bitrate / rc->fps;x264_log( h, X264_LOG_WARNING, "VBV buffer size cannot be smaller than one frame, using %d kbit\n",h->param.rc.i_vbv_buffer_size );}// 转换为kiloint kilobit_size = h->param.i_avcintra_class ? 1024 : 1000;int vbv_buffer_size = h->param.rc.i_vbv_buffer_size * kilobit_size;	// 整个水池的总容量int vbv_max_bitrate = h->param.rc.i_vbv_max_bitrate * kilobit_size;	// 水池最大输入码率// 初始化HRD信息,HRD常用于蓝光视频、电视广播及其它特殊领域(没有研究)/* Init HRD */if( h->param.i_nal_hrd && b_init ){h->sps->vui.hrd.i_cpb_cnt = 1;h->sps->vui.hrd.b_cbr_hrd = h->param.i_nal_hrd == X264_NAL_HRD_CBR;h->sps->vui.hrd.i_time_offset_length = 0;#define BR_SHIFT  6#define CPB_SHIFT 4// normalize HRD size and rate to the value / scale notationh->sps->vui.hrd.i_bit_rate_scale = x264_clip3( x264_ctz( vbv_max_bitrate ) - BR_SHIFT, 0, 15 );h->sps->vui.hrd.i_bit_rate_value = vbv_max_bitrate >> ( h->sps->vui.hrd.i_bit_rate_scale + BR_SHIFT );h->sps->vui.hrd.i_bit_rate_unscaled = h->sps->vui.hrd.i_bit_rate_value << ( h->sps->vui.hrd.i_bit_rate_scale + BR_SHIFT );h->sps->vui.hrd.i_cpb_size_scale = x264_clip3( x264_ctz( vbv_buffer_size ) - CPB_SHIFT, 0, 15 );h->sps->vui.hrd.i_cpb_size_value = vbv_buffer_size >> ( h->sps->vui.hrd.i_cpb_size_scale + CPB_SHIFT );h->sps->vui.hrd.i_cpb_size_unscaled = h->sps->vui.hrd.i_cpb_size_value << ( h->sps->vui.hrd.i_cpb_size_scale + CPB_SHIFT );#undef CPB_SHIFT#undef BR_SHIFT// arbitrary#define MAX_DURATION 0.5int max_cpb_output_delay = X264_MIN( h->param.i_keyint_max * MAX_DURATION * h->sps->vui.i_time_scale / h->sps->vui.i_num_units_in_tick, INT_MAX );int max_dpb_output_delay = h->sps->vui.i_max_dec_frame_buffering * MAX_DURATION * h->sps->vui.i_time_scale / h->sps->vui.i_num_units_in_tick;int max_delay = (int)(90000.0 * (double)h->sps->vui.hrd.i_cpb_size_unscaled / h->sps->vui.hrd.i_bit_rate_unscaled + 0.5);h->sps->vui.hrd.i_initial_cpb_removal_delay_length = 2 + x264_clip3( 32 - x264_clz( max_delay ), 4, 22 );h->sps->vui.hrd.i_cpb_removal_delay_length = x264_clip3( 32 - x264_clz( max_cpb_output_delay ), 4, 31 );h->sps->vui.hrd.i_dpb_output_delay_length  = x264_clip3( 32 - x264_clz( max_dpb_output_delay ), 4, 31 );#undef MAX_DURATIONvbv_buffer_size = h->sps->vui.hrd.i_cpb_size_unscaled;vbv_max_bitrate = h->sps->vui.hrd.i_bit_rate_unscaled;}else if( h->param.i_nal_hrd && !b_init ){x264_log( h, X264_LOG_WARNING, "VBV parameters cannot be changed when NAL HRD is in use\n" );return;}h->sps->vui.hrd.i_bit_rate_unscaled = vbv_max_bitrate;h->sps->vui.hrd.i_cpb_size_unscaled = vbv_buffer_size;if( rc->b_vbv_min_rate ) // 将输入码率转换成为kb/src->bitrate = (double)h->param.rc.i_bitrate * kilobit_size;rc->buffer_rate = vbv_max_bitrate / rc->fps;	// 水池最大的输入水量rc->vbv_max_rate = vbv_max_bitrate;				// 水池最大的输入速率rc->buffer_size = vbv_buffer_size;				// 水池最大的容量// 输入水量 * 1.1 > 最大容量,说明水池比较小,最多容纳一帧的数据量// single_frame_vbv在后面会被用于调整qscalerc->single_frame_vbv = rc->buffer_rate * 1.1 > rc->buffer_size;	// if( rc->b_abr && h->param.rc.i_rc_method == X264_RC_ABR )rc->cbr_decay = 1.0 - rc->buffer_rate / rc->buffer_size* 0.5 * X264_MAX(0, 1.5 - rc->buffer_rate * rc->fps / rc->bitrate);// 这里表明vbv和crf模式可以共存if( h->param.rc.i_rc_method == X264_RC_CRF && h->param.rc.f_rf_constant_max ){// 如果存在rf最大值,则计算一个增量rc->rate_factor_max_increment = h->param.rc.f_rf_constant_max - h->param.rc.f_rf_constant;if( rc->rate_factor_max_increment <= 0 ){x264_log( h, X264_LOG_WARNING, "CRF max must be greater than CRF\n" );rc->rate_factor_max_increment = 0;}}if( b_init ) {if( h->param.rc.f_vbv_buffer_init > 1. )h->param.rc.f_vbv_buffer_init = x264_clip3f( h->param.rc.f_vbv_buffer_init / h->param.rc.i_vbv_buffer_size, 0, 1 );h->param.rc.f_vbv_buffer_init = x264_clip3f( X264_MAX( h->param.rc.f_vbv_buffer_init, rc->buffer_rate / rc->buffer_size ), 0, 1);// buffer_fill_final表示水池中当前时刻剩余的水量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;rc->b_vbv = 1; // 启用vbvrc->b_vbv_min_rate = !rc->b_2pass&& h->param.rc.i_rc_method == X264_RC_ABR&& h->param.rc.i_vbv_max_bitrate <= h->param.rc.i_bitrate;}}
}

2.2 帧级别码率控制

开始执行帧级别的码率控制,先计算AQ和mbtree的相关信息,参考【x264】码率控制模块的简单分析—宏块级码控工具Mbtree和AQ,随后根据具体模式进行码控调整

2.2.1 计算一帧的AQ信息(x264_adaptive_quant_frame)

该函数是AQ工具实现的具体函数,通过区分不同的模式,为mb计算qp_adj,即qp的偏移量。主要工作流程为:

  1. 如果不使用自适应量化(AQ模式),aq_mode为X264_AQ_NONE或者aq_strength为0,则考虑为了使用Mbtree进行qp_offset和inv_qscale_factor的初始化
  2. 如果使用AQ,则需要计算qp_adj, strength等信息
    (1)如果使用AUTOVARIANCE或者是AUTOVARIANCE_BIASED,则要考虑调整qp和strength,具体地做法是计算每一个mb应该调整的qp量,随后取均值,然后计算avg_adj和strength。如果使用VARIANCE,则仅考虑strength
    (2)根据不同的AQ模式对应的计算公式,来计算qp_adj

2.2.2 计算一帧的mbtree信息(x264_lookahead_get_frames —> … —> macroblock_tree)

mbtree是一种宏块级别的码率控制工具,根据帧间预测相关信息,来有效的降低编码的码率。mbtree的工作原理可以简单描述为,在lookahead当中通过前向预测,来获得当前mb的重要程度,重要程度由被参考的情况来衡量。如果被参考的权值很高,说明这个mb很重要,此时这个mb会按照较低的qp编码,否则按照较高的qp编码。这个权值的衡量由当前mb的intra_cost、inter_cost和前面mb赋予当前mb的传播cost决定

在mbtree中,主要的计算过程位于macroblock_tree中,函数位于slicetype.c中,函数主要的工作流程如下:

  1. 如果当前帧为intra帧,则直接计算cost
  2. 为propagate_cost赋初始值
  3. 循环处理每一帧,计算cost
  4. 计算当前帧的帧内和帧间cost(slicetype_frame_cost)
  5. 计算宏块的传播cost(macroblock_tree_propagate),之后根据帧内、帧间cost和宏块传播cost来计算qp_offset(macroblock_tree_finish),从而调整当前mb的qp

2.2.3 码控模块启动(x264_ratecontrol_start)

该函数的主要功能是,在编码一帧之前选择一个合适的qp。函数的定义位于ratecontrol.c,主要的工作流程为:

  1. 状态读取,应该是2pass相关
  2. 初始化vbv模式相关参数
  3. 获取abr模式下的qp(rate_estimate_qscale)
  4. 获取2pass模式下的qp(rate_estimate_qscale)
  5. 计算CQP模式下的qp
  6. 更新qp
/* Before encoding a frame, choose a QP for it */
void x264_ratecontrol_start( x264_t *h, int i_force_qp, int overhead )
{x264_ratecontrol_t *rc = h->rc;ratecontrol_entry_t *rce = NULL;x264_zone_t *zone = get_zone( h, h->fenc->i_frame );float q;x264_emms();// ----- 1.状态读取,应该与2pass相关 ----- // if( h->param.rc.b_stat_read ){int frame = h->fenc->i_frame;assert( frame >= 0 && frame < rc->num_entries );rce = rc->rce = &rc->entry[frame];if( h->sh.i_type == SLICE_TYPE_B&& h->param.analyse.i_direct_mv_pred == X264_DIRECT_PRED_AUTO ){h->sh.b_direct_spatial_mv_pred = ( rce->direct_mode == 's' );h->mb.b_direct_auto_read = ( rce->direct_mode == 's' || rce->direct_mode == 't' );}}// ----- 2.初始化vbv模式相关参数 ----- //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;// 临时更新所有正在编码过程中的帧的VBV信息update_vbv_plan( h, overhead );const x264_level_t *l = x264_levels;while( l->level_idc != 0 && l->level_idc != h->param.i_level_idc )l++;int mincr = l->mincr;if( h->param.b_bluray_compat )mincr = 4;/* Profiles above High don't require minCR, so just set the maximum to a large value. */if( h->sps->i_profile_idc > PROFILE_HIGH )rc->frame_size_maximum = 1e9;else{/* The spec has a bizarre special case for the first frame. */if( h->i_frame == 0 ){//384 * ( Max( PicSizeInMbs, fR * MaxMBPS ) + MaxMBPS * ( tr( 0 ) - tr,n( 0 ) ) ) / MinCRdouble fr = 1. / (h->param.i_level_idc >= 60 ? 300 : 172);int pic_size_in_mbs = h->mb.i_mb_width * h->mb.i_mb_height;rc->frame_size_maximum = 384 * BIT_DEPTH * X264_MAX( pic_size_in_mbs, fr*l->mbps ) / mincr;}else{//384 * MaxMBPS * ( tr( n ) - tr( n - 1 ) ) / MinCRrc->frame_size_maximum = 384 * BIT_DEPTH * ((double)h->fenc->i_cpb_duration * h->sps->vui.i_num_units_in_tick / h->sps->vui.i_time_scale) * l->mbps / mincr;}}}if( h->sh.i_type != SLICE_TYPE_B )rc->bframes = h->fenc->i_bframes;// ----- 3.获取abr模式下的qp ----- //if( rc->b_abr ){q = qscale2qp( rate_estimate_qscale( h ) );}else if( rc->b_2pass ){ // ----- 4.获取2pass模式下的qp ----- //rce->new_qscale = rate_estimate_qscale( h );q = qscale2qp( rce->new_qscale );}else /* CQP */{ // ----- 5.获取cqp模式下的qp ----- //// 如果是b帧,则取均值if( h->sh.i_type == SLICE_TYPE_B && h->fdec->b_kept_as_ref )q = ( rc->qp_constant[ SLICE_TYPE_B ] + rc->qp_constant[ SLICE_TYPE_P ] ) / 2;elseq = rc->qp_constant[ h->sh.i_type ];if( zone ){if( zone->b_force_qp )q += zone->i_qp - rc->qp_constant[SLICE_TYPE_P];elseq -= 6*log2f( zone->f_bitrate_factor );}}if( i_force_qp != X264_QP_AUTO )q = i_force_qp - 1;q = x264_clip3f( q, h->param.rc.i_qp_min, h->param.rc.i_qp_max );rc->qpa_rc = rc->qpa_rc_prev =rc->qpa_aq = rc->qpa_aq_prev = 0;h->fdec->f_qp_avg_rc =h->fdec->f_qp_avg_aq =rc->qpm = q;if( rce )rce->new_qp = q;// ------ 6.更新qp ----- //accum_p_qp_update( h, rc->qpm );if( h->sh.i_type != SLICE_TYPE_B )rc->last_non_b_pict_type = h->sh.i_type;
}

2.2.3.1 估计qscale(rate_estimate_qscale)

该函数的功能是根据过去已经使用的比特数来更新qscale,主要工作流程为:

  1. 计算总比特数
  2. 检查B帧(没有研究过)
  3. 检查非B帧
    (3.1)检查2pass模式(没有研究过)
    (3.2)使用1pass的ABR(CRF和ABR都会进入这个分支)
      (3.2.1)计算行级SATD(x264_rc_analyse_slice)
      (3.2.2)利用SATD计算当前帧的图像模糊度blurred_complexity
      (3.2.3)根据CRF和ABR模式来计算qscale(get_qscale),如果是ABR模式还可能会用到VBV限制(vbv_pass1)
// update qscale for 1 frame based on actual bits used so far
static float rate_estimate_qscale( x264_t *h )
{float q;x264_ratecontrol_t *rcc = h->rc;ratecontrol_entry_t rce = {0};int pict_type = h->sh.i_type;// ----- 1.计算总比特数 ----- //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;if( rcc->b_2pass ){rce = *rcc->rce;if( pict_type != rce.pict_type ){x264_log( h, X264_LOG_ERROR, "slice=%c but 2pass stats say %c\n",slice_type_to_char[pict_type], slice_type_to_char[rce.pict_type] );}}// ----- 2.检查B帧 ----- //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 )q = (q0 + q1) / 2 + rcc->ip_offset;else if( i0 )q = q1;else if( i1 )q = q0;elseq = (q0*dt1 + q1*dt0) / (dt0 + dt1);if( h->fenc->b_kept_as_ref )q += rcc->pb_offset/2;elseq += rcc->pb_offset;rcc->qp_novbv = q;q = qp2qscale( q );if( rcc->b_2pass )rcc->frame_size_planned = qscale2bits( &rce, q );elsercc->frame_size_planned = predict_size( rcc->pred_b_from_p, q, h->fref[1][h->i_ref[1]-1]->i_satd );/* Apply MinCR and buffer fill restrictions */if( rcc->b_vbv ){double frame_size_maximum = X264_MIN( rcc->frame_size_maximum, X264_MAX( rcc->buffer_fill, 0.001 ) );if( rcc->frame_size_planned > frame_size_maximum ){q *= rcc->frame_size_planned / frame_size_maximum;rcc->frame_size_planned = frame_size_maximum;}}rcc->frame_size_estimated = rcc->frame_size_planned;/* For row SATDs */if( rcc->b_vbv )rcc->last_satd = x264_rc_analyse_slice( h );return q;}else{ // ----- 3.检查非B帧 ----- //// 计算abr模式的bufferdouble abr_buffer = 2 * rcc->rate_tolerance * rcc->bitrate;// 将总bit作为预测的bitdouble 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 )continue;bits = X264_MAX(bits, t->rc->frame_size_estimated);predicted_bits += bits;}}// 如果是2passif( rcc->b_2pass ){double lmin = rcc->lmin[pict_type];double lmax = rcc->lmax[pict_type];double diff;/* Adjust ABR buffer based on distance to the end of the video. */// 根据距离视频结尾的距离来调整ABR的bufferif( rcc->num_entries > h->i_frame ){double final_bits = rcc->entry_out[rcc->num_entries-1]->expected_bits;double video_pos = rce.expected_bits / final_bits;double scale_factor = sqrt( (1 - video_pos) * rcc->num_entries );abr_buffer *= 0.5 * X264_MAX( scale_factor, 0.5 );}// 预测比特数和期望比特数的差异diff = predicted_bits - rce.expected_bits;q = rce.new_qscale;q /= x264_clip3f((abr_buffer - diff) / abr_buffer, .5, 2);if( h->i_frame >= rcc->fps && rcc->expected_bits_sum >= 1 ){/* Adjust quant based on the difference between* achieved and expected bitrate so far */// 根据已经实现的和期望的bitrate的差异来调整量化参数double cur_time = (double)h->i_frame / rcc->num_entries;double w = x264_clip3f( cur_time*100, 0.0, 1.0 );q *= pow( (double)total_bits / rcc->expected_bits_sum, w );}rcc->qp_novbv = qscale2qp( q );// 如果使用了vbv模式,再进行调整if( rcc->b_vbv ){/* Do not overflow vbv */double expected_size = qscale2bits( &rce, q );double expected_vbv = rcc->buffer_fill + rcc->buffer_rate - expected_size;double expected_fullness = rce.expected_vbv / rcc->buffer_size;double qmax = q*(2 - expected_fullness);double size_constraint = 1 + expected_fullness;qmax = X264_MAX( qmax, rce.new_qscale );if( expected_fullness < .05 )qmax = lmax;qmax = X264_MIN(qmax, lmax);while( ((expected_vbv < rce.expected_vbv/size_constraint) && (q < qmax)) ||((expected_vbv < 0) && (q < lmax))){q *= 1.05;expected_size = qscale2bits(&rce, q);expected_vbv = rcc->buffer_fill + rcc->buffer_rate - expected_size;}rcc->last_satd = x264_rc_analyse_slice( h );}q = x264_clip3f( q, lmin, lmax );}else /* 1pass ABR */{ // 使用1pass的ABR/* Calculate the quantizer which would have produced the desired* average bitrate if it had been applied to all frames so far.* Then modulate that quant based on the current frame's complexity* relative to the average complexity so far (using the 2pass RCEQ).* Then bias the quant up or down if total size so far was far from* the target.* Result: Depending on the value of rate_tolerance, there is a* tradeoff between quality and bitrate precision. But at large* tolerances, the bit distribution approaches that of 2pass. */// 计算量化因子,如果该因子已经被用于之前的所有帧,则该因子能够生成期望的平均码率// 然后,根据当前帧的复杂度和之前所有帧的平均复杂度的比值来修正量化参数// 随后,如果总体大小远离目标大小,则将量化因子增大或者减小// 结果:根据码率容忍的误差值,在质量和码率控制精度之前有一个tradeoff;如果容忍度很大,则比特的分布接近2passdouble wanted_bits, overflow = 1;rcc->last_satd = x264_rc_analyse_slice( h ); // 计算当前帧的复杂度(按照一行计算)rcc->short_term_cplxsum *= 0.5;		// cplxsum为累积复杂度,由之前帧的cplx和当前帧的SATD组合而成rcc->short_term_cplxcount *= 0.5;	// cplxcount为统计帧数// cplxsum = cplxsum * 0.5 + SATDrcc->short_term_cplxsum += rcc->last_satd / (CLIP_DURATION(h->fenc->f_duration) / BASE_FRAME_DURATION);// cplxcount = cplxcount * 0.5 + 1rcc->short_term_cplxcount ++;rce.tex_bits = rcc->last_satd;// 计算模糊复杂度blurred_complexityrce.blurred_complexity = rcc->short_term_cplxsum / rcc->short_term_cplxcount;rce.mv_bits = 0;rce.p_count = rcc->nmb;rce.i_count = 0;rce.s_count = 0;rce.qscale = 1;rce.pict_type = pict_type;rce.i_duration = h->fenc->i_duration;// 使用CRF模式if( h->param.rc.i_rc_method == X264_RC_CRF ){q = get_qscale( h, &rce, rcc->rate_factor_constant, h->fenc->i_frame );}else{ // 使用ABR模式// wanted_bits_window是期望比特数,cplxr_sum是复杂度平均值// rc->wanted_bits_window = rc->bitrate / fps (初始值),表示目标编码比特q = get_qscale( h, &rce, rcc->wanted_bits_window / rcc->cplxr_sum, h->fenc->i_frame );/* ABR code can potentially be counterproductive in CBR, so just don't bother.* Don't run it if the frame complexity is zero either. */if( !rcc->b_vbv_min_rate && rcc->last_satd ){// FIXME is it simpler to keep track of wanted_bits in ratecontrol_end?int i_frame_done = h->i_frame;				// 已编码的帧数double time_done = i_frame_done / rcc->fps;	// 编码的时长if( h->param.b_vfr_input && i_frame_done > 0 )time_done = ((double)(h->fenc->i_reordered_pts - h->i_reordered_pts_delay)) * h->param.i_timebase_num / h->param.i_timebase_den;wanted_bits = time_done * rcc->bitrate;		// 计算已经编码的帧的bits大小if( wanted_bits > 0 ){abr_buffer *= X264_MAX( 1, sqrt( time_done ) );// 通过计算已编码比特数和预测编码比特数的差异来判断是否出现了上下溢,并使用这个比率来调整qscaleoverflow = x264_clip3f( 1.0 + (predicted_bits - wanted_bits) / abr_buffer, .5, 2 );q *= overflow;}}}if( pict_type == SLICE_TYPE_I && h->param.i_keyint_max > 1/* should test _next_ pict type, but that isn't decided yet */&& rcc->last_non_b_pict_type != SLICE_TYPE_I ){q = qp2qscale( rcc->accum_p_qp / rcc->accum_p_norm );q /= h->param.rc.f_ip_factor;}else if( h->i_frame > 0 ){if( h->param.rc.i_rc_method != X264_RC_CRF ){/* Asymmetric clipping, because symmetric would prevent* overflow control in areas of rapidly oscillating complexity */double lmin = rcc->last_qscale_for[pict_type] / rcc->lstep;double lmax = rcc->last_qscale_for[pict_type] * rcc->lstep;if( overflow > 1.1 && h->i_frame > 3 )lmax *= rcc->lstep;else if( overflow < 0.9 )lmin /= rcc->lstep;q = x264_clip3f(q, lmin, lmax);}}else if( h->param.rc.i_rc_method == X264_RC_CRF && rcc->qcompress != 1 ){q = qp2qscale( ABR_INIT_QP ) / h->param.rc.f_ip_factor;}rcc->qp_novbv = qscale2qp( q );q = vbv_pass1( h, pict_type, q );}rcc->last_qscale_for[pict_type] =rcc->last_qscale = q;if( !(rcc->b_2pass && !rcc->b_vbv) && h->fenc->i_frame == 0 )rcc->last_qscale_for[SLICE_TYPE_P] = q * h->param.rc.f_ip_factor;if( rcc->b_2pass )rcc->frame_size_planned = qscale2bits( &rce, q );elsercc->frame_size_planned = predict_size( &rcc->pred[h->sh.i_type], q, rcc->last_satd );/* Apply MinCR and buffer fill restrictions */if( rcc->b_vbv ){double frame_size_maximum = X264_MIN( rcc->frame_size_maximum, X264_MAX( rcc->buffer_fill, 0.001 ) );if( rcc->frame_size_planned > frame_size_maximum ){q *= rcc->frame_size_planned / frame_size_maximum;rcc->frame_size_planned = frame_size_maximum;}/* Always use up the whole VBV in this case. */if( rcc->single_frame_vbv )rcc->frame_size_planned = X264_MIN( rcc->buffer_rate, frame_size_maximum );}rcc->frame_size_estimated = rcc->frame_size_planned;return q;}
}

get_qscale的主要工作内容为根据rate_factor来调整qscale。如果使用crf模式,rate_factor为定值,如果使用abr模式,rate_factor由期望的比特数和复杂度的比值给出。在get_qscale的函数中,如果开启mbtree则使用时间信息来调整qscale,否则用图像模糊度先调整qscale,随后再除以rate_factor

/*** modify the bitrate curve from pass1 for one frame*/// 如果使用crf模式,rate_factor = rate_factor_constant 为定值// 如果使用abr模式,rate_factor = wanted_bits_window / cplxr_sum,由期望的比特数和复杂度的比值给出
static double get_qscale(x264_t *h, ratecontrol_entry_t *rce, double rate_factor, int frame_num)
{x264_ratecontrol_t *rcc= h->rc;x264_zone_t *zone = get_zone( h, frame_num );double q;// 调整qscale// 如果使用了mbtree(默认使用),则使用时间信息来调整qscaleif( h->param.rc.b_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 // 否则使用图像模糊度来调整qscaleq = pow( rce->blurred_complexity, 1 - rcc->qcompress );// avoid NaN's in the rc_eqif( !isfinite(q) || rce->tex_bits + rce->mv_bits == 0 )q = rcc->last_qscale_for[rce->pict_type];else{rcc->last_rceq = q;// 这里传递进来的rate_factor是wanted_bits_window/cplxr_sumq /= rate_factor; rcc->last_qscale = q;}// ...
}

在调整了qscale之后,加上vbv的限制,在vbv_pass1中实现,参考【x264】码率控制模块的简单分析—帧级码控策略(CQP、CRF、ABR)

  1. VBV参数的初始化(x264_ratecontrol_init_reconfigurable)
  2. 计算VBV的输入速率(update_vbv_plan)
  3. 添加VBV限制(vbv_pass1)
  4. 行级VBV控制(x264_ratecontrol_mb)

2.3 获取帧级qp(x264_ratecontrol_qp)

获取当前帧级别的初始qp

int x264_ratecontrol_qp( x264_t *h )
{x264_emms();return x264_clip3( h->rc->qpm + 0.5f, h->param.rc.i_qp_min, h->param.rc.i_qp_max );
}

2.4 获取当前mb的qp(x264_ratecontrol_mb_qp)

获取当前mb级别的qp,如果启用了aq,则对当前的qp进行调整。这里使用aq进行调整包括了aq和mbtree,因为mbtree开启时会强制开启aq,所以mbtree调整的qp也会在这里进行

int x264_ratecontrol_mb_qp( x264_t *h )
{x264_emms();float qp = h->rc->qpm;if( h->param.rc.i_aq_mode ){/* MB-tree currently doesn't adjust quantizers in unreferenced frames. */float qp_offset = h->fdec->b_kept_as_ref ? h->fenc->f_qp_offset[h->mb.i_mb_xy] : h->fenc->f_qp_offset_aq[h->mb.i_mb_xy];/* Scale AQ's effect towards zero in emergency mode. */if( qp > QP_MAX_SPEC )qp_offset *= (QP_MAX - qp) / (QP_MAX - QP_MAX_SPEC);qp += qp_offset;}return x264_clip3( qp + 0.5f, h->param.rc.i_qp_min, h->param.rc.i_qp_max );
}

2.5 x264_ratecontrol_mb

该函数执行了mb级别的码控,通过考虑前面已编码的信息来调整qscale,只对开启了VBV的mb有效。主要的工作流程为:

  1. 检查是否是这一行最后一个mb,不是则返回。VBV控制是一行一行进行的
  2. 检查是否使用vbv,不是则返回
  3. 更新预测器(update_predictor),这对于后续预测其他行消耗的比特数有指导作用
  4. 根据与预测大小的差异调整质量,使用的是重建帧
    计算当前及前面所有行已经使用了多少比特bits_so_fat
    (4.1) 如果当前行不是帧的最后一行
    (a)计算buffer在预期之外剩余的比特数量
    (b)预测当前帧可能会使用的总的比特数,当前及前面行已经使用的比特数量bits_sof_far + 后续行预测可能使用的比特数量(predict_row_size_to_end)
    (c)判断按照当前的编码qp继续编码时,后续使用的比特是否超出预期的比特,如果是则减小qpm
    (d)判断按照当前的编码qp继续编码时,后续使用的比特是否低于预期的比特,如果是则增大qpm
    (e)更新frame_size_estimated
    (f) 如果当前行在进行编码时出现了大的QP跳动,尝试重新编码
    (4.2) 如果当前行是帧的最后一行
    (a)更新frame_size_estimated
    (b)如果编码最后一行时,出现了VBV的下溢情况,重新尝试
/* TODO:*  eliminate all use of qp in row ratecontrol: make it entirely qscale-based.*  make this function stop being needlessly O(N^2)*  update more often than once per row? */
int x264_ratecontrol_mb( x264_t *h, int bits )
{x264_ratecontrol_t *rc = h->rc;const int y = h->mb.i_mb_y; // 第y行h->fdec->i_row_bits[y] += bits; // 将当前mb的bit加到当前行的总比特上rc->qpa_aq += h->mb.i_qp; // 将当前mb的qp加到自适应量化qp之后的均值qp上// ----- 1.检查是否是这一行最后一个mb,不是则返回 ------ //if( h->mb.i_mb_x != h->mb.i_mb_width - 1 )return 0;x264_emms();rc->qpa_rc += rc->qpm * h->mb.i_mb_width; // qpm是qscale_rate_estimate之后的qscale,加到qp ad之前的qp上// ----- 2.检查是否是这一行最后一个mb,不是则返回 ----- //if( !rc->b_vbv )return 0;float qscale = qp2qscale( rc->qpm );h->fdec->f_row_qp[y] = rc->qpm;h->fdec->f_row_qscale[y] = qscale; // 更新当前行的qscale,赋值到重建帧当中// ----- 3.更新预测器 ----- //update_predictor( &rc->row_pred[0], qscale, h->fdec->i_row_satd[y], h->fdec->i_row_bits[y] );if( h->sh.i_type != SLICE_TYPE_I && rc->qpm < h->fref[0][0]->f_row_qp[y] )update_predictor( &rc->row_pred[1], qscale, h->fdec->i_row_satds[0][0][y], h->fdec->i_row_bits[y] );/* update ratecontrol per-mbpair in MBAFF */// 如果mbaff, 并且为偶数行, 则直接返回if( SLICE_MBAFF && !(y&1) )return 0;/* FIXME: We don't currently support the case where there's a slice* boundary in between. */int can_reencode_row = h->sh.i_first_mb <= ((h->mb.i_mb_y - SLICE_MBAFF) * h->mb.i_mb_stride);/* tweak quality based on difference from predicted size */// ----- 4.根据与预测大小的差异调整质量,使用的是重建帧 ----- //float prev_row_qp = h->fdec->f_row_qp[y];float qp_absolute_max = h->param.rc.i_qp_max;if( rc->rate_factor_max_increment )qp_absolute_max = X264_MIN( qp_absolute_max, rc->qp_novbv + rc->rate_factor_max_increment );float qp_max = X264_MIN( prev_row_qp + h->param.rc.i_qp_step, qp_absolute_max );float qp_min = X264_MAX( prev_row_qp - h->param.rc.i_qp_step, h->param.rc.i_qp_min );float step_size = 0.5f;float slice_size_planned = h->param.b_sliced_threads ? rc->slice_size_planned : rc->frame_size_planned;float bits_so_far = row_bits_so_far( h, y ); // 第y行及之前已经使用了多少比特数rc->bits_so_far = bits_so_far;float max_frame_error = x264_clip3f( 1.0 / h->mb.i_mb_height, 0.05, 0.25 );float max_frame_size = rc->frame_size_maximum - rc->frame_size_maximum * max_frame_error;max_frame_size = X264_MIN( max_frame_size, rc->buffer_fill - rc->buffer_rate * max_frame_error );float size_of_other_slices = 0;if( h->param.b_sliced_threads ) // 多slice并行编码,因为在实际的使用中slice一般就是一帧,所以应该是没有到帧的结尾{float bits_so_far_of_other_slices = 0;for( int i = 0; i < h->param.i_threads; i++ )if( h != h->thread[i] ){size_of_other_slices += h->thread[i]->rc->frame_size_estimated;bits_so_far_of_other_slices += h->thread[i]->rc->bits_so_far;}float weight = x264_clip3f( (bits_so_far_of_other_slices + rc->frame_size_estimated) / (size_of_other_slices + rc->frame_size_estimated), 0.0, 1.0 );float frame_size_planned = rc->frame_size_planned - rc->frame_size_planned * max_frame_error;float size_of_other_slices_planned = X264_MIN( frame_size_planned, max_frame_size ) - rc->slice_size_planned;size_of_other_slices_planned = X264_MAX( size_of_other_slices_planned, bits_so_far_of_other_slices );size_of_other_slices = (size_of_other_slices - size_of_other_slices_planned) * weight + size_of_other_slices_planned;}if( y < h->i_threadslice_end-1 ) // 还没有到slice(frame)的最后一行{/* B-frames shouldn't use lower QP than their reference frames. */if( h->sh.i_type == SLICE_TYPE_B ){qp_min = X264_MAX( qp_min, X264_MAX( h->fref[0][0]->f_row_qp[y+1], h->fref[1][0]->f_row_qp[y+1] ) );rc->qpm = X264_MAX( rc->qpm, qp_min );}// 计算buffer在预期之外剩余的比特数量// rc->buffer_fill是预期的buffer大小,rc->frame_size_planned是预期的帧的大小float buffer_left_planned = rc->buffer_fill - rc->frame_size_planned;buffer_left_planned = X264_MAX( buffer_left_planned, 0.f );/* More threads means we have to be more cautious in letting ratecontrol use up extra bits. */// 预期之中剩余的buffer,分配给每个线程的rate_tolerance,说明还能容忍多少的比特float rc_tol = buffer_left_planned / h->param.i_threads * rc->rate_tolerance;// b1表示的是当前编码帧可能会使用的总的比特数// bits_so_far表示之前所有行已经使用的多少比特数// predict_row_size_to_end表示剩余所有行还需要的比特数// 因为编码时一般不使用多slice,则size_of_other_slices为0,因为不会有其他的slicefloat b1 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;// 置信系数,使用之前已经使用的比特数除以预计使用的比特数,表示已经使用的比特数的比例float trust_coeff = x264_clip3f( bits_so_far / slice_size_planned, 0.0, 1.0 );/* Don't increase the row QPs until a sufficient amount of the bits of the frame have been processed, in case a flat *//* area at the top of the frame was measured inaccurately. */// 在处理了足够数量的帧的比特数之前,不要增加行qp,以防帧顶部的平坦区域测量不准确// 如果现在使用的比特数还不足5%,则使用前一行的qp作为最大的qp// 从赋值关系来看,prev_row_qp最初来自于rc->qpm,即当前mb所使用的mb// 这里表示后续的mb的qp都不会超过当前mb使用的qp,即后续的mb会以更高质量的编码if( trust_coeff < 0.05f )qp_max = qp_absolute_max = prev_row_qp;// 如果不是Intra帧,则将rc的容忍度减半,即P帧或者B帧不能有太大的波动if( h->sh.i_type != SLICE_TYPE_I )rc_tol *= 0.5f;if( !rc->b_vbv_min_rate )qp_min = X264_MAX( qp_min, rc->qp_novbv );// 当前mb使用的qp小于qp_max,并且当前已经使用的比特偏大,分三种情况:// (1)大于预期的frame size + 可容忍的比特增加数// (2)大于预期的frame_size,并且当前mb的qp小于不使用vbv的qp(qpm比qp_novbv小,后续编码可能会消耗更多比特)// (3)大于预期的buffer_size - 剩余buffer的一半(感觉配置比较经验,为什么要乘以0.5倍而不是0.7倍?)// 总体来说,这里是对可能出现的上溢(使用的比特超出了预期的比特)进行判断// 如果可能出现上溢,则将qpm增大,降低预期的比特消耗,并且更新b1,直到b1符合预期while( rc->qpm < qp_max&& ((b1 > rc->frame_size_planned + rc_tol) ||(b1 > rc->frame_size_planned && rc->qpm < rc->qp_novbv) ||(b1 > rc->buffer_fill - buffer_left_planned * 0.5f)) ){rc->qpm += step_size;b1 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;}// 最大的比特数float b_max = b1 + ((rc->buffer_fill - rc->buffer_size + rc->buffer_rate) * 0.90f - b1) * trust_coeff;rc->qpm -= step_size;// 将qpm减去一个step_size之后,预期消耗的比特数b2float b2 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;// 如果qpm大于qp_min并且小于前一行mb的qp,需要三种情况都满足:// (1)当前mb大于一行的qp,或者会执行单帧的vbv// (2)b2小于最大的帧大小// (3)b2小于预期的帧大小的0.8倍,或者b2小于最大比特数// 如果满足,则将当前mb的qp降低,增大预期的比特消耗,并且更新b2,直到满足要求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;b2 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;}rc->qpm += step_size;/* avoid VBV underflow or MinCR violation */// 避免VBV下溢或者是MinCR超出限制while( rc->qpm < qp_absolute_max && (b1 > max_frame_size) ) // qp小于最大的qp,但是预期消耗的总比特数大于帧最大的比特数{rc->qpm += step_size;b1 = bits_so_far + predict_row_size_to_end( h, y, rc->qpm ) + size_of_other_slices;}// b1是预测的当前帧可能会消耗的总比特数// 如果不使用多slice,则size_of_other_slices为0,此时frame_size_estimated = b1rc->frame_size_estimated = b1 - size_of_other_slices;/* If the current row was large enough to cause a large QP jump, try re-encoding it. */// 如果当前行在进行编码时出现了大的QP跳动,尝试重新编码if( rc->qpm > qp_max && prev_row_qp < qp_max && can_reencode_row ){/* Bump QP to halfway in between... close enough. */rc->qpm = x264_clip3f( (prev_row_qp + rc->qpm)*0.5f, prev_row_qp + 1.0f, qp_max );rc->qpa_rc = rc->qpa_rc_prev;rc->qpa_aq = rc->qpa_aq_prev;h->fdec->i_row_bits[y] = 0;h->fdec->i_row_bits[y-SLICE_MBAFF] = 0;return -1;}}else{ // 如果编码的是最后一行rc->frame_size_estimated = bits_so_far;/* Last-ditch attempt: if the last row of the frame underflowed the VBV,* try again. */// 如果编码最后一行时,出现了VBV的下溢情况,重新尝试if( rc->qpm < qp_max && can_reencode_row&& (bits_so_far + size_of_other_slices > X264_MIN( rc->frame_size_maximum, rc->buffer_fill )) ){rc->qpm = qp_max;rc->qpa_rc = rc->qpa_rc_prev;rc->qpa_aq = rc->qpa_aq_prev;h->fdec->i_row_bits[y] = 0;h->fdec->i_row_bits[y-SLICE_MBAFF] = 0;return -1;}}rc->qpa_rc_prev = rc->qpa_rc;rc->qpa_aq_prev = rc->qpa_aq;return 0;
}

3.小结

本文记录了x264当中码控的比特分配部分的实现,涉及到帧级码率控制(CQP、CRF和ABR),mb级别码率控制(Mbtree和AQ),但不涉及到一些参数配置和调整,同时,帧级码率控制和mb级别码率控制分别在其他文中记录。如果从整体来看待码率控制思想以及具体实现的方式,码控的核心理念是在码率和质量之间做权衡,衡量的中间桥梁是视频复杂度,有时以码率为主,有时以质量为先,并且根据视频复杂度来调整具体的参数。在码控模块当中,许多参数是经验性设置的,能够适配通用场景,但无法适配特定场景,可以根据实际应用需求来修改。这种针对于实际应用场景的自适应调整将会更加复杂

在码率控制中,存在一个“蛋鸡悖论”问题。在视频编码发展早期,使用MAD衡量图像复杂度,如果要得到编码的残差信息MAD,需要根据qp进行模式选择和运动估计,但是qp应该在确定残差MAD之后,结合预先设定的r来确定,这种互相依赖的关系产生了矛盾,解决的方法之一是设定一个初始qp。但是,R-Q模型还存在一个问题,即一个rate可能对应多个qp,所以这个模型有待改进。在x264中,使用了r- λ \lambda λ模型,形成了单独的对应关系,也是后续265编码持续使用的模型,只是265当中的参数有所不同

CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen

这篇关于【x264】码率控制模块的简单分析—编码主流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1039816

相关文章

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

回调的简单理解

之前一直不太明白回调的用法,现在简单的理解下 就按这张slidingmenu来说,主界面为Activity界面,而旁边的菜单为fragment界面。1.现在通过主界面的slidingmenu按钮来点开旁边的菜单功能并且选中”区县“选项(到这里就可以理解为A类调用B类里面的c方法)。2.通过触发“区县”的选项使得主界面跳转到“区县”相关的新闻列表界面中(到这里就可以理解为B类调用A类中的d方法

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用

自制的浏览器主页,可以是最简单的桌面应用,可以把它当成备忘录桌面应用。如果你看不懂,请留言。 完整代码: <!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><ti

iptables(7)扩展模块state

简介         前面文章我们已经介绍了一些扩展模块,如iprange、string、time、connlimit、limit,还有扩展匹配条件如--tcp-flags、icmp。这篇文章我们介绍state扩展模块  state          在 iptables 的上下文中,--state 选项并不是直接关联于一个扩展模块,而是与 iptables 的 state 匹配机制相关,特

python实现最简单循环神经网络(RNNs)

Recurrent Neural Networks(RNNs) 的模型: 上图中红色部分是输入向量。文本、单词、数据都是输入,在网络里都以向量的形式进行表示。 绿色部分是隐藏向量。是加工处理过程。 蓝色部分是输出向量。 python代码表示如下: rnn = RNN()y = rnn.step(x) # x为输入向量,y为输出向量 RNNs神经网络由神经元组成, python

高度内卷下,企业如何通过VOC(客户之声)做好竞争分析?

VOC,即客户之声,是一种通过收集和分析客户反馈、需求和期望,来洞察市场趋势和竞争对手动态的方法。在高度内卷的市场环境下,VOC不仅能够帮助企业了解客户的真实需求,还能为企业提供宝贵的竞争情报,助力企业在竞争中占据有利地位。 那么,企业该如何通过VOC(客户之声)做好竞争分析呢?深圳天行健企业管理咨询公司解析如下: 首先,要建立完善的VOC收集机制。这包括通过线上渠道(如社交媒体、官网留言

Java研学-RBAC权限控制(八)

九 登录登出 1 登录作用   判断员工是否有权限访问,首先得知道现在操作的人是谁,所以必须先实现登录功能 2 登录流程   ① 提供登录页面,可输入用户名与密码信息,并添加执行登录的按钮。(登录页面不能被拦截)   ② 给按钮绑定点击事件(异步操作,POST请求)   ③ 事件中发送登录请求,使用 AJAX 方式提交。(使用 AJAX 原因:用户体验更好,既可保留用户刚输入的用户名和密码

python 在pycharm下能导入外面的模块,到terminal下就不能导入

项目结构如下,在ic2ctw.py 中导入util,在pycharm下不报错,但是到terminal下运行报错  File "deal_data/ic2ctw.py", line 3, in <module>     import util 解决方案: 暂时方案:在终端下:export PYTHONPATH=/Users/fujingling/PycharmProjects/PSENe

[FPGA][基础模块]跨时钟域传播脉冲信号

clk_a 周期为10ns clk_b 周期为34ns 代码: module pulse(input clk_a,input clk_b,input signal_a,output reg signal_b);reg [4:0] signal_a_widen_maker = 0;reg signal_a_widen;always @(posedge clk_a)if(signal_a)