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