x265源码分析之码率控制

码率控制

视频编码的目标是尽可能多的节省比特的同时尽量保持视频质量,码率控制是平衡码率和质量的重要工具。

码率控制并不是视频编码框架中具体的一个模块,而是在整个视频编码过程中扮演控制着的角色,一方面接收来自编码器外部的要求(编码缓冲区大小、人为要求的目标输出码率),另一方面分析并处理这些要求,为了完成要求对编码参数做出选择(量化参数、编码模式、运动矢量)。

在整个码率控制过程中,最为重要的三个编码参数是量化参数、编码模式、运动矢量。

在工业实现中一般都是选择量化参数建立率失真模型,将量化参数和编码模式、运动矢量分开,先有码率控制模块确定量化参数,再由RDO(率失真优化)确定编码模式和运动矢量。

所以码率控制就是负责量化参数的确定。

帧间级码率控制

ABR、CRF

帧间级别码率控制需要为每一帧分配QP,也就是分配每一帧的大小,每一帧的大小主要取决于两个变量:本帧的复杂度、码率预算。首先会根据每帧的复杂度,在帧与帧之间分配码率比例,然后根据码率预算,将每帧缩放到合适大小,这个缩放系数称为RateFactor,如果开启了VBV,还会经过VBV模块调整。ABR和CRF模式都是通过调整RateFactor实现。

QScale

转换qp

1
2
3
4
5
6
7
8
9
double x265_qScale2qp(double qScale)
{
return 12.0 + 6.0 * (double)X265_LOG2(qScale / 0.85);
}

double x265_qp2qScale(double qp)
{
return 0.85 * pow(2.0, (qp - 12.0) / 6.0);
}

初始值

QScale的初始值由当前帧的复杂度计算得出

复杂度SATD

在帧类型决策时,会对帧进行1/2下采样,对下采样后的小图进行预测,并计算SATD值。

帧内预测

在帧类型决策之前,会对每一帧都会进行预处理。在PreLookaheadGroup中进行下采样,并进行帧内预测,计算SATD值。

下采样后,遍历每个8x8的CU,在35中预测模式下选出最优的SATD值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void LookaheadTLD::lowresIntraEstimate(Lowres& fenc, uint32_t qgSize)
{
...
for (int cuY = 0; cuY < heightInCU; cuY++)
{
for (int cuX = 0; cuX < widthInCU; cuX++)
{
const int cuXY = cuX + cuY * widthInCU;
...

/* DC and planar */
primitives.cu[sizeIdx].intra_pred[DC_IDX](prediction, cuSize, samples, 0, cuSize <= 16);
cost = satd(fencIntra, cuSize, prediction, cuSize);
COPY2_IF_LT(icost, cost, ilowmode, DC_IDX);

primitives.cu[sizeIdx].intra_pred[PLANAR_IDX](prediction, cuSize, neighbours[planar], 0, 0);
cost = satd(fencIntra, cuSize, prediction, cuSize);
COPY2_IF_LT(icost, cost, ilowmode, PLANAR_IDX);

/* scan angular predictions */
int filter, acost = me.COST_MAX;
uint32_t mode, alowmode = 4;
for (mode = 5; mode < 35; mode += 5)
{
filter = !!(g_intraFilterFlags[mode] & cuSize);
primitives.cu[sizeIdx].intra_pred[mode](prediction, cuSize, neighbours[filter], mode, cuSize <= 16);
cost = satd(fencIntra, cuSize, prediction, cuSize);
COPY2_IF_LT(acost, cost, alowmode, mode);
}

...
}
}

fenc.costEst[0][0] = costEst;
fenc.costEstAq[0][0] = costEstAq;
}
帧间预测

在帧类型决策之后,如果当前的码控模式不是CQP,则会在CostEstimateGroup中提前对帧进行成本估计,计算帧的SATD值。

