VVC/H.266代码阅读(VTM8.0)(三. Slice到CTU的处理 )

本文是本系列的第三篇博客,内容是分析从Slice到CTU的处理代码。
该系列相关博客为:
VVC/H.266代码阅读(VTM8.0)(一. NALU提取)
VVC/H.266代码阅读(VTM8.0)(二. non-VCLU解码)
VVC/H.266代码阅读(VTM8.0)(三. Slice到CTU的处理 )
VVC/H.266代码阅读(VTM8.0)(四. CU划分 )

VVC/H.266常见资源为:
VVC/H.266常见资源整理(提案地址、代码、资料等)

注:

  1. 考虑到从解码端分析代码,一是更加简单(解码流程无需编码工具和编码参数的择优),二是可以配合Draft文本更好地理解视频编解码的流程(解码端也都包含预测、量化、环路滤波、熵解码等流程),所以本系列从解码端入手分析VVC解码大致流程。等到解码端代码分析完后,再从编码端深入分析。
  2. 本文分析的bin文件是利用VTM8.0的编码器,以All Intra配置(IBC 打开)编码100帧得到的二进制码流(TemporalSubsampleRatio: 8,实际编码 ⌈100 / 8⌉ = 13帧)。
  3. 解码用最简单的:-b str.bin -o dec.yuv
  • 本系列的第一篇博客分析了解码端将收到的二进制码流bin文件提取成一个个NALU的过程。第一篇博客的最后写道 “调用DecLib::decode()进行当前NALU的核心解码流程。该函数内,会根据当前NALU的类型进行针对性地解码。”
  • 本系列的第二篇博客针对non-VCLU解码进行了分析,如SPS、PPS等non-VCLU,其中对u(n)、ue(v)的代码进行了详细分析。
  • 本篇博客将分析VCLU的解码代码,从Slice到CTU的处理过程。

1. 延续之前的博客,继续从DecLib::decode()分析。
根据nal_unit_type调用不同的函数针对性地解码。
以该NALU为例,前两个字节为0x00 和 0x41 (01000 001),所以nal_unit_type为 01000 = 8 (IDR_N_LP),时域ID为0最高级。
在这里插入图片描述
对于IDR_N_LP的描述,可以参考draft的定义:

instantaneous decoding refresh (IDR) picture: An IRAP picture for which each VCL NAL unit has nal_unit_type equal to IDR_W_RADL or IDR_N_LP.
An IDR picture does not use inter prediction in its decoding process, and may be the first picture in the bitstream in decoding order, or may appear later in the bitstream. Each IDR picture is the first picture of a CVS in decoding order. When an IDR picture for which each VCL NAL unit has nal_unit_type equal to IDR_W_RADL, it may have associated RADL pictures. When an IDR picture for which each VCL NAL unit has nal_unit_type equal to IDR_N_LP, it does not have any associated leading pictures. An IDR picture does not have associated RASL pictures.
//IDR帧有两种,IDR_W_RADL和IDR_N_LP。
//IDR帧解码过程不使用帧间预测(是I帧)。每个IDR帧是按解码顺序CVS的第一帧。
//IDR_W_RADL有RADL图像。IDR_N_LP没有前置图像。IDR帧都没有RASL图像。
//注:关于RADL、RASL等图像的叙述,可以参考万帅老师《新一代高效视频编码H.265HEVC原理、标准与实现》的第九章274页。
在这里插入图片描述
在这里插入图片描述
由于当前NALU是IDR_N_LP,所以调用xDecodeSlice()解码。

2. 进入DecLib::xDecodeSlice()
① 首先,通过之前non-VCLU解码的SPS、PPS等信息获取相关参数。再根据参数做出相应的设置。

 m_apcSlicePilot->setPicHeader( &m_picHeader );
 m_apcSlicePilot->initSlice(); // the slice pilot is an object to prepare for a new slice
……
  m_apcSlicePilot->setNalUnitType(nalu.m_nalUnitType);
  m_apcSlicePilot->setTLayer(nalu.m_temporalId);
  m_HLSReader.setBitstream( &nalu.getBitstream() );
  m_apcSlicePilot->m_ccAlfFilterParam = m_cALF.getCcAlfFilterParam();
  m_HLSReader.parseSliceHeader( m_apcSlicePilot, &m_picHeader, &m_parameterSetManager, m_prevTid0POC );
  ……

② 再核心调用DecSlice::decompressSlice() 解码该slice。
(1) 首先设置了CodingStructure类cs,该CodingStructure结构非常重要,管理了一帧中所有的CU,PU和TU,方便读取等操作。

  // setup coding structure
  CodingStructure& cs = *pic->cs;

