【x264】码率控制模块的简单分析—帧级码控策略(CQP、CRF、ABR)
参考:
【x264编码器】章节3——x264的码率控制
X264码率控制二(vbv码率控制)
x264 vbv-maxrate与vbv-bufsize对码率控制
参数分析:
【x264】x264编码器参数配置
流程分析:
【x264】x264编码主流程简单分析
【x264】编码核心函数(x264_encoder_encode)的简单分析
【x264】分析模块(analyse)的简单分析—帧内预测
【x264】分析模块(analyse)的简单分析—帧间预测
【x264】码率控制模块的简单分析—宏块级码控工具Mbtree和AQ
1.码率控制模式
在x264当中,默认定义了三种帧级别码控方法,分别是CQP、CRF和ABR
#define X264_RC_CQP 0 // Constant Quanzation Parameter, CQP
#define X264_RC_CRF 1 // Constant Ratefactor, CRF
#define X264_RC_ABR 2 // Average Bitrate, ABR
这三者的含义分别是:
- X264_RC_CQP:恒定量化参数(Constant Quanzation Parameter,CQP)
- X264_RC_CRF:恒定量化因子(Constant Ratefactor,CRF)
- X264_RC_ABR:平均码率(Average Bitrate,ABR)
这三种模式是最基本的模式,其余模式如CBR、2pass是基于这三种模式衍生获得的。在进行码率控制之前,有一个普遍的认识或假设:
b
i
t
s
∗
q
s
c
a
l
e
∝
c
o
m
p
l
e
x
i
t
y
bits * qscale ∝ complexity
bits∗qscale∝complexity
其中bits为编码比特数,qscale为量化参数,complexity为编码复杂度。如果qscale恒定,编码内容越复杂,所需要的bit就越多;如果bit恒定,编码内容越复杂,qscale越高。后续各种编码模式都是基于这个假设进行设计的,这种正比例的关系可以通过大量实验的拟合确定对应的参数
2.恒定量化参数(Constant Quantization Parameter, CQP)
该模式下,会使用固定的qp参数,因为qp和qstep具有唯一对应关系,所以qstep也会固定,而qstep直接影响了量化过程。此时,如果视频场景发生了较大变化,由于qscale是固定的,所以瞬时的码率也会有较大的波动,一般不会使用该模式。264标准中,qp范围为[0, 51]。qstep影响量化过程的方式为:
Z
i
j
=
r
o
u
n
d
(
Y
i
j
Q
s
t
e
p
)
Z_{ij}=round(\frac{Y_{ij}}{Q_{step}})
Zij=round(QstepYij)
其中,
Z
i
j
Z_{ij}
Zij表示的是输出的量化系数,
Y
i
j
Y_{ij}
Yij表示的是待量化的转换系数矩阵,
Q
_
s
t
e
p
Q\_step
Q_step是量化步长。如果qp增大,则qstep也会增大, 此时输出的量化系数会偏小,则量化精度偏低
2.1 CQP初测
cqp模式的配置位于base.c文件中的x264_param_parse函数,如果设置了qp参数,则会配置为cqp模式,并且将输入的qp传递下去
OPT2("qp", "qp_constant")
{
p->rc.i_qp_constant = atoi(value);
p->rc.i_rc_method = X264_RC_CQP;
}
设置编码参数 --qp 32,测试序列分辨率为1920x1200,帧数100,其余使用默认参数,测试结果为
yuv [info]: 1920x1200p 0:0 @ 25/1 fps (cfr)
x264 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
x264 [info]: profile High, level 5.0, 4:2:0, 8-bit
x264 [info]: frame I:7 Avg QP:29.00 size:126289
x264 [info]: frame P:39 Avg QP:32.00 size: 64496
x264 [info]: frame B:54 Avg QP:33.67 size: 29591
x264 [info]: consecutive B-frames: 19.0% 18.0% 27.0% 36.0%
x264 [info]: mb I I16..4: 28.2% 50.9% 21.0%
x264 [info]: mb P I16..4: 16.8% 22.1% 10.0% P16..4: 17.8% 10.7% 4.6% 0.0% 0.0% skip:17.9%
x264 [info]: mb B I16..4: 3.4% 3.8% 1.4% B16..8: 35.5% 10.6% 2.3% direct: 2.9% skip:40.0% L0:43.7% L1:49.6% BI: 6.8%
x264 [info]: 8x8 transform intra:46.3% inter:66.0%
x264 [info]: coded y,uvDC,uvAC intra: 49.3% 23.5% 3.6% inter: 21.2% 5.0% 0.0%
x264 [info]: i16 v,h,dc,p: 60% 24% 10% 6%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 28% 18% 22% 5% 5% 4% 6% 4% 7%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 41% 18% 12% 4% 5% 5% 6% 4% 5%
x264 [info]: i8c dc,h,v,p: 78% 12% 10% 1%
x264 [info]: Weighted P-Frames: Y:33.3% UV:17.9%
x264 [info]: ref P L0: 67.1% 18.4% 10.6% 3.1% 0.8%
x264 [info]: ref B L0: 94.1% 4.7% 1.2%
x264 [info]: ref B L1: 98.6% 1.4%
x264 [info]: kb/s:9994.51
encoded 100 frames, 10.88 fps, 9994.51 kb/s
可以看出编码的p帧qp固定为32,intra帧和b帧的qp有出入,其中intra帧的qp较小,而b帧的qp略大
2.2 CQP的实现
cqp的调控位于encoder.c中,函数为validate_parameters,其中p帧的qp为外部输入的qp,intra帧的qp需要减去一个intra帧传递到p帧的一个传递因子系数,b帧的qp需要减去一个p帧到b帧的传递因子系数。如果设置的p帧的qp为32,则根据计算公式可以得出intra帧的qp为i_qp=32-6log2(1.4)=29.087439,b_qp=32+6log2(1.3)= 34.271069,与真实编码qp接近,但有出入,可能是后续做了如clip或者四舍五入等操作带来的误差
static int validate_parameters( x264_t *h, int b_open )
{
// ...
if( h->param.rc.i_rc_method == X264_RC_CQP )
{
float qp_p = h->param.rc.i_qp_constant; // 将输入的qp设置为P帧编码qp
float qp_i = qp_p - 6*log2f( h->param.rc.f_ip_factor ); // 减去一个i帧到p帧的传递因子,默认值为1.4
float qp_b = qp_p + 6*log2f( h->param.rc.f_pb_factor ); // 减去一个p帧到b帧的传递因子,默认值为1.3
if( qp_p < 0 )
{
x264_log( h, X264_LOG_ERROR, "qp not specified\n" );
return -1;
}
h->param.rc.i_qp_min = x264_clip3( (int)(X264_MIN3( qp_p, qp_i, qp_b )), 0, QP_MAX );
h->param.rc.i_qp_max = x264_clip3( (int)(X264_MAX3( qp_p, qp_i, qp_b ) + .999), 0, QP_MAX );
h->param.rc.i_aq_mode = 0;
h->param.rc.b_mb_tree = 0;
h->param.rc.i_bitrate = 0;
}
// ...
}
2.3 CQP存在的问题
cqp模式通过设定一个固定的qp实现编码,这种模式完全不考虑场景的复杂度变化,也没有考虑到码流大小,因此这种模式在实际应用中几乎不存在。如果是进行一些算法的测试,可以先通过设置cqp模式,通过设置一个固定的qp来获得一个目标码率,然后以这个目标码率作为基准,去进行其他模式的测试。例如设定qp为27,获得一个目标码率target_bitrate,然后将target_bitrate作为目标码率,来衡量qp为27条件下码控算法的控制精度
3.恒定质量因子(Constant Ratefactor, CRF)
crf是x264默认的码控模式,会使用固定的质量因子crf而不是固定qp。crf模式关注的是编码输出的质量保持恒定,不关注输出的编码码率,该模式下的码率只与视频纹理复杂度有关系。对于复杂度较高的区域,crf会提高编码qp,减小码流,对于复杂度低的区域,crf会降低qp,提高编码质量,这样能够保持整体视频质量比较稳定。但是,crf一个比较明显的缺点是无法精确的控制输出文件的大小,因此不推荐应用于流媒体视频传输
3.1 CRF初测
crf模式的配置由参数crf确定,如果配置了crf参数,则会将码控模式设置为CRF。配置仍然位于base.c中的x264_param_parse函数
OPT("crf")
{
p->rc.f_rf_constant = atof(value);
p->rc.i_rc_method = X264_RC_CRF;
}
配置时,设置–crf 32
yuv [info]: 1920x1200p 0:0 @ 25/1 fps (cfr)
x264 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
x264 [info]: profile High, level 5.0, 4:2:0, 8-bit
x264 [info]: frame I:8 Avg QP:32.96 size: 58744
x264 [info]: frame P:39 Avg QP:36.09 size: 29211
x264 [info]: frame B:53 Avg QP:37.07 size: 14952
x264 [info]: consecutive B-frames: 19.0% 22.0% 27.0% 32.0%
x264 [info]: mb I I16..4: 26.5% 56.9% 16.6%
x264 [info]: mb P I16..4: 12.8% 25.3% 3.9% P16..4: 24.0% 7.0% 2.5% 0.0% 0.0% skip:24.5%
x264 [info]: mb B I16..4: 2.8% 3.5% 0.4% B16..8: 39.0% 5.1% 0.5% direct: 1.8% skip:46.9% L0:48.3% L1:50.2% BI: 1.5%
x264 [info]: 8x8 transform intra:58.2% inter:81.3%
x264 [info]: coded y,uvDC,uvAC intra: 35.7% 15.9% 0.9% inter: 11.5% 2.7% 0.0%
x264 [info]: i16 v,h,dc,p: 59% 27% 7% 8%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 30% 18% 29% 4% 4% 3% 5% 3% 5%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 37% 22% 15% 5% 5% 4% 6% 3% 4%
x264 [info]: i8c dc,h,v,p: 85% 8% 6% 0%
x264 [info]: Weighted P-Frames: Y:23.1% UV:12.8%
x264 [info]: ref P L0: 72.2% 13.8% 9.5% 3.7% 0.7%
x264 [info]: ref B L0: 92.5% 6.0% 1.5%
x264 [info]: ref B L1: 98.5% 1.5%
x264 [info]: kb/s:4803.32
encoded 100 frames, 13.21 fps, 4803.32 kb/s
3.2 CRF的实现
在crf的实现过程中,使用x264_ratecontrol_init_reconfigurable来计算rate_factor_constant,依据的内容包括mb的数量,是否使用b帧,是否使用mbtree以及qcompress。因为涉及的参数在编码过程中不会变化,所以rate_factor_constant计算之后是一个定值
void x264_ratecontrol_init_reconfigurable( x264_t *h, int b_init )
{
// ...
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.6
double 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,则为0
rc->rate_factor_constant = pow( base_cplx, 1 - rc->qcompress )
/ qp2qscale( h->param.rc.f_rf_constant + mbtree_offset + QP_BD_OFFSET );
}
// ...
}
rate_factor_constant的计算公式为:
r
a
t
e
_
f
a
c
t
o
r
_
c
o
n
s
t
a
n
t
=
p
o
w
(
b
a
s
e
_
c
p
l
x
,
1
−
q
c
o
m
p
r
e
s
s
)
q
p
2
q
s
c
a
l
e
(
r
f
_
c
o
n
s
t
a
n
t
+
m
b
t
r
e
e
_
o
f
f
s
e
t
+
Q
P
_
B
D
_
O
F
F
S
E
T
)
rate\_factor\_constant = \frac{pow(base\_cplx, 1 - qcompress)}{qp2qscale(rf\_constant + mbtree\_offset + QP\_BD\_OFFSET)}
rate_factor_constant=qp2qscale(rf_constant+mbtree_offset+QP_BD_OFFSET)pow(base_cplx,1−qcompress)
rate_factor_constant的使用在get_qscale中,输入的rate_factor会被用于调整qscale
/**
* modify the bitrate curve from pass1 for one frame
*/
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;
/*
对于crf模式而言
(1)如果使用mbtree,则基于duration对qscale进行调整;此时,crf模式与纹理复杂度的关联由mbtree考量
mbtree会考虑前后帧的mb的重要程度,将纹理复杂度考虑在了其中
(2)如果不使用mbtree,则在这里使用纹理复杂度对qscale进行调整
*/
if( 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
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; // 如果使用crf模式,这里的rate_factor就是rate_factor_constant
rcc->last_qscale = q;
}
// ...
return q;
}
这里涉及到了3个概念,分别是mbtree、qcompress和rf_constant:
3.2.1 mbtree
宏块树mbtree是一种宏块级别的码率控制工具,能有效的降低编码的码率。mbtree的工作原理可以简单描述为,在lookahead当中通过前向预测,来获得当前mb的重要程度,重要程度由被参考的情况来衡量。如果被参考的权值很高,说明这个mb很重要,此时这个mb会按照较低的qp编码,否则按照较高的qp编码。这个权值的衡量由当前mb的intra_cost、inter_cost和前面mb赋予当前mb的传播cost决定。mbtree记录在 【x264】码率控制模块的简单分析—宏块级码控工具Mbtree和AQ
3.2.2 qcompress
qcompress在x264的介绍为
H2( " --qcomp <float> QP curve compression [%.2f]\n", defaults->rc.f_qcompress );
这是一个控制压缩质量的参数,用于调控宏块的量化步长,qcompress越高则视频质量越低,码率越小
3.2.3 rf_constant
在x264中,rf_constant是crf模式下调控qscale的一个参数,它是一个浮点数,其取值范围一般在0.0到51.0之间。rf_constant的值越小,编码的质量越好。rf_constant=18被认为是视觉上无损的,默认配置为23,可以外部配置。若Crf值加6,输出码率大概减少一半;若Crf值减6,输出码率翻倍。通常是在保证可接受视频质量的前提下选择一个最大的crf值,如果输出视频质量很好,可以尝试增大rf的值,否则就尝试减小rf的值
3.3 CRF小结
在crf模式当中,确定了一个固定的质量因子rate_factor_constant,保证编码的视频质量,但是由于编码的内容有波动,所以无法保证编码的码率。在确定了rate_factor之后,crf与视频复杂度的关联与mbtree有关,如果使用mbtree,则利用mbtree来考量complexity并更新qscale,否则利用blurred_complexity来更新qscale。对于一些对输出码率没有要求的应用场景,crf能够很好的保证输出质量的稳定性
4.平均码率(Average Bitrate, ABR)
平均码率abr通过给定一个码率的平均值,牺牲一定的编码质量来提供比crf更稳定的码率控制,让码率尽量维持在所设定的值附近。但是,由于编码器不知道未来的编码内容是什么,需要去猜测如何达到这个平均码率,瞬时码率会随着场景复杂度而变化,这种波动尽管会受到目标码率的约束,但有可能在短时间内引起编码质量的波动。我的理解是,尽管质量和码率的变化呈一定的相关性,但不是完全匹配(可能码率提升不足,或者降低太多),这种不匹配程度来源于编码器不知道未来的编码信息,进而导致质量波动。因此,在实际使用时,为了实现精准的码控,常使用VBV(Video Buffer Verifier,视频缓冲校验器)进行辅助
4.1 ABR和VBV初测
x264中,默认的码控模式为crf,当且仅当配置了bitrate,会使用abr模式;如果配置了bitrate,还配置了vbv的参数,则会加上vbv的控制,这里只配置bitrate参数
OPT("bitrate")
{
p->rc.i_bitrate = atoi(value);
p->rc.i_rc_method = X264_RC_ABR;
}
延续前面使用CRF时的码率4803 kb/s,配置项为–bitrate 4803(两个小短线),测试结果为
yuv [info]: 1920x1200p 0:0 @ 25/1 fps (cfr)
x264 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
x264 [info]: profile High, level 5.0, 4:2:0, 8-bit
x264 [info]: frame I:8 Avg QP:30.66 size: 85319
x264 [info]: frame P:39 Avg QP:36.83 size: 26646
x264 [info]: frame B:53 Avg QP:37.22 size: 15218
x264 [info]: consecutive B-frames: 19.0% 22.0% 27.0% 32.0%
x264 [info]: mb I I16..4: 22.2% 60.4% 17.4%
x264 [info]: mb P I16..4: 12.1% 25.0% 3.2% P16..4: 25.0% 6.3% 2.2% 0.0% 0.0% skip:26.1%
x264 [info]: mb B I16..4: 2.7% 3.4% 0.4% B16..8: 39.5% 5.0% 0.5% direct: 1.8% skip:46.8% L0:50.0% L1:47.9% BI: 2.1%
x264 [info]: final ratefactor: 31.44
x264 [info]: 8x8 transform intra:60.4% inter:81.7%
x264 [info]: coded y,uvDC,uvAC intra: 37.8% 17.2% 1.2% inter: 11.3% 2.6% 0.0%
x264 [info]: i16 v,h,dc,p: 61% 25% 7% 8%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 29% 18% 29% 4% 4% 3% 5% 3% 5%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 36% 22% 15% 5% 5% 4% 6% 3% 4%
x264 [info]: i8c dc,h,v,p: 84% 9% 7% 0%
x264 [info]: Weighted P-Frames: Y:23.1% UV:12.8%
x264 [info]: ref P L0: 72.6% 12.9% 9.7% 4.0% 0.7%
x264 [info]: ref B L0: 92.4% 6.0% 1.6%
x264 [info]: ref B L1: 98.5% 1.5%
x264 [info]: kb/s:5056.61
encoded 100 frames, 13.37 fps, 5056.61 kb/s
实际编码码率和目标码率误差较大(200kb/s),猜测一个主要的原因是序列存在几个突然的场景变化,而abr模式在这种情况下码控误差较大。现在加上vbv的控制参数,–bitrate 4803 --vbv-maxrate 5000 --vbv-bufsize 2000,测试的结果为
x264 [info]: using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
x264 [info]: profile High, level 5.0, 4:2:0, 8-bit
x264 [info]: frame I:8 Avg QP:33.28 size: 56388
x264 [info]: frame P:39 Avg QP:36.01 size: 29846
x264 [info]: frame B:53 Avg QP:37.09 size: 14805
x264 [info]: consecutive B-frames: 19.0% 22.0% 27.0% 32.0%
x264 [info]: mb I I16..4: 26.5% 57.7% 15.7%
x264 [info]: mb P I16..4: 12.8% 25.6% 4.0% P16..4: 23.7% 6.8% 2.5% 0.0% 0.0% skip:24.5%
x264 [info]: mb B I16..4: 2.9% 3.5% 0.4% B16..8: 39.0% 5.0% 0.5% direct: 1.7% skip:47.1% L0:47.4% L1:51.1% BI: 1.5%
x264 [info]: 8x8 transform intra:58.4% inter:80.9%
x264 [info]: coded y,uvDC,uvAC intra: 36.0% 16.2% 0.9% inter: 11.3% 2.8% 0.0%
x264 [info]: i16 v,h,dc,p: 60% 26% 7% 8%
x264 [info]: i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 30% 18% 28% 4% 4% 3% 5% 3% 5%
x264 [info]: i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 36% 22% 15% 5% 5% 4% 6% 3% 4%
x264 [info]: i8c dc,h,v,p: 85% 8% 7% 0%
x264 [info]: Weighted P-Frames: Y:23.1% UV:12.8%
x264 [info]: ref P L0: 72.8% 13.8% 9.0% 3.7% 0.7%
x264 [info]: ref B L0: 92.6% 5.7% 1.6%
x264 [info]: ref B L1: 98.5% 1.5%
x264 [info]: kb/s:4799.55
encoded 100 frames, 13.04 fps, 4799.55 kb/s
加上了vbv的控制之后,码率控制比较精准。这里vbv参数的选择参考x264 vbv-maxrate与vbv-bufsize对码率控制,但是经过测试发现也不是那么精准,例如选择–bitrate 4803 --vbv-maxrate 5000 --vbv-bufsize 6000时,bitrate误差反而增大了,这应该与序列有关,还是需要根据实际情况调整
4.2 ABR实现过程
ABR的实现流程为:
- 计算当前帧的SATD(x264_rc_analyse_slice)
- 利用SATD计算当前帧的图像模糊度blurred_complexity(rate_estimate_qscale)
- 根据图像模糊度计算qscale(get_qscale)
- qscale和qp互相转换(流程中可能会用到,即便不用也是码控的核心)(qp2qscale、qscale2qp)
- 根据qscale获取预测编码比特数(predict_size)
- 预测器参数更新(update_predictor)
4.2.1 计算当前帧的SATD
SATD是通过一行一行的方式进行计算的,调用函数x264_rc_analyse_slice实现
4.2.2 利用SATD计算当前帧的图像模糊度blurred_complexity
b
l
u
r
r
e
d
_
c
o
m
p
l
e
x
i
t
y
[
i
]
=
c
p
l
x
s
u
m
[
i
−
1
]
∗
0.5
+
S
A
T
D
[
i
]
c
p
l
x
c
o
u
n
t
[
i
−
1
]
∗
0.5
+
1
blurred\_complexity[i] = \frac{cplxsum[i-1] * 0.5 + SATD[i]}{cplxcount[i-1] * 0.5 + 1}
blurred_complexity[i]=cplxcount[i−1]∗0.5+1cplxsum[i−1]∗0.5+SATD[i]
即当前的模糊度由前面帧的模糊度和当前帧的SATD组合而来
上述流程的实现过程位于ratecontrol.c中,函数rate_estimate_qscale之下
static float rate_estimate_qscale( x264_t *h )
{
// ...
{ // 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;如果容忍度很大,则比特的分布接近2pass
double wanted_bits, overflow = 1;
// ----- 1.计算当前帧的复杂度satd,按照一行计算 ----- //
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 + SATD
rcc->short_term_cplxsum += rcc->last_satd / (CLIP_DURATION(h->fenc->f_duration) / BASE_FRAME_DURATION);
// cplxcount = cplxcount * 0.5 + 1
rcc->short_term_cplxcount ++;
rce.tex_bits = rcc->last_satd;
// ----- 2.计算blured_complexity ----- //
rce.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;
// ----- 3.根据图像模糊度计算qscale ----- //
// 有可能使用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模式
// 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 ) );
// 通过计算已编码比特数和预测编码比特数的差异来判断是否出现了上下溢,并使用这个比率来调整qscale
overflow = x264_clip3f( 1.0 + (predicted_bits - wanted_bits) / abr_buffer, .5, 2 );
q *= overflow;
}
}
// ...
}
4.2.3 根据图像模糊度计算qscale
在计算qscale时,会进行qscale的两次调整
第一次调整:
q
s
c
a
l
e
=
{
(
B
A
S
E
F
R
A
M
E
_
D
U
R
A
T
I
O
N
C
L
I
P
_
D
U
R
A
T
I
O
N
(
d
u
r
a
t
i
o
n
∗
t
i
m
e
s
c
a
l
e
)
)
1
−
q
c
o
m
p
r
e
s
s
,
如果开启
m
b
t
r
e
e
(
1
−
b
l
u
r
r
e
d
_
c
o
m
p
l
e
x
i
t
y
)
1
−
q
c
o
m
p
r
e
s
s
,
如果不开启
m
b
t
r
e
e
qscale= \left\{\begin{matrix} (\frac{BASEFRAME\_DURATION}{CLIP\_DURATION(duration * timescale)})^{1-qcompress}, 如果开启mbtree \\ (1-blurred\_complexity)^{1-qcompress}, 如果不开启mbtree \end{matrix}\right.
qscale={(CLIP_DURATION(duration∗timescale)BASEFRAME_DURATION)1−qcompress,如果开启mbtree(1−blurred_complexity)1−qcompress,如果不开启mbtree
第二次调整:
q
s
c
a
l
e
=
q
s
c
a
l
e
r
a
t
e
_
f
a
c
t
o
r
,
r
a
t
e
_
f
a
c
t
o
r
=
w
a
n
t
e
d
_
b
i
t
s
_
w
i
n
d
o
w
c
p
l
x
s
u
m
qscale = \frac{qscale}{rate\_factor},rate\_factor=\frac{wanted\_bits\_window}{cplxsum}
qscale=rate_factorqscale,rate_factor=cplxsumwanted_bits_window
wanted_bits_window默认值为
w
a
n
t
e
d
_
b
i
t
s
_
w
i
n
d
o
w
=
b
i
t
r
a
t
e
f
p
s
wanted\_bits\_window=\frac{bitrate}{fps}
wanted_bits_window=fpsbitrate
其中bitrate是由外部设置,wanted_bits_window是动态更新的
调用get_qscale对qscale进行调整的具体方式如下
/**
* modify the bitrate curve from pass1 for one frame
*/
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;
// ---- 4.调整qscale ---- //
// 如果使用了mbtree(默认使用),则使用时间信息来调整qscale
if( 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 // 否则使用图像模糊度来调整qscale
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;
// 这里传递进来的rate_factor是wanted_bits_window/cplxr_sum
q /= rate_factor;
rcc->last_qscale = q;
}
// ...
}
4.2.4 qscale和qp的互相转换
static inline float qscale2qp( float qscale )
{
return (12.0f + QP_BD_OFFSET) + 6.0f * log2f( qscale/0.85f );
}
反过来,qp也可以转换成qscale
static inline float qp2qscale( float qp )
{
return 0.85f * powf( 2.0f, ( qp - (12.0f + QP_BD_OFFSET) ) / 6.0f );
}
4.2.5 根据qscale获取预测编码比特数
这里就有一个疑惑,为什么不直接使用qp进行中间的计算而是使用qscale?我的理解是,在编码器中,qscale与编码的比特数有一个直接对应的关系,使用qscale能够更容易的根据视频内容的复杂度和目标比特率来调整编码参数,从而更有效地控制视频质量和文件大小。相反,如果使用qp的话,对应关系是非线性的,这样做中间计算会比较复杂,也容易产生较大的误差。根据qscale来预测使用的编码比特数的方式为
static float predict_size( predictor_t *p, float q, float var )
{
return (p->coeff*var + p->offset) / (q*p->count);
}
预测编码比特数是按照一行进行的,其中q为qscale,var是复杂度complex(通常用SATD描述),p为预测器,p->coeff,p->offset和p->count都是float类型。这里将这些系数做一些简化,上述过程可以表示为
p
r
e
d
_
s
i
z
e
=
c
∗
c
o
m
p
l
e
x
q
s
c
a
l
e
pred\_size = \frac{c * complex}{qscale}
pred_size=qscalec∗complex
即预测的比特数与纹理复杂度成正比,与qscale成反比,这里简化的系数c在编码过程中也是动态更新的
4.2.6 预测器参数更新
参数更新的函数为update_predictor,利用bits、qscale和satd(var变量)来更新coeff和offset,更新的方式为每行更新一次。其中,bits*qscale与complexity成正比,可以理解为是前面一行编码的复杂度,因此new_coeff的更新理解是前面一行编码复杂度和当前一行编码复杂度的比值,new_offset是前面一行的复杂度和当前行复杂度的偏移量。
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;
// new_coeff = (前一行复杂度 - old_offset) / 当前行复杂度(satd表示)
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 );
// new_offset = 前一行复杂度 - new_coeff * 当前行复杂度
float new_offset = bits*q - new_coeff_clipped * var;
if( new_offset >= 0 )
new_coeff = new_coeff_clipped;
else
new_offset = 0;
// 乘以一个衰减系数p->decay,默认为0.5
p->count *= p->decay;
p->coeff *= p->decay;
p->offset *= p->decay;
p->count ++;
p->coeff += new_coeff;
p->offset += new_offset;
}
4.3 视频缓冲校验(Video Buffer Verifier,VBV)
在前面的测试当中看到,使用ABR模式可能会带来码率控制不准确的情况,因此提出了VBV的控制思想。VBV是为了解决ABR模式控制码率不准确,视频质量波动较大的一项工具,通过为码率设定一个限制,使得码率波动在一定的范围之内,从而有效的控制码率,如果应用了VBV,则当前的编码模式可以理解为是CBR,即恒定码率(Const Bitrate)。这个限制的过程是一个Buffer,用于控制接收端缓存不出现上下溢的情况,实现了对视频短时码率的限制。
如果用一段话来描述VBV这一概念,我想可以这么来说:VBV是一种控制机制,它创建了一个缓冲池,无论输入的水流有多大,总能够保持池中的水处于一个相对稳定状态。如果输入的水流很大,则加大流出的水量,如果输入的水流很小,则减小流出的水量。这样操作的结果是,池中的水在一定范围内进行波动,输出的水量也在一定范围内波动,最终实现稳定的码率控制
4.4 VBV实现流程
VBV的实现流程为:
- VBV参数的初始化(x264_ratecontrol_init_reconfigurable)
- 计算VBV的输入速率(update_vbv_plan)
- 添加VBV限制(vbv_pass1)
- 行级VBV控制(x264_ratecontrol_mb)
4.4.1 VBV参数的初始化
VBV参数的初始化位于x264_ratecontrol_init_reconfigurable之中,代码为
void x264_ratecontrol_init_reconfigurable( x264_t *h, int b_init )
{
// ...
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 );
}
// 转换为kilo
int 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; // 水池最大输入码率
/* Init HRD */
// ...
if( rc->b_vbv_min_rate ) // 将输入码率转换成为kb/s
rc->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在后面会被用于调整qscale
rc->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; // 启用vbv
rc->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;
}
}
}
4.4.2 计算vbv的输入速率(update_vbv_plan)
函数在x264_ratecontrol_start中调用,先计算
/* Before encoding a frame, choose a QP for it */
void x264_ratecontrol_start( x264_t *h, int i_force_qp, int overhead )
{
// ...
if( rc->b_vbv ) // 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的主要功能是,根据目前正在进行的所有帧预测的比特数临时更新VBV,考虑了多线程问题
static void update_vbv_plan( x264_t *h, int overhead )
{
x264_ratecontrol_t *rcc = h->rc;
rcc->buffer_fill = h->thread[0]->rc->buffer_fill_final_min / h->sps->vui.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 )
continue;
bits = X264_MAX(bits, t->rc->frame_size_estimated);
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 = X264_MIN( rcc->buffer_fill, rcc->buffer_size );
rcc->buffer_fill -= overhead; // 减去头部的比特
}
4.4.3 添加VBV限制(vbv_pass1)
qscale的初值由get_qscale函数计算出,不开启VBV时,根据predicted_bits - wanted_bits调整qscale,开启VBV时,使用vbv_pass1,根据vbv buffer的状态调整qscale。vbv_pass1通过预测当前qscale下未来n帧的大小来估计vbv的状态,如果vbv buffer将overflow,则增大qscale;如果buffer将会underflow,则减小qscale
// apply VBV constraints
static double vbv_pass1( x264_t *h, int pict_type, double q )
{
x264_ratecontrol_t *rcc = h->rc;
/* B-frames are not directly subject to VBV,
* since they are controlled by the P-frames' QPs. */
// 检查是否进入vbv调整
if( rcc->b_vbv && rcc->last_satd > 0 )
{
double q0 = 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;
/* Lookahead VBV: raise the quantizer as necessary such that no frames in
* the lookahead overflow and such that the buffer is in a reasonable state
* by the end of the lookahead. */
// Lookahead VBV:根据需要提高量化器,这样在lookahead中不会有帧溢出,并且缓冲区在lookahead结束时处于合理的状态
if( h->param.rc.i_lookahead )
{
int terminate = 0;
/* Avoid an infinite loop. */
// 对于qscale的调整最多持续1000次,或者terminate=3(水位满足了需求)
for( int iterations = 0; iterations < 1000 && terminate != 3; iterations++ )
{
double frame_q[3];
double cur_bits = predict_size( &rcc->pred[h->sh.i_type], q, rcc->last_satd ); // 预估当前qscale会使用的比特数
double buffer_fill_cur = rcc->buffer_fill - cur_bits; // 当前水位 - 当前比特 = 出水之后剩余的水位
double target_fill;
double total_duration = 0;
double last_duration = fenc_cpb_duration;
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
/* Loop over the planned future frames. */
// 预估未来帧对于vbv的消耗
for( int j = 0; buffer_fill_cur >= 0 && buffer_fill_cur <= rcc->buffer_size; j++ )
{
total_duration += last_duration;
buffer_fill_cur += rcc->vbv_max_rate * last_duration; // rate * time = bits,加水
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 )
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 ); // 预测lookahead中帧所消耗的比特数
buffer_fill_cur -= cur_bits; // 减去lookahead中每帧所消耗的比特数
last_duration = h->fenc->f_planned_cpb_duration[j];
}
/* Try to get to get the buffer at least 50% filled, but don't set an impossible goal. */
// 尝试保持水池至少满足50%的水位,但是不要设定一个不可能的目标
// target_fill是目标水位
target_fill = X264_MIN( rcc->buffer_fill + total_duration * rcc->vbv_max_rate * 0.5, rcc->buffer_size * 0.5 );
// 当前的水位 < 目标水位,说明出水偏多,要减少出水量,需要增大qscale,这里乘以一个经验性参数1.01
if( buffer_fill_cur < target_fill )
{
q *= 1.01;
terminate |= 1;
continue;
}
/* Try to get the buffer no more than 80% filled, but don't set an impossible goal. */
// 尝试保持水池不要超过80%的水位,但是不要设定一个不可能的目标
// 上面保证了水池保证50%的水位,这里不超过80%的水位,VBV将水池的水位尽量控制在50%~80%
target_fill = x264_clip3f( rcc->buffer_fill - total_duration * rcc->vbv_max_rate * 0.5, rcc->buffer_size * 0.8, rcc->buffer_size );
// 当前的水位 > 目标水位,说明出水偏少,要增大出水量,需要减小qscale,这里除以一个经验性参数1.01
if( rcc->b_vbv_min_rate && buffer_fill_cur > target_fill )
{
q /= 1.01;
terminate |= 2;
continue;
}
break;
}
}
/* Fallback to old purely-reactive algorithm: no lookahead. */
else
{ // 不使用lookahead
// 如果是P帧或者是连续的I帧,并且当前水池水位低于50%,则需要将qscale增大
if( ( pict_type == SLICE_TYPE_P ||
( pict_type == SLICE_TYPE_I && rcc->last_non_b_pict_type == SLICE_TYPE_I ) ) &&
rcc->buffer_fill/rcc->buffer_size < 0.5 )
{
// qscale除以的因子为,水位比例 * 2,由于分母小于1,所以qscale变大
q /= x264_clip3f( 2.0*rcc->buffer_fill/rcc->buffer_size, 0.5, 1.0 );
}
/* Now a hard threshold to make sure the frame fits in VBV.
* This one is mostly for I-frames. */
// 使用一个强阈值来保证帧适应VBV,这个经常用于intra帧
double bits = predict_size( &rcc->pred[h->sh.i_type], q, rcc->last_satd );
/* For small VBVs, allow the frame to use up the entire VBV. */
// 对于小的VBV,允许帧使用整个VBV
double max_fill_factor = h->param.rc.i_vbv_buffer_size >= 5*h->param.rc.i_vbv_max_bitrate / rcc->fps ? 2 : 1;
/* For single-frame VBVs, request that the frame use up the entire VBV. */
// 对于单帧VBV,要求该帧使用整个VBV
double min_fill_factor = rcc->single_frame_vbv ? 1 : 2;
// 如果预测的比特 > 剩余可用的比特数,则增大qscale
if( bits > rcc->buffer_fill/max_fill_factor )
{
double qf = x264_clip3f( rcc->buffer_fill/(max_fill_factor*bits), 0.2, 1.0 ); // qf < 1
q /= qf;
bits *= qf;
}
// 如果预测的比特 < 剩余可用的比特数,则减小qscale
if( bits < rcc->buffer_rate/min_fill_factor )
{
double qf = x264_clip3f( bits*min_fill_factor/rcc->buffer_rate, 0.001, 1.0 ); // qf < 1
q *= qf;
}
q = X264_MAX( q0, q );
}
/* Check B-frame complexity, and use up any bits that would
* overflow before the next P-frame. */
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 ); // 当前编码帧的qp
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; // 所有b帧的时长
double minigop_cpb_duration;
for( int i = 0; i < nb; i++ )
bframe_cpb_duration += h->fenc->f_planned_cpb_duration[i]; // cpb(coded picture buffer)的持续时间
// 如果b帧的总预测比特 > b帧的入水量,则将b帧数量置为0
if( bbits * nb > bframe_cpb_duration * rcc->vbv_max_rate )
{
nb = 0;
bframe_cpb_duration = 0;
}
pbbits += nb * bbits;
// 计算了整个Mini-GOP(包括B帧和当前帧)的CPB持续时间
minigop_cpb_duration = bframe_cpb_duration + fenc_cpb_duration;
// 确定在下一个P帧之前可以填充的比特数
space = rcc->buffer_fill + minigop_cpb_duration*rcc->vbv_max_rate - rcc->buffer_size;
if( pbbits < space )
{
// 如果预测的帧大小(pbbits)小于剩余空间(space),则将量化参数(q)根据剩余空间的比例进行调整
q *= X264_MAX( pbbits / space, bits / (0.5 * rcc->buffer_size) );
}
q = X264_MAX( q0/2, q );
}
if( !rcc->b_vbv_min_rate )
q = X264_MAX( q0, q );
}
// 根据帧类型进行qscale的clip
return clip_qscale( h, pict_type, q );
}
4.4.4 行级VBV控制(x264_ratecontrol_mb)
每编码完一行后,会利用预测模型对当前帧大小进行预测,然后根据VBV Buffer fill对QP进行微调,执行的过程在x264_ratecontrol_mb中
/* 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上
// 如果没有到这一行的最后一个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上
// 如果不使用vbv,则直接返回
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,赋值到重建帧当中
// 更新预测器
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 */
// 根据与预测大小的差异调整质量,使用的是重建帧
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,因为不会有其他的slice
float 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之后,预期消耗的比特数b2
float 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 = b1
rc->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;
}
4.5 ABR小结
abr模式将码率稳定作为控制的主要目标,以过去编码帧和当前编码帧的纹理复杂度作为考量,预测后续帧使用的编码比特数,期望将编码过程中的实时码率约束在目标码率附近。但是,由于不知道未来实际编码帧的内容,预测比特数可能与实际编码内容不匹配,这样可能会带来短时较大的码率波动(因为要调整qscale来尽量做到匹配),从而使得编码质量不稳定。
为了解决这一问题,引入了vbv缓冲池机制,通过保持缓冲池中水量的相对稳定,来保证输出码率的相对稳定,进而保证编码质量的相对稳定。如果使用了lookahead,vbv缓冲池预测未来帧消耗的比特数,从而做到整体的码率控制,如果不使用lookahead,vbv缓冲池通过设置一些经验性参数来控制qscale。最后的结果是,vbv会将池中的水量保持在50%~80%之间,输出的水量也在对应的范围内稳定波动。在abr模式中加入vbv的控制时,如果vbv最大输入码率等于目标平均码率,此时就变成了cbr模式,这种模式就能够保证实际编码码率接近目标码率
5.小结
本文记录了x264当中帧级码控策略,主要模式包括cqp模式、crf模式和abr模式,没有详细记录cbr模式、2pass模式和vbr模式。
- CQP模式
恒定量化参数qp,不会进行量化参数qscale的调整,不推荐使用 - CRF模式
恒定质量因子rf,以保证编码质量作为主要目标,根据前后的视频纹理信息进行qscale的调整,能够保证视频质量稳定,是x264中默认的编码模式。但是不能够控制输出码率大小 - ABR模式
恒定目标码率bitrate,以保证码率稳定为主要目标,根据前后的视频纹理信息进行qscale的调整,尽量将码率约束在目标码率附近。但是,由于无法知晓未来帧的编码信息,可能会产生较大的码率波动和编码质量波动。
CBR、2pas和VBR模式
(1)CBR模式
cbr模式由abr模式和vbv机制组合而来,通过增加vbv机制,使得abr模式能够实现良好的码率控制,如果输入的最大码率等于目标码率,此时就是cbr模式
(2)2pass模式
2pass模式是基于主要模式的进行的两遍编码模式,第一遍编码模式(可用crf或abr)获取编码信息,第二遍编码模式依据记录下来的编码信息实现更精准的码控
(3)VBR模式
vbr模式是码率可变的模式,这种模式与cbr模式相对立。我理解这里并不是一种单独的编码模式,更像是一种概念,例如crf模式可以理解为是面向质量稳定的码率可变模式(quality-based vbr mode)
本文记录的码控当中,主要关注的内容为bit、qscale和complexity,不同的码率控制方法是围绕着这三者进行的,有的侧重于质量稳定,有的侧重于码率稳定,如何合理的对这几个方面进行调控,是实现良好码率控制的核心。此外,在码率控制当中使用了许多经验性的参数,这是利用实际应用中测试的参数拟合得到的,针对于不同的应用场景,这些参数应当进行特定的修正,包括外部配置的参数
CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen