1. 程式人生 > >【HEVC學習與研究】26、HEVC的算數編碼實現

【HEVC學習與研究】26、HEVC的算數編碼實現

關於HEVC的前25篇博文全文發表在新浪部落格,地址為:http://blog.sina.com.cn/s/articlelist_1376260467_0_1.html。從第26篇開始部落格全文發在CSDN,新浪同步更新摘要和連結地址。

在第13篇博文中貼出了我們在除錯程式碼時所採用的二進位制碼流的開頭一部分資料,並根據這些資料進行了NAL Header解析、引數集合解析和條帶頭解析等資訊的分析。今後的博文中如無特殊情況依然會採用這些資料作為學習材料。

通過對這段碼流進行分析,我們可以看出,在對於一個slice進行解碼的過程中,對slice header和slice data採取了不同的熵解碼方法:slice header的語法元素採用了變長碼(主要是指數哥倫布編碼),而slice data的語法元素採用的是算術編碼CABAC。由於對CABAC的基本概念不是非常熟悉,之前一段時間一直在回顧H.264中CABAC相關的知識,現在來看HEVC重的算術編碼相對於H.264做了什麼樣的改變。
1、資料準備 同JM86中在每一個slice中為算術編碼進行資料準備不同,HM 10.0 decoder在程式的一開始就為CABAC解碼的過程在做準備。在main函式中的最開始會定義一個解碼器應用的例項: TAppDecTop cTAppDecTop;  在TAppDecTop這個類的宣告中可以看出,該類包含一個TDecTop型別的成員m_cTDecTop,這個物件將實現對編碼碼流的解碼。在TDecTop這一層還將實現針對不同型別的NAL,如VPS, SPS, PPS, SEI和slice資料等型別的NAL進行分別處理等操作,在TDecTop::decode函式實現。 在TDecTop的宣告中找到了一個TDecSbac型別的成員m_cSbacDecoder,根據TDecSbac.cpp開頭的簡介提示可知,這個類實現的就是上下文自適應的熵解碼器類。並且在類的宣告中可以得知,該類對碼流中的多種不同語法元素定義了相應的解析方法,如parseMVPIdx(), parseSaoMaxUvlc()等。除了這些解析方法之外,TDecSbac中還定義了一個數組存放上下文模型:ContextModel m_contextModels[MAX_NUM_CTX_MOD];一共512個元素。這些元素的型別ContextModel是專門用來定義上下文模型的類,其成員包括了預定義的LPS和MPS的狀態索引m_aucNextStateMPS[ 128 ]、m_aucNextStateLPS[ 128 ]以及表示當前狀態的變數m_ucState。在該類的實現函式中,上述兩個陣列被分別賦予相應的初始值。
2、SbacDecoder的初始化 SbacDecoder類的建構函式內容幾乎就是空的,但是該建構函式同時呼叫了SbacDecoder類成員的建構函式對其進行了初始化,其中數量最多的就是多個ContextModel3DBuffer型別的成員變數分別用於不同型別的語法元素的上下文模型。從命名來看就可以得知這是一個儲存上下文模型的三維快取,其中的成員變數也包含一個指向ContextModel類的指標m_contextModel和三個維度變數m_sizeX、m_sizeXY和m_sizeXYZ。在ContextModel3DBuffer的建構函式裡將指定的上下文模型的指標指向m_contextModels[]的某個成員。這些ContextModel3DBuffer型別的語法元素上下文模型都將在TDecSbac的建構函式呼叫時進行初始化。這一步將完成上下文模型的初始化操作。
3、其他的初始化流程 在主函式decmain.cpp中,呼叫cTAppDecTop.decode()函式;該函式呼叫xInitDecLib();該函式會呼叫m_cTDecTop.init()函式實現TDecTop的初始化;TDecTop::init()函式分別呼叫了initROM()和m_cGopDecoder、m_cSliceDecoder和m_cEntropyDecoder的init函式進行初始化。在TGopGop的宣告中可以看到該類聲明瞭TDecEntropy、TDecSbac等型別的指標。m_cGopDecoder.init()函式的作用就是將TGopGop的各種指標指向了TDecTop的各個例項成員。 4、解碼過程 對條帶頭的解析在TDecEntropy::xDecodeSlice()中實現,在其中呼叫parseSliceHeader()函式。 碼流的實際解析過程在TDecGop::decompressSlice()中實現。decompressSlice函式中,m_pcBinCABAC指向的就是TDecTop中定義的TDecSbac的例項m_cSbacDecoder;另外在對m_pcEntropyDecoder、m_pcSbacDecoder、m_pcSbacDecoders、m_pcBinCABACs完成設定之後,TDecGop::decompressSlice()呼叫了TDecSlice::decompressSlice()函式進行解碼slice_segment_data部分。 slice_segment_data由多個coding_tree_unit( )組成,在色度或亮度sao flag設為true時,coding_tree_unit( )的第一個語法元素為sao(),解析過程由pcSbacDecoder->parseSaoOneLcuInterleaving()實現。該函式第一個實際呼叫的函式是TDecSbac::parseSaoTypeIdx(),其中進行cabac解碼的函式為TDecBinCABAC::decodeBin()函式。 TDecBinCABAC::decodeBin()函式的實現可以參考標準文件的9.3.4.3.2部分。
TDecBinCABAC::decodeBin( UInt& ruiBin, ContextModel &rcCtxModel )
{
    //實現匯出ivlLpsRange值的過程:通過選定的上下文模型獲取當前狀態,通過計算ivlCurrRange的值計算得到qRangeIdx(qRangeIdx =( ivlCurrRange >> 6 ) & 3);
  UInt uiLPS = TComCABACTables::sm_aucLPSTable[ rcCtxModel.getState() ][ ( m_uiRange >> 6 ) - 4 ];
  m_uiRange -= uiLPS; //m_uiRange(標準文件中描述為ivlCurrRange)初始化為510,並變更為ivlCurrRange − ivlLpsRange
  UInt scaledRange = m_uiRange << 7;//計算更新的ivlOffset,這個值是表示算數解碼器引擎狀態的變數之一,在解碼引擎初始化的時候進行賦值
 
  if( m_uiValue < scaledRange )//根據ivlOffset與ivlCurrRange的對比判斷當前屬於MPS還是LPS
  {
    // MPS path
    ruiBin = rcCtxModel.getMps();//直接將valMps返回給輸出值binVal
    rcCtxModel.updateMPS();//更新MPS的上下文索引,通過查詢表m_aucNextStateMPS[]獲得
   
    if ( scaledRange >= ( 256 << 7 ) )
    {
      return;
    }
   
    m_uiRange = scaledRange >> 6;
    m_uiValue += m_uiValue;
   
    if ( ++m_bitsNeeded == 0 )
    {
      m_bitsNeeded = -8;
      m_uiValue += m_pcTComBitstream->readByte();
    }
  }
  else
  {
    // LPS path
    Int numBits = TComCABACTables::sm_aucRenormTable[ uiLPS >> 3 ];
    m_uiValue = ( m_uiValue - scaledRange ) << numBits;
    m_uiRange = uiLPS << numBits;
    ruiBin = 1 - rcCtxModel.getMps();//輸出值valMps設為1-valMps
    rcCtxModel.updateLPS();//更新LPS的上下文模型索引,通過查詢表m_aucNextStateLPS[]獲得。
   
    m_bitsNeeded += numBits;
   
    if ( m_bitsNeeded >= 0 )
    {
      m_uiValue += m_pcTComBitstream->readByte() << m_bitsNeeded;
      m_bitsNeeded -= 8;
    }
  }
}