《H.264/AVC視頻編解碼技術詳解》視頻教程已經在“CSDN學院”上線,視頻中詳述了H.264的背景、標準協議和實現,並通過一個實戰工程的形式對H.264的標準進行解析和實現,歡迎觀看!
“紙上得來終覺淺,絕知此事要躬行”,只有自己按照標準文檔以代碼的形式操作一遍,才能對視頻壓縮編碼標準的思想和方法有足夠深刻的理解和體會!
鏈接地址:H.264/AVC視頻編解碼技術詳解
GitHub代碼地址:點擊這裏
本節視頻免費
1. H.264中的熵編碼基本方法
在成功從NAL Unit中獲取到語法元素的碼流之後,接下來就是對語法元素的碼流進行解析。根據我們在前面的博文中所講述的H.264編碼框架圖,經過預測、變換量化等步驟後得到的H.264語法元素將通過熵編碼器壓縮爲符合標準的H.264碼流。因此,爲了還原各個語法元素,必須對碼流使用熵編碼的解碼器進行解碼。
在H.264的標準協議中,不同的語法元素指定了不同的熵編碼方法。在協議文檔中共指定了10種語法元素的描述符,這些描述符表達了碼流解析爲語法元素值的方法,其中包含了H.264標準所支持的所有熵編碼方法:
語法元素描述符 | 編碼方法 |
---|---|
b(8) | 8位二進制比特位串,用於描述rbsp_byte() |
f(n) | n位固定模式比特位串,從最左bit開始計算 |
u(n) | 使用n位無符號整數表示,由n位bit換算得到 |
i(n) | 使用n位有符號整數表示,由n位bit換算得到 |
ue(v) | 使用無符號指數哥倫布編碼 |
se(v) | 使用有符號指數哥倫布編碼 |
te(v) | 使用截斷指數哥倫布編碼 |
me(v) | 使用映射指數哥倫布編碼 |
ce(v) | 上下文自適應的變長編碼 |
ae(v) | 上下文自適應的二進制算術編碼 |
2. 指數哥倫布編碼
同上篇介紹的哈夫曼編碼一樣,指數哥倫布編碼同樣屬於變長編碼(VLC)的一種。指數哥倫布編碼同哈夫曼編碼最顯著的一點不同在於,哈弗曼編碼構建完成後必須在傳遞的信息中加入碼字和碼元值的對應關係,也就是編碼的碼錶,而指數哥倫布編碼則不需要。
(1). 指數哥倫布編碼的分類
如上表指出,常用的指數哥倫布編碼通常可以分爲四類:
- ue(v):無符號指數哥倫布編碼;
- se(v):有符號指數哥倫布編碼;
- me(v):映射指數哥倫布編碼;
- te(v):截斷指數哥倫布編碼;
其中無符號指數哥倫布編碼ue(v)是其他編碼方式的基礎,其餘幾種方法基本可以由ue(v)推導得出。本文先從ue(v)來講述指數哥倫布算法的原理,而後再看如何推導至其他編碼方法。
(2). 無符號指數哥倫布編碼ue(v)
ue(v)的碼字可以分爲三個部分:[prefix] 1 [surfix]。其中前綴碼爲n個bit長度的0,後綴碼爲表示實際數值的信息位,信息位的長度爲前綴碼中0的個數。指數哥倫布編碼中前綴和後綴部分的長度根據碼元數值來確定:
0階指數哥倫布編碼模板 | 適用碼元值 |
---|---|
1 | 0 |
0 1 x | 1, 2 |
0 0 1 x x | 3~6 |
0 0 0 1 x x x | 7~14 |
0 0 0 0 1 x x x x | 15~30 |
0 0 0 0 0 1 x x x x x | 31~62 |
…… | …… |
在上標中編碼模板的後綴部分,xx以二進制的形式表示解碼後的數值。前綴0的長度以LeadingZeroBits表示,那麼解碼後數值爲:codeNum = 2^LeadingZeroBits - 1 + (xxx)。(xxx)爲二進制數值xxx的10進製表示。因此,指數哥倫布編碼的碼字與碼元值的對應關係如下表:
指數哥倫布編碼碼字 | 碼元數值 |
---|---|
1 | 0 |
0 1 0 | 1 |
0 1 1 | 2 |
0 0 1 0 0 | 3 |
0 0 1 0 1 | 4 |
0 0 1 1 0 | 5 |
0 0 1 1 1 | 6 |
0 0 0 1 0 0 0 | 7 |
…… | …… |
例如,當使用指數哥倫布編碼來表示數值codeNum = 10,那麼其前綴0的長度爲prefixLen = floor[log2(codeNum+1)] = 3,因此指數哥倫布碼的前綴爲 0 0 0。其後綴部分的二進制表示爲codeNum+1-2^prefixLen = 11-8 = 3 = b(0 1 1),因此10的指數哥倫布編碼碼字爲0 0 0 1 0 1 1。
又例如,當讀取到指數哥倫布碼0 0 0 0 1 0 1 0 1時,首先計算前綴0的個數,此處爲4,然後越過中間的1,讀取後面的0 1 0 1爲後綴碼。二進制0101表示爲十進制爲5,因此該指數哥倫布碼解碼後的數值爲2^4-1+5 = 20。
無符號指數哥倫布編碼是其餘多種變形算法的基礎,其餘的比如有符號指數哥倫布編碼、映射指數哥倫布編碼、截斷指數哥倫布編碼都是由無符號指數哥倫布編碼進一步處理得到的。
(3). 有符號指數哥倫布編碼
有符號的指數哥倫布編碼值是通過無符號的指數哥倫布編碼的值通過換算得到的,其語法元素描述符爲se(v)。每一個無符號指數哥倫布編碼的數值通過固定的換算關係轉換爲有符號的值,其換算關係爲:n = (-1)^(k+1) * Ceil(k/2)。下表表示了有符號和無符號指數哥倫布編碼之間的換算關係:
codeNum | syntax element value |
---|---|
0 | 0 |
1 | 1 |
2 | -1 |
3 | 2 |
4 | -2 |
5 | 3 |
6 | -3 |
k | (-1)^(k+1)*Ceil(k/2) |
(4). 截斷指數哥倫布編碼
截斷指數哥倫布編碼的語法元素描述符爲te(v)。當語法元素以te(v)解碼時,首先需要判斷的是語法元素的取值範圍,假定爲[0, x], x≥1。根據x的取值情況,語法元素根據下面不同情況進行解析:
- 若x>1,解析方法同ue(v)相同;
- 若x=1,語法元素值等同於下一位bit值的取反。
(5). 映射指數哥倫布編碼
映射指數哥倫布編碼的描述符爲me(v),適用於預測模式爲Intra_4x4, Intra_8x8或Inter的宏塊的coded_block_pattern的編碼。me(v)的映射方式並無指定的換算公式,通常由查表的方式進行。下表爲H.264 spec文檔的表9-4的一部分:
三. 指數哥倫布編碼同哈夫曼編碼的比較
指數哥倫布編碼同前文中提到的哈夫曼編碼都遵循了同一規律,即針對不同的碼元分配了bit位長度不同的碼字,因此各自都屬於變長編碼的一種。然而二者仍然具有較大的差別,具體如:
- 哈夫曼編碼在編碼過程中考慮了信源各個符號的概率分佈特性,根據符號的概率分佈進行編碼,因此對於不同的信源,即使是相同的符號的哈夫曼編碼的結果也是不同的;指數哥倫布編碼針對不同的信源採用的編碼是統一的,因此無論是什麼樣的輸入,輸出的編碼後的數據都是一致的。
- 由於哈夫曼編碼是針對信源特性進行的編碼,因此在存儲或傳輸編碼後的數據之前必須在前面保存一份碼錶供解碼段重建原始信息使用;而指數哥倫布編碼不需要存儲任何額外信息就可以進行解碼。
- 由於未考慮信源的實際特性,指數哥倫布編碼的壓縮比率通常比較低,對於有些信息甚至完全沒有壓縮效果,輸出數據比原始數據更大,在這一點上哈夫曼編碼作爲“最優編碼”在效率上更高;然而由於哈夫曼編碼運算較指數哥倫布編碼更爲複雜,且必須保存碼錶信息增加了傳輸負荷,也對壓縮比率造成了不利影響。
實際上,對於視頻壓縮這樣的需求而言,類似於哈夫曼編碼所提供的壓縮比率的優勢遠遠不夠,而且像H.264等編碼標準都不會指望靠這樣的方式來提高壓縮比率。因此在實際的視頻編碼方法中使用的是指數哥倫布編碼,但是隻作爲少數的輔助語法元素的編碼以及多數語法元素的二值化方法。真正貢獻了高壓縮比還需要後面詳述的CAVLC和CABAC等。
四. 0階無符號指數哥倫布編碼的實現Demo
程序代碼如下:
// ExpColum.cpp : 定義控制檯應用程序的入口點。
//
#include "stdafx.h"
#include <assert.h>
typedef unsigned char UINT8;
static int get_bit_at_position(UINT8 *buf, UINT8 &bytePotion, UINT8 &bitPosition)
{
UINT8 mask = 0, val = 0;
mask = 1 << (7 - bitPosition);
val = ((buf[bytePotion] & mask) != 0);
if (++bitPosition > 7)
{
bytePotion++;
bitPosition = 0;
}
return val;
}
static int get_uev_code_num(UINT8 *buf, UINT8 &bytePotion, UINT8 &bitPosition)
{
assert(bitPosition < 8);
UINT8 val = 0, prefixZeroCount = 0;
int prefix = 0, surfix = 0;
while (true)
{
val = get_bit_at_position(buf, bytePotion, bitPosition);
if (val == 0)
{
prefixZeroCount++;
}
else
{
break;
}
}
prefix = (1 << prefixZeroCount) - 1;
for (size_t i = 0; i < prefixZeroCount; i++)
{
val = get_bit_at_position(buf, bytePotion, bitPosition);
surfix += val * (1 << (prefixZeroCount - i - 1));
}
prefix += surfix;
return prefix;
}
int _tmain(int argc, _TCHAR* argv[])
{
UINT8 strArray[6] = { 0xA6, 0x42, 0x98, 0xE2, 0x04, 0x8A };
UINT8 bytePosition = 0, bitPosition = 0;
UINT8 dataLengthInBits = sizeof(strArray) * 8;
int codeNum = 0;
while ((bytePosition * 8 + bitPosition) < dataLengthInBits)
{
codeNum = get_uev_code_num(strArray, bytePosition, bitPosition);
printf("ExpoColumb codeNum = %d\n", codeNum);
}
return 0;
}
該例程的詳細解讀請觀看視頻教程:H.264/AVC視頻編解碼技術詳解