[x265] [PATCH] Add support to configure Bitrate, CRF, QP at frame level
Kirithika Kalirathnam
kirithika at multicorewareinc.com
Tue Nov 12 09:31:15 UTC 2024
>From 3f5c5bac03486d73ec5287f7e9138d7577d33b3c Mon Sep 17 00:00:00 2001
From: Karam Singh <karam.singh at multicorewareinc.com>
Date: Tue, 8 Oct 2024 12:54:35 +0530
Subject: [PATCH] Add support to configure Bitrate,CRF,QP at frame level
---
source/common/frame.cpp | 4 ++
source/common/frame.h | 7 +++
source/encoder/api.cpp | 2 +
source/encoder/encoder.cpp | 53 +++++++++++++++-
source/encoder/ratecontrol.cpp | 110 ++++++++++++++++++++++++++++++---
source/encoder/ratecontrol.h | 3 +
source/x265.h | 1 +
7 files changed, 169 insertions(+), 11 deletions(-)
diff --git a/source/common/frame.cpp b/source/common/frame.cpp
index fbecfb4e9..f497979f1 100644
--- a/source/common/frame.cpp
+++ b/source/common/frame.cpp
@@ -81,6 +81,10 @@ Frame::Frame()
m_valid = 0;
m_nextSubDPB = NULL;
m_prevSubDPB = NULL;
+
+ m_targetBitrate = 0;
+ m_targetCrf = 0;
+ m_targetQp = 0;
}
bool Frame::create(x265_param *param, float* quantOffsets)
diff --git a/source/common/frame.h b/source/common/frame.h
index 588fa6696..035c0655c 100644
--- a/source/common/frame.h
+++ b/source/common/frame.h
@@ -175,6 +175,13 @@ public:
Frame* m_nextSubDPB; // PicList doubly
linked list pointers
Frame* m_prevSubDPB;
+ /*Target bitrate*/
+ int64_t m_targetBitrate;
+ /* target CRF for this picture.*/
+ int m_targetCrf;
+ /* target QP for this picture.*/
+ int m_targetQp;
+
Frame();
bool create(x265_param *param, float* quantOffsets);
diff --git a/source/encoder/api.cpp b/source/encoder/api.cpp
index 9d8650008..df8f9a9d1 100644
--- a/source/encoder/api.cpp
+++ b/source/encoder/api.cpp
@@ -1398,6 +1398,7 @@ FILE* x265_csvlog_open(const x265_param* param)
#if ENABLE_LIBVMAF
fprintf(csvfp, ", VMAF Frame Score");
#endif
+ fprintf(csvfp, ", Target bitrate");
}
fprintf(csvfp, "\n");
}
@@ -1525,6 +1526,7 @@ void x265_csvlog_frame(const x265_param* param, const
x265_picture* pic)
#if ENABLE_LIBVMAF
fprintf(param->csvfpt, ", %lf", frameStats->vmafFrameScore);
#endif
+ fprintf(param->csvfpt, ", %I64d", frameStats->currTrBitrate);
}
fprintf(param->csvfpt, "\n");
fflush(stderr);
diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp
index 09f58c62a..dc0e344f6 100644
--- a/source/encoder/encoder.cpp
+++ b/source/encoder/encoder.cpp
@@ -1810,7 +1810,25 @@ int Encoder::encode(const x265_picture* pic_in,
x265_picture* pic_out)
}
if (m_reconfigureRc)
+ {
+ if (m_param->rc.rateControlMode == X265_RC_ABR)
+ inFrame[0]->m_targetBitrate = m_latestParam->rc.bitrate;
+ else if (m_param->rc.rateControlMode == X265_RC_CRF)
+ inFrame[0]->m_targetCrf =
(int)m_latestParam->rc.rfConstant;
+ else if (m_param->rc.rateControlMode == X265_RC_CQP)
+ inFrame[0]->m_targetQp = m_latestParam->rc.qp;
inFrame[0]->m_reconfigureRc = true;
+ m_reconfigureRc = false;
+ }
+ else
+ {
+ if (m_param->rc.rateControlMode == X265_RC_ABR)
+ inFrame[0]->m_targetBitrate = m_latestParam->rc.bitrate;
+ else if (m_param->rc.rateControlMode == X265_RC_CRF)
+ inFrame[0]->m_targetCrf =
(int)m_latestParam->rc.rfConstant;
+ else if (m_param->rc.rateControlMode == X265_RC_CQP)
+ inFrame[0]->m_targetQp = m_latestParam->rc.qp;
+ }
if (m_param->bEnableTemporalFilter)
{
@@ -1994,6 +2012,16 @@ int Encoder::encode(const x265_picture* pic_in,
x265_picture* pic_out)
if (m_param->bUseAnalysisFile)
x265_free_analysis_data(m_param,
&pic_out[sLayer].analysisData);
}
+ if (sLayer == 0)
+ {
+ if (outFrame->m_targetBitrate &&
m_param->rc.rateControlMode == X265_RC_ABR)
+ pic_out[sLayer].frameData.currTrBitrate =
outFrame->m_targetBitrate;
+ else
+ pic_out[sLayer].frameData.currTrBitrate = 0;
+ }
+ else
+ x265_log(m_param, X265_LOG_WARNING, "Frame wise
bitrate reconfigure are not supported for enhancement layers\n");
+
}
if (m_param->rc.bStatWrite &&
(m_param->analysisMultiPassRefine || m_param->analysisMultiPassDistortion))
{
@@ -2207,12 +2235,28 @@ int Encoder::encode(const x265_picture* pic_in,
x265_picture* pic_out)
}
}
- if (frameEnc[0]->m_reconfigureRc && m_reconfigureRc)
+ if (frameEnc[0]->m_reconfigureRc)
{
- x265_copy_params(m_param, m_latestParam);
+ m_rateControl->m_bRcReConfig = true;
+ if (m_param->rc.rateControlMode == X265_RC_ABR)
+ {
+ m_param->rc.bitrate =
(int)frameEnc[0]->m_targetBitrate;
+ m_rateControl->m_param->rc.bitrate =
(int)frameEnc[0]->m_targetBitrate;
+ }
+ else if (m_param->rc.rateControlMode == X265_RC_CRF)
+ {
+ m_param->rc.rfConstant =
(double)frameEnc[0]->m_targetCrf;
+ m_rateControl->m_param->rc.rfConstant =
frameEnc[0]->m_targetCrf;
+ }
+ else if (m_param->rc.rateControlMode == X265_RC_CQP)
+ {
+ m_param->rc.qp = frameEnc[0]->m_targetQp;
+ m_rateControl->m_param->rc.qp =
frameEnc[0]->m_targetQp;
+ }
m_rateControl->reconfigureRC();
m_reconfigureRc = false;
}
+
if (frameEnc[0]->m_reconfigureRc && !m_reconfigureRc)
frameEnc[0]->m_reconfigureRc = false;
if (curEncoder->m_reconfigure)
@@ -2483,6 +2527,8 @@ int Encoder::reconfigureParam(x265_param* encParam,
x265_param* param)
encParam->rc.bitrate = param->rc.bitrate;
m_reconfigureRc |= encParam->rc.rfConstant != param->rc.rfConstant;
encParam->rc.rfConstant = param->rc.rfConstant;
+ m_reconfigureRc |= encParam->rc.qp != param->rc.qp;
+ encParam->rc.qp = param->rc.qp;
}
else
{
@@ -2541,7 +2587,8 @@ bool Encoder::isReconfigureRc(x265_param*
latestParam, x265_param* param_in)
return (latestParam->rc.vbvMaxBitrate != param_in->rc.vbvMaxBitrate
|| latestParam->rc.vbvBufferSize != param_in->rc.vbvBufferSize
|| latestParam->rc.bitrate != param_in->rc.bitrate
- || latestParam->rc.rfConstant != param_in->rc.rfConstant);
+ || latestParam->rc.rfConstant != param_in->rc.rfConstant
+ || latestParam->rc.qp != param_in->rc.qp);
}
void Encoder::copyCtuInfo(x265_ctu_info_t** frameCtuInfo, int poc)
diff --git a/source/encoder/ratecontrol.cpp b/source/encoder/ratecontrol.cpp
index e46896987..ec4695125 100644
--- a/source/encoder/ratecontrol.cpp
+++ b/source/encoder/ratecontrol.cpp
@@ -352,6 +352,8 @@ RateControl::RateControl(x265_param& p, Encoder *top)
}
}
+ m_bRcReConfig = false;
+
/* qpstep - value set as encoder specific */
m_lstep = pow(2, m_param->rc.qpStep / 6.0);
@@ -458,6 +460,7 @@ bool RateControl::init(const SPS& sps)
m_partialResidualCost = 0;
m_amortizeFraction = 0.85;
m_amortizeFrames = 75;
+ m_bRcReConfig = false;
if (m_param->totalFrames && m_param->totalFrames <= 2 * m_fps &&
m_param->rc.bStrictCbr) /* Strict CBR segment encode */
{
m_amortizeFraction = 0.85;
@@ -1613,6 +1616,11 @@ int RateControl::rateControlStart(Frame* curFrame,
RateControlEntry* rce, Encode
else
m_qp -= (int)(6.0 * X265_LOG2(zone->bitrateFactor));
}
+ if (m_bRcReConfig)
+ {
+ m_qp = curFrame->m_targetQp;
+ curEncData.m_avgQpAq = curEncData.m_avgQpRc = m_qp;
+ }
}
if (m_sliceType != B_SLICE)
{
@@ -1855,10 +1863,14 @@ double
RateControl::tuneAbrQScaleFromFeedback(double qScale)
}
if (wantedBits > 0 && encodedBits > 0 && (!m_partialResidualFrames ||
- m_param->rc.bStrictCbr || m_isGrainEnabled))
+ m_param->rc.bStrictCbr || m_isGrainEnabled || (m_bRcReConfig &&
m_param->rc.rateControlMode == X265_RC_ABR)))
{
abrBuffer *= X265_MAX(1, sqrt(timeDone));
overflow = x265_clip3(.5, 2.0, 1.0 + (encodedBits - wantedBits) /
abrBuffer);
+ if (m_bRcReConfig && overflow > 1.05)
+ qScale *= m_lstep;
+ if (m_bRcReConfig && overflow < 0.95)
+ qScale /= m_lstep;
qScale *= overflow;
}
return qScale;
@@ -1918,6 +1930,16 @@ double RateControl::rateEstimateQscale(Frame*
curFrame, RateControlEntry *rce)
}
}
+ if (m_bRcReConfig)
+ {
+ if (m_param->rc.rateControlMode == X265_RC_ABR)
+ m_bitrate = (double)curFrame->m_targetBitrate * 1000;
+ else if (m_param->rc.rateControlMode == X265_RC_CRF)
+ m_param->rc.rfConstant = (double)curFrame->m_targetCrf;
+ else if (m_param->rc.rateControlMode == X265_RC_CQP)
+ m_param->rc.qp = curFrame->m_targetQp;
+ }
+
if ((m_param->bliveVBV2pass && m_param->rc.rateControlMode ==
X265_RC_ABR) || m_isAbr)
{
int pos = m_sliderPos % s_slidingWindowFrames;
@@ -1997,6 +2019,16 @@ double RateControl::rateEstimateQscale(Frame*
curFrame, RateControlEntry *rce)
m_lastQScaleFor[P_SLICE] = X265_MAX(minScenecutQscale,
m_lastQScaleFor[P_SLICE]);
}
+ if (m_bRcReConfig && m_param->rc.rateControlMode == X265_RC_CRF)
+ {
+ double rfConstant = m_param->rc.rfConstant;
+ double mbtree_offset = m_param->rc.cuTree ? (1.0 -
m_param->rc.qCompress) * 13.5 : 0;
+ double qComp = (m_param->rc.cuTree && !m_param->rc.hevcAq) ?
0.99 : m_param->rc.qCompress;
+ m_rateFactorConstant = pow(m_currentSatd, 1.0 - qComp) /
+ x265_qp2qScale(rfConstant + mbtree_offset);
+ double qScale = getQScale(rce, m_rateFactorConstant);
+ q = x265_qScale2qp(qScale);
+ }
double qScale = x265_qp2qScale(q);
rce->qpNoVbv = q;
@@ -2068,6 +2100,13 @@ double RateControl::rateEstimateQscale(Frame*
curFrame, RateControlEntry *rce)
qScale = x265_clip3(lmin, lmax, qScale);
m_lastQScaleFor[m_sliceType] = qScale;
}
+
+ if (m_bRcReConfig && m_param->rc.rateControlMode ==
X265_RC_ABR)
+ {
+ qScale = tuneQscaleToUpdatedBitrate(curFrame, qScale);
+ rce->qpNoVbv = x265_qScale2qp(qScale);
+ m_lastQScaleFor[m_sliceType] = qScale;
+ }
}
if (m_2pass)
@@ -2238,6 +2277,14 @@ double RateControl::rateEstimateQscale(Frame*
curFrame, RateControlEntry *rce)
m_rateFactorConstant = pow(m_currentSatd, 1.0 - qComp)
/
x265_qp2qScale(rfConstant + mbtree_offset);
}
+ if (m_bRcReConfig)
+ {
+ double rfConstant = m_param->rc.rfConstant;
+ double mbtree_offset = m_param->rc.cuTree ? (1.0 -
m_param->rc.qCompress) * 13.5 : 0;
+ double qComp = (m_param->rc.cuTree &&
!m_param->rc.hevcAq) ? 0.99 : m_param->rc.qCompress;
+ m_rateFactorConstant = pow(m_currentSatd, 1.0 - qComp)
/
+ x265_qp2qScale(rfConstant + mbtree_offset);
+ }
q = getQScale(rce, m_rateFactorConstant);
x265_zone* zone = getZone();
if (zone)
@@ -2263,8 +2310,8 @@ double RateControl::rateEstimateQscale(Frame*
curFrame, RateControlEntry *rce)
}
double tunedQScale =
tuneAbrQScaleFromFeedback(initialQScale);
overflow = tunedQScale / initialQScale;
- q = !m_partialResidualFrames ? tunedQScale : initialQScale;
- bool isEncodeEnd = (m_param->totalFrames &&
+ q = (!m_partialResidualFrames || m_bRcReConfig) ?
tunedQScale : initialQScale;
+ 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)
@@ -2290,7 +2337,7 @@ double RateControl::rateEstimateQscale(Frame*
curFrame, RateControlEntry *rce)
{
lqmin = m_lastQScaleFor[m_sliceType] / m_lstep;
lqmax = m_lastQScaleFor[m_sliceType] * m_lstep;
- if (!m_partialResidualFrames || m_isGrainEnabled)
+ if (!m_partialResidualFrames || m_isGrainEnabled ||
m_bRcReConfig)
{
if (overflow > 1.1 && m_framesDone > 3)
lqmax *= m_lstep;
@@ -2350,6 +2397,12 @@ double RateControl::rateEstimateQscale(Frame*
curFrame, RateControlEntry *rce)
}
q = clipQscale(curFrame, rce, q);
+ if (m_bRcReConfig && m_param->rc.rateControlMode ==
X265_RC_ABR)
+ {
+ q = tuneQscaleToUpdatedBitrate(curFrame, q);
+ rce->qpNoVbv = x265_qScale2qp(q);
+ }
+
if (m_2pass)
rce->frameSizePlanned = qScale2bits(rce, q);
else
@@ -2540,6 +2593,41 @@ double RateControl::tuneQscaleForSBRC(Frame*
curFrame, double q)
return q;
}
+double RateControl::tuneQscaleToUpdatedBitrate(Frame* curFrame, double q)
+{
+ int depth = 18;
+
+ for (int iterations = 0; iterations < 100; iterations++)
+ {
+ int i;
+ double frameBitsTotal = m_fps * predictSize(&m_pred[m_predType],
q, (double)m_currentSatd);
+ for (i = 0; i < depth; i++)
+ {
+ int type = curFrame->m_lowres.plannedType[i];
+ if (type == X265_TYPE_AUTO)
+ break;
+ int64_t satd = curFrame->m_lowres.plannedSatd[i] >>
(X265_DEPTH - 8);
+ type = IS_X265_TYPE_I(curFrame->m_lowres.plannedType[i]) ?
I_SLICE : IS_X265_TYPE_B(curFrame->m_lowres.plannedType[i]) ? B_SLICE :
P_SLICE;
+ int predType =
getPredictorType(curFrame->m_lowres.plannedType[i], type);
+ double curBits = m_fps * predictSize(&m_pred[predType], q,
(double)satd);
+ frameBitsTotal += curBits;
+ }
+ frameBitsTotal /= i;
+ double allowedSize = (double)(curFrame->m_targetBitrate * 1000);
+ if (frameBitsTotal >= 1.1 * allowedSize)
+ q = q * 1.1;
+ else if (frameBitsTotal >= 1.05 * allowedSize)
+ q = q * 1.05;
+ else if (frameBitsTotal <= 0.9 * allowedSize)
+ q = q / 1.1;
+ else if (frameBitsTotal <= 0.95 * allowedSize)
+ q = q / 1.05;
+ else
+ break;
+ }
+ return q;
+}
+
double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce,
double q)
{
// B-frames are not directly subject to VBV,
@@ -2577,7 +2665,8 @@ double RateControl::clipQscale(Frame* curFrame,
RateControlEntry* rce, double q)
if (type == X265_TYPE_AUTO || totalDuration >= 1.0)
break;
totalDuration += m_frameDuration;
- double wantedFrameSize = m_vbvMaxRate *
m_frameDuration;
+ double wantedFrameSize = ((m_bRcReConfig &&
m_param->rc.rateControlMode == X265_RC_ABR) ?
+ curFrame->m_targetBitrate * 1000 : m_vbvMaxRate) *
m_frameDuration;
if (bufferFillCur + wantedFrameSize <= m_bufferSize)
bufferFillCur += wantedFrameSize;
int64_t satd = curFrame->m_lowres.plannedSatd[j] >>
(X265_DEPTH - 8);
@@ -3105,8 +3194,13 @@ int RateControl::rateControlEnd(Frame* curFrame,
int64_t bits, RateControlEntry*
crfVal = x265_qScale2qp(pow(baseCplx, 1 - m_qCompress) /
crfFactor) - mbtree_offset;
}
else
- crfVal = rce->sliceType == I_SLICE ? m_param->rc.rfConstant -
m_ipOffset :
- (rce->sliceType == B_SLICE ? m_param->rc.rfConstant +
m_pbOffset : m_param->rc.rfConstant);
+ {
+ if (m_bRcReConfig)
+ crfVal = curFrame->m_targetCrf;
+ else
+ crfVal = rce->sliceType == I_SLICE ?
(m_param->rc.rfConstant - m_ipOffset) :
+ (rce->sliceType == B_SLICE ? (m_param->rc.rfConstant +
m_pbOffset) : m_param->rc.rfConstant);
+ }
curEncData.m_rateFactor = crfVal;
}
@@ -3144,7 +3238,7 @@ int RateControl::rateControlEnd(Frame* curFrame,
int64_t bits, RateControlEntry*
* 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;
+ m_wantedBitsWindow += m_frameDuration * (m_bRcReConfig ?
(curFrame->m_targetBitrate * 1000) : m_bitrate);
m_totalBits += bits - rce->rowTotalBits;
m_encodedBits += actualBits;
m_encodedSegmentBits += actualBits;
diff --git a/source/encoder/ratecontrol.h b/source/encoder/ratecontrol.h
index 7becd94ee..9a6b889b8 100644
--- a/source/encoder/ratecontrol.h
+++ b/source/encoder/ratecontrol.h
@@ -249,6 +249,8 @@ public:
RateControlEntry* m_rce2Pass;
Encoder* m_top;
+ bool m_bRcReConfig; /* RC-reconfigurtion */
+
struct
{
uint16_t *qpBuffer[2]; /* Global buffers for converting MB-tree
quantizer data. */
@@ -301,6 +303,7 @@ protected:
double tuneAbrQScaleFromFeedback(double qScale);
double tuneQScaleForZone(RateControlEntry *rce, double qScale); //
Tune qScale to adhere to zone budget
double tuneQscaleForSBRC(Frame* curFrame, double q); // Tune qScale to
adhere to segment budget
+ double tuneQscaleToUpdatedBitrate(Frame* curFrame, double q); // Tune
qScale according to updated bitrate
void accumPQpUpdate();
int getPredictorType(int lowresSliceType, int sliceType);
diff --git a/source/x265.h b/source/x265.h
index b4dceb406..e71eaaff8 100644
--- a/source/x265.h
+++ b/source/x265.h
@@ -313,6 +313,7 @@ typedef struct x265_frame_stats
double bufferFillFinal;
double unclippedBufferFillFinal;
uint8_t tLayer;
+ int64_t currTrBitrate;
} x265_frame_stats;
typedef struct x265_ctu_info_t
--
2.28.0.windows.1
*Thanks,*
*Kirithika*
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/x265-devel/attachments/20241112/46f7ef3a/attachment-0001.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: patch1-frame-rc.diff
Type: application/octet-stream
Size: 18999 bytes
Desc: not available
URL: <http://mailman.videolan.org/pipermail/x265-devel/attachments/20241112/46f7ef3a/attachment-0001.obj>
More information about the x265-devel
mailing list