Coverage Report

Created: 2026-04-01 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vvenc/source/Lib/EncoderLib/RateCtrl.cpp
Line
Count
Source
1
/* -----------------------------------------------------------------------------
2
The copyright in this software is being made available under the Clear BSD
3
License, included below. No patent rights, trademark rights and/or
4
other Intellectual Property Rights other than the copyrights concerning
5
the Software are granted under this license.
6
7
The Clear BSD License
8
9
Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVenC Authors.
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without modification,
13
are permitted (subject to the limitations in the disclaimer below) provided that
14
the following conditions are met:
15
16
     * Redistributions of source code must retain the above copyright notice,
17
     this list of conditions and the following disclaimer.
18
19
     * Redistributions in binary form must reproduce the above copyright
20
     notice, this list of conditions and the following disclaimer in the
21
     documentation and/or other materials provided with the distribution.
22
23
     * Neither the name of the copyright holder nor the names of its
24
     contributors may be used to endorse or promote products derived from this
25
     software without specific prior written permission.
26
27
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
28
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
31
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
32
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
36
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38
POSSIBILITY OF SUCH DAMAGE.
39
40
41
------------------------------------------------------------------------------------------- */
42
43
44
/** \file     RateCtrl.cpp
45
    \brief    Rate control manager class
46
*/
47
#ifdef VVENC_ENABLE_THIRDPARTY_JSON
48
#include <nlohmann/json.hpp>
49
#endif
50
51
#include "vvenc/version.h"
52
#include "RateCtrl.h"
53
#include "CommonLib/Picture.h"
54
55
#include <cmath>
56
57
namespace vvenc {
58
59
//sequence level
60
EncRCSeq::EncRCSeq()
61
0
{
62
0
  twoPass             = false;
63
0
  isLookAhead         = false;
64
0
  isIntraGOP          = false;
65
0
  isRateSavingMode    = false;
66
0
  frameRate           = 0.0;
67
0
  targetRate          = 0;
68
0
  maxGopRate          = 0;
69
0
  gopSize             = 0;
70
0
  intraPeriod         = 0;
71
0
  bitsUsed            = 0;
72
0
  bitsUsedQPLimDiff   = 0;
73
0
  estimatedBitUsage   = 0;
74
0
  rateBoostFac        = 1.0;
75
0
  std::memset (qpCorrection, 0, sizeof (qpCorrection));
76
0
  std::memset (actualBitCnt, 0, sizeof (actualBitCnt));
77
0
  std::memset (targetBitCnt, 0, sizeof (targetBitCnt));
78
0
  lastAverageQP       = 0;
79
0
  lastIntraQP         = 0;
80
0
  lastIntraSM         = 0.0;
81
0
  scRelax             = false;
82
0
  bitDepth            = 0;
83
0
}
84
85
EncRCSeq::~EncRCSeq()
86
0
{
87
0
  destroy();
88
0
}
89
90
void EncRCSeq::create( bool twoPassRC, bool lookAhead, int targetBitrate, int maxBitrate, double frRate, int intraPer, int GOPSize, int bitDpth, std::list<TRCPassStats> &firstPassStats )
91
0
{
92
0
  destroy();
93
0
  twoPass             = twoPassRC;
94
0
  isLookAhead         = lookAhead;
95
0
  targetRate          = std::min( INT32_MAX / 3, targetBitrate );
96
0
  maxGopRate          = int( 0.5 + std::min( (double) INT32_MAX, (double) std::min( 3 * targetRate, maxBitrate ) * GOPSize / frRate ) ); // 1.5x-3x is the valid range
97
0
  frameRate           = frRate;
98
0
  intraPeriod         = Clip3<unsigned>( GOPSize, 4 * VVENC_MAX_GOP, intraPer );
99
0
  gopSize             = GOPSize;
100
0
  firstPassData       = firstPassStats;
101
0
  bitDepth            = bitDpth;
102
103
0
  int bitdepthLumaScale = 2 * ( bitDepth - 8 - DISTORTION_PRECISION_ADJUSTMENT( bitDepth ) );
104
0
  minEstLambda = 0.1;
105
0
  maxEstLambda = 65535.9375 * pow( 2.0, bitdepthLumaScale );
106
107
0
  bitsUsed            = 0;
108
0
  bitsUsedQPLimDiff   = 0;
109
0
  estimatedBitUsage   = 0;
110
0
  std::memset (qpCorrection, 0, sizeof (qpCorrection));
111
0
  std::memset (actualBitCnt, 0, sizeof (actualBitCnt));
112
0
  std::memset (targetBitCnt, 0, sizeof (targetBitCnt));
113
0
}
114
115
void EncRCSeq::destroy()
116
0
{
117
0
  return;
118
0
}
119
120
void EncRCSeq::updateAfterPic (const int actBits, const int tgtBits)
121
0
{
122
0
  estimatedBitUsage += tgtBits;
123
124
0
  if (isLookAhead)
125
0
  {
126
0
    const uint64_t* const tlBits = actualBitCnt; // recently updated in EncRCPic::updateAfterPicture()
127
128
0
    bitsUsed = tlBits[0] + tlBits[1] + tlBits[2] + tlBits[3] + tlBits[4] + tlBits[5] + tlBits[6] + tlBits[7];
129
0
  }
130
0
  else
131
0
  {
132
0
    bitsUsed += actBits;
133
0
  }
134
0
}
135
136
//picture level
137
EncRCPic::EncRCPic()
138
0
{
139
0
  encRCSeq            = NULL;
140
0
  frameLevel          = 0;
141
0
  targetBits          = 0;
142
0
  tmpTargetBits       = 0;
143
0
  picQP               = 0;
144
0
  picBits             = 0;
145
0
  poc                 = 0;
146
0
  refreshParams       = false;
147
0
  visActSteady        = 0;
148
0
}
149
150
EncRCPic::~EncRCPic()
151
0
{
152
0
  destroy();
153
0
}
154
155
void EncRCPic::addToPictureList( std::list<EncRCPic*>& listPreviousPictures )
156
0
{
157
0
  if ( listPreviousPictures.size() > std::min( VVENC_MAX_GOP, 2 * encRCSeq->gopSize ) )
158
0
  {
159
0
    EncRCPic* p = listPreviousPictures.front();
160
0
    listPreviousPictures.pop_front();
161
0
    p->destroy();
162
0
    delete p;
163
0
  }
164
165
0
  listPreviousPictures.push_back( this );
166
0
}
167
168
void EncRCPic::create( EncRCSeq* encRcSeq, int frameLvl, int framePoc )
169
0
{
170
0
  destroy();
171
0
  encRCSeq   = encRcSeq;
172
0
  poc        = framePoc;
173
0
  frameLevel = frameLvl;
174
0
}
175
176
void EncRCPic::destroy()
177
0
{
178
0
  encRCSeq = NULL;
179
0
}
180
181
void EncRCPic::clipTargetQP (std::list<EncRCPic*>& listPreviousPictures, const int baseQP, const int refrIncrFac, const int maxTL, const double resRatio, int &qp, int* qpAvg)
182
0
{
183
0
  const int rShift = (resRatio < 0.03125 ? 12 : (resRatio < 0.125 ? 13 : (resRatio < 0.5 ? 14 : 15)));
184
0
  const int initQP = qp;
185
0
  int lastCurrTLQP = -1;
186
0
  int lastPrevTLQP = -1;
187
0
  int lastMaxTLBits = 0;
188
0
  int halvedAvgQP  = -1;
189
0
  std::list<EncRCPic*>::iterator it;
190
191
0
  for (it = listPreviousPictures.begin(); it != listPreviousPictures.end(); it++)
192
0
  {
193
0
    if ((*it)->frameLevel == frameLevel && (*it)->picQP >= 0)  // current temporal level
194
0
    {
195
0
      lastCurrTLQP = (*it)->picQP;
196
0
    }
197
0
    if ((*it)->frameLevel == frameLevel - 1 && (*it)->picQP >= 0) // last temporal level
198
0
    {
199
0
      lastPrevTLQP = (frameLevel == 1 ? ((*it)->picQP * 3) >> 2 : std::max<int> (encRCSeq->lastIntraQP, (*it)->picQP));
200
0
    }
201
0
    if ((*it)->frameLevel == 1 && frameLevel == 0 && refreshParams && lastCurrTLQP < 0)
202
0
    {
203
0
      lastCurrTLQP = (*it)->picQP;
204
0
    }
205
0
    if ((*it)->frameLevel > maxTL && (*it)->poc + 32 + encRCSeq->gopSize > poc)
206
0
    {
207
0
      lastMaxTLBits += (*it)->picBits;
208
0
    }
209
0
    halvedAvgQP += (*it)->picQP;
210
0
  }
211
212
0
  if (qpAvg && !listPreviousPictures.empty()) *qpAvg = int ((halvedAvgQP + 1 + (listPreviousPictures.size() >> 1)) / listPreviousPictures.size());
213
214
0
  if (listPreviousPictures.size() >= 1) halvedAvgQP = int ((halvedAvgQP + 1 + listPreviousPictures.size()) / (2 * listPreviousPictures.size()));
215
0
  if (frameLevel <= 1 && lastPrevTLQP < halvedAvgQP) lastPrevTLQP = halvedAvgQP; // TL0I
216
0
  if (frameLevel == 1 && lastCurrTLQP < 0) lastCurrTLQP = encRCSeq->lastIntraQP; // TL0B
217
0
  if (frameLevel == 0 && !(lastMaxTLBits >> rShift) && maxTL) qp++; // frozen-image mode
218
219
0
  qp = Clip3 (frameLevel + std::max (0, baseQP >> 1), MAX_QP, qp);
220
221
0
  if (lastCurrTLQP >= 0) // limit QP changes among prev. frames from same temporal level
222
0
  {
223
0
    const int clipRange = (refreshParams ? 5 + (encRCSeq->intraPeriod + (encRCSeq->gopSize >> 1)) / encRCSeq->gopSize : std::max (3, 6 - (frameLevel >> 1)));
224
225
0
    qp = Clip3 (lastCurrTLQP - clipRange, std::min (MAX_QP, lastCurrTLQP + (refreshParams ? (refrIncrFac * clipRange) >> 1 : clipRange)), qp);
226
0
  }
227
0
  if (lastPrevTLQP >= 0) // prevent QP from being lower than QPs at lower temporal level
228
0
  {
229
0
    qp = Clip3 (std::min (MAX_QP, lastPrevTLQP + 1), MAX_QP, qp);
230
0
  }
231
0
  else if (encRCSeq->lastIntraQP >= -1 && (frameLevel == 1 || frameLevel == 2))
232
0
  {
233
0
    qp = Clip3 ((encRCSeq->lastIntraQP >> 1) + 1, MAX_QP, qp);
234
0
  }
235
236
0
  if (qp != initQP) // adjust target bits according to QP change as in VCIP paper eq.(4)
237
0
  {
238
0
    targetBits = (int) std::max (1.0, 0.5 + targetBits * pow (2.0, (initQP - qp) / ((105.0 / 128.0) * sqrt ((double) std::max (1, initQP)))));
239
0
  }
240
0
}
241
242
void EncRCPic::updateAfterPicture (const int picActualBits, const int averageQP)
243
0
{
244
0
  picQP = averageQP;
245
0
  picBits = (uint16_t) Clip3 (0, 65535, picActualBits);
246
247
0
  if ((frameLevel <= 7) && (picActualBits > 0) && (targetBits > 0)) // update, for initRateControlPic()
248
0
  {
249
0
    const uint16_t vaMin = 1u << (encRCSeq->bitDepth - 6);
250
0
    const double clipVal = (visActSteady > 0 && visActSteady < vaMin + 48 ? 0.25 * (visActSteady - vaMin) : 12.0);
251
252
0
    encRCSeq->actualBitCnt[frameLevel] += (uint64_t) picActualBits;
253
0
    encRCSeq->targetBitCnt[frameLevel] += (uint64_t) targetBits;
254
255
0
    if (refreshParams && frameLevel <= 2) encRCSeq->lastAverageQP = 0;
256
257
0
    encRCSeq->qpCorrection[frameLevel] = (105.0 / 128.0) * sqrt ((double) std::max (1, encRCSeq->lastAverageQP)) * log ((double) encRCSeq->actualBitCnt[frameLevel] / (double) encRCSeq->targetBitCnt[frameLevel]) / log (2.0); // adjust target bits as in VCIP paper eq.(4)
258
0
    encRCSeq->qpCorrection[frameLevel] = Clip3 (-clipVal, clipVal, encRCSeq->qpCorrection[frameLevel]);
259
260
0
    if (frameLevel > std::max (1, int (log ((double) encRCSeq->gopSize) / log (2.0))))
261
0
    {
262
0
      double highTlQpCorr = 0.0;
263
264
0
      for (int l = 2; l <= 7; l++) // stabilization when corrections differ between low and high levels
265
0
      {
266
0
        highTlQpCorr += encRCSeq->qpCorrection[l];
267
0
      }
268
0
      if (highTlQpCorr > 1.0) // attenuate low-level QP correction towards 0 when bits need to be saved
269
0
      {
270
0
        if (encRCSeq->qpCorrection[0] < -1.0e-9) encRCSeq->qpCorrection[0] /= highTlQpCorr;
271
0
        if (encRCSeq->qpCorrection[1] < -1.0e-9) encRCSeq->qpCorrection[1] /= highTlQpCorr;
272
0
      }
273
0
    }
274
0
  }
275
0
}
276
277
RateCtrl::RateCtrl(MsgLog& logger)
278
0
: msg ( logger )
279
0
{
280
0
  m_pcEncCfg           = nullptr;
281
0
  encRCSeq             = NULL;
282
0
  encRCPic             = NULL;
283
0
  flushPOC             = -1;
284
0
  rcPass               = 0;
285
0
  rcIsFinalPass        = true;
286
0
#ifdef VVENC_ENABLE_THIRDPARTY_JSON
287
0
  m_pqpaStatsWritten   = 0;
288
0
#endif
289
0
  m_numPicStatsTotal   = 0;
290
0
  m_numPicAddedToList  = 0;
291
0
  m_updateNoisePoc     = -1;
292
0
  m_resetNoise         = true;
293
0
  m_gopMEErrorCBufIdx  = 0;
294
0
  m_maxPicMotionError  = 0;
295
0
  std::fill_n( m_gopMEErrorCBuf, QPA_MAX_NOISE_LEVELS, 0 );
296
0
  std::fill_n( m_minNoiseLevels, QPA_MAX_NOISE_LEVELS, 255u );
297
0
  std::fill_n( m_tempDownSamplStats, VVENC_MAX_TLAYER + 1, TRCPassStats() );
298
0
}
299
300
RateCtrl::~RateCtrl()
301
0
{
302
0
  destroy();
303
0
}
304
305
void RateCtrl::destroy()
306
0
{
307
0
  if ( encRCSeq != NULL )
308
0
  {
309
0
    delete encRCSeq;
310
0
    encRCSeq = NULL;
311
0
  }
312
0
  while ( m_listRCPictures.size() > 0 )
313
0
  {
314
0
    EncRCPic* p = m_listRCPictures.front();
315
0
    m_listRCPictures.pop_front();
316
0
    delete p;
317
0
  }
318
319
0
#ifdef VVENC_ENABLE_THIRDPARTY_JSON
320
0
  if ( m_rcStatsFHandle.is_open() )
321
0
  {
322
0
    m_rcStatsFHandle.close();
323
0
  }
324
0
  m_pqpaStatsWritten = 0;
325
0
#endif
326
0
}
327
328
void RateCtrl::init( const VVEncCfg& encCfg )
329
0
{
330
0
  destroy();
331
332
0
  m_pcEncCfg = &encCfg;
333
334
0
  encRCSeq = new EncRCSeq;
335
0
  encRCSeq->create( m_pcEncCfg->m_RCNumPasses == 2, m_pcEncCfg->m_LookAhead == 1, m_pcEncCfg->m_RCTargetBitrate, m_pcEncCfg->m_RCMaxBitrate, (double)m_pcEncCfg->m_FrameRate / m_pcEncCfg->m_FrameScale,
336
0
                    m_pcEncCfg->m_IntraPeriod, m_pcEncCfg->m_GOPSize, m_pcEncCfg->m_internalBitDepth[CH_L], getFirstPassStats() );
337
0
}
338
339
int RateCtrl::getBaseQP()
340
0
{
341
  // estimate near-optimal base QP for PPS in second RC pass
342
0
  double d = (3840.0 * 2160.0) / double (m_pcEncCfg->m_SourceWidth * m_pcEncCfg->m_SourceHeight);
343
0
  const double firstQPOffset = sqrt ((d * m_pcEncCfg->m_RCTargetBitrate) / 500000.0);
344
0
  std::list<TRCPassStats>& firstPassData = m_listRCFirstPassStats;
345
0
  int baseQP = MAX_QP;
346
347
0
  if (firstPassData.size() > 0 && encRCSeq->frameRate > 0.0)
348
0
  {
349
0
    const int firstPassBaseQP = (m_pcEncCfg->m_RCInitialQP > 0 ? std::min (MAX_QP, m_pcEncCfg->m_RCInitialQP) : std::max (0, MAX_QP_INIT_QPA - (m_pcEncCfg->m_FirstPassMode > 2 ? 4 : 2) - int (0.5 + firstQPOffset)));
350
0
    uint64_t sumFrBits = 0;  // sum of first-pass frame bits
351
352
0
    for (auto& stats : firstPassData)
353
0
    {
354
0
      sumFrBits += stats.numBits;
355
0
    }
356
0
    if (m_pcEncCfg->m_usePerceptQPA && m_pcEncCfg->m_LookAhead) // account for very low visual activity
357
0
    {
358
0
      const double hpEnerPic = sqrt (32.0 * double (1 << (2 * encRCSeq->bitDepth - 10)) * sqrt (d));
359
0
      uint32_t hpEner = 0;
360
361
0
      for (auto& stats : firstPassData)
362
0
      {
363
0
        hpEner += stats.visActY;
364
0
      }
365
0
      if (hpEner > 0 && hpEner < hpEnerPic * firstPassData.size()) // similar to applyQPAdaptationSlice
366
0
      {
367
0
        sumFrBits = uint64_t (0.5 + sumFrBits * sqrt (hpEner / (hpEnerPic * firstPassData.size())));
368
0
      }
369
0
    }
370
0
    baseQP = int (24.5 - log (std::max (1.0, d)) / log (2.0)); // QPstart, round(24 + 2*log2(resRatio))
371
0
    d = (double) m_pcEncCfg->m_RCTargetBitrate * (double) firstPassData.size() / (encRCSeq->frameRate * sumFrBits);
372
0
    d = firstPassBaseQP - (105.0 / 128.0) * sqrt ((double) std::max (1, firstPassBaseQP)) * log (d) / log (2.0);
373
0
    baseQP = int (0.5 + d + 0.5 * std::max (0.0, baseQP - d));
374
0
  }
375
0
  else if (m_pcEncCfg->m_LookAhead)
376
0
  {
377
0
    baseQP = int (24.5 - log (std::max (1.0, d)) / log (2.0)); // QPstart, round(24 + 2*log2(resRatio))
378
0
    d = MAX_QP_INIT_QPA - 2.0 - 1.5 * firstQPOffset - 0.5 * log ((double) encRCSeq->intraPeriod / encRCSeq->gopSize) / log (2.0);
379
0
    baseQP = int (0.5 + d + 0.5 * std::max (0.0, baseQP - d));
380
0
  }
381
382
0
  return Clip3 (0, MAX_QP, baseQP);
383
0
}
384
385
void RateCtrl::setRCPass(const VVEncCfg& encCfg, const int pass, const char* statsFName)
386
0
{
387
0
  m_pcEncCfg    = &encCfg;
388
0
  rcPass        = pass;
389
0
  rcIsFinalPass = (pass >= m_pcEncCfg->m_RCNumPasses - 1);
390
391
0
#ifdef VVENC_ENABLE_THIRDPARTY_JSON
392
0
  if( m_rcStatsFHandle.is_open() ) m_rcStatsFHandle.close();
393
394
0
  const std::string name = statsFName != nullptr ? statsFName : "";
395
0
  if( name.length() )
396
0
  {
397
0
    openStatsFile( name );
398
0
    if( rcIsFinalPass )
399
0
    {
400
0
      readStatsFile();
401
0
    }
402
0
  }
403
#else
404
  CHECK( statsFName != nullptr && strlen( statsFName ) > 0, "reading/writing rate control statistics file not supported, please compile with json enabled" );
405
#endif
406
407
0
  if (rcIsFinalPass && (encCfg.m_FirstPassMode > 2))
408
0
  {
409
0
    adjustStatsDownsample();
410
0
  }
411
0
}
412
413
#ifdef VVENC_ENABLE_THIRDPARTY_JSON
414
void RateCtrl::openStatsFile(const std::string& name)
415
0
{
416
0
  if( rcIsFinalPass )
417
0
  {
418
0
    m_rcStatsFHandle.open( name, std::ios::in );
419
0
    CHECK( m_rcStatsFHandle.fail(), "unable to open rate control statistics file for reading" );
420
0
    readStatsHeader();
421
0
  }
422
0
  else
423
0
  {
424
0
    m_rcStatsFHandle.open( name, std::ios::trunc | std::ios::out );
425
0
    CHECK( m_rcStatsFHandle.fail(), "unable to open rate control statistics file for writing" );
426
0
    writeStatsHeader();
427
0
  }
428
0
}
429
430
void RateCtrl::writeStatsHeader()
431
0
{
432
0
  nlohmann::json header = {
433
0
    { "version",      VVENC_VERSION },
434
0
    { "SourceWidth",  m_pcEncCfg->m_SourceWidth },
435
0
    { "SourceHeight", m_pcEncCfg->m_SourceHeight },
436
0
    { "CTUSize",      m_pcEncCfg->m_CTUSize },
437
0
    { "GOPSize",      m_pcEncCfg->m_GOPSize },
438
0
    { "IntraPeriod",  m_pcEncCfg->m_IntraPeriod },
439
0
    { "PQPA",         m_pcEncCfg->m_usePerceptQPA },
440
0
    { "QP",           m_pcEncCfg->m_QP },
441
0
    { "RCInitialQP",  m_pcEncCfg->m_RCInitialQP }
442
0
  };
443
0
  m_rcStatsFHandle << header << std::endl;
444
0
}
445
446
void RateCtrl::readStatsHeader()
447
0
{
448
0
  std::string line;
449
0
  if( ! std::getline( m_rcStatsFHandle, line ) )
450
0
  {
451
0
    THROW( "unable to read header from rate control statistics file" );
452
0
  }
453
0
  nlohmann::json header = nlohmann::json::parse( line );
454
0
  if( header.find( "version" )         == header.end() || ! header[ "version" ].is_string()
455
0
      || header.find( "SourceWidth" )  == header.end() || ! header[ "SourceWidth" ].is_number()
456
0
      || header.find( "SourceHeight" ) == header.end() || ! header[ "SourceHeight" ].is_number()
457
0
      || header.find( "CTUSize" )      == header.end() || ! header[ "CTUSize" ].is_number()
458
0
      || header.find( "GOPSize" )      == header.end() || ! header[ "GOPSize" ].is_number()
459
0
      || header.find( "IntraPeriod" )  == header.end() || ! header[ "IntraPeriod" ].is_number()
460
0
      || header.find( "PQPA" )         == header.end() || ! header[ "PQPA" ].is_boolean()
461
0
      || header.find( "QP" )           == header.end() || ! header[ "QP" ].is_number()
462
0
      || header.find( "RCInitialQP" )  == header.end() || ! header[ "RCInitialQP" ].is_number()
463
0
    )
464
0
  {
465
0
    THROW( "header line in rate control statistics file not recognized" );
466
0
  }
467
0
  if( header[ "version" ]      != VVENC_VERSION )              msg.log( VVENC_WARNING, "WARNING: wrong version in rate control statistics file\n" );
468
0
  if( header[ "SourceWidth" ]  != m_pcEncCfg->m_SourceWidth )  msg.log( VVENC_WARNING, "WARNING: wrong frame width in rate control statistics file\n" );
469
0
  if( header[ "SourceHeight" ] != m_pcEncCfg->m_SourceHeight ) msg.log( VVENC_WARNING, "WARNING: wrong frame height in rate control statistics file\n" );
470
0
  if( header[ "CTUSize" ]      != m_pcEncCfg->m_CTUSize )      msg.log( VVENC_WARNING, "WARNING: wrong CTU size in rate control statistics file\n" );
471
0
  if( header[ "GOPSize" ]      != m_pcEncCfg->m_GOPSize )      msg.log( VVENC_WARNING, "WARNING: wrong GOP size in rate control statistics file\n" );
472
0
  if( header[ "IntraPeriod" ]  != m_pcEncCfg->m_IntraPeriod )  msg.log( VVENC_WARNING, "WARNING: wrong intra period in rate control statistics file\n" );
473
0
}
474
#endif // VVENC_ENABLE_THIRDPARTY_JSON
475
476
void RateCtrl::storeStatsData( TRCPassStats statsData )
477
0
{
478
0
  if( m_pcEncCfg->m_FirstPassMode == 2 || m_pcEncCfg->m_FirstPassMode == 4)
479
0
  {
480
0
    CHECK( statsData.tempLayer > VVENC_MAX_TLAYER, "array index out of bounds" );
481
0
    if( statsData.numBits )
482
0
    {
483
0
      m_tempDownSamplStats[ statsData.tempLayer ] = statsData;
484
0
    }
485
0
    else
486
0
    {
487
0
      const TRCPassStats& srcData = m_tempDownSamplStats[ statsData.tempLayer ];
488
0
      CHECK( srcData.numBits == 0,                                 "miss stats data from previous frame for temporal down-sampling" );
489
0
      CHECK( statsData.poc - srcData.poc >= m_pcEncCfg->m_GOPSize, "miss stats data from previous frame for temporal down-sampling" );
490
0
      statsData.qp        = srcData.qp;
491
0
      statsData.lambda    = srcData.lambda;
492
0
      if( statsData.visActY == 0 && statsData.spVisAct == 0 )
493
0
        statsData.spVisAct = srcData.spVisAct;
494
0
      if( statsData.visActY == 0 )
495
0
        statsData.visActY = srcData.visActY;
496
0
      statsData.numBits   = srcData.numBits;
497
0
      statsData.psnrY     = srcData.psnrY;
498
0
    }
499
0
  }
500
0
#ifdef VVENC_ENABLE_THIRDPARTY_JSON
501
0
  nlohmann::json data = {
502
0
    { "poc",            statsData.poc },
503
0
    { "qp",             statsData.qp },
504
0
    { "lambda",         statsData.lambda },
505
0
    { "visActY",        statsData.visActY },
506
0
    { "numBits",        statsData.numBits },
507
0
    { "psnrY",          statsData.psnrY },
508
0
    { "isIntra",        statsData.isIntra },
509
0
    { "tempLayer",      statsData.tempLayer },
510
0
    { "isStartOfIntra", statsData.isStartOfIntra },
511
0
    { "isStartOfGop",   statsData.isStartOfGop },
512
0
    { "gopNum",         statsData.gopNum },
513
0
    { "scType",         statsData.scType },
514
0
  };
515
516
0
  if( m_pcEncCfg->m_FirstPassMode > 2 )
517
0
  {
518
0
    data[ "spVisAct" ] = statsData.spVisAct;
519
0
    statsData.spVisAct = data[ "spVisAct" ];
520
0
  }
521
522
0
  if( m_rcStatsFHandle.is_open() )
523
0
  {
524
0
    CHECK( ! m_rcStatsFHandle.good(), "unable to write to rate control statistics file" );
525
0
    if( m_listRCIntraPQPAStats.size() > m_pqpaStatsWritten )
526
0
    {
527
0
      std::vector<uint8_t> pqpaTemp;
528
0
      while( m_pqpaStatsWritten < (int)m_listRCIntraPQPAStats.size() )
529
0
      {
530
0
        pqpaTemp.push_back( m_listRCIntraPQPAStats[ m_pqpaStatsWritten ] );
531
0
        m_pqpaStatsWritten++;
532
0
      }
533
0
      data[ "pqpaStats" ] = pqpaTemp;
534
0
    }
535
0
    m_rcStatsFHandle << data << std::endl;
536
0
  }
537
0
  else
538
0
  {
539
    // ensure same precision for internal and written data by serializing internal data as well
540
0
    std::stringstream iss;
541
0
    iss << data;
542
0
    data = nlohmann::json::parse( iss.str() );
543
0
    std::list<TRCPassStats>& listRCFirstPassStats = m_pcEncCfg->m_LookAhead ? m_firstPassCache : m_listRCFirstPassStats;
544
0
    listRCFirstPassStats.push_back( TRCPassStats( data[ "poc" ],
545
0
                                                    data[ "qp" ],
546
0
                                                    data[ "lambda" ],
547
0
                                                    data[ "visActY" ],
548
0
                                                    data[ "numBits" ],
549
0
                                                    data[ "psnrY" ],
550
0
                                                    data[ "isIntra" ],
551
0
                                                    data[ "tempLayer" ],
552
0
                                                    data[ "isStartOfIntra" ],
553
0
                                                    data[ "isStartOfGop" ],
554
0
                                                    data[ "gopNum" ],
555
0
                                                    data[ "scType" ],
556
0
                                                    statsData.spVisAct,
557
0
                                                    statsData.motionEstError,
558
0
                                                    statsData.minNoiseLevels
559
0
                                                    ) );
560
0
  }
561
0
  m_numPicStatsTotal++;
562
#else
563
  m_listRCFirstPassStats.push_back( statsData );
564
565
  if( m_pcEncCfg->m_LookAhead && (int) m_listRCFirstPassStats.size() > encRCSeq->intraPeriod + encRCSeq->gopSize + 1 )
566
  {
567
    m_listRCFirstPassStats.pop_front();
568
  }
569
#endif
570
0
}
571
572
#ifdef VVENC_ENABLE_THIRDPARTY_JSON
573
void RateCtrl::readStatsFile()
574
0
{
575
0
  CHECK( ! m_rcStatsFHandle.good(), "unable to read from rate control statistics file" );
576
577
0
  uint8_t minNoiseLevels[ QPA_MAX_NOISE_LEVELS ];
578
0
  std::fill_n( minNoiseLevels, QPA_MAX_NOISE_LEVELS, 255u );
579
580
0
  int lineNum = 2;
581
0
  std::string line;
582
0
  while( std::getline( m_rcStatsFHandle, line ) )
583
0
  {
584
0
    nlohmann::json data = nlohmann::json::parse( line );
585
0
    if( data.find( "poc" )               == data.end() || ! data[ "poc" ].is_number()
586
0
        || data.find( "qp" )             == data.end() || ! data[ "qp" ].is_number()
587
0
        || data.find( "lambda" )         == data.end() || ! data[ "lambda" ].is_number()
588
0
        || data.find( "visActY" )        == data.end() || ! data[ "visActY" ].is_number()
589
0
        || data.find( "numBits" )        == data.end() || ! data[ "numBits" ].is_number()
590
0
        || data.find( "psnrY" )          == data.end() || ! data[ "psnrY" ].is_number()
591
0
        || data.find( "isIntra" )        == data.end() || ! data[ "isIntra" ].is_boolean()
592
0
        || data.find( "tempLayer" )      == data.end() || ! data[ "tempLayer" ].is_number()
593
0
        || data.find( "isStartOfIntra" ) == data.end() || ! data[ "isStartOfIntra" ].is_boolean()
594
0
        || data.find( "isStartOfGop" )   == data.end() || ! data[ "isStartOfGop" ].is_boolean()
595
0
        || data.find( "gopNum" )         == data.end() || ! data[ "gopNum" ].is_number()
596
0
        || data.find( "scType" )         == data.end() || ! data[ "scType" ].is_number() )
597
0
    {
598
0
      THROW( "syntax of rate control statistics file in line " << lineNum << " not recognized: (" << line << ")" );
599
0
    }
600
0
    int spVisAct = 0;
601
0
    if( data.find( "spVisAct" ) != data.end() )
602
0
    {
603
0
      CHECK( ! data[ "spVisAct" ].is_number(), "spatial visual activity in rate control statistics file must be a number" );
604
0
      spVisAct = data[ "spVisAct" ];
605
0
    }
606
0
    m_listRCFirstPassStats.push_back( TRCPassStats( data[ "poc" ],
607
0
                                                    data[ "qp" ],
608
0
                                                    data[ "lambda" ],
609
0
                                                    data[ "visActY" ],
610
0
                                                    data[ "numBits" ],
611
0
                                                    data[ "psnrY" ],
612
0
                                                    data[ "isIntra" ],
613
0
                                                    data[ "tempLayer" ],
614
0
                                                    data[ "isStartOfIntra" ],
615
0
                                                    data[ "isStartOfGop" ],
616
0
                                                    data[ "gopNum" ],
617
0
                                                    data[ "scType" ],
618
0
                                                    spVisAct,
619
0
                                                    0, // motionEstError
620
0
                                                    minNoiseLevels
621
0
                                                    ) );
622
0
    if( data.find( "pqpaStats" ) != data.end() )
623
0
    {
624
0
      CHECK( ! data[ "pqpaStats" ].is_array(), "pqpa array data in rate control statistics file not recognized" );
625
0
      std::vector<uint8_t> pqpaTemp = data[ "pqpaStats" ];
626
0
      for( auto el : pqpaTemp )
627
0
      {
628
0
        m_listRCIntraPQPAStats.push_back( el );
629
0
      }
630
0
    }
631
0
    lineNum++;
632
0
  }
633
0
}
634
#endif
635
636
void RateCtrl::adjustStatsDownsample()
637
0
{
638
0
  int64_t meanValue = 0; //MCTF or Activity
639
0
  int amount = 0;
640
0
  auto itr = m_listRCFirstPassStats.begin();
641
0
  for (; itr != m_listRCFirstPassStats.end(); itr++)
642
0
  {
643
0
    auto& stat = *itr;
644
0
    int statValue = stat.spVisAct;
645
0
    if (statValue != 0)
646
0
    {
647
0
      meanValue += statValue;
648
0
      amount++;
649
0
    }
650
0
    stat.numBits = stat.numBits << 1;
651
0
  }
652
0
  if (meanValue != 0)
653
0
  {
654
0
    meanValue = meanValue / amount;
655
0
    int64_t sumVar = 0;
656
0
    int numVar = 0;
657
0
    auto itrv = m_listRCFirstPassStats.begin();
658
0
    for (; itrv != m_listRCFirstPassStats.end(); itrv++)
659
0
    {
660
0
      auto& stat = *itrv;
661
0
      if (stat.spVisAct != 0)
662
0
      {
663
0
        sumVar += (std::abs(stat.spVisAct - meanValue) * std::abs(stat.spVisAct - meanValue));
664
0
        numVar++;
665
0
      }
666
0
    }
667
0
    if (numVar)
668
0
    {
669
0
      sumVar = sumVar / std::max(1, numVar - 1);
670
0
      sumVar = int64_t (0.5 + sqrt((double) sumVar));
671
0
    }
672
0
    int value_gopbefore = 0;
673
0
    int value_gopcur = 0;
674
0
    int num_gopcur = 0;
675
0
    int gopcur = 0;
676
0
    bool doChangeBits = false;
677
0
    auto itr = m_listRCFirstPassStats.begin();
678
0
    for (; itr != m_listRCFirstPassStats.end(); itr++)
679
0
    {
680
0
      auto& stat = *itr;
681
0
      int statValue = stat.spVisAct;
682
0
      if (gopcur != stat.gopNum)
683
0
      {
684
0
        gopcur = stat.gopNum;
685
0
        if (num_gopcur)
686
0
        {
687
0
          value_gopbefore = value_gopcur / num_gopcur;
688
0
        }
689
0
        else
690
0
        {
691
0
          value_gopbefore = value_gopcur;
692
0
        }
693
0
        value_gopcur = 0;
694
0
        num_gopcur = 0;
695
0
        doChangeBits = false;
696
0
      }
697
0
      if (statValue != 0)
698
0
      {
699
0
        value_gopcur += statValue;
700
0
        num_gopcur++;
701
0
        doChangeBits = false;
702
0
        if (stat.gopNum != 0)
703
0
        {
704
0
          const int64_t var_cur = std::abs(statValue - meanValue);
705
0
          if (var_cur > (sumVar << 1))
706
0
          {
707
0
            doChangeBits = true;
708
0
          }
709
0
          else
710
0
          {
711
0
            int rate1 = (((value_gopcur / num_gopcur) * 100) / meanValue);
712
0
            int rate2 = (value_gopbefore == 0) ? 100 : (((value_gopcur / num_gopcur) * 100) / value_gopbefore);
713
0
            if ((rate1 > 140) || (rate1 < 60)
714
0
              || (rate2 > 140) || (rate2 < 60))
715
0
            {
716
0
              doChangeBits = true;
717
0
            }
718
0
          }
719
0
        }
720
0
      }
721
0
      if ((stat.gopNum != 0) && doChangeBits && (stat.tempLayer > 1))
722
0
      {
723
0
        stat.numBits = (stat.numBits * 3) >> 1;
724
0
      }
725
0
    }
726
0
  }
727
0
}
728
729
void RateCtrl::setRCRateSavingState( const int maxRate )
730
0
{
731
0
  if ( m_pcEncCfg->m_LookAhead && maxRate < encRCSeq->targetRate ) // end of video: incomplete I-period?
732
0
  {
733
0
    encRCSeq->isIntraGOP = false;
734
0
  }
735
0
  encRCSeq->isRateSavingMode = true;
736
0
}
737
738
void RateCtrl::processFirstPassData( const bool flush, const int poc /*= -1*/ )
739
0
{
740
0
  if( m_pcEncCfg->m_RCNumPasses > 1 )
741
0
  {
742
    // two pass rc
743
0
    CHECK( m_pcEncCfg->m_LookAhead, "two pass rc does not support look-ahead mode" );
744
745
0
    xProcessFirstPassData( flush, poc );
746
0
  }
747
0
  else
748
0
  {
749
    // single pass rc
750
0
    CHECK( !m_pcEncCfg->m_LookAhead,     "single pass rc should be only used in look-ahead mode" );
751
0
    CHECK( m_firstPassCache.size() == 0, "no data available from the first pass" );
752
0
    CHECK( poc < 0,                      "no valid poc given" );
753
754
    // fetch RC data for the next look-ahead chunk
755
    // the next look-ahead chunk starts with a given POC, so find a pic for a given POC in cache
756
    // NOTE!!!: pictures in cache are in coding order
757
758
0
    auto picCacheItr = find_if( m_firstPassCache.begin(), m_firstPassCache.end(), [poc]( auto& picStat ) { return picStat.poc == poc; } );
759
760
0
    for( int count = 0; picCacheItr != m_firstPassCache.end(); ++picCacheItr )
761
0
    {
762
0
      auto& picStat = *picCacheItr;
763
0
      count++;
764
0
      if( !picStat.addedToList )
765
0
      {
766
0
        picStat.addedToList = true;
767
0
        m_numPicAddedToList++;
768
0
        m_listRCFirstPassStats.push_back( picStat );
769
0
        if( m_pcEncCfg->m_LookAhead && (int) m_listRCFirstPassStats.size() > encRCSeq->intraPeriod + encRCSeq->gopSize + 1 )
770
0
        {
771
0
          m_listRCFirstPassStats.pop_front();
772
0
          m_firstPassCache.pop_front();
773
0
        }
774
775
        // the chunk is considered to contain a particular number of pictures up to the picture starting the next GOP (including it)
776
        // in flush-mode, ensure the deterministic definition of last chunk
777
0
        if( ( count >= m_pcEncCfg->m_GOPSize + 1 || ( m_listRCFirstPassStats.size() > 2 && picStat.isStartOfGop ) ) && !( flush && m_numPicAddedToList > m_numPicStatsTotal - m_pcEncCfg->m_GOPSize ) )
778
0
          break;
779
0
      }
780
0
    }
781
    // enable flush only in last chunk (provides correct calculation of flushPOC)
782
0
    xProcessFirstPassData( flush && ( m_numPicAddedToList == m_numPicStatsTotal ), poc );
783
0
  }
784
0
}
785
786
void RateCtrl::xProcessFirstPassData( const bool flush, const int poc )
787
0
{
788
0
  CHECK( m_listRCFirstPassStats.size() == 0, "No data available from the first pass!" );
789
790
0
  m_listRCFirstPassStats.sort( []( const TRCPassStats& a, const TRCPassStats& b ) { return a.poc < b.poc; } );
791
792
0
  if ( flush || !m_pcEncCfg->m_LookAhead )
793
0
  {
794
    // store start POC of last chunk of pictures
795
0
    flushPOC = m_listRCFirstPassStats.back().poc - std::max( 32, m_pcEncCfg->m_GOPSize );
796
0
  }
797
0
  if ( flush && m_pcEncCfg->m_LookAhead )
798
0
  {
799
0
    encRCSeq->isRateSavingMode = true;
800
0
  }
801
802
  // perform a simple scene change detection on first-pass data and update RC parameters when new scenes are detected
803
0
  detectSceneCuts();
804
805
  // process and scale GOP and frame bits using the data from the first pass to account for different target bitrates
806
0
  processGops();
807
808
0
  if( m_pcEncCfg->m_GOPSize > 8
809
0
      && m_pcEncCfg->m_IntraPeriod >= m_pcEncCfg->m_GOPSize
810
0
      && ( m_pcEncCfg->m_usePerceptQPA || m_pcEncCfg->m_vvencMCTF.MCTF > 0 )
811
0
      && m_pcEncCfg->m_RCNumPasses == 1 )
812
0
  {
813
0
    updateMotionErrStatsGop( flush, poc );
814
0
  }
815
816
0
  encRCSeq->firstPassData = m_listRCFirstPassStats;
817
0
}
818
819
double RateCtrl::getAverageBitsFromFirstPass()
820
0
{
821
0
  const uint16_t vaMin = 1u << (encRCSeq->bitDepth - 6);
822
0
  const int16_t factor = (m_pcEncCfg->m_QP > 27 ? 2 : 3);
823
0
  const uint32_t shift = (m_pcEncCfg->m_QP > 37 ? 1 : 4);
824
0
  int vaTm1 = 0, vaTm2 = 0; // temporal memories
825
0
  uint64_t totalBitsFirstPass = 0;
826
0
  std::list<TRCPassStats>::iterator it;
827
828
0
  if (encRCSeq->intraPeriod > 1 && encRCSeq->gopSize > 1 && m_pcEncCfg->m_LookAhead)
829
0
  {
830
0
    const int gopsInIp  = (2 * encRCSeq->intraPeriod + (encRCSeq->gopSize >> 1)) / encRCSeq->gopSize;
831
0
    int l = 2 - (gopsInIp & 1); // fract. tuning
832
0
    uint64_t tlBits [8] = { 0 };
833
0
    unsigned tlCount[8] = { 0 };
834
835
0
    for (it = m_listRCFirstPassStats.begin(); it != m_listRCFirstPassStats.end(); it++) // sum per level
836
0
    {
837
0
      if (tlBits[it->tempLayer] > 0 && it->refreshParameters)
838
0
      {
839
        // take maximum of average temporal level-wise bit counts from last scene and from current scene
840
0
        tlBits[it->tempLayer] = (tlBits[it->tempLayer] + (tlCount[it->tempLayer] >> 1)) / std::max (1u, tlCount[it->tempLayer]);
841
0
        tlBits[it->tempLayer] = std::max (tlBits[it->tempLayer], (uint64_t) it->numBits);
842
0
        tlCount[it->tempLayer] = 1;
843
0
      }
844
0
      else
845
0
      {
846
0
        tlBits[it->tempLayer] += it->numBits;
847
0
        tlCount[it->tempLayer]++;
848
0
      }
849
0
    }
850
0
    if (tlBits[0] == 0)
851
0
    {
852
0
      l = 0; // no I-frame in the analysis range
853
0
    }
854
855
0
    totalBitsFirstPass = (2 * tlBits[0] + (tlCount[0] >> 1)) / std::max (1u, tlCount[0]) +
856
0
            ((gopsInIp - l) * tlBits[1] + (tlCount[1] >> 1)) / std::max (1u, tlCount[1]);
857
0
    for (l = 2; l <= 7; l++)
858
0
    {
859
0
      totalBitsFirstPass += ((gopsInIp << (l - 2)) * tlBits[l] + (tlCount[l] >> 1)) / std::max (1u, tlCount[l]);
860
0
    }
861
862
0
    return totalBitsFirstPass / (2.0 * encRCSeq->intraPeriod);
863
0
  }
864
865
0
  for (it = m_listRCFirstPassStats.begin(); it != m_listRCFirstPassStats.end(); it++) // for two-pass RC
866
0
  {
867
0
    const int vaTmp = std::max (0, (it->visActY << (12 - encRCSeq->bitDepth)) - it->spVisAct);
868
0
    const int vaSum = vaTmp + vaTm2; // improve rate match on temporally downsampled or very dark videos
869
870
0
    if (vaSum > 0 && vaTm1 * 2 * factor < vaSum && vaTm2 * 4 > vaTmp * 3 && vaTm2 * 3 < vaTmp * 4)
871
0
    {
872
0
      totalBitsFirstPass += (it->numBits * uint64_t (vaSum + vaTm1 * 2) * factor + (vaSum * 2)) / (vaSum * (factor + 1));
873
0
    }
874
0
    else
875
0
    {
876
0
      totalBitsFirstPass += (it->visActY >= vaMin && it->visActY < vaMin + (1u << shift) ? (it->numBits * (it->visActY + 1u - vaMin)) >> shift : it->numBits);
877
0
    }
878
879
0
    vaTm2 = vaTm1;
880
0
    vaTm1 = vaTmp;
881
0
  }
882
883
0
  return totalBitsFirstPass / (double) m_listRCFirstPassStats.size();
884
0
}
885
886
void RateCtrl::detectSceneCuts()
887
0
{
888
0
  const int minPocDif = (encRCSeq->gopSize + 1) >> 1;
889
0
  double psnrTL01Prev = 0.0;
890
0
  int sceneCutPocPrev = -minPocDif;
891
0
  uint16_t visActPrev = 0;
892
0
  bool needRefresh[8] = { false };
893
0
  std::list<TRCPassStats>::iterator it = m_listRCFirstPassStats.begin();
894
895
0
  visActPrev = it->visActY;
896
0
  if (it->tempLayer <= 1) psnrTL01Prev = it->psnrY;
897
0
  it->refreshParameters = (it->poc == 0);
898
899
0
  for (it++; it != m_listRCFirstPassStats.end(); it++)
900
0
  {
901
0
    const int tmpLevel = it->tempLayer;
902
0
    const bool  isTL01 = (tmpLevel <= 1);
903
904
0
    it->isNewScene = ((it->visActY * 64 > visActPrev * 181) || (isTL01 && it->visActY <= (1u << (encRCSeq->bitDepth - 6))) || (isTL01 && it->visActY + 3 > visActPrev && psnrTL01Prev > 0.0 && std::abs (it->psnrY - psnrTL01Prev) > 4.5));
905
906
0
    if (it->isNewScene) // filter out scene cuts which happen too closely to the last detected scene cut
907
0
    {
908
0
      if (it->poc >= sceneCutPocPrev + minPocDif)
909
0
      {
910
0
        for (int frameLevel = 0; frameLevel <= m_pcEncCfg->m_maxTLayer + 1; frameLevel++)
911
0
        {
912
0
          needRefresh[frameLevel] = true;
913
0
        }
914
0
        sceneCutPocPrev = it->poc;
915
0
      }
916
0
      else
917
0
      {
918
0
        it->isNewScene = false;
919
0
      }
920
0
    }
921
0
    if (it->scType == SCT_TL0_SCENE_CUT && !needRefresh[0]) // assume scene cuts at all adapted I-frames
922
0
    {
923
0
      it->isNewScene = needRefresh[0] = needRefresh[1] = needRefresh[2] = true;
924
0
    }
925
926
0
    it->refreshParameters = needRefresh[tmpLevel];
927
0
    needRefresh[tmpLevel] = false;
928
929
0
    visActPrev = it->visActY;
930
0
    if (isTL01) psnrTL01Prev = it->psnrY;
931
0
  }
932
0
}
933
934
void RateCtrl::processGops()
935
0
{
936
0
  const double bp1pf = getAverageBitsFromFirstPass();  // first-pass bits/frame
937
0
  const double ratio = (double) encRCSeq->targetRate / (encRCSeq->frameRate * bp1pf); // 2nd-to-1st pass
938
0
  double fac;
939
0
  int vecIdx;
940
0
  int gopNum;
941
0
  std::list<TRCPassStats>::iterator it;
942
0
  std::vector<uint32_t> gopBits (2 + (m_listRCFirstPassStats.back().gopNum - m_listRCFirstPassStats.front().gopNum)); // +2 for the first I frame (GOP) and a potential last incomplete GOP
943
0
  std::vector<float> gopTempVal (2 + (m_listRCFirstPassStats.back().gopNum - m_listRCFirstPassStats.front().gopNum));
944
945
0
  vecIdx = 0;
946
0
  gopNum = m_listRCFirstPassStats.front().gopNum;
947
0
  for (it = m_listRCFirstPassStats.begin(); it != m_listRCFirstPassStats.end(); it++) // scaling, part 1
948
0
  {
949
0
    if (it->gopNum > gopNum)
950
0
    {
951
0
      vecIdx++;
952
0
      gopNum = it->gopNum;
953
0
    }
954
0
    CHECK (vecIdx >= (int) gopBits.size(), "array idx out of bounds");
955
0
    it->targetBits = (int) std::max (1.0, 0.5 + it->numBits * ratio);
956
0
    gopBits[vecIdx] += (uint32_t) it->targetBits; // sum is gf in VCIP'21 paper
957
958
0
    if (it->poc == 0 && it->isIntra) // put the first I-frame into separate GOP
959
0
    {
960
0
      vecIdx++;
961
0
    }
962
0
  }
963
964
0
  vecIdx = 0;
965
0
  fac = 1.0 / gopBits[vecIdx];
966
0
  gopTempVal[vecIdx] = 1.0f;
967
0
  gopNum = m_listRCFirstPassStats.front().gopNum;
968
0
  for (it = m_listRCFirstPassStats.begin(); it != m_listRCFirstPassStats.end(); it++) // scaling, part 2
969
0
  {
970
0
    if (it->gopNum > gopNum)
971
0
    {
972
0
      vecIdx++;
973
0
      fac = 1.0 / gopBits[vecIdx];
974
0
      gopTempVal[vecIdx] = (it->isIntra ? float (it->targetBits * fac) : 0.0f);
975
0
      gopNum = it->gopNum;
976
0
    }
977
0
    it->frameInGopRatio = it->targetBits * fac; // i.e., rf/gf in VCIP'21 paper
978
979
0
    if (it->poc == 0 && it->isIntra) // put the first I-frame into separate GOP
980
0
    {
981
0
      vecIdx++;
982
0
      fac = 1.0 / gopBits[vecIdx];
983
0
      gopTempVal[vecIdx] = 0.0f; // relax rate constraint a bit in first 2 GOPs
984
0
    }
985
0
  }
986
987
0
  if (!encRCSeq->isLookAhead && encRCSeq->maxGopRate < INT32_MAX) // pre-capper
988
0
  {
989
0
    double savedBits = 0.0, scale = 0.0, maxBits;
990
0
    uint32_t savedGOPs = 0;
991
992
0
    for (vecIdx = 0; vecIdx < (int) gopBits.size(); vecIdx++)
993
0
    {
994
0
      if (gopTempVal[vecIdx] > 0.0f)
995
0
      {
996
0
        scale = (double) encRCSeq->maxGopRate * encRCSeq->intraPeriod / (encRCSeq->intraPeriod + gopTempVal[vecIdx] * encRCSeq->gopSize);
997
0
      }
998
0
      maxBits = scale * (1.0 + gopTempVal[vecIdx]);
999
1000
0
      if ((double) gopBits[vecIdx] > maxBits)
1001
0
      {
1002
0
        gopTempVal[vecIdx] = float (maxBits / gopBits[vecIdx]); // saving ratio
1003
0
        savedBits += gopBits[vecIdx] - maxBits;
1004
0
        savedGOPs++;
1005
0
      }
1006
0
      else
1007
0
      {
1008
0
        gopTempVal[vecIdx] = 0.f; // signal to next loop that rate is below cap
1009
0
      }
1010
0
    }
1011
1012
0
    if (savedGOPs > 0)  // distribute saved bits equally across not capped GOPs
1013
0
    {
1014
0
      savedBits /= gopBits.size() - savedGOPs;
1015
0
      vecIdx = 0;
1016
0
      scale   = (double) encRCSeq->maxGopRate * encRCSeq->intraPeriod / (encRCSeq->intraPeriod + 1.0 /*frameInGopRatio*/ * encRCSeq->gopSize);
1017
0
      maxBits = scale * (1.0 + 1.0 /*frameInGopRatio*/);
1018
0
      fac = (gopTempVal[vecIdx] > 0.0f ? gopTempVal[vecIdx] : std::min (maxBits, gopBits[vecIdx] + savedBits) / gopBits[vecIdx]);
1019
0
      gopNum = m_listRCFirstPassStats.front().gopNum;
1020
0
      for (it = m_listRCFirstPassStats.begin(); it != m_listRCFirstPassStats.end(); it++) // cap, part 3
1021
0
      {
1022
0
        if (it->gopNum > gopNum)
1023
0
        {
1024
0
          vecIdx++;
1025
0
          if (it->isIntra)
1026
0
          {
1027
0
            scale = (double) encRCSeq->maxGopRate * encRCSeq->intraPeriod / (encRCSeq->intraPeriod + it->frameInGopRatio * encRCSeq->gopSize);
1028
0
          }
1029
0
          maxBits = scale * (it->isIntra ? 1.0 + it->frameInGopRatio : 1.0);
1030
0
          fac = (gopTempVal[vecIdx] > 0.0f ? gopTempVal[vecIdx] : std::min (maxBits, gopBits[vecIdx] + savedBits) / gopBits[vecIdx]);
1031
0
          gopNum = it->gopNum;
1032
0
        }
1033
0
        it->targetBits = (int) std::max (1.0, 0.5 + it->targetBits * fac);
1034
1035
0
        if (it->poc == 0 && it->isIntra) // put first I-frame into separate GOP
1036
0
        {
1037
0
          vecIdx++;
1038
          // maxBits = scale * 1.0; relax rate constraint a bit in first 2 GOPs
1039
0
          fac = (gopTempVal[vecIdx] > 0.0f ? gopTempVal[vecIdx] : std::min (maxBits, gopBits[vecIdx] + savedBits) / gopBits[vecIdx]);
1040
0
        }
1041
0
      }
1042
0
    }
1043
0
  }
1044
0
}
1045
1046
void RateCtrl::updateMotionErrStatsGop( const bool flush, const int poc )
1047
0
{
1048
0
  CHECK( poc <= m_updateNoisePoc, "given TL0 poc before last TL0 poc" );
1049
1050
0
  const bool bIncomplete = ( poc - m_updateNoisePoc ) < m_pcEncCfg->m_GOPSize;
1051
1052
  // reset only if full gop pics available or previous gop ends with intra frame
1053
0
  if( ! bIncomplete || m_resetNoise )
1054
0
  {
1055
0
    m_maxPicMotionError = 0;
1056
0
    std::fill_n( m_minNoiseLevels, QPA_MAX_NOISE_LEVELS, 255u );
1057
0
  }
1058
0
  m_resetNoise = true;
1059
1060
  // currently disabled for last incomplete gop (TODO: check)
1061
0
  if( bIncomplete && flush )
1062
0
  {
1063
0
    m_maxPicMotionError = 0;
1064
0
    std::fill_n( m_minNoiseLevels, QPA_MAX_NOISE_LEVELS, 255u );
1065
0
    return;
1066
0
  }
1067
1068
  // continue with stats after last used poc
1069
0
  const int startPoc = m_updateNoisePoc + 1;
1070
0
  auto itr           = find_if( m_listRCFirstPassStats.begin(), m_listRCFirstPassStats.end(), [ startPoc ]( const auto& stat ) { return stat.poc == startPoc; } );
1071
0
  if( itr == m_listRCFirstPassStats.end() )
1072
0
  {
1073
0
    itr = m_listRCFirstPassStats.begin();
1074
0
  }
1075
1076
0
  for( ; itr != m_listRCFirstPassStats.end(); itr++ )
1077
0
  {
1078
0
    const auto& stat = *itr;
1079
0
    if( stat.poc > poc )
1080
0
    {
1081
0
      m_gopMEErrorCBufIdx = ( m_gopMEErrorCBufIdx + 1u ) & uint8_t( QPA_MAX_NOISE_LEVELS - 1 );
1082
0
      m_gopMEErrorCBuf[ m_gopMEErrorCBufIdx ] = m_maxPicMotionError;
1083
0
      m_resetNoise = stat.isIntra; // in case last update poc is intra, we cannot reuse the old noise levels for the next gop
1084
0
      break;
1085
0
    }
1086
0
    m_maxPicMotionError = std::max( m_maxPicMotionError, stat.motionEstError );
1087
1088
0
    for( int i = 0; i < QPA_MAX_NOISE_LEVELS; i++ )
1089
0
    {
1090
0
      if( stat.minNoiseLevels[ i ] < m_minNoiseLevels[ i ] )
1091
0
      {
1092
0
        m_minNoiseLevels[ i ] = stat.minNoiseLevels[ i ];
1093
0
      }
1094
0
    }
1095
0
  }
1096
1097
  // store highest poc used for current update
1098
0
  m_updateNoisePoc = poc;
1099
0
}
1100
1101
double RateCtrl::getLookAheadBoostFac( const int thresholdDivisor )
1102
0
{
1103
0
  const unsigned thresh = 64 / std::max (1, thresholdDivisor);
1104
0
  unsigned num = 0, sum = 0;
1105
1106
0
  for (int i = 0; i < QPA_MAX_NOISE_LEVELS; i++) // go through non-zero values in circ. buffer
1107
0
  {
1108
0
    if (m_gopMEErrorCBuf[i] > 0)
1109
0
    {
1110
0
      num++;
1111
0
      sum += m_gopMEErrorCBuf[i];
1112
0
    }
1113
0
  }
1114
1115
0
  if (num > 0 && m_gopMEErrorCBuf[m_gopMEErrorCBufIdx] * num > sum + thresh * num) // boosting
1116
0
  {
1117
0
    return double (m_gopMEErrorCBuf[m_gopMEErrorCBufIdx] * num) / (sum + thresh * num);
1118
0
  }
1119
0
  return 1.0;
1120
0
}
1121
1122
double RateCtrl::updateQPstartModelVal()
1123
0
{
1124
0
  unsigned num = 0, sum = 0;
1125
1126
0
  for (int avgIndex = 0; avgIndex < QPA_MAX_NOISE_LEVELS; avgIndex++) // go through all ranges
1127
0
  {
1128
0
    if (m_minNoiseLevels[avgIndex] < 255)
1129
0
    {
1130
0
      num++;
1131
0
      sum += m_minNoiseLevels[avgIndex];
1132
0
    }
1133
0
  }
1134
1135
0
  if (num == 0 || sum == 0) return 24.0; // default if no data, else noise level equivalent QP
1136
1137
0
  return 24.0 + 0.5 * (6.0 * log ((double) sum / (double) num) / log (2.0) - 1.0 - 24.0);
1138
0
}
1139
1140
void RateCtrl::addRCPassStats( const int poc,
1141
                               const int qp,
1142
                               const double lambda,
1143
                               const uint16_t visActY,
1144
                               const uint32_t numBits,
1145
                               const double psnrY,
1146
                               const bool isIntra,
1147
                               const uint32_t tempLayer,
1148
                               const bool isStartOfIntra,
1149
                               const bool isStartOfGop,
1150
                               const int gopNum,
1151
                               const SceneType scType,
1152
                               int spVisAct,
1153
                               const uint16_t motEstError,
1154
                               const uint8_t minNoiseLevels[ QPA_MAX_NOISE_LEVELS ] )
1155
0
{
1156
0
  storeStatsData (TRCPassStats (poc, qp, lambda, visActY, numBits, psnrY, isIntra, tempLayer + int (!isIntra), isStartOfIntra, isStartOfGop, gopNum, scType, spVisAct, motEstError, minNoiseLevels));
1157
0
}
1158
1159
void RateCtrl::updateAfterPicEncRC( const Picture* pic )
1160
0
{
1161
0
  const int clipBits = std::max( encRCPic->targetBits, pic->actualTotalBits );
1162
0
  EncRCPic* encRCPic = pic->encRCPic;
1163
1164
0
  encRCPic->updateAfterPicture( pic->isMeanQPLimited ? clipBits : pic->actualTotalBits, pic->slices[ 0 ]->sliceQp );
1165
0
  encRCPic->addToPictureList( getPicList() );
1166
0
  encRCSeq->updateAfterPic( pic->actualTotalBits, encRCPic->tmpTargetBits );
1167
1168
0
  if ( encRCSeq->isLookAhead )
1169
0
  {
1170
0
    if ( pic->isMeanQPLimited ) encRCSeq->bitsUsedQPLimDiff += pic->actualTotalBits - clipBits;
1171
0
    encRCSeq->bitsUsed += encRCSeq->bitsUsedQPLimDiff;  // sum up actual, not ideal bit counts
1172
0
  }
1173
0
}
1174
1175
void RateCtrl::initRateControlPic( Picture& pic, Slice* slice, int& qp, double& finalLambda )
1176
0
{
1177
0
  const int frameLevel = ( slice->isIntra() ? 0 : slice->TLayer + 1 );
1178
0
  EncRCPic*   encRcPic = new EncRCPic;
1179
0
  double lambda = encRCSeq->maxEstLambda;
1180
0
  int   sliceQP = MAX_QP;
1181
1182
0
  encRcPic->create( encRCSeq, frameLevel, slice->poc );
1183
0
  pic.encRCPic = encRCPic = encRcPic;
1184
1185
0
  if ( frameLevel <= 7 )
1186
0
  {
1187
0
    if ( m_pcEncCfg->m_RCNumPasses == 2 || m_pcEncCfg->m_LookAhead )
1188
0
    {
1189
0
      std::list<TRCPassStats>::iterator it;
1190
1191
0
      for ( it = encRCSeq->firstPassData.begin(); it != encRCSeq->firstPassData.end(); it++ )
1192
0
      {
1193
0
        if ( it->poc == slice->poc && it->numBits > 0 )
1194
0
        {
1195
0
          const double sqrOfResRatio = std::min( 1.0, double( m_pcEncCfg->m_SourceWidth * m_pcEncCfg->m_SourceHeight ) / ( 3840.0 * 2160.0 ) );
1196
0
          const int firstPassSliceQP = it->qp;
1197
0
          const int budgetRelaxScale = ( encRCSeq->maxGopRate + 0.5 < 2.0 * (double)encRCSeq->targetRate * encRCSeq->gopSize / encRCSeq->frameRate ? 2 : 3 ); // quarters
1198
0
          const bool isRateCapperMax = ( encRCSeq->maxGopRate + 0.5 >= 3.0 * (double)encRCSeq->targetRate * encRCSeq->gopSize / encRCSeq->frameRate );
1199
0
          const bool isEndOfSequence = ( it->poc >= flushPOC && flushPOC >= 0 );
1200
0
          const double dLimit = ( isRateCapperMax ? 3.0 : 0.5 * budgetRelaxScale + 0.5 );
1201
0
          double d = (double)it->targetBits, tmpVal;
1202
1203
0
          encRcPic->visActSteady = it->visActY;
1204
1205
0
          if ( it->refreshParameters ) // reset counters for budget usage in subsequent frames
1206
0
          {
1207
0
            encRCSeq->qpCorrection[ frameLevel ] = ( it->poc == 0 && it->isIntra && d < it->numBits ? std::max( -1.0 * it->visActY / double( 1 << ( encRCSeq->bitDepth - 3 ) ), 1.0 - it->numBits / d ) : 0.0 );
1208
0
            if ( !m_pcEncCfg->m_LookAhead )
1209
0
            {
1210
0
              encRCSeq->actualBitCnt[ frameLevel ] = encRCSeq->targetBitCnt[ frameLevel ] = 0;
1211
0
            }
1212
0
            encRcPic->refreshParams = true;
1213
0
          }
1214
0
          if ( it->isIntra ) // update temp stationarity for budget usage in subsequent frames
1215
0
          {
1216
0
            encRCSeq->lastIntraSM = it->frameInGopRatio;
1217
0
          }
1218
0
          if ( it->isStartOfGop )
1219
0
          {
1220
0
            bool allowMoreRatePeaks = false;
1221
0
            encRCSeq->rateBoostFac  = 1.0;
1222
1223
0
            if ( m_pcEncCfg->m_LookAhead && !encRCSeq->isRateSavingMode ) // rate boost factor
1224
0
            {
1225
0
              encRCSeq->rateBoostFac = getLookAheadBoostFac( isRateCapperMax ? 2 : budgetRelaxScale - 1 );
1226
0
              allowMoreRatePeaks = ( encRCSeq->rateBoostFac > 1.0 && isRateCapperMax );
1227
0
              if ( allowMoreRatePeaks && !isEndOfSequence )
1228
0
              {
1229
0
                encRCSeq->lastIntraSM = 1.0;
1230
0
              }
1231
0
              if ( encRCSeq->rateBoostFac > 1.0 && !isEndOfSequence )
1232
0
              {
1233
0
                encRCSeq->estimatedBitUsage = std::max( encRCSeq->estimatedBitUsage, encRCSeq->bitsUsed ); // TL<2: relax constraint
1234
0
              }
1235
0
            }
1236
0
            encRCSeq->isIntraGOP = ( ( it->isStartOfIntra || allowMoreRatePeaks ) && !isEndOfSequence && !encRCSeq->isRateSavingMode );
1237
0
          }
1238
0
          else if ( frameLevel == 2 && it->refreshParameters && encRCSeq->scRelax && !isEndOfSequence && !encRCSeq->isRateSavingMode )
1239
0
          {
1240
0
            encRCSeq->estimatedBitUsage = std::max( encRCSeq->estimatedBitUsage, encRCSeq->bitsUsed ); // cut: relax rate constraint
1241
0
          }
1242
0
          encRCSeq->scRelax = ( frameLevel < 2 && it->refreshParameters && encRCSeq->estimatedBitUsage > encRCSeq->bitsUsed );
1243
1244
0
          CHECK( slice->TLayer >= 7, "analyzed RC frame must have TLayer < 7" );
1245
1246
0
          tmpVal = ( encRCSeq->isIntraGOP ? 0.5 + 0.5 * encRCSeq->lastIntraSM : 0.5 ); // IGOP
1247
0
          if ( !isEndOfSequence && !m_pcEncCfg->m_LookAhead && frameLevel == 2 && encRCSeq->maxGopRate + 0.5 < 3.0 * (double)encRCSeq->targetRate * encRCSeq->gopSize / encRCSeq->frameRate )
1248
0
          {
1249
0
            encRCSeq->estimatedBitUsage = std::max( encRCSeq->estimatedBitUsage, ( budgetRelaxScale * encRCSeq->estimatedBitUsage + ( 4 - budgetRelaxScale ) * encRCSeq->bitsUsed + 2 ) >> 2 );
1250
0
          }
1251
1252
0
          if ( encRCSeq->rateBoostFac > 1.0 )
1253
0
          {
1254
0
            it->targetBits = int( it->targetBits * encRCSeq->rateBoostFac + 0.5 ); // boosting
1255
0
          }
1256
0
          encRcPic->tmpTargetBits = it->targetBits;
1257
1258
          // calculate the difference of under or overspent bits and adjust the current target bits based on the GOP and frame ratio
1259
0
          d = encRcPic->tmpTargetBits + std::min( (int64_t) encRCSeq->maxGopRate, encRCSeq->estimatedBitUsage - encRCSeq->bitsUsed ) * tmpVal * it->frameInGopRatio;
1260
1261
          // try to hit target rate more aggressively in last coded frames, lambda/QP clipping below will ensure smooth value change
1262
0
          if ( isEndOfSequence )
1263
0
          {
1264
0
            d += std::min( (int64_t) encRCSeq->maxGopRate, encRCSeq->estimatedBitUsage - encRCSeq->bitsUsed ) * ( 1.0 - tmpVal ) * it->frameInGopRatio;
1265
0
          }
1266
0
          else if ( d > dLimit * encRcPic->tmpTargetBits )
1267
0
          {
1268
0
            d = encRcPic->tmpTargetBits * dLimit; // prevent large spendings after easy scenes
1269
0
          }
1270
0
          else if ( d * dLimit < encRcPic->tmpTargetBits )
1271
0
          {
1272
0
            d = encRcPic->tmpTargetBits / dLimit; // prevent small spendings after hard scenes
1273
0
          }
1274
1275
0
          if ( !isEndOfSequence && it->isStartOfGop && it->poc > 0 ) // target bitrate capping
1276
0
          {
1277
0
            const double scale = encRCSeq->intraPeriod / ( encRCSeq->intraPeriod + encRCSeq->lastIntraSM * encRCSeq->gopSize );
1278
0
            const double fBits = scale * ( encRCSeq->isIntraGOP ? 1.0 + encRCSeq->lastIntraSM : 1.0 ) * it->frameInGopRatio; // IGOP
1279
1280
0
            if ( d > fBits * encRCSeq->maxGopRate ) d = fBits * encRCSeq->maxGopRate;
1281
0
          }
1282
0
          d = std::max( 1.0, d );
1283
0
          encRcPic->targetBits = int( d + 0.5 );
1284
1285
0
          tmpVal = updateQPstartModelVal() + log (sqrOfResRatio) / log (2.0); // GOP's QPstart
1286
0
          d /= (double)it->numBits;
1287
0
          d = firstPassSliceQP - ( 105.0 / 128.0 ) * sqrt( (double)std::max( 1, firstPassSliceQP ) ) * log( d ) / log( 2.0 );
1288
0
          sliceQP = int( 0.5 + d + ( it->isIntra ? 0.375 : 0.5 ) * std::max( 0.0, tmpVal - d ) + encRCSeq->qpCorrection[ frameLevel ] );
1289
1290
0
          encRcPic->clipTargetQP( getPicList(), ( m_pcEncCfg->m_LookAhead ? getBaseQP() : m_pcEncCfg->m_QP ) + ( it->isIntra ? m_pcEncCfg->m_intraQPOffset : 0 ), 5 - budgetRelaxScale,
1291
0
                                  ( it->poc < encRCSeq->gopSize ? 0 : ( m_pcEncCfg->m_maxTLayer + 1 ) >> 1 ), sqrOfResRatio, sliceQP, &encRCSeq->lastAverageQP );
1292
0
          lambda = it->lambda * pow( 2.0, double( sliceQP - firstPassSliceQP ) / 3.0 );
1293
0
          lambda = Clip3( encRCSeq->minEstLambda, encRCSeq->maxEstLambda, lambda );
1294
1295
0
          if ( m_pcEncCfg->m_LookAhead ) // update frame bit ratios for bit budget calculation
1296
0
          {
1297
0
            if ( it->isStartOfGop )
1298
0
            {
1299
0
              if ( it->poc == 0 && it->isIntra ) // handle first I-frame being in separate GOP
1300
0
              {
1301
0
                uint32_t gopBits = it->numBits;
1302
0
                std::list<TRCPassStats>::iterator itNext = it;
1303
1304
0
                for ( itNext++; itNext != encRCSeq->firstPassData.end() && !itNext->isStartOfGop; itNext++ )
1305
0
                {
1306
0
                  gopBits += itNext->numBits;
1307
0
                }
1308
0
                encRCSeq->lastIntraSM = (double)it->numBits / gopBits;
1309
0
              }
1310
0
              else if ( it->isIntra && !it->isStartOfIntra && !isEndOfSequence && !encRCSeq->isRateSavingMode && encRCSeq->estimatedBitUsage - encRCSeq->bitsUsed < encRcPic->targetBits )
1311
0
              {
1312
0
                encRCSeq->bitsUsedQPLimDiff += ( encRCSeq->estimatedBitUsage - encRCSeq->bitsUsed - encRcPic->targetBits ) >> 1;
1313
0
              }
1314
0
            }
1315
0
          }
1316
0
          if ( it->isIntra ) // update history for parameter clipping in subsequent key frames
1317
0
          {
1318
0
            encRCSeq->lastIntraQP = sliceQP;
1319
0
          }
1320
1321
0
          break;
1322
0
        }
1323
0
      }
1324
0
    }
1325
0
  }
1326
1327
0
  qp = sliceQP;
1328
0
  finalLambda = lambda;
1329
0
}
1330
1331
}