如果是I帧则直接使用帧内预测的SATD值,否则在参考帧中进行运动估计,计算每个8x8CU的运动矢量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int64_t CostEstimateGroup::estimateFrameCost(LookaheadTLD& tld, int p0, int p1, int b, bool bIntraPenalty)
{
Lowres* fenc = m_frames[b];
x265_param* param = m_lookahead.m_param;
int64_t score = 0;

if (fenc->costEst[b - p0][p1 - b] >= 0 && fenc->rowSatds[b - p0][p1 - b][0] != -1)
score = fenc->costEst[b - p0][p1 - b];
else
{
...
{
/* Calculate MVs for 1/16th resolution*/
bool lastRow;
lastRow = true;
for (int cuY = m_lookahead.m_8x8Height - 1; cuY >= 0; cuY--)
{
fenc->rowSatds[b - p0][p1 - b][cuY] = 0;

for (int cuX = m_lookahead.m_8x8Width - 1; cuX >= 0; cuX--)
estimateCUCost(tld, cuX, cuY, p0, p1, b, bDoSearch, lastRow, -1, 0);

lastRow = false;
}
}

score = fenc->costEst[b - p0][p1 - b];
}
return score;
}

模糊复杂度

模糊复杂度是基于邻近已编码帧的复杂度加权平均得到,避免QP波动。

m_shortTermCplxSum 当前非B帧及以前所有非B帧 satd * (fps/25)的和,fps越大,权重越大
Sum[i] = Sum[i - 1] * 0.5 + satd * (fps / 25)

m_shortTermCplxCount 当前帧以前非B帧个数 + 1
Count[i] = Count[i - 1] * 0.5 + 1

1
2
3
4
5
6
7
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;

感知编码优化

对于复杂度高的场景,细节丢失比较难以发现,因此可以使用比较高的QP,可以对帧的复杂度进行压缩。qcomp是由外部控制的参数。

rceq = BlurCplx^(1 - qcomp);

  • 当qcomp = 1,各帧的rceq都一样,分配给平缓帧和复杂度的比特都一样;
  • 当qcomp = 0,相当于关闭了感知编码优化;
1
2
3
4
5
6
7
8
if (m_param->rc.cuTree && !m_param->rc.hevcAq)
{
// 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
q = pow(rce->blurredComplexity, 1 - m_param->rc.qCompress);

RateFactor修正

1
QScale = rceq / RateFactor;

CRF

CRF(Constant RateFactor),可以保持整个视频流质量恒定。CRF接受一个固定的QP值,取值为0-51之间的整数,x265默认值时28,CRF增减6会导致码率减半或加倍。在CRF模式下,RateFactor是固定的,QScale是由每帧的复杂度决定。

首帧

在CRF模式下,首帧的QScale由CRF接受的QP值来决定,外部指定的QP都是P帧的QP,对于I帧需要作用ipFactor。ipFactor是I帧与P帧QP的偏移系数,pbFactor是P帧和B帧QP的偏移系数。

1
2
3
4
5
6
#define CRF_INIT_QP (int)m_param->rc.rfConstant

if (m_qCompress != 1 && m_param->rc.rateControlMode == X265_RC_CRF)
{
q = x265_qp2qScale(CRF_INIT_QP) / fabs(m_param->rc.ipFactor);
}
I帧、P帧

m_rateFactorConstant根据外部指定的QP来计算出来的固定RateFactor,除了首帧外,所有的非B帧都是使用该系数来压缩复杂度。

1
2
3
4
if (m_param->rc.rateControlMode == X265_RC_CRF)
{
q = getQScale(rce, m_rateFactorConstant);
}

m_rateFactorConstant = rceq / QScale
m_rateFactorConstant根据公式反算回来,其中QScale为外部指定的QP,rceq为8x8CU个数 * 120。

1
2
3
4
5
6
7
8
9
10
11
12
13
int lowresCuWidth = ((m_param->sourceWidth / 2) + X265_LOWRES_CU_SIZE - 1) >> X265_LOWRES_CU_BITS;
int lowresCuHeight = ((m_param->sourceHeight / 2) + X265_LOWRES_CU_SIZE - 1) >> X265_LOWRES_CU_BITS;
m_ncu = lowresCuWidth * lowresCuHeight;

if (m_param->rc.rateControlMode == X265_RC_CRF)
{
m_param->rc.qp = (int)m_param->rc.rfConstant;
m_param->rc.bitrate = 0;

double baseCplx = m_ncu * (m_param->bframes ? 120 : 80);
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);
}
B帧

