1. 程式人生 > >【H.264/AVC視訊編解碼技術詳解】十三、熵編碼演算法(4):H.264使用CAVLC解析巨集塊的殘差資料

【H.264/AVC視訊編解碼技術詳解】十三、熵編碼演算法(4):H.264使用CAVLC解析巨集塊的殘差資料

《H.264/AVC視訊編解碼技術詳解》視訊教程已經在“CSDN學院”上線,視訊中詳述了H.264的背景、標準協議和實現,並通過一個實戰工程的形式對H.264的標準進行解析和實現,歡迎觀看!

“紙上得來終覺淺,絕知此事要躬行”,只有自己按照標準文件以程式碼的形式操作一遍,才能對視訊壓縮編碼標準的思想和方法有足夠深刻的理解和體會!

GitHub程式碼地址:點選這裡

1. H.264的CAVLC解析巨集塊殘差資料的流程

在H.264的解碼器在解析巨集塊的殘差資料時,其流程類似於上文提到的CAVLC編碼的逆過程。在解析一個巨集塊殘差的時候,首先解析的是殘差矩陣的非零係數以及拖尾係數的個數numCoeff

trailingOnes。隨後是每一個拖尾係數的符號trailingSigns。而後是每一個非拖尾非零係數level的值。然後解析的是最高頻非零係數前面的零的總個數totalZeros。最後是每一個非零係數前連續零的個數runBefore

2. 計算CAVLC解析殘差的上下文引數

CAVLC編解碼過程中的上下文即為當前塊值numberCurrent。該值與當前畫素塊的左側鄰塊和上方鄰塊中非零係數的個數有關。

以尺寸為4×4巨集塊分割方式為例。當前畫素塊同左側和上方鄰塊的相對位置關係如下圖:

對於當前畫素塊,若其上方和左側相鄰塊都不可見(unavailable),那麼當前畫素塊的numberCurrent值為0;若上方或左側,有且僅有一個相鄰塊是可見的,那麼當前畫素塊的numberCurrent值即為這個鄰塊中非零係數的個數numCoeff;若兩個鄰塊都是可見的,那麼當前畫素塊的numberCurrent值為兩個鄰塊numCoeff的四捨五入平均值。

3. 解析非零係數總個數和拖尾係數個數

在CAVLC的解析過程中,非零係數總個數numCoeff和拖尾係數個數trailingOnes兩個值是一起解析出來的。解析這兩個值依據的是標準文件中的表9-5,如下表即是表9-5的部分:

根據之前解析出來的numberCurrent值,在這個表格中選擇一列作為解碼資料的參考。此後,從碼流中讀取相應長度的二進位制碼流,與表格中的值相比較。當碼流與表格中的值匹配時,表格的前兩列作為陣列的下標,其值即等於希望解析出來的numCoeff和trailingOnes的值。

4. 解析拖尾係數的符號

我們知道變換系數矩陣中最高頻的幾個絕對值為1的非零係數稱之為拖尾係數,其個數範圍為0~3個。表示每一個拖尾係數的符號可以一個bit的trailing_ones_sign_flag表示:

  • 當trailing_ones_sign_flag為1,拖尾係數符號為-;
  • 當trailing_ones_sign_flag為0,拖尾係數符號為+;

5. 解析非零係數的幅值

非拖尾的非零係數的幅值通常表示為levels。Levels的解析相對較為複雜。該部分是從最高頻開始解析到最低頻的非零係數為止。也就是說,levels部分是按頻率倒序解析的。

在解析每一個level的時候,每一個值都會按照字首(prefix)和字尾(suffix)兩部分進行解析。

5.1 解析level_prefix部分:

Level_prefix部分即level的字首部分,該部分的解析較為簡單,以偽程式碼表示如:

leadingZeroBits = −1
for( b = 0; !b; leadingZeroBits++ )
    b = read_bits( 1 )
level_prefix = leadingZeroBits

結合標準文件中的表9-6的表述可知,level的字首值即為當前碼流的下一個位元1之前連續的位元0的個數。

5.2 解析level_suffix部分:

Level_suffix部分的解析比prefix部分複雜,總體上可以分為以下幾個步驟:

  1. 解析過程開始之前,初始化suffixLength的值:當非零係數總數numCoeff大於10且拖尾係數個數trailingOnes等於3時,suffixLength初始化為1,否則初始化為0;
  2. 確定levelSuffixSize的值:通常情況下,levelSuffixSize的值等於當前的suffixLength,除了下列兩種意外情況:第一,level_prefix的值等於14且suffixLength為0,此時levelSuffixSize設為4;第二,level_prefix大於等於15,此時levelSuffixSize設為level_prefix-3;
  3. 解析level_suffix的值:根據levelSuffixSize的值作為長度,在碼流中讀取對應的二進位制資料作為level_suffix;若levelSuffixSize為0,則level_suffix的值為0;

5.3 由level_prefix和level_suffix部分組合成為levelCode

在解析完成level_prefix和level_suffix之後,將二者組合生成levelCode。計算方法為:levelCode=(Min(15,level_prefix)<

5.3 由levelCode計算level

根據計算得到的levelCode的奇偶性,判斷level的符號:

  • 若levelCode是偶數,返回level值為(levelCode + 2)>>1;
  • 若levelCode為奇數,返回level值為(−levelCode−1)>>1;

5.4 更新suffixLength的值

在解析過程中更新suffixLength體現了上下文自適應的思想。

  • 當suffixLength = 0時,suffixLength更新為1;
  • 當suffixLength小於6,且剛剛解析出來的level值大於閾值threshold時,suffixLength自增1;閾值threshold定義為( 3 << ( suffixLength − 1 ) );

6. 解析零係數資訊

變換系數矩陣中的零係數也是重要的資訊。CAVLC解析的零係數資訊主要分兩類:

  • totalZeros:每個矩陣一個值,表示最高頻非零係數前零係數的總個數;
  • runBefore:每個非零係數一個值,表示該非零係數前連續0的總個數;

解析totalZeros的過程與解析numCoeff和trailingOnes類似,都是從一個二維表格中查詢某列表格,在從碼流中查詢與表格中匹配的值,然後索引便是所求的totalZeros值。解析totalZeros的表格為標準文件中的表9-7。下圖是表9-7的區域性:

在解析totalZeros的過程中,選擇表格的索引值等於當前矩陣塊的非零係數個數numCoeff。

解析每個非零係數的runBefore時,也是按照從高頻到低頻逆序處理的。每次解析的runBefore也是按照類似上述的解析方法,從碼流中讀取相應長度的碼流並與表格中的值比對,匹配後返回索引值作為解析的值。解析runBefore參考標準文件的表9-10:

每次解析出一個runBefore後,totalZeros都要減去該值,然後進行下一次處理。若有n個非零係數,則總共需要解析n-1個runBefore。最低頻率的非零係數前的runBefore不需要寫在碼流中,因為可以通過上述資訊推算出。

以上就是解析一個巨集塊的4×4殘差係數矩陣相應語法元素的主要思想和過程。當然實際的解析過程比此要複雜得多,更詳細的情況可到CSDN學院的課程:H.264/AVC視訊編解碼技術詳解中觀看。