VTM10.0代码学习1:DecApp_decode()

此系列是为了记录自己学习VTM10.0的过程和锻炼表达能力,主要是从解码端进行入手。由于本人水平有限,出现的错误恳请大家指正,欢迎与大家一起交流进步。

1.初始化

int                 poc;//picture order count
PicList* pcListPic = NULL;//存有图片的线性表
ifstream bitstreamFile(m_bitstreamFileName.c_str(), ifstream::in | ifstream::binary);//c_str()将string类型转换为c语言的字符串,in代表输入,binary代表为二进制模式。创建一个文件输入比特流。
InputByteStream bytestream(bitstreamFile);//将比特流转为字节流
// 创建解码器类
xCreateDecLib();
//舍弃RAP的前置图像中为RASL,更新the last displayed POC?
m_iPOCLastDisplay += m_iSkipFrame;      // set the last displayed POC correctly for skip forward.
bool loopFiltered[MAX_VPS_LAYERS] = {
    
     false };//标记是否已进行环路滤波
bool bPicSkipped = false;//表示是否跳过解码图像
bool isEosPresentInPu = false;//表示前一个NALU所在的PU是否是Eos

poc:帧的播放顺序

pcListPic:存放着解码出来的帧

bitstreamFile和bytestream:解码端的输入码流,一个是以比特为单位,另一个是以字节为单位

xCreateDecLib():函数包含着解码器类的创建和初始化,存在ROM上变量的初始化,量化和变换相关的初始化

m_iPOCLastDisplay += m_iSkipFrame :不确定

loopFiltered:标记是否已经环路滤波

bPicSkipped:是否跳过解码上一个NALU所在的图像

isEosPresentInPu:判断前一个NALU是否是EOS

2.循环进行NALU解码

  while (!!bitstreamFile)
  {
    
    
    //创建NALU类
    InputNALUnit nalu;
    nalu.m_nalUnitType = NAL_UNIT_INVALID;
      
    bool bNewPicture = m_cDecLib.isNewPicture(&bitstreamFile, &bytestream);//将要解码的NALU是否是图像中的第一个NALU
    bool bNewAccessUnit = bNewPicture && m_cDecLib.isNewAccessUnit( bNewPicture, &bitstreamFile, &bytestream );//将要解码的NALU是否是新的一帧中的第一个NALU,同时也是新的AU中的第一个NALU
      
    if(!bNewPicture)
    {
    
    //分支1
    }
      
    if ((bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS) && !m_cDecLib.getFirstSliceInSequence(nalu.m_nuhLayerId) && !bPicSkipped)
    {
    
    //分支2
     //满足不是跳过解码的图像,同时满足不是sequence中的第一个slice,同时满足以下至少一个条件:1)将要解码的NALU是图像中的第一个NALU;2)比特流文件eof?;3)上一个NALU的类型是EOS
    }
    else if ( (bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS ) &&
      m_cDecLib.getFirstSliceInSequence(nalu.m_nuhLayerId))//在下一个NALU所在的slice将是sequence中的第一个slice的情况下,同时满足以下至少一个条件:1)将要解码的NALU是图像中的第一个NALU;
    {
    
                                                          //2)比特流文件eof?;3)上一个NALU的类型是EOS。则下一个NALU所在的slice也是picture中的第一个slice。
      m_cDecLib.setFirstSliceInPicture (true);
    }
      
    if( pcListPic )
    {
    
    //分支3
    }
      
    if( bNewPicture )
    {
    
    
    }
      
    if (bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS)
    {
    
    
    }
      
    if (bNewAccessUnit || !bitstreamFile)
    {
    
    
    }
      
    if(bNewAccessUnit)
    {
    
    
    }
  }

进入循环只要bitstreamFile有效,就进行NALU解码

这里有两个重要的flag,bNewPicture和bNewAccessUnit

bNewPicture:将要解码的NALU是否是一帧中的第一个NALU

bNewAccessUnit:将要解码的NALU是否是AU中第一个NALU

