[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