B帧是没有独立码控的,B帧的QP是由参考帧的QP来决定。

  • 前向参考帧和后向参考帧都是I帧,QP = 平均QP值
  • 前向参考帧为I帧,QP = 后向参考帧
  • 后向参考帧为I帧,QP = 前向参考帧
  • 都不是I帧,QP = 按POC距离加权的平均QP值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    if (m_sliceType == B_SLICE)
    {
    /* B-frames don't have independent rate control, but rather get the
    * average QP of the two adjacent P-frames + an offset */
    Slice* prevRefSlice = m_curSlice->m_refFrameList[0][0]->m_encData->m_slice;
    Slice* nextRefSlice = m_curSlice->m_refFrameList[1][0]->m_encData->m_slice;
    double q0 = m_curSlice->m_refFrameList[0][0]->m_encData->m_avgQpRc;
    double q1 = m_curSlice->m_refFrameList[1][0]->m_encData->m_avgQpRc;
    bool i0 = prevRefSlice->m_sliceType == I_SLICE;
    bool i1 = nextRefSlice->m_sliceType == I_SLICE;
    int dt0 = abs(m_curSlice->m_poc - prevRefSlice->m_poc);
    int dt1 = abs(m_curSlice->m_poc - nextRefSlice->m_poc);

    if (i0 && i1)
    q = (q0 + q1) / 2 + m_ipOffset;
    else if (i0)
    q = q1;
    else if (i1)
    q = q0;
    else
    q = (q0 * dt1 + q1 * dt0) / (dt0 + dt1);

    if (IS_REFERENCED(curFrame))
    q += m_pbOffset / 2;
    else
    q += m_pbOffset;

ABR

平均比特率(Average Bitrate),给定一个目标码率,编码器计算如何达到这个码率。但是由于编码器在编码当前帧时是不知道后面还未编码的内容,所以它不得不猜测如何达到给定码率,这意味着码率要一直变化,因此ABR不是一种恒定码率模式而是可变码率模式。在ABR模式下,就是通过不断调整RateFactor来实现的平均码率控制。

I帧、P帧

在ABR模式下,RateFactor = wanted_bits_window / complexity_sum,其中wanted_bits_window表示已编码帧的目标文件大小,complexity_sum是系数。

wanted_bits_window是(bitrate / fps)的累积值,bitrate是外部指定的平均比特率,bitrate / fps表示平均一帧的比特率,所以wanted_bits_window表示累积的期望比特大小。
complexity_sum是(bits * qscale / rceq)的累积值,bits表示实际编码得到的帧大小

初始化

1
2
m_cplxrSum = .01 * pow(7.0e5, m_qCompress) * pow(m_ncu, 0.5) * tuneCplxFactor;
m_wantedBitsWindow = m_bitrate * m_frameDuration;

当前帧编码结束后,更新

1
2
3
4
5
6
7
8
9
10
11
12
13
if (rce->sliceType != B_SLICE)
{
/* The factor 1.5 is to tune up the actual bits, otherwise the cplxrSum is scaled too low
* to improve short term compensation for next frame. */
m_cplxrSum += (bits * x265_qp2qScale(rce->qpaRc) / rce->qRceq) - (rce->rowCplxrSum);
}
else
{
/* Depends on the fact that B-frame's QP is an offset from the following P-frame's.
* Not perfectly accurate with B-refs, but good enough. */
m_cplxrSum += (bits * x265_qp2qScale(rce->qpaRc) / (rce->qRceq * fabs(m_param->rc.pbFactor))) - (rce->rowCplxrSum);
}
m_wantedBitsWindow += m_frameDuration * m_bitrate;

计算QScale

1
2
double initialQScale = getQScale(rce, m_wantedBitsWindow / m_cplxrSum);
q = initialQScale;

在ABR模式下,首帧的QP不能超过37。

1
2
3
4
5
6
7
8
9
#define ABR_INIT_QP_MAX (37)

if (m_framesDone == 0 && !m_isVbv && m_param->rc.rateControlMode == X265_RC_ABR)
{
/* for ABR alone, clip the first I frame qp */
lqmax = (m_isGrainEnabled && m_lstep) ? x265_qp2qScale(ABR_INIT_QP_GRAIN_MAX) :
x265_qp2qScale(ABR_INIT_QP_MAX);
q = X265_MIN(lqmax, q);
}
B帧

同CRF

VBV修正

VBV

vbv模型假想编码码率通过一个容量受限的信道传输到解码设备,解码设备在解码前有一个缓存,解码器实时从缓存区取数据解码,vbv必须保证缓存区不上溢也不下溢。

解码器的缓存可以理解成一个水池,如下图所示,水池的输入速度是恒定的,也就是设定的码率,水池的输出是不恒定的,就是视频的实际瞬时码率。

vbv model

缓存区的存在使得视频的瞬时码率可以在一定范围内波动,但是如果视频实际瞬时码率过大,从缓存区取数据的速度就会大于填数据的速度,这时缓存区有可能就被取空了,这叫做缓冲区的下溢。相反,如果视频实际瞬时码率过小,从缓存区取数据的速度就会小于填数据的速度,这时缓存区有可能就会溢出,这叫做缓存区的上溢。

参数设置:
–vbv-maxrate:设置缓存区最大填入速度(非CBR模式下,实际码率可以小于最大码率)
–vbv-bufsize:设置缓存区大小
–vbv-init:缓冲区初始充盈度,默认0.9
–min-vbv-fullness:缓冲区最小充盈度,默认50
–max-vbv-fullness:缓冲区最大充盈度,默认80

vbv能容忍的最大码率波动和缓存区的大小,以及缓存区初始充盈程度有关。
假如:设置maxrate=1M,bufsize = 2M,缓存区初始是充盈的,假设某时峰值率是2M,那么能容忍的峰值的最大长度为2/(2-1) = 2s;
假如:某时峰值率是3M,那么能容忍的峰值的最大长度为2/(3-1) = 1s;

获取速度:当前帧的预测大小
填入速度:vbv-maxrate / fps

CQP模式下
无VBV
ABR模式下
vbvBufferSize 必须设置
vbvMaxBitrate == 0 ? vbvMaxBitrate = bitrate
vbvMaxBitrate < bitrate ? bitrate = vbvMaxBitrate
也就是在ABR模式下,vbvMaxBitrate必须>=bitrate

CRF模式下
vbvBufferSize 必须设置
vbvMaxBitrate 必须设置

预测大小

帧类型预测器,有四种类型小B帧、P帧、I帧、大B帧

1
2
3
4
5
// p是当前预测类型对应的预测器,q是当前帧的QScale,var是当前帧的SATD值。
double RateControl::predictSize(Predictor *p, double q, double var)
{
return (p->coeff * var + p->offset) / (q * p->count);
}

每一帧编码结束后,会根据实际编码的码率来调整对应类型的预测器,使得下一次同类型帧的预测更加准确。
(((bits * q) / var) * var) / q

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// var是当前帧的SATD值,bits是当前帧最终编码的大小。
void RateControl::updatePredictor(Predictor *p, double q, double var, double bits)
{
if (var < 10)
return;
const double range = 2;
double old_coeff = p->coeff / p->count;
double old_offset = p->offset / p->count;
double new_coeff = X265_MAX((bits * q - old_offset) / var, p->coeffMin );
double new_coeff_clipped = x265_clip3(old_coeff / range, old_coeff * range, new_coeff);
double new_offset = bits * q - new_coeff_clipped * var;
if (new_offset >= 0)
new_coeff = new_coeff_clipped;
else
new_offset = 0;
p->count *= p->decay;
p->coeff *= p->decay;
p->offset *= p->decay;
p->count++;
p->coeff += new_coeff;
p->offset += new_offset;
}

修正

预测当前帧以及后续帧的predictSize,模拟VBV的获取和填充,根据VBV剩余的充盈度来调整当前帧的QP,使得VBV充盈度保持在固定的范围内,当缓冲区保持在固定的水平,则说明输入和输出大致持平,也就是输出的实际码率和期望的码率相持平。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce, double q)
{
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))
{
int loopTerminate = 0;
/* Avoid an infinite loop. */
// 轮询调整QP,退出轮询有三种情况
// 1. 使用当前QP进行模拟后,VBV充盈度在合理范围内
// 2. 当前QP经过了上调又经过了下调,说明调整QP依旧无法使得VBV充盈度在合理范围内,继续下去只会浪费
// 3. 轮询1000次
for (int iterations = 0; iterations < 1000 && loopTerminate != 3; iterations++)
{
double frameQ[3];
double curBits;
// 预测当前帧的大小
curBits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd);
// m_bufferFill是当前VBV的充盈度,这里是VBV中获取当前帧大小的buffer
double bufferFillCur = m_bufferFill - curBits;
double targetFill;
double totalDuration = m_frameDuration;
// 后续帧由于没有开始编码,没有QP,使用当前帧的QP进行预测
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;
// 模拟后续1秒内所有帧的获取和填充
// 退出模拟有三种情况
// 1. VBV的充盈度小于0,发生了下溢
// 2. 已经预测了一秒内所有帧
// 3. 后续帧类型还没有预测
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;
// 没有上溢,填入
if (bufferFillCur + wantedFrameSize <= m_bufferSize)
bufferFillCur += wantedFrameSize;
// 获取下一帧SATD
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;
int predType = getPredictorType(curFrame->m_lowres.plannedType[j], type);
// 预测下一帧大小
curBits = predictSize(&m_pred[predType], frameQ[type], (double)satd);
// 从VBV中获取
bufferFillCur -= curBits;
if (!m_param->bResetZoneConfig && ((uint64_t)j == (m_param->reconfigWindowSize - 1)))
iter = false;
}

