目录
- 1.x265中的帧级码控模式
- 2.CQP模式
- 3.CRF模式
- 3.1 质量因子的计算
- 3.2 CRF模式的实现
- 4.ABR模式
- 4.1 ABR的实现
- 4.2 VBV技术
- 4.2.1 VBV初始化
- 4.2.2 VBV在lookahead中的计算
- 4.2.3 计算VBV输入速率(updateVbvPlan)
- 4.2.4 添加Vbv限制(clipQscale)
- 4.2.5 行级VBV控制(rowVbvRateControl)
x265相关:
【x265】x265编码器参数配置
【x265】预测模块的简单分析—帧内预测
【x265】预测模块的简单分析—帧间预测
【x265】码率控制模块的简单分析—块级码控工具(AQ和cuTree)
【x265】码率控制模块的简单分析—帧级码控模式(CQP、CRF和ABR)
1.x265中的帧级码控模式
在x265当中,主要的码控模式如下
/* rate tolerance method */
typedef enum
{
X265_RC_ABR, // Average Bitrate
X265_RC_CQP, // Constant QP
X265_RC_CRF // Constant Rate Factor
} X265_RC_METHODS;
X265_RC_ABR:表示使用平均码率进行编码
X265_RC_CQP:表示使用固定的量化参数进行编码
X265_RC_CRF:表示使用固定的质量因子进行编码
上述三种模式为最基础的模式,其余的模式例如CBR、2pass等模式都是基于这几种模式演化而来的。与x264类似,x265当中的码率控制主要思想是基于一种假设
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表示纹理复杂度。这里表示的含义是:
(1)如果希望量化参数qscale恒定,视频越复杂(complexity越高),则使用的bits越多
(2)如果希望使用的bits恒定,视频越复杂,则量化参数应该越高,此时编码的质量就越低
这种关系具体的数值,可以通过大量的数据拟合来实现
2.CQP模式
CQP模式全称为Constant QP模式,在这个模式下使用固定的qp值进行编码,编码过程中qp值不会发生变化。在使用x265时,如果指定了qp,则将模式自动配置为CQP模式
OPT("qp")
{
p->rc.qp = atoi(value);
p->rc.rateControlMode = X265_RC_CQP;
}
如果配置了CQP模式,则不会使用如AQ和cuTree等码控工具
if (p->rc.rateControlMode == X265_RC_CQP)
{
p->rc.aqMode = X265_AQ_NONE;
p->rc.hevcAq = 0;
p->rc.bitrate = 0;
p->rc.cuTree = 0;
p->rc.aqStrength = 0;
}
如果使用CQP模式,则将输入的qp进行赋值。如果是P帧,直接赋值;如果是I帧和B帧,需要根据对应的offset调整qp值
if (m_param->rc.rateControlMode == X265_RC_CQP)
{
// 不使用无损模式
if (m_qp && !m_param->bLossless)
{
// 直接赋值给P帧
m_qpConstant[P_SLICE] = m_qp;
/*
对于I帧和B帧,需要调整qp值
(1)I帧qp需要根据m_ipOffset进行调整
(a)m_ipOffset = 6.0 * X265_LOG2(m_param->rc.ipFactor);
(b)ipFactor默认为1.4
(2)B帧qp需要根据m_pbOffset进行调整
(a)m_pbOffset = 6.0 * X265_LOG2(m_param->rc.pbFactor);
(b)pbFactor默认为1.3
*/
m_qpConstant[I_SLICE] = x265_clip3(QP_MIN, QP_MAX_MAX, (int)(m_qp - m_ipOffset + 0.5));
m_qpConstant[B_SLICE] = x265_clip3(QP_MIN, QP_MAX_MAX, (int)(m_qp + m_pbOffset + 0.5));
}
else
{
m_qpConstant[P_SLICE] = m_qpConstant[I_SLICE] = m_qpConstant[B_SLICE] = m_qp;
}
}
3.CRF模式
前面的CQP模式让编码器按照固定QP进行编码,这样的策略让编码器没有灵活性。CRF模式全称为Constant Rate Factor,表示固定质量因子,是一种期望让编码器按照固定质量进行编码的模式,也是x265编码器默认使用的模式。质量因子使用rfConstant描述,可以通过外部设置质量因子。rfConstant在x265当中默认为28,rfConstant增大则编码质量
OPT("crf")
{
p->rc.rfConstant = atof(value);
p->rc.rateControlMode = X265_RC_CRF;
}
3.1 质量因子的计算
如果使用CRF模式,在进行RateControl初始化时,会使用rfConstant去计算一个内部质量因子m_rateFactorConstant。下面代码实现的位置为RateControl::RateControl(),位于encoder\ratecontrol.cpp中。下面计算m_rateFactorConstant的公式为
m
_
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
−
m
_
q
C
o
m
p
r
e
s
s
)
x
265
_
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
)
m\_rateFactorConstant = \frac{pow(baseCplx, 1 - m\_qCompress)}{x265\_qp2qscale(rfConstant + mbtree\_offset)}
m_rateFactorConstant=x265_qp2qscale(rfConstant+mbtree_offset)pow(baseCplx,1−m_qCompress)
如果控制变量,我对这个公式中的部分参数的简单理解是:
(1)m_rateFactorConstant描述了一个调整qscale的比例,这个比例由基本复杂度(由图像尺寸决定),量化曲线压缩因子(qCompress),质量因子(rfConstant)和cuTree影响因子(mbtree_offset)共同决定
(2)baseCplx越大,则m_rateFactorConstant越大。这表明为了满足更高分辨率的感知质量,需要以更大的力度调整qscale。m_qCompress用于调整复杂度的影响程度
(3)rfConstant越大,则m_rateFactorConstant越小。由于rfConstant是用户可以配置的(x265中默认为28),所以这表明用户希望降低qscale调整的幅度
(4)mbtree_offset如果不为0,即使用了cuTree技术,说明进行qscale调整时还需要考虑到其他帧的影响,应该适当降低qscale的调整幅度,因为cuTree(可能还包括aq)后续可能还会对qscale进行调整
PS:m_rateFactorConstant在使用时,作为分母来调整qscale,即 qscale /= m_rateFactorConstant。如果m_rateFactorConstant越大,则qscale越小
3.2 CRF模式的实现
代码实现的位置为RateControl::RateControl(),位于encoder\ratecontrol.cpp中
if (m_param->rc.rateControlMode == X265_RC_CRF)
{
m_param->rc.qp = (int)m_param->rc.rfConstant;
m_param->rc.bitrate = 0;
// 如果输入为1280x720,m_ncu = lowresCuWidth * lowresCuHeight = 1280x720 / 16x16 = 3600
double baseCplx = m_ncu * (m_param->bframes ? 120 : 80);
/*
(1)qCompress是量化曲线压缩因子,它根据残差的复杂度(通过lookahead计算)对帧量化器进行加权
(2)如果使用cuTree,mbtree_offset = 5.4
m_rateFactorConstant表示质量因子,即恒定的质量因子,后续是基于此来调整qscale
*/
double mbtree_offset = m_param->rc.cuTree ? (1.0 - m_param->rc.qCompress) * 13.5 : 0;
m_rateFactorConstant = pow(baseCplx, 1 - m_qCompress) /
x265_qp2qScale(m_param->rc.rfConstant + mbtree_offset);
if (m_param->rc.rfConstantMax) // rfConstantMax默认为0
{
m_rateFactorMaxIncrement = m_param->rc.rfConstantMax - m_param->rc.rfConstant;
if (m_rateFactorMaxIncrement <= 0)
{
x265_log(m_param, X265_LOG_WARNING, "CRF max must be greater than CRF\n");
m_rateFactorMaxIncrement = 0;
}
}
if (m_param->rc.rfConstantMin) // rfConstantMin默认为0
m_rateFactorMaxDecrement = m_param->rc.rfConstant - m_param->rc.rfConstantMin;
}
这个内部质量因子m_rateFactorConstant会在rateEstimateQscale()中被使用,计算qp
if (m_param->rc.rateControlMode == X265_RC_CRF)
{
// 根据质量因子计算QScale
q = getQScale(rce, m_rateFactorConstant);
x265_zone* zone = getZone();
if (zone)
{
if (zone->bForceQp)
q = x265_qp2qScale(zone->qp);
else
q /= zone->bitrateFactor;
}
}
getQScale()的实现方式如下,首先根据是否使用cuTree和hevcAq来获取qscale初始值,然后根据rateFactor调整qscale。获取qscale初始值有两种情况:
(1)使用cuTree且不使用hevcAq,直接利用fps和qcompress获取qscale
(2)否则,利用blurredComplexity和qcompress获取qscale
/* modify the bitrate curve from pass1 for one frame */
double RateControl::getQScale(RateControlEntry *rce, double rateFactor)
{
double q;
/*
我理解使用这种计算方式的原因是,qscale的大小与当前图像的复杂度和fps有直接的关系
(1)如果使用了cuTree且不使用hevcAq,说明存在别的帧会影响当前qscale的计算,这里先用fps计算qscale的初值,
后续使用cuTree进行调整,因为cuTree当中就包括了复杂度的信息
(2)否则,使用blurredComplexity复杂度直接计算qscale初值
*/
if (m_param->rc.cuTree && !m_param->rc.hevcAq)
{ // 如果使用cuTree并且不启用hevcAq,使用时间尺度(fps)来调整qscale
// Scale and units are obtained from rateNum and rateDenom for videos with fixed frame rates.
double timescale = (double)m_param->fpsDenom / (2 * m_param->fpsNum);
q = pow(BASE_FRAME_DURATION / CLIP_DURATION(2 * timescale), 1 - m_param->rc.qCompress);
}
else // 否则使用复杂度来调整qscale
q = pow(rce->blurredComplexity, 1 - m_param->rc.qCompress);
// avoid NaN's in the Rceq
if (rce->coeffBits + rce->mvBits == 0)
q = m_lastQScaleFor[rce->sliceType];
else
{
m_lastRceq = q;
q /= rateFactor; // 调整qscale
}
return q;
}
4.ABR模式
ABR模式全称为Average Bitrate模式,这种模式期望在编码过程中使用平均编码码率,即整体码率平稳,利于网络传输。与CRF模式相比,ABR模式的码率更加稳定,但可能会有一些质量的牺牲(主要体现在视频的质量波动)。如果调用时设置了bitrate,则会使用ABR模式
OPT("bitrate")
{
p->rc.bitrate = atoi(value);
p->rc.rateControlMode = X265_RC_ABR;
}
4.1 ABR的实现
ABR模式由rateEstimateQScale()实现,略去一部分代码之后,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
* trade-off between quality and bitrate precision. But at large
* tolerances, the bit distribution approaches that of 2pass. */
/*
(1)计算qscale,它将产生所需的平均比特率
(2)根据当前帧的复杂度相对于到目前为止的平均复杂度(使用2pass RCEQ)来调制qscale
(3)如果到目前为止总bit与目标bit相差甚远,还需要调整qscale
结果:根据rate_tolerance的值,在质量和比特率精度之间存在权衡。
但在较大的容差下,比特分配接近2pass
*/
double overflow = 1;
double lqmin = m_lmin[m_sliceType];
double lqmax = m_lmax[m_sliceType];
/*
(1) m_shortTermCplxSum自乘0.5,表示前一个cplx的权重系数为0.5
(2) m_shortTermCplxSum加上当前的SATD损失(增加了fps的考虑)
(3) blurredComplexity表示cplx的平均值,即平均复杂度
*/
m_shortTermCplxSum *= 0.5;
m_shortTermCplxCount *= 0.5;
m_shortTermCplxSum += m_currentSatd / (CLIP_DURATION(m_frameDuration) / BASE_FRAME_DURATION);
m_shortTermCplxCount++;
/* coeffBits to be used in 2-pass */
rce->coeffBits = (int)m_currentSatd;
rce->blurredComplexity = m_shortTermCplxSum / m_shortTermCplxCount;
rce->mvBits = 0;
rce->sliceType = m_sliceType;
if (m_param->rc.rateControlMode == X265_RC_CRF)
{
q = getQScale(rce, m_rateFactorConstant);
x265_zone* zone = getZone();
if (zone)
{
if (zone->bForceQp)
q = x265_qp2qScale(zone->qp);
else
q /= zone->bitrateFactor;
}
}
else
{
if (!m_param->rc.bStatRead)
checkAndResetABR(rce, false);
/*
在ABR模式下,期望保持平均码率的恒定
(1) m_wantedBitsWindow += m_bitrate * m_frameDuration;
表示目标编码比特(窗口时间内)
(2) m_cplxrSum += (bits * x265_qp2qScale(rce->qpaRc) / rce->qRceq) - (rce->rowCplxrSum);
表示sum of bits * qscale/rceq,其中rceq是一个码控调控因子,所以
bits * qscale / rceq表示的是复杂度,这是基于一个假设,即 bits * qscale ∝ complexity
(3) m_wantedBitsWindow / m_cplxrSum 可以理解为单位复杂度需要的比特数,作为一个调控因子
*/
double initialQScale = getQScale(rce, m_wantedBitsWindow / m_cplxrSum);
x265_zone* zone = getZone();
if (zone)
{
if (zone->bForceQp)
initialQScale = x265_qp2qScale(zone->qp);
else
initialQScale /= zone->bitrateFactor;
}
// 根据feedback调整abr的qscale
double tunedQScale = tuneAbrQScaleFromFeedback(initialQScale);
// 计算溢出比例
overflow = tunedQScale / initialQScale;
/*
m_partialResidualFrames使得编码器可以将部分I帧的编码成本分摊到后续的帧中 ,
这样可以避免在码率控制过程中过度补偿关键帧的高成本,实现更平滑的码率控制
(1) m_partialResidualFrames = X265_MIN((int)rce->amortizeFrames, m_param->keyframeMax);
默认取值
(a) rce->amortizeFrames = 75;
(b) m_param->keyframeMax = 250;
(2) 如果使用m_partialResidualFrames,则使用修正之后的tunedQscale
*/
q = !m_partialResidualFrames? tunedQScale : initialQScale;
// 检查是否完成编码帧数的75%,即编码的结尾
bool isEncodeEnd = (m_param->totalFrames &&
m_framesDone > 0.75 * m_param->totalFrames) ? 1 : 0;
// 检查是否是编码的开始
bool isEncodeBeg = m_framesDone < (int)(m_fps + 0.5);
if (m_isGrainEnabled) // m_isGrainEnabled默认为0
{
if(m_sliceType!= I_SLICE && m_framesDone && !isEncodeEnd &&
((overflow < 1.05 && overflow > 0.95) || isEncodeBeg))
{
q = tuneQScaleForGrain(overflow);
}
}
}
4.2 VBV技术
VBV全称为Video Buffer Verifier,即视频缓冲区校验,通常用于调控ABR模式的技术。因为ABR模式不能保证完全的平均码率,由于视频质量的突变或其他一些原因,ABR模式下的码率不一定能够保持平稳,所以添加了VBV这一技术,用于控制码率的平稳。如果当前使用ABR模式,并且使用了VBV技术,则当前的模式就是CBR(Const Bitrate)模式
引用我在x264中记录VBV技术的一段话:VBV是一种控制机制,它创建了一个缓冲池,无论输入的水流有多大,总能够保持池中的水处于一个相对稳定状态。如果输入的水流很大,则加大流出的水量,如果输入的水流很小,则减小流出的水量。这样操作的结果是,池中的水在一定范围内进行波动,输出的水量也在一定范围内波动,最终实现稳定的码率控制
举例来说,如果视频场景出现了突变,导致编码码率突然增大,VBV此时会增大qscale值,降低编码码率;反过来,视频编码码率偏低时,VBV会减小qscale值,以高质量编码,增大编码码率
VBV的实现分为几个步骤:
(1)VBV初始化(init)
(2)VBV在Lookahead中的计算,获取预期的framecost和frametype(vbvLookahead)
(3)计算VBV输入速率(updateVbvPlan)
(4)添加Vbv限制(clipQscale)
(5)行级VBV控制(rowVbvRateControl)
4.2.1 VBV初始化
VBV的初始化位于RateControl::init()函数中
bool RateControl::init(const SPS& sps)
{
if (m_isVbv && !m_initVbv) // 使用vbv但没有初始化vbv信息
{
/* We don't support changing the ABR bitrate right now,
* so if the stream starts as CBR, keep it CBR. */
// VBV缓冲区大小不能小于单帧大小, vbvBufferSize为VBV整个缓冲区的大小
if (m_param->rc.vbvBufferSize < (int)(m_param->rc.vbvMaxBitrate / m_fps))
{
m_param->rc.vbvBufferSize = (int)(m_param->rc.vbvMaxBitrate / m_fps);
x265_log(m_param, X265_LOG_WARNING, "VBV buffer size cannot be smaller than one frame, using %d kbit\n",
m_param->rc.vbvBufferSize);
}
// 转换成kilo单位
int vbvBufferSize = m_param->rc.vbvBufferSize * 1000;
int vbvMaxBitrate = m_param->rc.vbvMaxBitrate * 1000;
if (m_param->bEmitHRDSEI && !m_param->decoderVbvMaxRate) // bEmitHRDSEI默认为0
{
const HRDInfo* hrd = &sps.vuiParameters.hrdParameters;
vbvBufferSize = hrd->cpbSizeValue << (hrd->cpbSizeScale + CPB_SHIFT);
vbvMaxBitrate = hrd->bitRateValue << (hrd->bitRateScale + BR_SHIFT);
}
// 每帧后添加到buffer_fill的比特数
m_bufferRate = vbvMaxBitrate / m_fps; // 水池最大每帧输入水量 (处理每帧消耗的最大bit (bits/frame))
m_vbvMaxRate = vbvMaxBitrate; // 水池最大输入速率 (单位时间输入到VBV缓冲区的bit)
m_bufferSize = vbvBufferSize; // 水池最大容量 (bits)
// 如果每帧消耗的比特数 * 1.1 大于VBV缓冲区大小,说明水池比较小,仅能容纳一帧
m_singleFrameVbv = m_bufferRate * 1.1 > m_bufferSize; // 是否是单帧VBV
if (m_param->rc.vbvBufferInit > 1.) // vbvBufferInit表示VBV初始水量比例,默认为0.9
m_param->rc.vbvBufferInit = x265_clip3(0.0, 1.0, m_param->rc.vbvBufferInit / m_param->rc.vbvBufferSize);
if (m_param->vbvBufferEnd > 1.) // vbvBufferEnd表示VBV结束水量比例,默认为0
m_param->vbvBufferEnd = x265_clip3(0.0, 1.0, m_param->vbvBufferEnd / m_param->rc.vbvBufferSize);
if (m_param->vbvEndFrameAdjust > 1.) // vbvEndFrameAdjust表示允许编码器在编码最后一帧时,调整qp从而确保VBV缓冲区的水平符合预期, 默认为0
m_param->vbvEndFrameAdjust = x265_clip3(0.0, 1.0, m_param->vbvEndFrameAdjust);
m_param->rc.vbvBufferInit = x265_clip3(0.0, 1.0, X265_MAX(m_param->rc.vbvBufferInit, m_bufferRate / m_bufferSize));
m_bufferFillFinal = m_bufferSize * m_param->rc.vbvBufferInit; // 当前时刻剩余的水量
m_bufferFillActual = m_bufferFillFinal;
m_bufferExcess = 0;
m_initVbv = true; // 已初始化Vbv信息
}
// ...
}
4.2.2 VBV在lookahead中的计算
在lookahead模块中,会计算低分辨率的用于VBV的帧级损失,用于后续码率控制模块的VBV调控,具体使用vbvFrameCost()来实现。
(1)跳过连续的B帧
(2)遍历lookahead中的帧,计算损失及预测类型(vbvFrameCost)
(3)下一帧的类型待定,即AUTO
代码会考虑到B帧的计算,在这里我只简单考虑 I P P P … 这种结构
void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe)
{
int prevNonB = 0, curNonB = 1, idx = 0;
// 1.找到第一个非B帧
while (curNonB < numFrames && IS_X265_TYPE_B(frames[curNonB]->sliceType))
curNonB++;
int nextNonB = keyframe ? prevNonB : curNonB;
int nextB = prevNonB + 1;
int nextBRef = 0, curBRef = 0;
if (m_param->bBPyramid && curNonB - prevNonB > 1)
curBRef = (prevNonB + curNonB + 1) / 2;
/*
miniGopEnd表示一个小gop的结束
(1) 如果是keyframe,说明gop结束的位置位于前一个节点,即prevNonB
(2) 如果不是keyframe,则将当前节点作为gop结束的位置,即curNonB
PS: 如果是 I P P P P ...结构,则miniGopEnd为0 (keyframe) 或1 (not keyframe)
*/
int miniGopEnd = keyframe ? prevNonB : curNonB;
// 2.遍历lookahead中的帧,计算损失及预测帧类型
while (curNonB <= numFrames)
{
/* P/I cost: This shouldn't include the cost of nextNonB */
if (nextNonB != curNonB)
{
// 如果当前帧为I帧,则p0 = curNonB
int p0 = IS_X265_TYPE_I(frames[curNonB]->sliceType) ? curNonB : prevNonB;
// 预估SATD损失,如果p0 = curNonB,则计算I帧的损失
frames[nextNonB]->plannedSatd[idx] = vbvFrameCost(frames, p0, curNonB, curNonB);
// 预估帧类型
frames[nextNonB]->plannedType[idx] = frames[curNonB]->sliceType;
/* Save the nextNonB Cost in each B frame of the current miniGop */
// 如果当前节点已经不再属于前一个gop,说明进入新的gop
if (curNonB > miniGopEnd)
{
// nextB = 1, 如果是 IPPPPP 结构,miniGopEnd为0或1,不会进入分支
for (int j = nextB; j < miniGopEnd; j++)
{
frames[j]->plannedSatd[frames[j]->indB] = frames[nextNonB]->plannedSatd[idx];
frames[j]->plannedType[frames[j]->indB++] = frames[nextNonB]->plannedType[idx];
}
}
idx++;
}
/* Handle the B-frames: coded order */
if (m_param->bBPyramid && curNonB - prevNonB > 1)
nextBRef = (prevNonB + curNonB + 1) / 2;
// 按照编码顺序来处理B帧
for (int i = prevNonB + 1; i < curNonB; i++, idx++)
{
int64_t satdCost = 0;
int type = X265_TYPE_B;
if (nextBRef)
{
if (i == nextBRef)
{
satdCost = vbvFrameCost(frames, prevNonB, curNonB, nextBRef);
type = X265_TYPE_BREF;
}
else if (i < nextBRef)
satdCost = vbvFrameCost(frames, prevNonB, nextBRef, i);
else
satdCost = vbvFrameCost(frames, nextBRef, curNonB, i);
}
else
satdCost = vbvFrameCost(frames, prevNonB, curNonB, i);
frames[nextNonB]->plannedSatd[idx] = satdCost;
frames[nextNonB]->plannedType[idx] = type;
/* Save the nextB Cost in each B frame of the current miniGop */
for (int j = nextB; j < miniGopEnd; j++)
{
if (curBRef && curBRef == i)
break;
if (j >= i && j !=nextBRef)
continue;
frames[j]->plannedSatd[frames[j]->indB] = satdCost;
frames[j]->plannedType[frames[j]->indB++] = type;
}
}
prevNonB = curNonB;
curNonB++;
while (curNonB <= numFrames && IS_X265_TYPE_B(frames[curNonB]->sliceType))
curNonB++;
}
// 3.下一帧的类型待定
frames[nextNonB]->plannedType[idx] = X265_TYPE_AUTO;
}
其中,vbvFrameCost()的实现方式如下,首先计算intra&inter损失,随后根据aq和cuTree的使用情况来重新计算损失,使用的函数是frameCostRecalculate()
/*
(1) p0 : 前向参考帧
(2) p1 : 后向参考帧
(3) b : 当前帧
(1) 如果p0 = p1 = b,说明是Intra帧
(2) 如果p1 = b,说明是前向参考的P帧
*/
int64_t Lookahead::vbvFrameCost(Lowres **frames, int p0, int p1, int b)
{
CostEstimateGroup estGroup(*this, frames);
// 计算单帧损失 (intra, inter cost)
int64_t cost = estGroup.singleCost(p0, p1, b);
// 如果使用了aq或cuTree,需要重新计算损失
if (m_param->rc.aqMode || m_param->bAQMotion)
{
if (m_param->rc.cuTree)
return frameCostRecalculate(frames, p0, p1, b);
else
return frames[b]->costEstAq[b - p0][p1 - b];
}
return cost;
}
frameCostRecalculate()的实现如下所示,会根据前面计算的qp_adj来调整帧的损失值
/* If MB-tree changes the quantizers, we need to recalculate the frame cost without
* re-running lookahead. */
int64_t Lookahead::frameCostRecalculate(Lowres** frames, int p0, int p1, int b)
{
if (frames[b]->sliceType == X265_TYPE_B)
return frames[b]->costEstAq[b - p0][p1 - b];
int64_t score = 0;
int *rowSatd = frames[b]->rowSatds[b - p0][p1 - b];
x265_emms();
if (m_param->rc.hevcAq) // 一般不使用hevcAq模式
{
// ...
}
else
{
double *qp_offset = frames[b]->qpCuTreeOffset;
for (int cuy = m_8x8Height - 1; cuy >= 0; cuy--)
{
rowSatd[cuy] = 0;
for (int cux = m_8x8Width - 1; cux >= 0; cux--)
{
int cuxy = cux + cuy * m_8x8Width;
int cuCost = frames[b]->lowresCosts[b - p0][p1 - b][cuxy] & LOWRES_COST_MASK;
double qp_adj;
if (m_param->rc.qgSize == 8)
qp_adj = (qp_offset[cux * 2 + cuy * m_8x8Width * 4] +
qp_offset[cux * 2 + cuy * m_8x8Width * 4 + 1] +
qp_offset[cux * 2 + cuy * m_8x8Width * 4 + frames[b]->maxBlocksInRowFullRes] +
qp_offset[cux * 2 + cuy * m_8x8Width * 4 + frames[b]->maxBlocksInRowFullRes + 1]) / 4;
else
qp_adj = qp_offset[cuxy];
// 根据qp_adj来调整帧的损失
cuCost = (cuCost * x265_exp2fix8(qp_adj) + 128) >> 8;
rowSatd[cuy] += cuCost;
if ((cuy > 0 && cuy < m_8x8Height - 1 &&
cux > 0 && cux < m_8x8Width - 1) ||
m_8x8Width <= 2 || m_8x8Height <= 2)
{
score += cuCost;
}
}
}
}
return score;
}
4.2.3 计算VBV输入速率(updateVbvPlan)
updateVBVPlan()根据目前正在进行的所有帧预测的比特数临时更新VBV,在码率控制开始的位置rateControlStart()中被调用
void RateControl::updateVbvPlan(Encoder* enc)
{
/* (1) m_bufferFill表示当前水位,
(2) m_bufferFillFinal表示经过前面计算之后的最终水位
将之前的最终水位作为新一轮Vbv的初始水位 */
m_bufferFill = m_bufferFillFinal;
enc->updateVbvPlan(this);
}
enc->updateVbvPlan的实现如下,考虑了多线程问题
void Encoder::updateVbvPlan(RateControl* rc)
{
/* frameNumThreads表示含义为
(1) 帧级并行线程数,通常为2-6之间
(2) 使用不止1个线程,会影响运动搜索的下方搜索,但其他行为不影响
(3) 使用CQP模式时,即便使用帧级并行,不影响输出码流
(4) 线程数量增加,可能对RC产生负面影响,因为额外的并发性增加了比特率估计的不确定性
(5) 帧并行性通常受限于cpu的行数 */
for (int i = 0; i < m_param->frameNumThreads; i++)
{
FrameEncoder *encoder = m_frameEncoder[i];
// 如果当前encoder的RC被激活,并且poc不等于当前slice的poc
if (encoder->m_rce.isActive && encoder->m_rce.poc != rc->m_curSlice->m_poc)
{
/*
(1) bEnableConstVbv默认为0
(2) frameSizePlanned表示根据预估的qscale,获取的预估的frame消耗的比特
(3) frameSizeEstimated表示根据CU级RC更新的比特
*/
int64_t bits = m_param->rc.bEnableConstVbv ? (int64_t)encoder->m_rce.frameSizePlanned : (int64_t)X265_MAX(encoder->m_rce.frameSizeEstimated, encoder->m_rce.frameSizePlanned);
rc->m_bufferFill -= bits; // m_bufferFill表示当前水位,bits表示出水
// 检查出水之后,当前水位是否低于0
rc->m_bufferFill = X265_MAX(rc->m_bufferFill, 0);
rc->m_bufferFill += encoder->m_rce.bufferRate; // 入水
// 检查入水之后,当前水位是否超出总容量
rc->m_bufferFill = X265_MIN(rc->m_bufferFill, rc->m_bufferSize);
if (rc->m_2pass)
rc->m_predictedBits += bits;
}
}
}
4.2.4 添加Vbv限制(clipQscale)
实际执行VBV限制的函数为clipQscale(),粗略来说,根据已编码的信息对VBV进行帧级调控
(1)如果使用Lookahead,会尝试将VBV中的水量控制在50%~80%之间,并以此为目标来调整qscale
(2)如果不使用Lookahead,会按照当前水位情况来粗略调整qscale
double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce, double q)
{
// B-frames are not directly subject to VBV,
// since they are controlled by referenced P-frames' QPs.
// b帧不直接受到VBV的影响,因为它们是由参考p帧的qp控制的
double lmin = m_lmin[rce->sliceType];
double lmax = m_lmax[rce->sliceType];
double q0 = q;
// 进行VBV处理
if (m_isVbv && m_currentSatd > 0 && curFrame)
{
if (m_param->lookaheadDepth || m_param->rc.cuTree ||
(m_param->scenecutThreshold || m_param->bHistBasedSceneCut) ||
(m_param->bFrameAdaptive && m_param->bframes))
{
/* Lookahead VBV: If lookahead is done, 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. */
/*
1.Lookahead VBV: 如果完成了lookahead,必要时提高量化器,
这样在lookahead结束时,没有帧溢出,缓冲区处于合理的状态
*/
int loopTerminate = 0;
/* Avoid an infinite loop. */
/* 根据当前的qscale,计算应用Vbv之后的情况
(1) 执行至多1000次的循环
(2) 或者loopTerminate = 3 (水位满足要求)
(a) 水位不低于50%
(b) 水位不高于80% */
for (int iterations = 0; iterations < 1000 && loopTerminate != 3; iterations++)
{
double frameQ[3];
double curBits;
// 根据当前的qscale和不同帧类型的预测器,来预测消耗比特数
curBits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);
// m_bufferFill表示当前的水位
double bufferFillCur = m_bufferFill - curBits; // 剩余水位 = 当前水位 - 输出水量
double targetFill;
double totalDuration = m_frameDuration;
// 计算3种帧类型的qscale
frameQ[P_SLICE] = m_sliceType == I_SLICE ? q * m_param->rc.ipFactor : (m_sliceType == B_SLICE ? q / m_param->rc.pbFactor : q);
frameQ[B_SLICE] = frameQ[P_SLICE] * m_param->rc.pbFactor;
frameQ[I_SLICE] = frameQ[P_SLICE] / m_param->rc.ipFactor;
/* Loop over the planned future frames. */
// 预估未来帧的消耗
bool iter = true;
for (int j = 0; bufferFillCur >= 0 && iter ; j++)
{
int type = curFrame->m_lowres.plannedType[j];
if (type == X265_TYPE_AUTO || totalDuration >= 1.0)
break;
totalDuration += m_frameDuration;
double wantedFrameSize = m_vbvMaxRate * m_frameDuration; // wantedFrameSize表示要加的水量
if (bufferFillCur + wantedFrameSize <= m_bufferSize) // 检查是否可以加水
bufferFillCur += wantedFrameSize;
// plannedSatd在vbvLookahead当中计算获得
int64_t satd = curFrame->m_lowres.plannedSatd[j] >> (X265_DEPTH - 8);
type = IS_X265_TYPE_I(type) ? I_SLICE : IS_X265_TYPE_B(type) ? B_SLICE : P_SLICE;
// plannedType在vbvLookahead当中计算获得
int predType = getPredictorType(curFrame->m_lowres.plannedType[j], type);
curBits = predictSize(&m_pred[predType], frameQ[type], (double)satd); // 预估需要的比特数
bufferFillCur -= curBits; // 减去当前输入水量
if (!m_param->bResetZoneConfig && ((uint64_t)j == (m_param->reconfigWindowSize - 1)))
iter = false;
}
// 是否允许进行最后一帧的qp调整,vbvEndAdj默认为0
if (rce->vbvEndAdj)
{
bool loopBreak = false;
double bufferDiff = m_param->vbvBufferEnd - (m_bufferFill / m_bufferSize);
rce->targetFill = m_bufferFill + m_bufferSize * (bufferDiff / (m_param->totalFrames - rce->encodeOrder));
if (bufferFillCur < rce->targetFill)
{
q *= 1.01;
loopTerminate |= 1;
loopBreak = true;
}
if (bufferFillCur > m_param->vbvBufferEnd * m_bufferSize)
{
q /= 1.01;
loopTerminate |= 2;
loopBreak = true;
}
if (!loopBreak)
break;
}
else
{
/* Try to get the buffer at least 50% filled, but don't set an impossible goal. */
// 尽量保持不低于50%的水位
double finalDur = 1;
if (m_param->rc.bStrictCbr)
{
finalDur = x265_clip3(0.4, 1.0, totalDuration);
}
targetFill = X265_MIN(m_bufferFill + totalDuration * m_vbvMaxRate * 0.5, m_bufferSize * ((m_param->minVbvFullness / 100) * finalDur));
// 如果当前水位 < 目标水位,说明出水偏多,需要减少出水,则适当增加qscale
if (bufferFillCur < targetFill)
{
q *= 1.01; // qscale增大的量为经验性的1%
loopTerminate |= 1; // 满足水位不低于50%的条件
continue;
}
/* Try to get the buffer not more than 80% filled, but don't set an impossible goal. */
// 尽量保持不高于80%的水位
targetFill = x265_clip3(m_bufferSize * ((m_param->maxVbvFullness / 100) * finalDur), m_bufferSize, m_bufferFill - totalDuration * m_vbvMaxRate * 0.5);
// 如果是CBR模式,并且当前水位 > 目标水位,说明出水偏少,需要增加出水,则适当降低qscale
if ((m_isCbr || m_2pass) && bufferFillCur > targetFill && !m_isSceneTransition)
{
q /= 1.01;
loopTerminate |= 2; // 满足水位不高于80%的条件
continue;
}
break;
}
}
q = X265_MAX(q0 / 2, q);
}
else // 不使用lookahead
{
/* Fallback to old purely-reactive algorithm: no lookahead. */
// 如果是P或B帧,或连续的I帧
if ((m_sliceType == P_SLICE || m_sliceType == B_SLICE ||
(m_sliceType == I_SLICE && m_lastNonBPictType == I_SLICE)) &&
m_bufferFill / m_bufferSize < (m_param->minVbvFullness / 100))
{
q /= x265_clip3(0.5, 1.0, 2.0 * m_bufferFill / m_bufferSize);
}
// Now a hard threshold to make sure the frame fits in VBV.
// This one is mostly for I-frames.
// 使用一个强阈值来保证帧适应VBV,这个经常用于intra帧
double bits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);
// For small VBVs, allow the frame to use up the entire VBV.
// 对于小VBV,允许帧使用整个VBV
double maxFillFactor;
maxFillFactor = m_bufferSize >= 5 * m_bufferRate ? 2 : 1;
// For single-frame VBVs, request that the frame use up the entire VBV.
// 对于单帧VBV,要求帧使用整个VBV
double minFillFactor = m_singleFrameVbv ? 1 : 2;
for (int iterations = 0; iterations < 10; iterations++)
{
double qf = 1.0;
// 如果预测的比特 > 剩余可用的比特数,需要减小qscale
if (bits > m_bufferFill / maxFillFactor)
qf = x265_clip3(0.2, 1.0, m_bufferFill / (maxFillFactor * bits));
q /= qf;
bits *= qf;
// 如果预测的比特 < 剩余可用的比特数,需要增大qscale
if (bits < m_bufferRate / minFillFactor)
q *= bits * minFillFactor / m_bufferRate;
bits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);
}
q = X265_MAX(q0, q);
}
/* Apply MinCR restrictions */
// MinCR : Minimum Compression Ratio
// 根据前面预测的qscale,预测消耗的比特数
double pbits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);
if (pbits > rce->frameSizeMaximum)
q *= pbits / rce->frameSizeMaximum;
/* To detect frames that are more complex in SATD costs compared to prev window, yet
* lookahead vbv reduces its qscale by half its value. Be on safer side and avoid drastic
* qscale reductions for frames high in complexity */
// 为了检测更复杂的帧,lookahead vbv将其qscale折半。对于复杂的帧,尽量不要大幅降低qscale
bool mispredCheck = rce->movingAvgSum && m_currentSatd >= rce->movingAvgSum && q <= q0 / 2;
if (!m_isCbr || ((m_isAbr || m_2pass) && mispredCheck))
q = X265_MAX(q0, q);
if (m_rateFactorMaxIncrement)
{
double qpNoVbv = x265_qScale2qp(q0);
double qmax = X265_MIN(lmax,x265_qp2qScale(qpNoVbv + m_rateFactorMaxIncrement));
return x265_clip3(lmin, qmax, q);
}
}
if (!curFrame && m_2pass) // 2pass
{
double min = log(lmin);
double max = log(lmax);
q = (log(q) - min) / (max - min) - 0.5;
q = 1.0 / (1.0 + exp(-4 * q));
q = q*(max - min) + min;
return exp(q);
}
// 修正qscale
return x265_clip3(lmin, lmax, q);
}
在上面的实现过程中,使用了predictSize()来预测使用的比特数,其实现方式如下
double RateControl::predictSize(Predictor *p, double q, double var)
{
return (p->coeff * var + p->offset) / (q * p->count);
}
用公式来描述如下,即比特数与纹理复杂度、量化参数之间的关系。这种比较简易的关系使得qscale成为码率控制中的一个通用数据存储变量(或数据传递变量)
r
=
c
∗
c
o
m
p
l
e
x
q
s
c
a
l
e
r = c * \frac{complex}{qscale}
r=c∗qscalecomplex
4.2.5 行级VBV控制(rowVbvRateControl)
前面进行了帧级别的VBV控制,在行级也存在VBV控制,实现函数为rowVbvRateControl()。每编码完一行后,会利用预测模型对当前帧大小进行预测,然后对QP进行微调
int RateControl::rowVbvRateControl(Frame* curFrame, uint32_t row, RateControlEntry* rce, double& qpVbv, uint32_t* m_sliceBaseRow, uint32_t sliceId)
{
FrameData& curEncData = *curFrame->m_encData;
double qScaleVbv = x265_qp2qScale(qpVbv);
uint64_t rowSatdCost = curEncData.m_rowStat[row].rowSatd; // 行级SATD损失
double encodedBits = curEncData.m_rowStat[row].encodedBits; // 已编码的比特
rowSatdCost >>= X265_DEPTH - 8;
// 根据行级SATD损失和已编码比特来更新预测器
updatePredictor(rce->rowPred[0], qScaleVbv, (double)rowSatdCost, encodedBits);
// 当前帧不为Intra帧,并且不使用const vbv,还会使用参考帧信息来更新预测器
if (curEncData.m_slice->m_sliceType != I_SLICE && !m_param->rc.bEnableConstVbv)
{
Frame* refFrame = curEncData.m_slice->m_refFrameList[0][0];
if (qpVbv < refFrame->m_encData->m_rowStat[row].rowQp)
{
uint64_t intraRowSatdCost = curEncData.m_rowStat[row].rowIntraSatd;
intraRowSatdCost >>= X265_DEPTH - 8;
updatePredictor(rce->rowPred[1], qScaleVbv, (double)intraRowSatdCost, encodedBits);
}
}
int canReencodeRow = 1;
/* tweak quality based on difference from predicted size */
double prevRowQp = qpVbv;
double qpAbsoluteMax = m_param->rc.qpMax;
double qpAbsoluteMin = m_param->rc.qpMin;
if (m_rateFactorMaxIncrement)
qpAbsoluteMax = X265_MIN(qpAbsoluteMax, rce->qpNoVbv + m_rateFactorMaxIncrement);
if (m_rateFactorMaxDecrement)
qpAbsoluteMin = X265_MAX(qpAbsoluteMin, rce->qpNoVbv - m_rateFactorMaxDecrement);
// qp clip
double qpMax = X265_MIN(prevRowQp + m_param->rc.qpStep, qpAbsoluteMax);
double qpMin = X265_MAX(prevRowQp - m_param->rc.qpStep, qpAbsoluteMin);
double stepSize = 0.5;
double bufferLeftPlanned = rce->bufferFill - rce->frameSizePlanned; // 当前水位 - 预计要消耗的水量 = 剩余水量
const SPS& sps = *curEncData.m_slice->m_sps;
double maxFrameError = X265_MAX(0.05, 1.0 / sps.numCuInHeight);
// 是否为最后一行
if (row < m_sliceBaseRow[sliceId + 1] - 1)
{
/* More threads means we have to be more cautious in letting ratecontrol use up extra bits. */
// 根据VBV剩余水量, 计算RC tolerance
double rcTol = bufferLeftPlanned / m_param->frameNumThreads * m_rateTolerance;
int32_t encodedBitsSoFar = 0;
// 预测当前帧消耗比特(accFrameBits),并且获取当前行及之前的行已经使用了多少比特(encodedBitsSoFar)
double accFrameBits = predictRowsSizeSum(curFrame, rce, qpVbv, encodedBitsSoFar);
double vbvEndBias = 0.95;
/* * Don't increase the row QPs until a sufficent amount of the bits of
* the frame have been processed, in case a flat area at the top of the
* frame was measured inaccurately. */
// 如果已编码比特不足预估比特的 5%,则将前一行的qp作为qp的最大值
// 这表示后续的qp都会小于等于前一行的qp,即后续的行都会以更高质量进行编码
if (encodedBitsSoFar < 0.05f * rce->frameSizePlanned)
qpMax = qpAbsoluteMax = prevRowQp;
// 如果当前帧不是I帧,将rc容忍度减半
if (rce->sliceType != I_SLICE || (m_param->rc.bStrictCbr && rce->poc > 0))
rcTol *= 0.5;
if (!m_isCbr)
qpMin = X265_MAX(qpMin, rce->qpNoVbv);
// m_wantedBitsWindow表示窗口期内期望的比特数
double totalBitsNeeded = m_wantedBitsWindow;
// totalFrames表示需要编码的总帧数
if (m_param->totalFrames)
totalBitsNeeded = (m_param->totalFrames * m_bitrate) / m_fps;
// ABR模式下超出预期比特数的比例 = (当前帧可能使用的总比特 + 所有已编码帧使用的比特 - 窗口期内期望的比特数 ) / 总共需要的比特数
double abrOvershoot = (accFrameBits + m_totalBits - m_wantedBitsWindow) / totalBitsNeeded;
/*
当前qp小于最大qp,并且满足以下条件之一时,会增大qp
(1) 当前帧可能使用的总比特 > 预估使用的比特 + 容忍误差
(2) 当前帧可能使用的总比特 > 当前buffer水位 - buffer预估剩余水位 * 0.5
(3) 当前帧可能使用的总比特 > 预估使用的比特 && qp < qpNoVbv
(4) 允许vbvEndAdj && 当前帧可能使用的总比特 > 当前buffer水位 - 目标水位 * vbvEndBias
必须满足条件:
使用bStrictCbr 或 abrOverShoot > 0.1
PS: bStrictCbr表示使用严格Cbr,否则允许一定量的码率波动,默认为0
*/
while (qpVbv < qpMax
&& (((accFrameBits > rce->frameSizePlanned + rcTol) ||
(rce->bufferFill - accFrameBits < bufferLeftPlanned * 0.5) || // accFrameBits > rce->bufferFill - bufferLeftPlanned * 0.5
(accFrameBits > rce->frameSizePlanned && qpVbv < rce->qpNoVbv) ||
(rce->vbvEndAdj && ((rce->bufferFill - accFrameBits) < (rce->targetFill * vbvEndBias)))) // accFrameBits > rce->bufferFill - rce->targetFill * vbvEndBias
&& (!m_param->rc.bStrictCbr ? 1 : abrOvershoot > 0.1)))
{
// 增大qp,增加的单位为stepSize
qpVbv += stepSize;
// 根据新的qp
accFrameBits = predictRowsSizeSum(curFrame, rce, qpVbv, encodedBitsSoFar);
abrOvershoot = (accFrameBits + m_totalBits - m_wantedBitsWindow) / totalBitsNeeded;
}
/*
当前qp > qpMin,并且满足以下所有条件,则减小qp
(1) qp大于当前行qp 或 使用单帧VBV
(2) 当前帧可能使用的总比特 < (当前水位 - 总水位 + 输入水量) * 1.1
或 使用vbvEndAdj && 当前帧可能使用的总比特 < 当前水位 - 目标水位 * vbvEndBias
必须满足条件:
使用bStrictCbr 或 abrOverShoot < 0
*/
while (qpVbv > qpMin
&& (qpVbv > curEncData.m_rowStat[0].rowQp || m_singleFrameVbv)
&& (((accFrameBits < rce->frameSizePlanned * 0.8f && qpVbv <= prevRowQp)
|| accFrameBits < (rce->bufferFill - m_bufferSize + m_bufferRate) * 1.1
// accFrameBits < rce->bufferFill - rce->targetFill * vbvEndBias
|| (rce->vbvEndAdj && ((rce->bufferFill - accFrameBits) > (rce->targetFill * vbvEndBias))))
&& (!m_param->rc.bStrictCbr ? 1 : abrOvershoot < 0)))
{
// 降低qp
qpVbv -= stepSize;
// 根据新计算的qp来预测当前帧可能使用的比特数
accFrameBits = predictRowsSizeSum(curFrame, rce, qpVbv, encodedBitsSoFar);
abrOvershoot = (accFrameBits + m_totalBits - m_wantedBitsWindow) / totalBitsNeeded;
}
// 如果使用严格的CBR
if (m_param->rc.bStrictCbr && m_param->totalFrames)
{
double timeDone = (double)(m_framesDone) / m_param->totalFrames;
// timeDone > 0.75表示接近视频的末尾,abrOverShoot表示未来可能使用的比特略比预期多一些,此时适当增大qp
while (qpVbv < qpMax && (qpVbv < rce->qpNoVbv + (m_param->rc.qpStep * timeDone)) &&
(timeDone > 0.75 && abrOvershoot > 0))
{
qpVbv += stepSize;
accFrameBits = predictRowsSizeSum(curFrame, rce, qpVbv, encodedBitsSoFar);
abrOvershoot = (accFrameBits + m_totalBits - m_wantedBitsWindow) / totalBitsNeeded;
}
// 再适当减小一点qp
if (qpVbv > curEncData.m_rowStat[0].rowQp &&
abrOvershoot < -0.1 && timeDone > 0.5 && accFrameBits < rce->frameSizePlanned - rcTol)
{
qpVbv -= stepSize;
accFrameBits = predictRowsSizeSum(curFrame, rce, qpVbv, encodedBitsSoFar);
}
}
/* avoid VBV underflow or MinCr violation */
// 避免VBV下溢
while ((qpVbv < qpAbsoluteMax)
&& ((rce->bufferFill - accFrameBits < m_bufferRate * maxFrameError) ||
(rce->frameSizeMaximum - accFrameBits < rce->frameSizeMaximum * maxFrameError)))
{
qpVbv += stepSize;
accFrameBits = predictRowsSizeSum(curFrame, rce, qpVbv, encodedBitsSoFar);
}
rce->frameSizeEstimated = accFrameBits;
/* If the current row was large enough to cause a large QP jump, try re-encoding it. */
// 如果当前行的qp过大,可能会产生qp大幅跳变,尝试重新编码
if (qpVbv > qpMax && prevRowQp < qpMax && canReencodeRow)
{
/* Bump QP to halfway in between... close enough. */
qpVbv = x265_clip3(prevRowQp + 1.0f, qpMax, (prevRowQp + qpVbv) * 0.5);
return -1;
}
if (m_param->rc.rfConstantMin)
{
if (qpVbv < qpMin && prevRowQp > qpMin && canReencodeRow)
{
qpVbv = x265_clip3(qpMin, prevRowQp, (prevRowQp + qpVbv) * 0.5);
return -1;
}
}
}
else
{ // 如果当前行为最后一行
int32_t encodedBitsSoFar = 0;
rce->frameSizeEstimated = predictRowsSizeSum(curFrame, rce, qpVbv, encodedBitsSoFar);
/* Last-ditch attempt: if the last row of the frame underflowed the VBV,
* try again. */
// 如果最后一行使得VBV下溢,再尝试一次编码
if ((rce->frameSizeEstimated > (rce->bufferFill - m_bufferRate * maxFrameError) &&
qpVbv < qpMax && canReencodeRow))
{
qpVbv = qpMax;
return -1;
}
}
return 0;
}