/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 | | } |