{
/* Try to get the buffer at least 50% filled, but don't set an impossible goal. */
double finalDur = 1;
if (m_param->rc.bStrictCbr)
{
finalDur = x265_clip3(0.4, 1.0, totalDuration);
}
// m_bufferSize * (1 - m_minBufferFill * finalDur) 是外部设置的最小充盈度
targetFill = X265_MIN(m_bufferFill + totalDuration * m_vbvMaxRate * 0.5, m_bufferSize * (1 - m_minBufferFill * finalDur));
if (bufferFillCur < targetFill)
{
// 充盈度小于最小充盈度时,说明predictSize过大,需要提高QP
q *= 1.01;
loopTerminate |= 1;
continue;
}
/* Try to get the buffer not more than 80% filled, but don't set an impossible goal. */
// m_bufferSize * (1 - m_maxBufferFill * finalDur) 是外部设置的最大充盈度
targetFill = x265_clip3(m_bufferSize * (1 - m_maxBufferFill * finalDur), m_bufferSize, m_bufferFill - totalDuration * m_vbvMaxRate * 0.5);
// 非CBR模式下,实际码率可以小于最大码率
if ((m_isCbr || m_2pass) && bufferFillCur > targetFill && !m_isSceneTransition)
{
// 充盈度大于最大充盈度,说明predictSize过小,需要减小QP
q /= 1.01;
loopTerminate |= 2;
continue;
}
break;
}
}
q = X265_MAX(q0 / 2, q);
}

}

