码率控制 视频编码的目标是尽可能多的节省比特的同时尽量保持视频质量,码率控制是平衡码率和质量的重要工具。
码率控制并不是视频编码框架中具体的一个模块,而是在整个视频编码过程中扮演控制着的角色,一方面接收来自编码器外部的要求(编码缓冲区大小、人为要求的目标输出码率),另一方面分析并处理这些要求,为了完成要求对编码参数做出选择(量化参数、编码模式、运动矢量)。
在整个码率控制过程中,最为重要的三个编码参数是量化参数、编码模式、运动矢量。
在工业实现中一般都是选择量化参数建立率失真模型,将量化参数和编码模式、运动矢量分开,先有码率控制模块确定量化参数,再由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; ... 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); 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 { ... { 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++; 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){ 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) { 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){ m_cplxrSum += (bits * x265_qp2qScale (rce->qpaRc) / rce->qRceq) - (rce->rowCplxrSum); } else { 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){ 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-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 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 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 ; for (int iterations = 0 ; iterations < 1000 && loopTerminate != 3 ; iterations++) { double frameQ[3 ]; double curBits; curBits = predictSize (&m_pred[m_predType], q, (double )m_currentSatd); double bufferFillCur = m_bufferFill - curBits; double targetFill; double totalDuration = m_frameDuration; 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; 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; if (bufferFillCur + wantedFrameSize <= m_bufferSize) bufferFillCur += wantedFrameSize; 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); bufferFillCur -= curBits; if (!m_param->bResetZoneConfig && ((uint64_t )j == (m_param->reconfigWindowSize - 1 ))) iter = false ; } { 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 * (1 - m_minBufferFill * finalDur)); if (bufferFillCur < targetFill) { q *= 1.01 ; loopTerminate |= 1 ; continue ; } targetFill = x265_clip3 (m_bufferSize * (1 - m_maxBufferFill * finalDur), m_bufferSize, m_bufferFill - totalDuration * m_vbvMaxRate * 0.5 ); if ((m_isCbr || m_2pass) && bufferFillCur > targetFill && !m_isSceneTransition) { 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 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 if ((m_isCbr || m_2pass) && bufferFillCur > targetFill && !m_isSceneTransition){ 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 (); 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 ; 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 ; else m_qp = m_qpConstant[m_sliceType];