source: 3DVCSoftware/trunk/source/App/TAppDecoder/TAppDecTop.cpp @ 1313

Last change on this file since 1313 was 1313, checked in by tech, 9 years ago

Merged 14.1-update-dev1@1312.

  • Property svn:eol-style set to native
File size: 38.1 KB
Line 
1/* The copyright in this software is being made available under the BSD
2 * License, included below. This software may be subject to other third party
3 * and contributor rights, including patent rights, and no such rights are
4 * granted under this license.
5 *
6 * Copyright (c) 2010-2015, ITU/ISO/IEC
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 *
12 *  * Redistributions of source code must retain the above copyright notice,
13 *    this list of conditions and the following disclaimer.
14 *  * Redistributions in binary form must reproduce the above copyright notice,
15 *    this list of conditions and the following disclaimer in the documentation
16 *    and/or other materials provided with the distribution.
17 *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
18 *    be used to endorse or promote products derived from this software without
19 *    specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
31 * THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34/** \file     TAppDecTop.cpp
35    \brief    Decoder application class
36*/
37
38#include <list>
39#include <vector>
40#include <stdio.h>
41#include <fcntl.h>
42#include <assert.h>
43
44#include "TAppDecTop.h"
45#include "TLibDecoder/AnnexBread.h"
46#include "TLibDecoder/NALread.h"
47#if RExt__DECODER_DEBUG_BIT_STATISTICS
48#include "TLibCommon/TComCodingStatistics.h"
49#endif
50
51//! \ingroup TAppDecoder
52//! \{
53
54// ====================================================================================================================
55// Constructor / destructor / initialization / destroy
56// ====================================================================================================================
57
58TAppDecTop::TAppDecTop()
59#if !NH_MV
60: m_iPOCLastDisplay(-MAX_INT)
61#else
62: m_numDecoders( 0 )
63#endif
64{
65#if NH_MV
66  for (Int i = 0; i < MAX_NUM_LAYER_IDS; i++) 
67  {
68    m_layerIdToDecIdx[i] = -1; 
69    m_layerInitilizedFlags[i] = false; 
70  }
71#endif
72#if NH_3D
73    m_pScaleOffsetFile  = 0;
74#endif
75
76#if NH_MV
77    m_markedForOutput = false; 
78#endif
79
80}
81
82Void TAppDecTop::create()
83{
84}
85
86Void TAppDecTop::destroy()
87{
88#if NH_MV
89  // destroy internal classes
90  xDestroyDecLib();
91#endif
92
93  if (m_pchBitstreamFile)
94  {
95    free (m_pchBitstreamFile);
96    m_pchBitstreamFile = NULL;
97  }
98#if NH_MV
99  for (Int decIdx = 0; decIdx < m_numDecoders; decIdx++)
100  {
101    if (m_pchReconFiles[decIdx])
102    {
103      free (m_pchReconFiles[decIdx]);
104      m_pchReconFiles[decIdx] = NULL;
105    }
106  }
107#endif
108  if (m_pchReconFile)
109  {
110    free (m_pchReconFile);
111    m_pchReconFile = NULL;
112  }
113#if NH_3D
114  if (m_pchScaleOffsetFile)
115  {
116    free (m_pchScaleOffsetFile);
117    m_pchScaleOffsetFile = NULL; 
118  }
119#endif
120}
121
122// ====================================================================================================================
123// Public member functions
124// ====================================================================================================================
125
126/**
127 - create internal class
128 - initialize internal class
129 - until the end of the bitstream, call decoding function in TDecTop class
130 - delete allocated buffers
131 - destroy internal class
132 .
133 */
134Void TAppDecTop::decode()
135{
136  Int                 poc;
137#if NH_MV
138  poc = -1; 
139#endif
140  TComList<TComPic*>* pcListPic = NULL;
141
142  ifstream bitstreamFile(m_pchBitstreamFile, ifstream::in | ifstream::binary);
143  if (!bitstreamFile)
144  {
145    fprintf(stderr, "\nfailed to open bitstream file `%s' for reading\n", m_pchBitstreamFile);
146    exit(EXIT_FAILURE);
147  }
148#if NH_3D
149  if( m_pchScaleOffsetFile ) 
150  { 
151    m_pScaleOffsetFile = ::fopen( m_pchScaleOffsetFile, "wt" ); 
152    AOF( m_pScaleOffsetFile ); 
153  }
154#endif
155
156  InputByteStream bytestream(bitstreamFile);
157
158  if (!m_outputDecodedSEIMessagesFilename.empty() && m_outputDecodedSEIMessagesFilename!="-")
159  {
160    m_seiMessageFileStream.open(m_outputDecodedSEIMessagesFilename.c_str(), std::ios::out);
161    if (!m_seiMessageFileStream.is_open() || !m_seiMessageFileStream.good())
162    {
163      fprintf(stderr, "\nUnable to open file `%s' for writing decoded SEI messages\n", m_outputDecodedSEIMessagesFilename.c_str());
164      exit(EXIT_FAILURE);
165    }
166  }
167
168  // create & initialize internal classes
169  xCreateDecLib();
170  xInitDecLib  ();
171#if !NH_MV
172  m_iPOCLastDisplay += m_iSkipFrame;      // set the last displayed POC correctly for skip forward.
173
174  // main decoder loop
175  Bool openedReconFile = false; // reconstruction file not yet opened. (must be performed after SPS is seen)
176#else
177
178  Int  pocCurrPic        = -MAX_INT;     
179  Int  pocLastPic        = -MAX_INT;   
180
181  Int  layerIdLastPic    = -MAX_INT; 
182  Int  layerIdCurrPic    = 0; 
183
184  Int  decIdxLastPic     = 0; 
185  Int  decIdxCurrPic     = 0; 
186
187  Bool firstSlice        = true; 
188#endif
189  Bool loopFiltered = false;
190
191  while (!!bitstreamFile)
192  {
193    /* location serves to work around a design fault in the decoder, whereby
194     * the process of reading a new slice that is the first slice of a new frame
195     * requires the TDecTop::decode() method to be called again with the same
196     * nal unit. */
197#if RExt__DECODER_DEBUG_BIT_STATISTICS
198    TComCodingStatistics::TComCodingStatisticsData backupStats(TComCodingStatistics::GetStatistics());
199    streampos location = bitstreamFile.tellg() - streampos(bytestream.GetNumBufferedBytes());
200#else
201    streampos location = bitstreamFile.tellg();
202#endif
203#if NH_MV
204#if ENC_DEC_TRACE
205    Int64 symCount = g_nSymbolCounter;
206#endif
207#endif
208    AnnexBStats stats = AnnexBStats();
209
210    InputNALUnit nalu;
211    byteStreamNALUnit(bytestream, nalu.getBitstream().getFifo(), stats);
212
213    // call actual decoding function
214    Bool bNewPicture = false;
215#if NH_MV
216    Bool newSliceDiffPoc   = false;
217    Bool newSliceDiffLayer = false;
218    Bool sliceSkippedFlag  = false; 
219    Bool allLayersDecoded  = false;     
220#endif
221    if (nalu.getBitstream().getFifo().empty())
222    {
223      /* this can happen if the following occur:
224       *  - empty input file
225       *  - two back-to-back start_code_prefixes
226       *  - start_code_prefix immediately followed by EOF
227       */
228      fprintf(stderr, "Warning: Attempt to decode an empty NAL unit\n");
229    }
230    else
231    {
232      read(nalu);
233#if NH_MV     
234      if( (m_iMaxTemporalLayer >= 0 && nalu.m_temporalId > m_iMaxTemporalLayer) 
235          || !isNaluWithinTargetDecLayerIdSet(&nalu)
236          || nalu.m_nuhLayerId > MAX_NUM_LAYER_IDS-1
237          || (nalu.m_nalUnitType == NAL_UNIT_VPS && nalu.m_nuhLayerId > 0)           
238          || (nalu.m_nalUnitType == NAL_UNIT_EOB && nalu.m_nuhLayerId > 0)             
239         ) 
240      {
241        bNewPicture = false;
242        if ( !bitstreamFile )
243        {
244          decIdxLastPic     = decIdxCurrPic; 
245        }
246      }
247      else
248      { 
249        Int decIdx     = xGetDecoderIdx( nalu.m_nuhLayerId , true );     
250        newSliceDiffLayer = nalu.isSlice() && ( nalu.m_nuhLayerId != layerIdCurrPic ) && !firstSlice;
251        newSliceDiffPoc   = m_tDecTop[decIdx]->decode(nalu, m_iSkipFrame, m_pocLastDisplay[decIdx], newSliceDiffLayer, sliceSkippedFlag );
252        // decode function only returns true when all of the following conditions are true
253        // - poc in particular layer changes
254        // - nalu does not belong to first slice in layer
255        // - nalu.isSlice() == true     
256
257        bNewPicture       = ( newSliceDiffLayer || newSliceDiffPoc ) && !sliceSkippedFlag; 
258        if ( nalu.isSlice() && firstSlice && !sliceSkippedFlag )       
259        {
260          layerIdCurrPic = nalu.m_nuhLayerId; 
261          pocCurrPic     = m_tDecTop[decIdx]->getCurrPoc(); 
262          decIdxCurrPic  = decIdx; 
263          firstSlice     = false; 
264
265          /// Use VPS activated by the first slice to determine OLS
266          m_vps = m_tDecTop[decIdx]->getActiveVPS( );
267          if ( m_targetDecLayerIdSetFileEmpty )
268          {           
269            if ( m_targetOptLayerSetIdx == -1 )
270            {
271              m_targetOptLayerSetIdx = m_tDecTop[decIdx]->getTargetOlsIdx(); 
272            }
273            else
274            {
275              assert( m_tDecTop[decIdx]->getTargetOlsIdx() == m_targetOptLayerSetIdx );
276            }
277
278            if ( m_targetOptLayerSetIdx < 0 || m_targetOptLayerSetIdx >= m_vps->getNumOutputLayerSets() )
279            {
280              fprintf(stderr, "\ntarget output layer set index must be in the range of 0 to %d, inclusive \n", m_vps->getNumOutputLayerSets() - 1 );           
281              exit(EXIT_FAILURE);
282            }
283            m_targetDecLayerIdSet = m_vps->getTargetDecLayerIdList( m_targetOptLayerSetIdx ); 
284          }
285
286          if (m_outputVpsInfo )
287          {
288            m_vps->printScalabilityId();
289            m_vps->printLayerDependencies();
290            m_vps->printLayerSets();
291            m_vps->printPTL(); 
292          }
293        }       
294
295        if ( bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS )
296        { 
297          layerIdLastPic    = layerIdCurrPic; 
298          layerIdCurrPic    = nalu.m_nuhLayerId; 
299          pocLastPic        = pocCurrPic; 
300          pocCurrPic        = m_tDecTop[decIdx]->getCurrPoc(); 
301          decIdxLastPic     = decIdxCurrPic; 
302          decIdxCurrPic     = decIdx; 
303          allLayersDecoded = ( pocCurrPic != pocLastPic ) && ( nalu.m_nalUnitType != NAL_UNIT_EOS );
304        }
305#else
306      if( (m_iMaxTemporalLayer >= 0 && nalu.m_temporalId > m_iMaxTemporalLayer) || !isNaluWithinTargetDecLayerIdSet(&nalu)  )
307      {
308        bNewPicture = false;
309      }
310      else
311      {
312        bNewPicture = m_cTDecTop.decode(nalu, m_iSkipFrame, m_iPOCLastDisplay);
313#endif
314        if (bNewPicture)
315        {
316          bitstreamFile.clear();
317          /* location points to the current nalunit payload[1] due to the
318           * need for the annexB parser to read three extra bytes.
319           * [1] except for the first NAL unit in the file
320           *     (but bNewPicture doesn't happen then) */
321#if RExt__DECODER_DEBUG_BIT_STATISTICS
322          bitstreamFile.seekg(location);
323          bytestream.reset();
324          TComCodingStatistics::SetStatistics(backupStats);
325#else
326          bitstreamFile.seekg(location-streamoff(3));
327          bytestream.reset();
328#endif
329#if H_MV_ENC_DEC_TRAC
330#if ENC_DEC_TRACE
331          const Bool resetCounter = false; 
332          if ( resetCounter )
333          {
334            g_nSymbolCounter  = symCount; // Only reset counter SH becomes traced twice
335          }
336          else
337          {
338            g_disableHLSTrace = true;     // Tracing of second parsing of SH is not carried out
339          }     
340#endif
341#endif
342        }
343      }
344    }
345
346    if ( (bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS) &&
347#if NH_MV     
348      !m_tDecTop[decIdxLastPic]->getFirstSliceInSequence () )
349#else
350      !m_cTDecTop.getFirstSliceInSequence () )
351#endif
352
353    {
354      if (!loopFiltered || bitstreamFile)
355      {
356#if NH_MV
357        assert( decIdxLastPic != -1 ); 
358        m_tDecTop[decIdxLastPic]->endPicDecoding(poc, pcListPic, m_targetDecLayerIdSet );
359        xMarkForOutput( allLayersDecoded, poc, layerIdLastPic ); 
360#else
361        m_cTDecTop.executeLoopFilters(poc, pcListPic);
362#endif
363      }
364      loopFiltered = (nalu.m_nalUnitType == NAL_UNIT_EOS);
365      if (nalu.m_nalUnitType == NAL_UNIT_EOS)
366      {
367#if NH_MV     
368        m_tDecTop[decIdxLastPic]->setFirstSliceInSequence(true);
369#else
370        m_cTDecTop.setFirstSliceInSequence(true);
371#endif
372      }
373    }
374    else if ( (bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS ) &&
375#if NH_MV     
376              m_tDecTop[decIdxLastPic]->getFirstSliceInSequence () ) 
377#else
378              m_cTDecTop.getFirstSliceInSequence () ) 
379#endif
380    {
381#if NH_MV     
382      m_tDecTop[decIdxLastPic]->setFirstSliceInPicture (true);
383#else
384      m_cTDecTop.setFirstSliceInPicture (true);
385#endif
386    }
387
388#if NH_3D
389    if ( allLayersDecoded || !bitstreamFile )
390    {
391      for( Int dI = 0; dI < m_numDecoders; dI++ )
392      {
393        TComPic* picLastCoded = m_ivPicLists.getPic( m_tDecTop[dI]->getLayerId(), pocLastPic );
394        assert( picLastCoded != NULL );       
395        picLastCoded->compressMotion(1);
396      }
397    }
398#endif
399
400    if( pcListPic )
401    {
402#if NH_MV
403      if ( m_pchReconFiles[decIdxLastPic] && !m_reconOpen[decIdxLastPic] )
404#else
405      if ( m_pchReconFile && !openedReconFile )
406#endif
407      {
408        const BitDepths &bitDepths=pcListPic->front()->getPicSym()->getSPS().getBitDepths(); // use bit depths of first reconstructed picture.
409        for (UInt channelType = 0; channelType < MAX_NUM_CHANNEL_TYPE; channelType++)
410        {
411          if (m_outputBitDepth[channelType] == 0)
412          {
413            m_outputBitDepth[channelType] = bitDepths.recon[channelType];
414          }
415        }
416#if NH_MV
417        m_tVideoIOYuvReconFile[decIdxLastPic]->open( m_pchReconFiles[decIdxLastPic], true, m_outputBitDepth, m_outputBitDepth, bitDepths.recon ); // write mode
418        m_reconOpen[decIdxLastPic] = true;
419      }
420      // write reconstruction to file
421      if( bNewPicture )
422      {
423        // Bumping after picture has been decoded
424#if ENC_DEC_TRACE
425        g_bJustDoIt = true; 
426        writeToTraceFile( "Bumping after decoding \n", g_decTracePicOutput  );         
427        g_bJustDoIt = false; 
428#endif
429        xWriteOutput( pcListPic, decIdxLastPic, nalu.m_temporalId );
430      }
431      if ( (bNewPicture || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA) && m_tDecTop[decIdxLastPic]->getNoOutputPriorPicsFlag() )
432      {
433        m_tDecTop[decIdxLastPic]->checkNoOutputPriorPics( pcListPic );
434        m_tDecTop[decIdxLastPic]->setNoOutputPriorPicsFlag (false);
435      }
436
437      if ( bNewPicture && newSliceDiffPoc && 
438#else
439        m_cTVideoIOYuvReconFile.open( m_pchReconFile, true, m_outputBitDepth, m_outputBitDepth, bitDepths.recon ); // write mode
440        openedReconFile = true;
441      }
442      // write reconstruction to file
443      if( bNewPicture )
444      {
445        xWriteOutput( pcListPic, nalu.m_temporalId );
446      }
447      if ( (bNewPicture || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA) && m_cTDecTop.getNoOutputPriorPicsFlag() )
448      {
449        m_cTDecTop.checkNoOutputPriorPics( pcListPic );
450        m_cTDecTop.setNoOutputPriorPicsFlag (false);
451      }
452
453      if ( bNewPicture &&
454#endif
455           (   nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL
456            || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP
457            || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_N_LP
458            || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_RADL
459            || nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_LP ) )
460      {
461#if NH_MV
462        xFlushOutput( pcListPic, decIdxLastPic );
463#else
464        xFlushOutput( pcListPic );
465#endif
466      }
467      if (nalu.m_nalUnitType == NAL_UNIT_EOS)
468      {
469#if NH_MV
470        xWriteOutput( pcListPic, decIdxCurrPic, nalu.m_temporalId );
471#else
472        xWriteOutput( pcListPic, nalu.m_temporalId );
473#endif
474#if NH_MV
475        m_tDecTop[decIdxCurrPic]->setFirstSliceInPicture (false);
476#else
477        m_cTDecTop.setFirstSliceInPicture (false);
478#endif
479      }
480      // write reconstruction to file -- for additional bumping as defined in C.5.2.3
481#if NH_MV
482      // Above comment seems to be wrong
483#endif
484      if(!bNewPicture && nalu.m_nalUnitType >= NAL_UNIT_CODED_SLICE_TRAIL_N && nalu.m_nalUnitType <= NAL_UNIT_RESERVED_VCL31)
485      {
486#if NH_MV       
487        // Bumping after reference picture set has been applied (here after first vcl nalu.
488#if ENC_DEC_TRACE
489        g_bJustDoIt = true; 
490        writeToTraceFile( "Bumping after reference picture set has been applied \n", g_decTracePicOutput  );         
491        g_bJustDoIt = false; 
492#endif
493
494        xWriteOutput( m_tDecTop[decIdxCurrPic]->getListPic(), decIdxCurrPic, nalu.m_temporalId );
495#else
496        xWriteOutput( pcListPic, nalu.m_temporalId );
497#endif
498      }
499    }
500  }
501#if NH_MV
502#if NH_3D
503  if( m_cCamParsCollector.isInitialized() )
504  {
505    m_cCamParsCollector.setSlice( 0 );
506  }
507#endif
508  for(UInt decIdx = 0; decIdx < m_numDecoders; decIdx++)
509  {
510    xFlushOutput( m_tDecTop[decIdx]->getListPic(), decIdx );
511  }
512#else 
513  xFlushOutput( pcListPic );
514  // delete buffers
515  m_cTDecTop.deletePicBuffer();
516  // destroy internal classes
517  xDestroyDecLib();
518#endif
519}
520
521// ====================================================================================================================
522// Protected member functions
523// ====================================================================================================================
524
525Void TAppDecTop::xCreateDecLib()
526{
527#if NH_MV
528  // initialize global variables
529  initROM(); 
530#if NH_3D_DMM
531  initWedgeLists();
532#endif
533#else
534  // create decoder class
535  m_cTDecTop.create();
536#endif
537}
538
539Void TAppDecTop::xDestroyDecLib()
540{
541#if NH_MV
542  // destroy ROM
543  destroyROM();
544
545  for(Int decIdx = 0; decIdx < m_numDecoders ; decIdx++)
546  {
547    if( m_tVideoIOYuvReconFile[decIdx] )
548    {
549      m_tVideoIOYuvReconFile[decIdx]->close();
550      delete m_tVideoIOYuvReconFile[decIdx]; 
551      m_tVideoIOYuvReconFile[decIdx] = NULL ;
552    }
553
554    if( m_tDecTop[decIdx] )
555    {
556      m_tDecTop[decIdx]->deletePicBuffer();
557      m_tDecTop[decIdx]->destroy() ;
558    }
559    delete m_tDecTop[decIdx] ; 
560    m_tDecTop[decIdx] = NULL ;
561  }
562#else
563  if ( m_pchReconFile )
564  {
565    m_cTVideoIOYuvReconFile. close();
566  }
567
568  // destroy decoder class
569  m_cTDecTop.destroy();
570#endif
571#if NH_3D
572  m_cCamParsCollector.uninit();
573  if( m_pScaleOffsetFile ) 
574  { 
575    ::fclose( m_pScaleOffsetFile ); 
576  }
577#endif
578}
579
580Void TAppDecTop::xInitDecLib()
581{
582
583#if NH_3D
584  m_cCamParsCollector.setCodeScaleOffsetFile( m_pScaleOffsetFile );
585#endif
586#if !NH_MV
587  // initialize decoder class
588  m_cTDecTop.init();
589  m_cTDecTop.setDecodedPictureHashSEIEnabled(m_decodedPictureHashSEIEnabled);
590#if O0043_BEST_EFFORT_DECODING
591  m_cTDecTop.setForceDecodeBitDepth(m_forceDecodeBitDepth);
592#endif
593  if (!m_outputDecodedSEIMessagesFilename.empty())
594  {
595    std::ostream &os=m_seiMessageFileStream.is_open() ? m_seiMessageFileStream : std::cout;
596    m_cTDecTop.setDecodedSEIMessageOutputStream(&os);
597  }
598#endif
599}
600
601/** \param pcListPic list of pictures to be written to file
602    \param tId       temporal sub-layer ID
603 */
604#if NH_MV
605Void TAppDecTop::xWriteOutput( TComList<TComPic*>* pcListPic, Int decIdx, Int tId )
606#else
607Void TAppDecTop::xWriteOutput( TComList<TComPic*>* pcListPic, UInt tId )
608#endif
609{
610  if (pcListPic->empty())
611  {
612    return;
613  }
614
615  TComList<TComPic*>::iterator iterPic   = pcListPic->begin();
616  Int numPicsNotYetDisplayed = 0;
617  Int dpbFullness = 0;
618  const TComSPS* activeSPS = &(pcListPic->front()->getPicSym()->getSPS());
619
620  UInt numReorderPicsHighestTid;
621  UInt maxDecPicBufferingHighestTid;
622  UInt maxNrSublayers = activeSPS->getMaxTLayers();
623
624  if(m_iMaxTemporalLayer == -1 || m_iMaxTemporalLayer >= maxNrSublayers)
625  {
626    numReorderPicsHighestTid = activeSPS->getNumReorderPics(maxNrSublayers-1);
627    maxDecPicBufferingHighestTid =  activeSPS->getMaxDecPicBuffering(maxNrSublayers-1); 
628  }
629  else
630  {
631    numReorderPicsHighestTid = activeSPS->getNumReorderPics(m_iMaxTemporalLayer);
632    maxDecPicBufferingHighestTid = activeSPS->getMaxDecPicBuffering(m_iMaxTemporalLayer); 
633  }
634
635  while (iterPic != pcListPic->end())
636  {
637    TComPic* pcPic = *(iterPic);
638#if NH_MV
639    if(pcPic->getOutputMark() && pcPic->getPOC() > m_pocLastDisplay[decIdx])
640#else
641    if(pcPic->getOutputMark() && pcPic->getPOC() > m_iPOCLastDisplay)
642#endif
643    {
644       numPicsNotYetDisplayed++;
645      dpbFullness++;
646    }
647    else if(pcPic->getSlice( 0 )->isReferenced())
648    {
649      dpbFullness++;
650    }
651    iterPic++;
652  }
653
654  iterPic = pcListPic->begin();
655
656  if (numPicsNotYetDisplayed>2)
657  {
658    iterPic++;
659  }
660
661  TComPic* pcPic = *(iterPic);
662  if (numPicsNotYetDisplayed>2 && pcPic->isField()) //Field Decoding
663  {
664    TComList<TComPic*>::iterator endPic   = pcListPic->end();
665    endPic--;
666    iterPic   = pcListPic->begin();
667    while (iterPic != endPic)
668    {
669      TComPic* pcPicTop = *(iterPic);
670      iterPic++;
671      TComPic* pcPicBottom = *(iterPic);
672
673#if NH_MV
674      if ( pcPicTop->getOutputMark() && pcPicBottom->getOutputMark() &&
675        (numPicsNotYetDisplayed >  numReorderPicsHighestTid || dpbFullness > maxDecPicBufferingHighestTid) &&
676        (!(pcPicTop->getPOC()%2) && pcPicBottom->getPOC() == pcPicTop->getPOC()+1) &&
677        (pcPicTop->getPOC() == m_pocLastDisplay[decIdx]+1 || m_pocLastDisplay[decIdx] < 0))
678#else
679      if ( pcPicTop->getOutputMark() && pcPicBottom->getOutputMark() &&
680          (numPicsNotYetDisplayed >  numReorderPicsHighestTid || dpbFullness > maxDecPicBufferingHighestTid) &&
681          (!(pcPicTop->getPOC()%2) && pcPicBottom->getPOC() == pcPicTop->getPOC()+1) &&
682          (pcPicTop->getPOC() == m_iPOCLastDisplay+1 || m_iPOCLastDisplay < 0))
683#endif
684      {
685        // write to file
686        numPicsNotYetDisplayed = numPicsNotYetDisplayed-2;
687#if NH_MV
688      if ( m_pchReconFiles[decIdx] )
689#else
690        if ( m_pchReconFile )
691#endif
692        {
693          const Window &conf = pcPicTop->getConformanceWindow();
694          const Window  defDisp = m_respectDefDispWindow ? pcPicTop->getDefDisplayWindow() : Window();
695          const Bool isTff = pcPicTop->isTopField();
696
697          Bool display = true;
698          if( m_decodedNoDisplaySEIEnabled )
699          {
700            SEIMessages noDisplay = getSeisByType(pcPic->getSEIs(), SEI::NO_DISPLAY );
701            const SEINoDisplay *nd = ( noDisplay.size() > 0 ) ? (SEINoDisplay*) *(noDisplay.begin()) : NULL;
702            if( (nd != NULL) && nd->m_noDisplay )
703            {
704              display = false;
705            }
706          }
707
708          if (display)
709          {
710#if NH_MV
711        assert( conf   .getScaledFlag() );
712        assert( defDisp.getScaledFlag() );
713#if ENC_DEC_TRACE
714        g_bJustDoIt = true; 
715        writeToTraceFile( "OutputPic Poc"   , pcPic->getPOC    (), g_decTracePicOutput  ); 
716        writeToTraceFile( "OutputPic LayerId", pcPic->getLayerId(), g_decTracePicOutput );         
717        g_bJustDoIt = false; 
718#endif
719        m_tVideoIOYuvReconFile[decIdx]->write( pcPicTop->getPicYuvRec(), pcPicBottom->getPicYuvRec(),
720#else
721        m_cTVideoIOYuvReconFile.write( pcPicTop->getPicYuvRec(), pcPicBottom->getPicYuvRec(),
722#endif
723                                           m_outputColourSpaceConvert,
724                                           conf.getWindowLeftOffset() + defDisp.getWindowLeftOffset(),
725                                           conf.getWindowRightOffset() + defDisp.getWindowRightOffset(),
726                                           conf.getWindowTopOffset() + defDisp.getWindowTopOffset(),
727#if NH_3D
728                                           conf.getWindowBottomOffset() + defDisp.getWindowBottomOffset(), m_depth420OutputFlag && pcPicTop->getIsDepth() ? CHROMA_420 : NUM_CHROMA_FORMAT, isTff );
729#else
730                                           conf.getWindowBottomOffset() + defDisp.getWindowBottomOffset(), NUM_CHROMA_FORMAT, isTff );
731#endif
732          }
733        }
734
735        // update POC of display order
736#if NH_MV
737        m_pocLastDisplay[decIdx] = pcPic->getPOC();
738#else
739        m_iPOCLastDisplay = pcPicBottom->getPOC();
740#endif
741
742        // erase non-referenced picture in the reference picture list after display
743        if ( !pcPicTop->getSlice(0)->isReferenced() && pcPicTop->getReconMark() == true )
744        {
745          pcPicTop->setReconMark(false);
746
747          // mark it should be extended later
748          pcPicTop->getPicYuvRec()->setBorderExtension( false );
749        }
750        if ( !pcPicBottom->getSlice(0)->isReferenced() && pcPicBottom->getReconMark() == true )
751        {
752          pcPicBottom->setReconMark(false);
753
754          // mark it should be extended later
755          pcPicBottom->getPicYuvRec()->setBorderExtension( false );
756        }
757        pcPicTop->setOutputMark(false);
758        pcPicBottom->setOutputMark(false);
759      }
760    }
761  }
762  else if (!pcPic->isField()) //Frame Decoding
763  {
764    iterPic = pcListPic->begin();
765
766    while (iterPic != pcListPic->end())
767    {
768      pcPic = *(iterPic);
769
770#if NH_MV
771      if(pcPic->getOutputMark() && pcPic->getPOC() > m_pocLastDisplay[decIdx] &&
772        (numPicsNotYetDisplayed >  numReorderPicsHighestTid || dpbFullness > maxDecPicBufferingHighestTid))
773#else     
774      if(pcPic->getOutputMark() && pcPic->getPOC() > m_iPOCLastDisplay &&
775        (numPicsNotYetDisplayed >  numReorderPicsHighestTid || dpbFullness > maxDecPicBufferingHighestTid))
776#endif
777      {
778        // write to file
779         numPicsNotYetDisplayed--;
780        if(pcPic->getSlice(0)->isReferenced() == false)
781        {
782          dpbFullness--;
783        }
784#if NH_MV
785      if ( m_pchReconFiles[decIdx] )
786#else
787        if ( m_pchReconFile )
788#endif
789        {
790          const Window &conf    = pcPic->getConformanceWindow();
791          const Window defDisp = m_respectDefDispWindow ? pcPic->getDefDisplayWindow() : Window();
792#if NH_MV
793        assert( conf   .getScaledFlag() );
794        assert( defDisp.getScaledFlag() );
795#if ENC_DEC_TRACE
796        g_bJustDoIt = true; 
797        writeToTraceFile( "OutputPic Poc"   , pcPic->getPOC    (), g_decTracePicOutput  ); 
798        writeToTraceFile( "OutputPic LayerId", pcPic->getLayerId(), g_decTracePicOutput );         
799        g_bJustDoIt = false; 
800#endif
801        m_tVideoIOYuvReconFile[decIdx]->write( pcPic->getPicYuvRec(),
802#else
803          m_cTVideoIOYuvReconFile.write( pcPic->getPicYuvRec(),
804#endif
805                                         m_outputColourSpaceConvert,
806                                         conf.getWindowLeftOffset() + defDisp.getWindowLeftOffset(),
807                                         conf.getWindowRightOffset() + defDisp.getWindowRightOffset(),
808                                         conf.getWindowTopOffset() + defDisp.getWindowTopOffset(),
809                                         conf.getWindowBottomOffset() + defDisp.getWindowBottomOffset(), 
810#if NH_3D
811                                          m_depth420OutputFlag && pcPic->getIsDepth() ? CHROMA_420 : NUM_CHROMA_FORMAT,
812#else
813                                          NUM_CHROMA_FORMAT,
814#endif
815           m_bClipOutputVideoToRec709Range   );
816        }
817
818        // update POC of display order
819#if NH_MV
820        m_pocLastDisplay[decIdx] = pcPic->getPOC();
821#else
822        m_iPOCLastDisplay = pcPic->getPOC();
823#endif
824
825        // erase non-referenced picture in the reference picture list after display
826        if ( !pcPic->getSlice(0)->isReferenced() && pcPic->getReconMark() == true )
827        {
828          pcPic->setReconMark(false);
829
830          // mark it should be extended later
831          pcPic->getPicYuvRec()->setBorderExtension( false );
832        }
833        pcPic->setOutputMark(false);
834#if NH_MV
835        pcPic->setPicOutputFlag(false);
836#endif
837      }
838
839      iterPic++;
840    }
841  }
842}
843
844/** \param pcListPic list of pictures to be written to file
845 */
846#if NH_MV
847Void TAppDecTop::xFlushOutput( TComList<TComPic*>* pcListPic, Int decIdx )
848#else
849Void TAppDecTop::xFlushOutput( TComList<TComPic*>* pcListPic )
850#endif
851{
852  if(!pcListPic || pcListPic->empty())
853  {
854    return;
855  }
856  TComList<TComPic*>::iterator iterPic   = pcListPic->begin();
857
858  iterPic   = pcListPic->begin();
859  TComPic* pcPic = *(iterPic);
860
861  if (pcPic->isField()) //Field Decoding
862  {
863    TComList<TComPic*>::iterator endPic   = pcListPic->end();
864    endPic--;
865    TComPic *pcPicTop, *pcPicBottom = NULL;
866    while (iterPic != endPic)
867    {
868      pcPicTop = *(iterPic);
869      iterPic++;
870      pcPicBottom = *(iterPic);
871
872      if ( pcPicTop->getOutputMark() && pcPicBottom->getOutputMark() && !(pcPicTop->getPOC()%2) && (pcPicBottom->getPOC() == pcPicTop->getPOC()+1) )
873      {
874        // write to file
875#if NH_MV
876      if ( m_pchReconFiles[decIdx] )
877#else
878        if ( m_pchReconFile )
879#endif
880        {
881          const Window &conf = pcPicTop->getConformanceWindow();
882          const Window  defDisp = m_respectDefDispWindow ? pcPicTop->getDefDisplayWindow() : Window();
883          const Bool isTff = pcPicTop->isTopField();
884#if NH_MV
885        assert( conf   .getScaledFlag() );
886        assert( defDisp.getScaledFlag() );
887#if ENC_DEC_TRACE
888        g_bJustDoIt = true; 
889        writeToTraceFile( "OutputPic Poc"   , pcPic->getPOC    (), g_decTracePicOutput  ); 
890        writeToTraceFile( "OutputPic LayerId", pcPic->getLayerId(), g_decTracePicOutput );         
891        g_bJustDoIt = false; 
892#endif
893        m_tVideoIOYuvReconFile[decIdx]->write( pcPicTop->getPicYuvRec(), pcPicBottom->getPicYuvRec(),
894#else
895          m_cTVideoIOYuvReconFile.write( pcPicTop->getPicYuvRec(), pcPicBottom->getPicYuvRec(),
896#endif
897                                         m_outputColourSpaceConvert,
898                                         conf.getWindowLeftOffset() + defDisp.getWindowLeftOffset(),
899                                         conf.getWindowRightOffset() + defDisp.getWindowRightOffset(),
900                                         conf.getWindowTopOffset() + defDisp.getWindowTopOffset(),
901#if NH_3D
902                                         conf.getWindowBottomOffset() + defDisp.getWindowBottomOffset(), m_depth420OutputFlag && pcPicTop->getIsDepth() ? CHROMA_420 : NUM_CHROMA_FORMAT, isTff );
903#else
904                                         conf.getWindowBottomOffset() + defDisp.getWindowBottomOffset(), NUM_CHROMA_FORMAT, isTff );
905#endif
906        }
907
908        // update POC of display order
909#if NH_MV
910      m_pocLastDisplay[decIdx] = pcPic->getPOC();
911#else
912        m_iPOCLastDisplay = pcPicBottom->getPOC();
913#endif       
914        // erase non-referenced picture in the reference picture list after display
915        if ( !pcPicTop->getSlice(0)->isReferenced() && pcPicTop->getReconMark() == true )
916        {
917          pcPicTop->setReconMark(false);
918
919          // mark it should be extended later
920          pcPicTop->getPicYuvRec()->setBorderExtension( false );
921        }
922        if ( !pcPicBottom->getSlice(0)->isReferenced() && pcPicBottom->getReconMark() == true )
923        {
924          pcPicBottom->setReconMark(false);
925
926          // mark it should be extended later
927          pcPicBottom->getPicYuvRec()->setBorderExtension( false );
928        }
929        pcPicTop->setOutputMark(false);
930        pcPicBottom->setOutputMark(false);
931
932        if(pcPicTop)
933        {
934          pcPicTop->destroy();
935          delete pcPicTop;
936          pcPicTop = NULL;
937        }
938      }
939    }
940    if(pcPicBottom)
941    {
942      pcPicBottom->destroy();
943      delete pcPicBottom;
944      pcPicBottom = NULL;
945    }
946  }
947  else //Frame decoding
948  {
949    while (iterPic != pcListPic->end())
950    {
951      pcPic = *(iterPic);
952
953      if ( pcPic->getOutputMark() )
954      {
955        // write to file
956#if NH_MV
957      if ( m_pchReconFiles[decIdx] )
958#else
959        if ( m_pchReconFile )
960#endif
961        {
962          const Window &conf    = pcPic->getConformanceWindow();
963          const Window  defDisp = m_respectDefDispWindow ? pcPic->getDefDisplayWindow() : Window();
964#if NH_MV
965        assert( conf   .getScaledFlag() );
966        assert( defDisp.getScaledFlag() );
967#if ENC_DEC_TRACE
968        g_bJustDoIt = true; 
969        writeToTraceFile( "OutputPic Poc"   , pcPic->getPOC    (), g_decTracePicOutput  ); 
970        writeToTraceFile( "OutputPic LayerId", pcPic->getLayerId(), g_decTracePicOutput );         
971        g_bJustDoIt = false; 
972#endif
973        m_tVideoIOYuvReconFile[decIdx]->write( pcPic->getPicYuvRec(),
974#else
975          m_cTVideoIOYuvReconFile.write( pcPic->getPicYuvRec(),
976#endif
977                                         m_outputColourSpaceConvert,
978                                         conf.getWindowLeftOffset() + defDisp.getWindowLeftOffset(),
979                                         conf.getWindowRightOffset() + defDisp.getWindowRightOffset(),
980                                         conf.getWindowTopOffset() + defDisp.getWindowTopOffset(),
981                                         conf.getWindowBottomOffset() + defDisp.getWindowBottomOffset(), 
982#if NH_3D
983                                         m_depth420OutputFlag && pcPic->getIsDepth() ? CHROMA_420 : NUM_CHROMA_FORMAT
984#else
985                                         NUM_CHROMA_FORMAT
986#endif
987                                         , m_bClipOutputVideoToRec709Range);
988        }
989
990        // update POC of display order
991#if NH_MV
992      m_pocLastDisplay[decIdx] = pcPic->getPOC();
993#else
994        m_iPOCLastDisplay = pcPic->getPOC();
995#endif
996
997        // erase non-referenced picture in the reference picture list after display
998        if ( !pcPic->getSlice(0)->isReferenced() && pcPic->getReconMark() == true )
999        {
1000          pcPic->setReconMark(false);
1001
1002          // mark it should be extended later
1003          pcPic->getPicYuvRec()->setBorderExtension( false );
1004        }
1005        pcPic->setOutputMark(false);
1006#if NH_MV
1007        pcPic->setPicOutputFlag(false);
1008#endif
1009      }
1010#if !NH_MV
1011      if(pcPic != NULL)
1012      {
1013        pcPic->destroy();
1014        delete pcPic;
1015        pcPic = NULL;
1016      }
1017#endif
1018      iterPic++;
1019    }
1020  }
1021#if NH_MV
1022  m_pocLastDisplay[decIdx] = -MAX_INT;
1023#else
1024  pcListPic->clear();
1025  m_iPOCLastDisplay = -MAX_INT;
1026#endif
1027}
1028
1029/** \param nalu Input nalu to check whether its LayerId is within targetDecLayerIdSet
1030 */
1031Bool TAppDecTop::isNaluWithinTargetDecLayerIdSet( InputNALUnit* nalu )
1032{
1033  if ( m_targetDecLayerIdSet.size() == 0 ) // By default, the set is empty, meaning all LayerIds are allowed
1034  {
1035    return true;
1036  }
1037  for (std::vector<Int>::iterator it = m_targetDecLayerIdSet.begin(); it != m_targetDecLayerIdSet.end(); it++)
1038  {
1039#if NH_MV
1040    if ( nalu->m_nuhLayerId == (*it) )
1041#else
1042    if ( nalu->m_nuhLayerId == (*it) )
1043#endif
1044    {
1045      return true;
1046    }
1047  }
1048  return false;
1049}
1050
1051#if NH_MV
1052Int TAppDecTop::xGetDecoderIdx( Int layerId, Bool createFlag /*= false */ )
1053{
1054  Int decIdx = -1; 
1055
1056  if ( layerId > MAX_NUM_LAYER_IDS-1 ) 
1057  {
1058    return decIdx; 
1059  }
1060
1061  if ( m_layerIdToDecIdx[ layerId ] != -1 ) 
1062  {     
1063    decIdx = m_layerIdToDecIdx[ layerId ]; 
1064  }
1065  else
1066  {     
1067    assert ( createFlag ); 
1068    assert( m_numDecoders < MAX_NUM_LAYERS ); 
1069
1070    decIdx = m_numDecoders; 
1071
1072    // Init decoder
1073    m_tDecTop[ decIdx ] =  new TDecTop;
1074    m_tDecTop[ decIdx ]->create();
1075    m_tDecTop[ decIdx ]->init( );
1076    m_tDecTop[ decIdx ]->setLayerId( layerId );
1077    m_tDecTop[ decIdx ]->setDecodedPictureHashSEIEnabled(m_decodedPictureHashSEIEnabled);
1078    m_tDecTop[ decIdx ]->setIvPicLists( &m_ivPicLists ); 
1079    m_tDecTop[ decIdx ]->setLayerInitilizedFlags( m_layerInitilizedFlags );
1080    m_tDecTop[ decIdx ]->setTargetOlsIdx( m_targetOptLayerSetIdx );   
1081#if O0043_BEST_EFFORT_DECODING
1082    m_cTDecTop[ decIdx ]->setForceDecodeBitDepth(m_forceDecodeBitDepth);
1083#endif
1084    if (!m_outputDecodedSEIMessagesFilename.empty())
1085    {
1086      std::ostream &os=m_seiMessageFileStream.is_open() ? m_seiMessageFileStream : std::cout;
1087      m_tDecTop[ decIdx ]->setDecodedSEIMessageOutputStream(&os);
1088    }
1089#if NH_3D
1090   m_tDecTop[ decIdx ]->setCamParsCollector( &m_cCamParsCollector );
1091#endif
1092
1093    // append pic list of new decoder to PicLists
1094    assert( m_ivPicLists.size() == m_numDecoders );
1095    m_ivPicLists.push_back( m_tDecTop[ decIdx ]->getListPic() );
1096
1097    // create recon file related stuff     
1098    Char* pchTempFilename = NULL;
1099    if ( m_pchReconFile )
1100    {     
1101      Char buffer[4];     
1102      sprintf(buffer,"_%i", layerId );
1103      assert ( m_pchReconFile ); 
1104      xAppendToFileNameEnd( m_pchReconFile , buffer, pchTempFilename );
1105      assert( m_pchReconFiles.size() == m_numDecoders );
1106    }
1107
1108    m_pchReconFiles.push_back( pchTempFilename );   
1109
1110    m_tVideoIOYuvReconFile[ decIdx ] = new TVideoIOYuv;
1111    m_reconOpen           [ decIdx ] = false;
1112
1113    // set others
1114    m_pocLastDisplay      [ decIdx ] = -MAX_INT;
1115    m_layerIdToDecIdx     [ layerId ] = decIdx; 
1116
1117    m_numDecoders++; 
1118  };
1119  return decIdx;
1120
1121}
1122
1123Void TAppDecTop::xMarkForOutput( Bool allLayersDecoded, Int pocLastPic, Int layerIdLastPic )
1124{ 
1125  vector<Int> targetOptLayerIdList = m_vps->getTargetOptLayerIdList( m_targetOptLayerSetIdx );
1126
1127  if (m_vps->getAltOutputLayerFlagVar( m_targetOptLayerSetIdx ) )
1128  {
1129    assert( targetOptLayerIdList.size() == 1 ); 
1130    Int targetLayerId = targetOptLayerIdList[0];     
1131
1132    TComPic* curPic = m_ivPicLists.getPic( layerIdLastPic, pocLastPic );
1133    assert( curPic != NULL );
1134
1135    if ( layerIdLastPic == targetLayerId )
1136    {
1137      if ( curPic->getPicOutputFlag() )
1138      {
1139        curPic->setOutputMark( true );
1140      }
1141      else
1142      {       
1143        xMarkAltOutPic( targetLayerId, pocLastPic ); 
1144      }
1145      m_markedForOutput = true; 
1146    }
1147    else if ( ( layerIdLastPic > targetLayerId || allLayersDecoded ) && !m_markedForOutput )
1148    {
1149      xMarkAltOutPic( targetLayerId, pocLastPic );
1150    }
1151
1152    if ( allLayersDecoded )
1153    {
1154      m_markedForOutput = false; 
1155    }
1156  }
1157  else
1158  { 
1159    for( Int dI = 0; dI < m_numDecoders; dI++ )
1160    {     
1161      Int layerId = m_tDecTop[dI]->getLayerId(); 
1162      TComPic* curPic = m_ivPicLists.getPic( layerId, pocLastPic );
1163      if ( curPic != NULL )
1164      {
1165        if ( curPic->getReconMark() )
1166        {
1167          Bool isTargetOptLayer = std::find(targetOptLayerIdList.begin(), targetOptLayerIdList.end(), layerId) != targetOptLayerIdList.end();
1168          curPic->setOutputMark( isTargetOptLayer ? curPic->getPicOutputFlag() : false ); 
1169        }
1170      }
1171    }
1172  }
1173}
1174
1175Void TAppDecTop::xMarkAltOutPic( Int targetOutputLayer, Int pocLastPic )
1176{
1177  Int optLayerIdxInVps = m_vps->getLayerIdInNuh( targetOutputLayer ); 
1178  Int highestNuhLayerId = -1; 
1179  TComPic* picWithHighestNuhLayerId = NULL; 
1180  for (Int dIdx = 0; dIdx < m_numDecoders; dIdx++)
1181  {
1182    Int curLayerId = m_tDecTop[dIdx]->getLayerId();
1183    Int curLayerIdxInVps = m_vps->getLayerIdInNuh( curLayerId  ); 
1184    if ( m_vps->getDependencyFlag(optLayerIdxInVps, curLayerIdxInVps ) )
1185    {
1186      TComPic* curPic = m_ivPicLists.getPic( curLayerId, pocLastPic ); 
1187      if (curPic != NULL)
1188      {
1189        if (curPic->getReconMark() && curPic->getPicOutputFlag() )
1190        {
1191          curPic->setOutputMark   ( false ); 
1192          curPic->setPicOutputFlag( false ); 
1193          if ( curLayerId > highestNuhLayerId)
1194          {
1195            highestNuhLayerId = curLayerId ; 
1196            picWithHighestNuhLayerId = curPic; 
1197          }           
1198        }
1199      }
1200    }
1201  }
1202  if ( picWithHighestNuhLayerId != NULL )
1203  {
1204    picWithHighestNuhLayerId->setPicOutputFlag(true); 
1205    picWithHighestNuhLayerId->setOutputMark   (true); 
1206  }
1207}
1208
1209#endif
1210//! \}
Note: See TracBrowser for help on using the repository browser.