Coverage Report

Created: 2026-10-28 06:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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