bNewPicture为false进入第一个分支,具体参考2.1节

本节分支2:满足以下条件之一

  • 要解码的NALU是一帧中的第一个NALU

  • eof

  • 上一个NALU的类型是EOS

如果同时满足目前的解码过程不处于CLVS中的第一个slice且上一个NALU所处的帧未被跳过解码则进行一些操作,具体参考2.2节

如果同时满足目前的解码过程处于CLVS中的第一个slice则标志着解码过程进入一帧中的第一个slice。

说明:m_cDecLib.setFirstSliceInPicture (true)会使bNewPicture判断为False

本节分支3:存储的帧不为空,则进行一些操作,具体参考2.3节

之后还有四个分支和之前两个flag有关,由于能力有限就不展开了

2.1 if(!bNewPicture)

只要解码的NALU不是一帧中的第一个NALU就可进入此分支

AnnexBStats stats = AnnexBStats();//JVET-S2001中AnnexB有关的信息
// 将字节流的下一个NALU的所有比特流信息存入NALU类中的m_Bitstream的m_fifo,将统计信息存入stats,具体过程可以参考JVET-S2001中的AnnexB
byteStreamNALUnit(bytestream, nalu.getBitstream().getFifo(), stats);

// 读取NALU头信息,参考JVET-S2001 7.3.1.2 P83
read(nalu);

// 判断是否是IDR图像中的第一个slice
if(m_cDecLib.getFirstSliceInPicture() &&//是否是图片中的第一个slice,在解码器类初始化时设置为true
        (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL ||
         nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP))
{
    
    //分支1
 m_newCLVS[nalu.m_nuhLayerId] = true;   // m_newCLVS标记是否是一个新的CLVS
 xFlushOutput(pcListPic, nalu.m_nuhLayerId);//将pcListPic中存有的图片清空,并写入文件
}

if (m_cDecLib.getFirstSliceInPicture() && nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA && isEosPresentInPu)
{
    
    //分支2
 // 在EOS后面紧接着的CRA图像是CLVSS
 m_newCLVS[nalu.m_nuhLayerId] = true;
}
else if (m_cDecLib.getFirstSliceInPicture() && nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA && !isEosPresentInPu)
{
    
    
 // 如果CRA图像前面不是EOS,那CRA图像就不是CLVSS
 m_newCLVS[nalu.m_nuhLayerId] = false;
}

// temporal_id应该小于cfg中的m_iMaxTemporalLayer,同时nuh_layer_id应该在cfg的m_targetDecLayerIdSet中
if( ( m_iMaxTemporalLayer < 0 || nalu.m_temporalId <= m_iMaxTemporalLayer ) && xIsNaluWithinTargetDecLayerIdSet( &nalu ) )
{
    
    //分支3
}
else//不满足条件,跳过解码此图像
{
    
    
 bPicSkipped = true;
}

if (nalu.m_nalUnitType == NAL_UNIT_EOS)
{
    
    //分支4
 isEosPresentInPu = true;//当NALU的类型为EOS,将isEosPresentInPu设置为true
 m_newCLVS[nalu.m_nuhLayerId] = true;  //The presence of EOS means that the next picture is the beginning of new CLVS
}

byteStreamNALUnit():主要是将字节流掐头去尾,详细过程参考JVET-S2001中AnnexB一章,这里不再展开

read():读取NALU的头信息,相应格式在JVET-S2001 7.3.1.2 P83

本小节分支1:判断是否进入IDR图像中的第一个slice解码过程中,主要是由解码器类来决定。如果是则意味着进入新的CLVS,并将之前缓存的帧清除

本小节分支2:只有当前一个NALU是EOS(end of sequence)时,当前CRA图像才意味着进入新的CLVS

本小节分支3:是整个函数中最重要的分支,包含调用解码器类解码的过程。但是需要满足NALU的时域层在输出范围内,多图像层也在输出范围内。不满足就跳过解码。具体参考2.1.1节

本小节分支4:当前解码NALU为EOS类型时,就将isEosPresentInPu设置为true。并意味着下一个NALU就是CLVS的开始

