解析完成條帶頭之後下一步的工作是解析條帶資料slice_segment_data。slice_segment_data資料主要由一個個的Coding_Tree_Unit(CTU)組成。每一個CTU的結構如下所示:
可以看出,每一個CTU的coding_quadtree部分之前是文法結構sao。那麼現在來研究一下如何從碼流中解碼出sao,這裡着重讨論cabac而非sao的意義。
對sao的解析部分在TDecSlice::decompressSlice()中。在真正開始解碼之前,TDecSlice::decompressSlice()調用了m_pcEntropyDecoderIf->resetEntropy(p)函數進行熵解碼器的一些初始化操作,如為不同文法元素進行initBuffer。在最後,調用了m_pcTDecBinIf->start()函數。該函數讀出了碼流中的前兩個位元組組成一個UInt16的數字賦給m_uiValue。
以下是TDecSlice::decompressSlice()中sao的解析部分:
具體的解析過程在pcSbacDecoder->parseSaoOneLcuInterleaving()中實作。實作如下:
在調試過程中,對于第一個ctu來講,rx和ry均為0,是以不會解析MergeUp和MergeLeft文法元素。實際上執行的部分是parseSaoOffset(&(pSaoParam->saoLcuParam[iCompIdx][iAddr]), iCompIdx);
在該函數中執行parseSaoTypeIdx(uiSymbol);
該函數調用了上篇文章中解釋過的decodeBin函數實作解析。回顧上篇文章中對該函數的解釋,可以看出該函數的輸出值實際就是valMps或者1-valMps,組成一串二進制碼流,也就是算數編碼前的二值化過的文法元素。通過這個函數輸出了一個二進制字元,但是卻并沒有從原始碼流中讀取資料,這種現象開始時相當讓我感到費解,不過想了一下便明白了這是很正常的,因為這正是算數編碼過程的逆過程。在編碼的時候,編碼器“吸收”了一個、兩個甚至更多個字元,多次進行MPS/LPS判斷和區間劃分,直到滿足一定條件後才會輸出一個0或1。那麼在解碼的時候,也會可能會出現輸出了多個字元才“消耗”了1bit碼流的情況。從上述函數的實作中可以得知,parseSaoTypeIdx函數傳回給uiSymbol的值為0,那麼parseSaoOffset(SaoLcuParam*
psSaoLcuParam, UInt compIdx)函數便不會做後續操作,将psSaoLcuParam->length置0後直接傳回。
從parseSaoOneLcuInterleaving函數中可以看出,parseSaoOffset将會循環調用3次。在第二次調用中,我們的demo程式顯示,parseSaoTypeIdx中uiCode傳回值為1,是以,下面繼續調用了decodeBinEP函數:
該函數向實參uiCode輸出值為0,根據條件,parseSaoTypeIdx的輸出值為5。并且将執行4次parseSaoMaxUvlc,将解碼的結果賦予psSaoLcuParam->offset[i]。
在執行第一次parseSaoMaxUvlc中,隻執行了函數①,code等于0是以傳回val=0;執行第二次parseSaoMaxUvlc,①傳回code=1,是以将會執行下面的while循環,共執行三次,傳回val=3;值得注意的是,在第三次循環中,decodeBinEP函數讀取了碼流中的第三個字元加到了m_uiValue上;執行第三次parseSaoMaxUvlc,①傳回code=1,下面的while循環隻會執行一次,傳回val=1;第四次執行parseSaoMaxUvlc,①傳回code=1,while循環執行一次,傳回val=1。
接下來,對第2~4個offset值,将各調用一次decodeBinEP,為1則取其負值。
調用parseSaoUflc(5, uiSymbol ),該函數調用了decodeBinsEP,該函數可以認為一次處理多位。實作如下:
在該函數中讀取了碼流中的第四個位元組(107),在for循環中比較m_uiValue和scaledRange的值并決定bins加1或倍增,最終傳回值為13。該值賦予psSaoLcuParam->subTypeIdx。
自此,parseSaoOffset的任務便已完成。而類似的工作将循環三次以完成pcSbacDecoder->parseSaoOneLcuInterleaving的功能。
PS:跟蹤了這麼久的代碼,但是現在的問題依然是知其然不知其是以然……看來接下來需要在标準文檔和其他一些資料上面下些功夫了。