(2) 再调用CABACReader::initCtxModels()进行了CABAC的上下文模型参数初始化。

  • 简单来说,就是根据qp和slice_type(I / B / P),从ContextSetCfg::sm_InitTables中选择了设定好的初值(该值是编解码两端商量好的,初值可以在draft中找到),用于CABAC的熵解码。具体初始化流程可以参考draft 9.3.2.2,对应代码如下:
void CtxStore<BinProbModel>::init( int qp, int initId )
{
  //initId 就是slice_type
  const std::vector<uint8_t>& initTable = ContextSetCfg::getInitTable( initId );
  //获取slice_type对应的初值
  const std::vector<uint8_t> &rateInitTable = ContextSetCfg::getInitTable(NUMBER_OF_SLICE_TYPES);
  int clippedQP = Clip3( 0, MAX_QP, qp );
  //qp限幅
  for( std::size_t k = 0; k < m_CtxBuffer.size(); k++ )
  {
    m_CtxBuffer[k].init( clippedQP, initTable[k] );
    //调用BinProbModel_Std::init()根据qp处理初值。
    m_CtxBuffer[k].setLog2WindowSize(rateInitTable[k]);
  }
}
void BinProbModel_Std::init( int qp, int initId )
{
  int slope = (initId >> 3) - 4;
  int offset = ((initId & 7) * 18) + 1;
  //对应draft中的描述的以下操作
  //slopeIdx = initValue  >>  3
  //offsetIdx = initValue & 7
  //m = slopeIdx − 4
  //n = ( offsetIdx  *  18 ) + 1
  int inistate = ((slope   * (qp - 16)) >> 1) + offset;
  int state_clip = inistate < 1 ? 1 : inistate > 127 ? 127 : inistate;
  //preCtxState = Clip3( 1, 127, ( ( m * ( Clip3( 0, 51, SliceQpY ) − 16 ) )  >>  1 ) + n )
  const int p1 = (state_clip << 8);
  m_state[0]   = p1 & MASK_0;
  m_state[1]   = p1 & MASK_1;
  //m_state[0..1]就是draft中的pStateIdx0/1
  //pStateIdx0 = preCtxState << 3
  //pStateIdx1 = preCtxState << 7

}

(3) 将slice分割成若干个CTU,对每个CTU进行解码(对应draft内7.3.10.1 General slice data syntax)。(编码端没有设置多Slice和多Tile编码,部分代码省略)。核心调用coding_tree_unit() 和 decompressCtu()。

  • 关于该Slice中CTU的数量,是在DecLib::xDecodeSlice()的第①步解析相关参数时,调用HLSyntaxReader::parseSliceHeader() ==> HLSyntaxReader::parsePictureHeader() ==> PPS::initRectSlices() ==> addCtusToSlice() ,根据图像宽高、slice分割信息进行设置的。
 void  addCtusToSlice( uint32_t startX, uint32_t stopX, uint32_t startY, uint32_t stopY, uint32_t picWidthInCtbsY ) 
  {
    CHECK( startX >= stopX || startY >= stopY, "Invalid slice definition");
    for( uint32_t ctbY = startY; ctbY < stopY; ctbY++ ) 
    {
      for( uint32_t ctbX = startX; ctbX < stopX; ctbX++ ) 
      {
        m_ctuAddrInSlice.push_back( ctbY * picWidthInCtbsY + ctbX );
        m_numCtuInSlice++;
        //m_numCtuInSlice就是该Slice中CTU的数目。
      }
    }
  }
};
  • 将slice分割成若干个CTU,对每个CTU进行解码(对应draft内7.3.10.1 General slice data syntax)。

slice

  for( unsigned ctuIdx = 0; ctuIdx < slice->getNumCtuInSlice(); ctuIdx++ )
  {
    const unsigned  ctuRsAddr       = slice->getCtuAddrInSlice(ctuIdx);
    const unsigned  ctuXPosInCtus   = ctuRsAddr % widthInCtus;
    const unsigned  ctuYPosInCtus   = ctuRsAddr / widthInCtus;    
    //CTU位置信息
    ……
    const unsigned  maxCUSize             = sps->getMaxCUWidth();
    Position pos( ctuXPosInCtus*maxCUSize, ctuYPosInCtus*maxCUSize) ;
    UnitArea ctuArea(cs.area.chromaFormat, Area( pos.x, pos.y, maxCUSize, maxCUSize ) );
	……
    cabacReader.coding_tree_unit( cs, ctuArea, pic->m_prevQP, ctuRsAddr );
    m_pcCuDecoder->decompressCtu( cs, ctuArea );
    ……
  }

③ 调用CABACReader::coding_tree_unit() 解码该CTU,遵循draft内7.3.10.2 Coding tree unit syntax。该部分代码会在下一篇博客展开分析。

猜你喜欢

转载自blog.csdn.net/weixin_37524256/article/details/105888833