2.1.1 分支3

if (bPicSkipped)
{
    
    
    if ((nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_TRAIL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_STSA) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_RASL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_RADL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA) || (nalu.m_nalUnitType == NAL_UNIT_CODED_SLICE_GDR))
    {
    
    //满足前一个NALU所在的图像是被跳过解码的,且当前NALU的nal_unit_type属于VCL(除去保留的)
     //分支1
        if (m_cDecLib.isSliceNaluFirstInAU(true, nalu))//图片中的第一个VCL类型的NALU是否是AU中的第一个VCL类型的NALU
        {
    
    
            //清除一些AU相关的缓存信息
            m_cDecLib.resetAccessUnitNals();
            m_cDecLib.resetAccessUnitApsNals();
            m_cDecLib.resetAccessUnitPicInfo();
        }
        bPicSkipped = false;
    }
}

m_cDecLib.decode(nalu, m_iSkipFrame, m_iPOCLastDisplay, m_targetOlsIdx);//调用解码器类进行解码NALU

if (nalu.m_nalUnitType == NAL_UNIT_VPS)//如果NALU类型是VPS,则提取一些信息
{
    
    //分支2
    m_cDecLib.deriveTargetOutputLayerSet( m_targetOlsIdx );
    m_targetDecLayerIdSet = m_cDecLib.getVPS()->m_targetLayerIdSet;//更新需要解码图片的nuh_layer_id集
    m_targetOutputLayerIdSet = m_cDecLib.getVPS()->m_targetOutputLayerIdSet;//更新需要输出图片的nuh_layer_id集
}

本小节的分支1:前一个NALU所在的图像是被跳过解码的,当前要解码NALU所在的图像不是被跳过解码的。当前NALU的类型又恰巧是VCL(除去保留的),又很恰巧这是AU中第一个VCL类型的NALU。那么就要调用解码器类进行以下三步操作

  • resetAccessUnitNals()

  • resetAccessUnitApsNals()

  • resetAccessUnitPicInfo()

都是跟AU相关的,没有跟进去看,具体啥作用也不知道。同时也要把bPicSkipped设置为false。

m_cDecLib.decode():调用解码器类进行解码的函数,需要另开篇幅仔细描述的。

本小节分支2:如果解码过的NALU类型是VPS(video parameter set),还需要提取一些信息。

2.2 if((bNewPicture || !bitstreamFile || nalu.m_nalUnitType == NAL_UNIT_EOS)…)

      if (!loopFiltered[nalu.m_nuhLayerId] || bitstreamFile)
      {
    
    //满足以下至少一个条件:1)eof且还未进行环路滤波?;2)将要解码的NALU是图像中的第一个NALU;3)上一个NALU的类型是EOS
        m_cDecLib.executeLoopFilters();//调用解码器类进行环路滤波
        m_cDecLib.finishPicture(poc, pcListPic, INFO, m_newCLVS[nalu.m_nuhLayerId]);//一张图像解码完后的一些操作?
      }

      loopFiltered[nalu.m_nuhLayerId] = (nalu.m_nalUnitType == NAL_UNIT_EOS);//如果NALU的类型为EOS,则将loopFiltered设置为true
      if (nalu.m_nalUnitType == NAL_UNIT_EOS)
      {
    
    
        m_cDecLib.setFirstSliceInSequence(true, nalu.m_nuhLayerId);//如果NALU的类型为EOS,下一个NALU所在的slice将是sequence中的第一个slice
      }

	  //图像解码完成后有关于IRAP和GDR的操作
      m_cDecLib.updateAssociatedIRAP();
      m_cDecLib.updatePrevGDRInSameLayer();
      m_cDecLib.updatePrevIRAPAndGDRSubpic();

只要不是eof并且已经滤波那么执行以下操作

  • m_cDecLib.executeLoopFilters():调用解码器类进行环路滤波
  • m_cDecLib.finishPicture():结束编码一帧并放入pcListPic中

