/work/vvenc/source/Lib/EncoderLib/PreProcess.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 PreProcess.cpp |
45 | | \brief |
46 | | */ |
47 | | |
48 | | |
49 | | #include "PreProcess.h" |
50 | | #include "BitAllocation.h" |
51 | | |
52 | | //! \ingroup EncoderLib |
53 | | //! \{ |
54 | | |
55 | | namespace vvenc { |
56 | | |
57 | | |
58 | | PreProcess::PreProcess( MsgLog& _m ) |
59 | 1.49k | : m_encCfg ( nullptr ) |
60 | 1.49k | , m_gopCfg ( _m ) |
61 | 1.49k | , m_lastPoc ( 0 ) |
62 | 1.49k | , m_isHighRes ( false ) |
63 | 1.49k | , m_doSTA ( false ) |
64 | 1.49k | , m_doTempDown ( false ) |
65 | 1.49k | , m_doVisAct ( false ) |
66 | 1.49k | , m_doVisActQpa( false ) |
67 | 1.49k | , m_cappedCQF ( false ) |
68 | 1.49k | { |
69 | 1.49k | } |
70 | | |
71 | | |
72 | | PreProcess::~PreProcess() |
73 | 1.49k | { |
74 | 1.49k | } |
75 | | |
76 | | |
77 | | void PreProcess::init( const VVEncCfg& encCfg, bool isFinalPass ) |
78 | 1.49k | { |
79 | 1.49k | m_gopCfg.initGopList( encCfg.m_DecodingRefreshType, encCfg.m_poc0idr, encCfg.m_IntraPeriod, encCfg.m_GOPSize, encCfg.m_leadFrames, encCfg.m_picReordering, encCfg.m_GOPList, encCfg.m_vvencMCTF, encCfg.m_FirstPassMode, encCfg.m_minIntraDist ); |
80 | 1.49k | CHECK( m_gopCfg.getMaxTLayer() != encCfg.m_maxTLayer, "max temporal layer of gop configuration does not match pre-configured value" ); |
81 | | |
82 | 1.49k | m_encCfg = &encCfg; |
83 | | |
84 | 1.49k | m_lastPoc = std::numeric_limits<int>::min(); |
85 | 1.49k | m_isHighRes = (std::min (m_encCfg->m_SourceWidth, m_encCfg->m_SourceHeight) > 1280); |
86 | | |
87 | 1.49k | m_doSTA = m_encCfg->m_sliceTypeAdapt > 0; |
88 | 1.49k | m_cappedCQF = m_encCfg->m_RCNumPasses != 2 && m_encCfg->m_rateCap; |
89 | 1.49k | m_doTempDown = m_encCfg->m_FirstPassMode == 2 || m_encCfg->m_FirstPassMode == 4; |
90 | 1.49k | m_doVisAct = m_encCfg->m_usePerceptQPA |
91 | 0 | || (m_encCfg->m_LookAhead && m_encCfg->m_RCTargetBitrate > 0) |
92 | 0 | || (m_encCfg->m_RCNumPasses > 1 && (!isFinalPass)); |
93 | 1.49k | m_doVisActQpa = m_encCfg->m_usePerceptQPA; |
94 | 1.49k | } |
95 | | |
96 | | |
97 | | void PreProcess::initPicture( Picture* pic ) |
98 | 1.49k | { |
99 | 1.49k | } |
100 | | |
101 | | |
102 | | void PreProcess::processPictures( const PicList& picList, AccessUnitList& auList, PicList& doneList, PicList& freeList ) |
103 | 2.98k | { |
104 | | // continue with next poc |
105 | 2.98k | if( ! picList.empty() && picList.back()->poc > m_lastPoc ) |
106 | 1.49k | { |
107 | 1.49k | auto pic = picList.back(); |
108 | | |
109 | | // set gop entry |
110 | 1.49k | m_gopCfg.getNextGopEntry( pic->m_picShared->m_gopEntry ); |
111 | 1.49k | CHECK( pic->m_picShared->m_gopEntry.m_POC != pic->poc, "invalid state" ); |
112 | | |
113 | 1.49k | if( ! pic->m_picShared->isLeadTrail() ) |
114 | 1.49k | { |
115 | | // link previous frames |
116 | 1.49k | xLinkPrevQpaBufs( pic, picList ); |
117 | | |
118 | | // compute visual activity |
119 | 1.49k | xGetVisualActivity( pic, picList ); |
120 | | |
121 | | // detect scc |
122 | 1.49k | xDetectScc( pic ); |
123 | | |
124 | | // scene cuts, where the film grain parameters switch |
125 | 1.49k | if( m_encCfg->m_fga ) |
126 | 0 | { |
127 | 0 | xDetectFgaSceneCut( pic ); |
128 | 0 | } |
129 | | |
130 | | // slice type adaptation |
131 | 1.49k | if( m_doSTA && pic->gopEntry->m_temporalId == 0 ) |
132 | 0 | { |
133 | | // detect scene cut in gop and adapt slice type |
134 | 0 | xDetectSTA( pic, picList ); |
135 | | |
136 | | // disable temporal downsampling for gop with scene cut |
137 | 0 | if( m_doTempDown && pic->gopEntry->m_scType == SCT_TL0_SCENE_CUT ) |
138 | 0 | { |
139 | 0 | xDisableTempDown( pic, picList ); |
140 | 0 | } |
141 | 0 | } |
142 | | |
143 | | // disable temporal downsampling in lowest 3 temp. layers |
144 | | // with one-pass RC and QPA; this stabilizes the RC a bit |
145 | 1.49k | if( m_doTempDown && m_doVisActQpa && pic->gopEntry->m_temporalId <= 2 && m_encCfg->m_LookAhead && m_encCfg->m_RCTargetBitrate > 0 ) |
146 | 0 | { |
147 | 0 | xDisableTempDown( pic, picList, 0 /*for faster - TODO: 2 for fast*/ ); |
148 | 0 | } |
149 | 1.49k | } |
150 | 0 | else if( pic->gopEntry->m_temporalId == 0 ) |
151 | 0 | { |
152 | 0 | xGetVisualActivity( pic, picList ); |
153 | 0 | } |
154 | | |
155 | | // cleanup pic list |
156 | 1.49k | xFreeUnused( pic, picList, doneList, freeList ); |
157 | | |
158 | 1.49k | m_lastPoc = picList.back()->poc; |
159 | 1.49k | } |
160 | 1.49k | else if( ! picList.empty() && picList.back()->isFlush ) |
161 | 1.49k | { |
162 | | // first flush call, fix start of last gop |
163 | 1.49k | if( ! picList.empty() ) |
164 | 1.49k | { |
165 | 1.49k | Picture* pic = xGetStartOfLastGop( picList ); |
166 | 1.49k | if( pic ) |
167 | 1.49k | { |
168 | 1.49k | m_gopCfg.fixStartOfLastGop( pic->m_picShared->m_gopEntry ); |
169 | | // compute visual activity for start of last GOP |
170 | | // this works in combination with xUpdateVAStartOfLastGop() |
171 | 1.49k | xGetVisualActivity( pic, picList ); |
172 | 1.49k | } |
173 | 1.49k | } |
174 | | |
175 | | // cleanup when flush is set and all pics are done |
176 | 1.49k | for( auto pic : picList ) |
177 | 1.49k | { |
178 | 1.49k | freeList.push_back( pic ); |
179 | 1.49k | } |
180 | 1.49k | } |
181 | 2.98k | } |
182 | | |
183 | | |
184 | | void PreProcess::xFreeUnused( Picture* pic, const PicList& picList, PicList& doneList, PicList& freeList ) const |
185 | 1.49k | { |
186 | | // current picture is done |
187 | 1.49k | doneList.push_back( pic ); |
188 | | |
189 | | // free unused previous frames |
190 | 1.49k | bool foundTl0 = ! m_doSTA; // is sta is off, not need to keep previous tl0 pic |
191 | 1.49k | int idx = 0; |
192 | 1.49k | Picture* startOfGop = xGetStartOfLastGop( picList ); |
193 | 2.98k | for( auto itr = picList.rbegin(); itr != picList.rend(); itr++, idx++ ) |
194 | 1.49k | { |
195 | 1.49k | Picture* tp = *itr; |
196 | 1.49k | bool keepPic = false; |
197 | | |
198 | | // keep previous frames for visual activity |
199 | 1.49k | keepPic |= ( m_doVisAct || m_cappedCQF || m_encCfg->m_GOPQPA ) && idx < NUM_QPA_PREV_FRAMES; |
200 | | // keep previous (first) tl0 pic for sta |
201 | 1.49k | keepPic |= ! foundTl0 && ( tp->gopEntry->m_temporalId == 0 || m_doTempDown ); |
202 | 1.49k | foundTl0 |= tp->gopEntry->m_temporalId == 0; // update found tl0 |
203 | | // keep start of last gop |
204 | 1.49k | keepPic |= ( tp == startOfGop ); |
205 | | |
206 | 1.49k | if( ! keepPic ) |
207 | 0 | { |
208 | 0 | freeList.push_back( tp ); |
209 | 0 | } |
210 | 1.49k | } |
211 | 1.49k | } |
212 | | |
213 | | |
214 | | void PreProcess::xGetPrevPics( const Picture* pic, const PicList& picList, const Picture* prevPics[ NUM_QPA_PREV_FRAMES ] ) const |
215 | 1.49k | { |
216 | 1.49k | std::fill_n( prevPics, NUM_QPA_PREV_FRAMES, nullptr ); |
217 | | |
218 | | // find previous pics |
219 | 1.49k | int prevPoc = pic->poc; |
220 | 1.49k | int prevIdx = 0; |
221 | 2.98k | for( auto itr = picList.rbegin(); itr != picList.rend() && prevIdx < NUM_QPA_PREV_FRAMES; itr++ ) |
222 | 1.49k | { |
223 | 1.49k | Picture* tp = *itr; |
224 | 1.49k | if( tp->poc >= pic->poc ) |
225 | 1.49k | continue; |
226 | 0 | if( tp->poc != prevPoc - 1 ) |
227 | 0 | break; |
228 | 0 | prevPics[ prevIdx ] = tp; |
229 | 0 | prevPoc -= 1; |
230 | 0 | prevIdx += 1; |
231 | 0 | } |
232 | | // if first prev not found, set link to picture itself |
233 | 1.49k | if( prevPics[ 0 ] == nullptr ) |
234 | 1.49k | { |
235 | 1.49k | prevPics[ 0 ] = pic; |
236 | 1.49k | } |
237 | 1.49k | } |
238 | | |
239 | | |
240 | | Picture* PreProcess::xGetPrevTL0Pic( const Picture* pic, const PicList& picList ) const |
241 | 2.98k | { |
242 | | // find previous tl0 picture |
243 | 2.98k | Picture* prevTL0 = nullptr; |
244 | 5.97k | for( auto itr = picList.rbegin(); itr != picList.rend(); itr++ ) |
245 | 2.98k | { |
246 | 2.98k | Picture* tp = *itr; |
247 | 2.98k | if( tp == pic ) |
248 | 2.98k | continue; |
249 | 0 | if( tp->gopEntry->m_temporalId == 0 ) |
250 | 0 | { |
251 | 0 | prevTL0 = tp; |
252 | 0 | break; |
253 | 0 | } |
254 | 0 | } |
255 | 2.98k | return prevTL0; |
256 | 2.98k | } |
257 | | |
258 | | |
259 | | Picture* PreProcess::xGetStartOfLastGop( const PicList& picList ) const |
260 | 2.98k | { |
261 | | // use only non lead trail pics |
262 | 2.98k | std::vector<Picture*> cnList; |
263 | 2.98k | cnList.reserve( picList.size() ); |
264 | 2.98k | for( auto pic : picList ) |
265 | 2.98k | { |
266 | 2.98k | if( ! pic->m_picShared->isLeadTrail() ) |
267 | 2.98k | { |
268 | 2.98k | cnList.push_back( pic ); |
269 | 2.98k | } |
270 | 2.98k | } |
271 | | |
272 | 2.98k | if( cnList.empty() ) |
273 | 0 | { |
274 | 0 | return nullptr; |
275 | 0 | } |
276 | | |
277 | | // sort pics by coding number |
278 | 2.98k | std::sort( cnList.begin(), cnList.end(), []( auto& a, auto& b ){ return a->gopEntry->m_codingNum < b->gopEntry->m_codingNum; } ); |
279 | | |
280 | | // find start of current gop |
281 | 2.98k | Picture* pic = cnList.back(); |
282 | 2.98k | const int poc0Offset = (m_encCfg->m_poc0idr ? -1 : 0); // place leading poc 0 idr in GOP -1 |
283 | 2.98k | const int lastGopNum = pic->gopEntry->m_gopNum + (pic->gopEntry->m_POC == 0 ? poc0Offset : 0); |
284 | 5.97k | for( auto itr = cnList.rbegin(); itr != cnList.rend(); itr++ ) |
285 | 2.98k | { |
286 | 2.98k | Picture* tp = *itr; |
287 | 2.98k | const int tpGopNum = tp->gopEntry->m_gopNum + (tp->gopEntry->m_POC == 0 ? poc0Offset : 0); |
288 | 2.98k | if( tpGopNum != lastGopNum ) |
289 | 0 | { |
290 | 0 | return pic; |
291 | 0 | } |
292 | 2.98k | pic = tp; |
293 | 2.98k | } |
294 | 2.98k | return pic; |
295 | 2.98k | } |
296 | | |
297 | | |
298 | | void PreProcess::xLinkPrevQpaBufs( Picture* pic, const PicList& picList ) const |
299 | 1.49k | { |
300 | 1.49k | PicShared* picShared = pic->m_picShared; |
301 | | |
302 | | // find max previous pictures |
303 | 1.49k | if( m_doVisAct ) |
304 | 1.49k | { |
305 | 1.49k | const Picture* prevPics[ NUM_QPA_PREV_FRAMES ]; |
306 | 1.49k | xGetPrevPics( pic, picList, prevPics ); |
307 | 4.47k | for( int i = 0; i < NUM_QPA_PREV_FRAMES; i++ ) |
308 | 2.98k | { |
309 | 2.98k | if( prevPics[ i ] ) |
310 | 1.49k | { |
311 | 1.49k | picShared->m_prevShared[ i ] = prevPics[ i ]->m_picShared; |
312 | 1.49k | } |
313 | 2.98k | } |
314 | 1.49k | } |
315 | 1.49k | } |
316 | | |
317 | | |
318 | | void PreProcess::xGetVisualActivity( Picture* pic, const PicList& picList ) const |
319 | 2.98k | { |
320 | 2.98k | VisAct va[ MAX_NUM_CH ]; |
321 | 2.98k | VisAct vaTL0; |
322 | | |
323 | 2.98k | const bool doChroma = m_cappedCQF && pic->gopEntry->m_isStartOfGop && isChromaEnabled( pic->chromaFormat ); |
324 | | // for the time being qpa activity done on ctu basis in applyQPAdaptationSlice(), which for now sums up luma activity |
325 | 2.98k | const bool doVisAct = m_doVisAct && !m_doVisActQpa; |
326 | 2.98k | const bool doSpatAct = pic->gopEntry->m_isStartOfGop && m_encCfg->m_GOPQPA; |
327 | 2.98k | const bool doVisActStartOfGOP = pic->gopEntry->m_isStartOfGop && m_cappedCQF; |
328 | 2.98k | const bool doVisActTL0 = pic->gopEntry->m_temporalId == 0 && ( m_doSTA || m_cappedCQF || !doVisAct ); |
329 | | |
330 | | // spatial activity |
331 | 2.98k | if( doSpatAct || doVisAct || doVisActStartOfGOP || doVisActTL0 ) |
332 | 1.49k | { |
333 | 1.49k | xGetSpatialActivity( pic, true, doChroma, va ); |
334 | | // copy luma spatial activity for prev TL0 |
335 | 1.49k | if( doVisActTL0 ) |
336 | 1.49k | { |
337 | 1.49k | vaTL0 = va[ CH_L ]; |
338 | 1.49k | } |
339 | 1.49k | } |
340 | | |
341 | | // visual activity to previous pics (luma only) |
342 | 2.98k | if( doVisAct || doVisActStartOfGOP ) |
343 | 0 | { |
344 | | // get temporal activity for picture |
345 | 0 | const Picture* prevPics[ NUM_QPA_PREV_FRAMES ]; |
346 | 0 | xGetPrevPics( pic, picList, prevPics ); |
347 | 0 | xGetTemporalActivity( pic, prevPics[ 0 ], prevPics[ 1 ], va[ CH_L ] ); |
348 | | // visual activity for picture |
349 | 0 | updateVisAct( va[ CH_L ], m_encCfg->m_internalBitDepth[ CH_L ] ); |
350 | 0 | } |
351 | | |
352 | | // visual activity to previous TL0 pic (luma only) |
353 | 2.98k | const Picture* prevTL0 = xGetPrevTL0Pic( pic, picList ); |
354 | 2.98k | if( doVisActTL0 && prevTL0 ) |
355 | 0 | { |
356 | | // get temporal activity for picture |
357 | 0 | xGetTemporalActivity( pic, prevTL0, nullptr, vaTL0 ); |
358 | | // visual activity for picture |
359 | 0 | updateVisAct( vaTL0, m_encCfg->m_internalBitDepth[ CH_L ] ); |
360 | 0 | } |
361 | | |
362 | | // store visual activity |
363 | 2.98k | PicShared* picShared = pic->m_picShared; |
364 | 2.98k | picShared->m_picVA.spatAct[ CH_L ] = ClipBD( (uint16_t)va[ CH_L ].spatAct, 12 ); |
365 | 2.98k | picShared->m_picVA.spatAct[ CH_C ] = ClipBD( (uint16_t)va[ CH_C ].spatAct, 12 ); |
366 | 2.98k | picShared->m_picVA.visAct = va[ CH_L ].visAct; |
367 | 2.98k | picShared->m_picVA.visActTL0 = vaTL0.visAct; |
368 | 2.98k | if( prevTL0 ) |
369 | 0 | { |
370 | 0 | picShared->m_picVA.prevTL0spatAct[ CH_L ] = prevTL0->m_picShared->m_picVA.spatAct[ CH_L ]; |
371 | 0 | picShared->m_picVA.prevTL0spatAct[ CH_C ] = prevTL0->m_picShared->m_picVA.spatAct[ CH_C ]; |
372 | 0 | } |
373 | | // update visual activity in pic, this is needed for STA detection in this stage |
374 | 2.98k | pic->picVA = picShared->m_picVA; |
375 | 2.98k | } |
376 | | |
377 | | |
378 | | void PreProcess::xGetSpatialActivity( Picture* pic, bool doLuma, bool doChroma, VisAct va[ MAX_NUM_CH ] ) const |
379 | 1.49k | { |
380 | | // luma part |
381 | 1.49k | if( doLuma ) |
382 | 1.49k | { |
383 | 1.49k | const int bitDepth = m_encCfg->m_internalBitDepth[ CH_L ]; |
384 | 1.49k | CPelBuf origBuf = pic->getOrigBuf( COMP_Y ); |
385 | 1.49k | calcSpatialVisAct( origBuf.buf, origBuf.stride, origBuf.height, origBuf.width, bitDepth, m_isHighRes, va[ CH_L ] ); |
386 | 1.49k | } |
387 | | |
388 | | // chroma part |
389 | 1.49k | if( doChroma ) |
390 | 0 | { |
391 | 0 | const int bitDepth = m_encCfg->m_internalBitDepth[ CH_C ]; |
392 | 0 | const bool isUHD = m_isHighRes && ( pic->chromaFormat == CHROMA_444 ); |
393 | 0 | const int numComp = getNumberValidComponents( pic->chromaFormat ); |
394 | | // accumulate spatial activity over chroma components |
395 | 0 | for( int comp = 1; comp < numComp; comp++ ) |
396 | 0 | { |
397 | 0 | VisAct chVA; |
398 | 0 | const ComponentID compID = (ComponentID) comp; |
399 | 0 | CPelBuf origBuf = pic->getOrigBuf( compID ); |
400 | 0 | calcSpatialVisAct( origBuf.buf, origBuf.stride, origBuf.height, origBuf.width, bitDepth, isUHD, chVA ); |
401 | 0 | va[ CH_C ].hpSpatAct += chVA.hpSpatAct; |
402 | 0 | } |
403 | | // mean value over chroma components |
404 | 0 | va[ CH_C ].hpSpatAct = va[ CH_C ].hpSpatAct / (double)( numComp - 1 ); |
405 | | // spatial in 12 bit |
406 | 0 | va[ CH_C ].spatAct = unsigned (0.5 + va[ CH_C ].hpSpatAct * double (bitDepth < 12 ? 1 << (12 - bitDepth) : 1)); |
407 | 0 | } |
408 | 1.49k | } |
409 | | |
410 | | |
411 | | void PreProcess::xGetTemporalActivity( Picture* curPic, const Picture* refPic1, const Picture* refPic2, VisAct& va ) const |
412 | 0 | { |
413 | 0 | CHECK( curPic == nullptr || refPic1 == nullptr, "no pictures given to compute visual activity" ); |
414 | |
|
415 | 0 | const int bitDepth = m_encCfg->m_internalBitDepth[ CH_L ]; |
416 | |
|
417 | 0 | CPelBuf origBufs[ 3 ]; |
418 | 0 | origBufs[ 0 ] = curPic->getOrigBuf( COMP_Y ); |
419 | 0 | origBufs[ 1 ] = refPic1->getOrigBuf( COMP_Y ); |
420 | 0 | if( refPic2 ) |
421 | 0 | { |
422 | 0 | origBufs[ 2 ] = refPic2->getOrigBuf( COMP_Y ); |
423 | 0 | } |
424 | |
|
425 | 0 | calcTemporalVisAct( origBufs[0].buf, origBufs[0].stride, origBufs[0].height, origBufs[0].width, |
426 | 0 | origBufs[1].buf, origBufs[1].stride, |
427 | 0 | origBufs[2].buf, origBufs[2].stride, |
428 | 0 | m_encCfg->m_FrameRate / m_encCfg->m_FrameScale, |
429 | 0 | bitDepth, |
430 | 0 | m_isHighRes, |
431 | 0 | va |
432 | 0 | ); |
433 | 0 | } |
434 | | |
435 | | |
436 | | void PreProcess::xDetectSTA( Picture* pic, const PicList& picList ) |
437 | 0 | { |
438 | 0 | const Picture* prevTL0 = xGetPrevTL0Pic( pic, picList ); |
439 | |
|
440 | 0 | int picMemorySTA = 0; |
441 | 0 | bool isSta = false; |
442 | 0 | bool intraAllowed = m_gopCfg.isSTAallowed( pic->poc ); |
443 | |
|
444 | 0 | if( prevTL0 && prevTL0->picVA.visActTL0 > 0 && intraAllowed ) |
445 | 0 | { |
446 | 0 | const int scThreshold = ( ( pic->isSccStrong ? 6 : ( pic->isSccWeak ? 5 : 4 ) ) * ( m_isHighRes ? 19 : 15 ) ) >> 2; |
447 | |
|
448 | 0 | if( pic->picVA.visActTL0 * 11 > prevTL0->picVA.visActTL0 * scThreshold |
449 | 0 | || prevTL0->picVA.visActTL0 * 11 > pic->picVA.visActTL0 * ( scThreshold + 1 ) ) |
450 | 0 | { |
451 | 0 | const int dir = pic->picVA.visActTL0 < prevTL0->picVA.visActTL0 ? -1 : 1; |
452 | 0 | picMemorySTA = prevTL0->picVA.visActTL0 * dir; |
453 | 0 | isSta = ( picMemorySTA * prevTL0->picMemorySTA ) >= 0; |
454 | 0 | } |
455 | 0 | } |
456 | |
|
457 | 0 | if( isSta ) |
458 | 0 | { |
459 | 0 | PicShared* picShared = pic->m_picShared; |
460 | 0 | pic->picMemorySTA = picMemorySTA; |
461 | 0 | picShared->m_picMemorySTA = picMemorySTA; |
462 | 0 | picShared->m_gopEntry.m_sliceType = 'I'; |
463 | 0 | picShared->m_gopEntry.m_scType = SCT_TL0_SCENE_CUT; |
464 | 0 | m_gopCfg.setLastIntraSTA( pic->poc ); |
465 | |
|
466 | 0 | if( m_encCfg->m_sliceTypeAdapt == 2 ) |
467 | 0 | { |
468 | 0 | m_gopCfg.startIntraPeriod( picShared->m_gopEntry ); |
469 | 0 | } |
470 | 0 | } |
471 | 0 | } |
472 | | |
473 | | |
474 | | // A picture starts a new scene when its luma histogram (64 bins, every 4th sample of every 4th row) differs from that |
475 | | // of the picture before it by more than 0.3 (L1 distance of the normalized histograms, 0 to 2) and the next picture's |
476 | | // stays within a quarter of that of it, so that a single flash frame is no cut. On three minutes of The Beekeeper (4316 |
477 | | // pictures) the distance is 0.014 at the median; the 41 cuts found are the same from thresholds 0.15 to 0.3, and |
478 | | // no GOP of 32 pictures has two of them. The cut is marked on the picture once the next one is processed, and every |
479 | | // picture carries the latest cut found before it, so that the film grain analysis of a GOP's first coded picture knows |
480 | | // the cuts among the pictures it covers. |
481 | | void PreProcess::xDetectFgaSceneCut( Picture* pic ) |
482 | 0 | { |
483 | 0 | const CPelBuf y = pic->getOrigBuf( COMP_Y ); |
484 | 0 | const int shift = std::max( 0, m_encCfg->m_internalBitDepth[ CH_L ] - 6 ); |
485 | 0 | std::vector<int> hist( FGA_HIST_BINS, 0 ); |
486 | 0 | int numSamples = 0; |
487 | 0 | for( int row = 0; row < (int) y.height; row += 4 ) |
488 | 0 | { |
489 | 0 | const Pel* src = y.bufAt( 0, row ); |
490 | 0 | for( int x = 0; x < (int) y.width; x += 4, numSamples++ ) |
491 | 0 | { |
492 | 0 | hist[ std::min( FGA_HIST_BINS - 1, std::max( 0, src[ x ] >> shift ) ) ]++; |
493 | 0 | } |
494 | 0 | } |
495 | |
|
496 | 0 | const bool follows = pic->poc == m_fgaPrevPoc + 1; |
497 | 0 | int distance = 0; // in samples: the L1 distance times numSamples |
498 | 0 | for( int b = 0; follows && b < FGA_HIST_BINS; b++ ) |
499 | 0 | { |
500 | 0 | distance += std::abs( hist[ b ] - m_fgaPrevHist[ b ] ); |
501 | 0 | } |
502 | | // the previous picture is a cut when it jumped from its predecessor (above 0.3) and this one stays (below 0.075) |
503 | 0 | if( follows && m_fgaPrevShared && m_fgaPrevDistance * 10 > 3 * numSamples && distance * 40 < 3 * numSamples ) |
504 | 0 | { |
505 | 0 | m_fgaPrevShared->fgaSceneCut() = true; |
506 | 0 | m_fgaLastCutPoc = m_fgaPrevPoc; |
507 | 0 | } |
508 | 0 | pic->m_picShared->fgaLastSceneCutPoc() = m_fgaLastCutPoc; |
509 | |
|
510 | 0 | m_fgaPrevHist = std::move( hist ); |
511 | 0 | m_fgaPrevPoc = pic->poc; |
512 | 0 | m_fgaPrevDistance = follows ? distance : 0; |
513 | 0 | m_fgaPrevShared = pic->m_picShared; |
514 | 0 | } |
515 | | |
516 | | void PreProcess::xDisableTempDown( Picture* pic, const PicList& picList, const int thresh /*= INT32_MAX*/ ) |
517 | 0 | { |
518 | 0 | for( auto itr = picList.rbegin(); itr != picList.rend(); itr++ ) |
519 | 0 | { |
520 | 0 | Picture* tp = *itr; |
521 | 0 | if( pic->gopEntry->m_gopNum != tp->gopEntry->m_gopNum ) |
522 | 0 | break; |
523 | 0 | if( tp->gopEntry->m_temporalId <= thresh ) |
524 | 0 | tp->m_picShared->m_gopEntry.m_skipFirstPass = false; |
525 | 0 | } |
526 | 0 | } |
527 | | |
528 | | |
529 | | #if FIX_FOR_TEMPORARY_COMPILER_ISSUES_ENABLED && defined( __GNUC__ ) && __GNUC__ == 5 |
530 | | #pragma GCC diagnostic push |
531 | | #pragma GCC diagnostic ignored "-Wstrict-overflow" |
532 | | #endif |
533 | | |
534 | | |
535 | | void PreProcess::xDetectScc( Picture* pic ) const |
536 | 1.49k | { |
537 | 1.49k | if( m_encCfg->m_forceScc > 0 ) |
538 | 0 | { |
539 | 0 | pic->isSccStrong = pic->m_picShared->m_isSccStrong = m_encCfg->m_forceScc >= 3; |
540 | 0 | pic->isSccWeak = pic->m_picShared->m_isSccWeak = m_encCfg->m_forceScc >= 2; |
541 | 0 | return; |
542 | 0 | } |
543 | | |
544 | 1.49k | CPelBuf yuvOrgBuf = pic->getOrigBuf().Y(); |
545 | | |
546 | | // blocksize and threshold |
547 | 1.49k | static constexpr int SIZE_BL = 4; |
548 | 1.49k | static constexpr int K_SC = 23; |
549 | 1.49k | static constexpr int K_noSC = 8; |
550 | | |
551 | | // mean and variance fixed point accuracy |
552 | 1.49k | static constexpr int accM = 4; |
553 | 1.49k | static constexpr int accV = 2; |
554 | | |
555 | 1.49k | static_assert( accM <= 4 && accV <= 4, "Maximum Mean and Variance accuracy of 4 allowed!" ); |
556 | 1.49k | static constexpr int shfM = 4 - accM; |
557 | 1.49k | static constexpr int shfV = 4 + accM - accV; |
558 | 1.49k | static constexpr int addM = 1 << shfM >> 1; |
559 | 1.49k | static constexpr int addV = 1 << shfV >> 1; |
560 | | |
561 | 1.49k | static constexpr int SizeS = SIZE_BL << 1; |
562 | | |
563 | 1.49k | const int minLevel = 1 << ( m_encCfg->m_internalBitDepth[CH_L] - ( m_encCfg->m_videoFullRangeFlag ? 6 : 4 ) ); // 1/16th or 1/64th of range |
564 | | |
565 | 1.49k | const Pel* piSrc = yuvOrgBuf.buf; |
566 | 1.49k | const uint32_t uiStride = yuvOrgBuf.stride; |
567 | 1.49k | const uint32_t uiWidth = yuvOrgBuf.width; |
568 | 1.49k | const uint32_t uiHeight = yuvOrgBuf.height; |
569 | | |
570 | 1.49k | CHECK( ( uiWidth & 7 ) != 0 || ( uiHeight & 7 ) != 0, "Width and height have to be multiples of 8!" ); |
571 | | |
572 | 1.49k | const int amountBlock = ( uiWidth >> 2 ) * ( uiHeight >> 2 ); |
573 | | |
574 | 1.49k | int sR[4] = { 0, 0, 0, 0 }; // strong SCC data |
575 | 1.49k | int zR[4] = { 0, 0, 0, 0 }; // zero input data |
576 | | |
577 | | // sums and absolute deviations of the 4x4 blocks of the two block rows of an 8x8 block row |
578 | 1.49k | static_assert( SIZE_BL == 4 && accM == 4, "blockStats4x4 computes 4x4 block sums and deviations from 16 * sample" ); |
579 | 1.49k | const int numBlocks = uiWidth / SIZE_BL; |
580 | 1.49k | std::vector<int> blkSum[2] = { std::vector<int>( numBlocks ), std::vector<int>( numBlocks ) }; |
581 | 1.49k | std::vector<int> blkDev[2] = { std::vector<int>( numBlocks ), std::vector<int>( numBlocks ) }; |
582 | 1.49k | const int bitDepth = m_encCfg->m_internalBitDepth[CH_L]; |
583 | | |
584 | 31.2k | for( int hh = 0; hh < uiHeight; hh += SizeS ) |
585 | 29.7k | { |
586 | 89.2k | for( int r = 0; r < 2; r++ ) |
587 | 59.5k | { |
588 | 59.5k | g_pelBufOP.blockStats4x4( &piSrc[( hh + r * SIZE_BL ) * uiStride], uiStride, numBlocks, bitDepth, blkSum[r].data(), blkDev[r].data() ); |
589 | 59.5k | } |
590 | | |
591 | 649k | for( int ww = 0; ww < uiWidth; ww += SizeS ) |
592 | 619k | { |
593 | 619k | int Rx = ww >= ( uiWidth >> 1 ) ? 1 : 0; |
594 | 619k | int Ry = hh >= ( uiHeight >> 1 ) ? 2 : 0; |
595 | 619k | Ry = Ry | Rx; |
596 | | |
597 | 619k | int n = 0; |
598 | 619k | int Var[4]; |
599 | | |
600 | 1.85M | for( int j = 0; j < 2; j++ ) |
601 | 1.23M | { |
602 | 3.71M | for( int i = ( ww / SIZE_BL ); i < ( ww / SIZE_BL ) + 2; i++ ) |
603 | 2.47M | { |
604 | 2.47M | const int Mit = ( blkSum[j][i] + addM ) >> shfM; |
605 | 2.47M | const int V = blkDev[j][i]; |
606 | | |
607 | | // if variance is lower than 1 and mean is lower/equal to minLevel |
608 | 2.47M | if( V < ( 1 << ( accM + 4 ) ) && Mit <= ( minLevel << accM ) ) |
609 | 2.47M | { |
610 | 2.47M | Var[n] = -1; |
611 | 2.47M | } |
612 | 0 | else |
613 | 0 | { |
614 | 0 | Var[n] = ( V + addV ) >> shfV; |
615 | 0 | } |
616 | | |
617 | 2.47M | n++; |
618 | 2.47M | } |
619 | 1.23M | } |
620 | | |
621 | 1.85M | for( int i = 0; i < 2; i++ ) |
622 | 1.23M | { |
623 | 1.23M | const int var0 = Var[ i]; |
624 | 1.23M | const int var1 = Var[ i + 2]; |
625 | 1.23M | const int var2 = Var[ i << 1]; |
626 | 1.23M | const int var3 = Var[(i << 1) + 1]; |
627 | | |
628 | 1.23M | if( var0 < 0 && var1 < 0 && zR[Ry] * 20 < amountBlock ) |
629 | 250k | { |
630 | 250k | zR[Ry]++; |
631 | 250k | } |
632 | 988k | else if( var0 == var1 ) |
633 | 988k | { |
634 | 988k | sR[Ry]++; |
635 | 988k | } |
636 | | |
637 | 1.23M | if( var2 < 0 && var3 < 0 && zR[Ry] * 20 < amountBlock ) |
638 | 247k | { |
639 | 247k | zR[Ry]++; |
640 | 247k | } |
641 | 991k | else if( var2 == var3 ) |
642 | 991k | { |
643 | 991k | sR[Ry]++; |
644 | 991k | } |
645 | 1.23M | } |
646 | 619k | } |
647 | 29.7k | } |
648 | | |
649 | 1.49k | bool isSccWeak = false; |
650 | 1.49k | bool isSccStrong = false; |
651 | 1.49k | bool isNoSccStrong = false; |
652 | | |
653 | 1.49k | int numAll = 0; |
654 | 1.49k | int numMin = amountBlock, numMax = 0; |
655 | 1.49k | int numBelow = 0; |
656 | | |
657 | 7.46k | for( int r = 0; r < 4; r++ ) |
658 | 5.97k | { |
659 | 5.97k | numAll += sR[r]; |
660 | 5.97k | numMax = std::max( numMax, sR[r] ); |
661 | 5.97k | numMin = std::min( numMin, sR[r] ); |
662 | 5.97k | numBelow += sR[r] * 100 <= K_SC * ( amountBlock >> 2 ) ? 1 : 0; |
663 | 5.97k | } |
664 | | |
665 | | // lowest quarter is above K_SC threshold |
666 | 1.49k | isSccStrong = numMin * 100 > K_SC * ( amountBlock >> 2 ); |
667 | | // lowest quarter is below K_noSC threshold and theres more than one quarter below K_SC threshold |
668 | 1.49k | isNoSccStrong = numMin * 100 <= K_noSC * ( amountBlock >> 2 ) && numBelow > 1; |
669 | | // overall is above K_SC threshold |
670 | 1.49k | isSccWeak = numAll * 100 > K_SC * amountBlock; |
671 | | // peak quarter is above 2.15*K_SC threshold |
672 | 1.49k | isSccStrong |= isSccWeak && !isNoSccStrong && numMax * 186 > K_SC * amountBlock; |
673 | | |
674 | 1.49k | PicShared* picShared = pic->m_picShared; |
675 | 1.49k | pic->isSccWeak = isSccWeak; |
676 | 1.49k | pic->isSccStrong = isSccStrong; |
677 | 1.49k | picShared->m_isSccWeak = isSccWeak; |
678 | 1.49k | picShared->m_isSccStrong = isSccStrong; |
679 | 1.49k | } |
680 | | |
681 | | |
682 | | #if FIX_FOR_TEMPORARY_COMPILER_ISSUES_ENABLED && defined( __GNUC__ ) && __GNUC__ == 5 |
683 | | #pragma GCC diagnostic pop |
684 | | #endif |
685 | | |
686 | | |
687 | | } // namespace vvenc |
688 | | |
689 | | //! \} |
690 | | |