return x265_clip3(lmin, lmax, q);
}

CBR

在ABR+VBV模式下,并且bitrate==vbvMaxBitrate,开启CBR模式

1
2
// 当bitrate == MaxBitrate时,开启cbr;
m_isCbr = m_param->rc.rateControlMode == X265_RC_ABR && m_isVbv && m_param->rc.vbvMaxBitrate <= m_param->rc.bitrate;

开启CBR后,再VBV修正时,才会将码率控制在指定码率上,码率不足会降低QP

1
2
3
4
5
6
7
8
// CBR模式下,降低QP,提高码率到指定码率
if ((m_isCbr || m_2pass) && bufferFillCur > targetFill && !m_isSceneTransition)
{
// 充盈度大于最大充盈度,说明predictSize过小,需要减小QP
q /= 1.01;
loopTerminate |= 2;
continue;
}
StrictCbr

只有在CBR模式下,才能开启严格CBR模式,开启StrictCbr模式后,当VBV上溢时,会将溢出的部分取出,填入oxff数据到编码数据中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int RateControl::updateVbv(int64_t bits, RateControlEntry* rce)
{
int predType = rce->sliceType;
int filler = 0;
double bufferBits;

m_bufferFillFinal -= bits;
m_bufferFillFinal = X265_MAX(m_bufferFillFinal, 0);
m_bufferFillFinal += rce->bufferRate;
if (m_param->csvLogLevel >= 2)
m_unclippedBufferFillFinal = m_bufferFillFinal;

if (m_param->rc.bStrictCbr)
{
// 当缓冲区充盈度超过了缓冲区大小,发生上溢
if (m_bufferFillFinal > m_bufferSize)
{
// 取出上溢的部分
filler = (int)(m_bufferFillFinal - m_bufferSize);
filler += FILLER_OVERHEAD * 8;
}
// 消除溢出
m_bufferFillFinal -= filler;
bufferBits = X265_MIN(bits + filler + m_bufferExcess, rce->bufferRate);
m_bufferExcess = X265_MAX(m_bufferExcess - bufferBits + bits + filler, 0);
m_bufferFillActual += bufferBits - bits - filler;
}

return filler;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (filler > 0)
{
filler = (filler - FILLER_OVERHEAD * 8) >> 3;
m_bs.resetBits();
// 向编码数据流中写入0xff,强行增加NALU的大小,解码器会忽略这些数据
while (filler > 0)
{
m_bs.write(0xff, 8);
filler--;
}
m_bs.writeByteAlignment();
m_nalList.serialize(NAL_UNIT_FILLER_DATA, m_bs);
bytes += m_nalList.m_nal[m_nalList.m_numNal - 1].sizeBytes;
bytes -= 3; //exclude start code prefix
m_accessUnitBits = bytes << 3;
}

CQP

固定QP(Constant QP),量化参数控制着压缩的大小,QP越大压缩率越高同时质量越低,QP越小压缩率越低同时质量越高,在H.265中QP的范围是0-51之间的整数,采用CQP模式会导致根据场景复杂度不同比特率波动很大,无法控制实际比特率。CQP模式下没有VBV。

在无损模式下,P帧、I帧、B帧的QP都是一样的,在非无损模式下,外部设置的是P帧的QP,I帧和B帧会根据偏移系数进行偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
m_qp = m_param->rc.qp;

if (m_param->rc.rateControlMode == X265_RC_CQP)
{
if (m_qp && !m_param->bLossless)
{
m_qpConstant[P_SLICE] = m_qp;
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;
}
}

对于大B帧,其QP是B帧和P帧的平均值

1
2
3
4
if (m_sliceType == B_SLICE && IS_REFERENCED(curFrame))
m_qp = (m_qpConstant[B_SLICE] + m_qpConstant[P_SLICE]) / 2; // Bref
else
m_qp = m_qpConstant[m_sliceType]; // I/B/P