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。該部分代碼會在下一篇博客展開分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章