如果上一个NALU的类型是EOS,那还需要将loopFiltered设置为true,并标记解码过程处于CLVS中的第一个slice

之后还有一些与IRAP和GDR相关的操作,没有跟进去看,具体啥作用也不知道。

2.3 if( pcListPic )

      if( !m_reconFileName.empty() && !m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].isOpen() )//存在m_reconFileName,且m_cVideoIOYuvReconFile不能使用
      {
    
    //分支1
		// 使用pcListPic中的第一张图的BitDepths作为m_outputBitDepth
        const BitDepths &bitDepths=pcListPic->front()->cs->sps->getBitDepths(); 
        for( uint32_t channelType = 0; channelType < MAX_NUM_CHANNEL_TYPE; channelType++ )
        {
    
    
            if( m_outputBitDepth[channelType] == 0 )
            {
    
    
                m_outputBitDepth[channelType] = bitDepths.recon[channelType];
            }
        }
        
        std::string reconFileName = m_reconFileName;
        if( ( m_cDecLib.getVPS() != nullptr && ( m_cDecLib.getVPS()->getMaxLayers() == 1 || xIsNaluWithinTargetOutputLayerIdSet( &nalu ) ) ) || m_cDecLib.getVPS() == nullptr )
        {
    
    //要么不存在VPS,要么当VPS存在的时候满足以下条件之一:1)最大允许层等于1;2)上一个NALU的nuh_layer_id在m_targetOutputLayerIdSet中
          m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].open( reconFileName, true, m_outputBitDepth, m_outputBitDepth, bitDepths.recon ); // 将文件流设置为write mode
        }
      }

      // write reconstruction to file
      if( bNewPicture )//如果要解码的NALU是图像中的第一个NALU,将重构图像写入文件
      {
    
    
        xWriteOutput( pcListPic, nalu.m_temporalId );
      }
      if (nalu.m_nalUnitType == NAL_UNIT_EOS)//如果上一个NALU类型是EOS,将重构图像写入文件,将m_bFirstSliceInPicture设置为false
      {
    
    
        xWriteOutput( pcListPic, nalu.m_temporalId );
        m_cDecLib.setFirstSliceInPicture (false);
      }
      // write reconstruction to file -- for additional bumping as defined in C.5.2.3
      if (!bNewPicture && ((nalu.m_nalUnitType >= NAL_UNIT_CODED_SLICE_TRAIL && nalu.m_nalUnitType <= NAL_UNIT_RESERVED_IRAP_VCL_12)
        || (nalu.m_nalUnitType >= NAL_UNIT_CODED_SLICE_IDR_W_RADL && nalu.m_nalUnitType <= NAL_UNIT_CODED_SLICE_GDR)))
      {
    
    
        xWriteOutput( pcListPic, nalu.m_temporalId );
      }

本节分支1:如果存在输出文件名,且输出文件流未打开。则取pcListPic中的第一张图的BitDepths作为以后输出的比特位数。然后打开相应的输出文件流

之后三个分支都与将重构图像写入文件有关,分别是当:

  • 如果要解码的NALU是图像中的第一个NALU

  • 上一个NALU类型是EOS

  • 是C.5.2.3定义的情况

第二种情况还要标记解码过程未进入一帧中的第一个slice

3. 收尾

  xFlushOutput( pcListPic );//结束解码,清空pcListPic

  // get the number of checksum errors
  uint32_t nRet = m_cDecLib.getNumberOfChecksumErrorsDetected();

  // delete buffers
  m_cDecLib.deletePicBuffer();
  // destroy internal classes
  xDestroyDecLib();

  destroyROM();//清除存放在ROM的变量

xFlushOutput():清空之前的缓存帧

m_cDecLib.getNumberOfChecksumErrorsDetected():统计checksum errors的数量,并将其返回

m_cDecLib.deletePicBuffer():清除解码器类的picture buffer

xDestroyDecLib():摧毁解码器类

destroyROM():清除存放在ROM的变量

猜你喜欢

转载自blog.csdn.net/hjhyxq2014/article/details/108965223