1 不同標准中的huffman解碼原理
1.1標准MP3的huffman解碼原理
在MP3即mpeg-1 audio標准中,無噪聲編碼模塊的輸入是一組576個己量化的頻譜數據。無噪聲編碼首先對頻譜進行無噪聲的動態范圍壓縮。編碼模塊最多可以分別對四個模值超過1的系數進行編碼,並且在量化系數隊列中保留符號位,用來傳送符號。
為了使格組量化頻譜系數所需的比特數最少,無噪聲編碼把一組576個量化頻譜系數分成3個region,每個region一個霍夫曼碼書。由低頻到高頻分別為big_value區,count1區,rzero區,big_value區一個huffman碼字表示2個量化系數,使用32個huffman表,count1區一個huffman碼字表示4個量化系數,一共使用了2本碼書。Nzero區不用解碼。表示余下子帶譜線值全為0。
1.2標准MPEG2 AAC的Huffman解碼原理
在13818-7即AAC標准中,無噪聲編碼模塊的輸入是一組1024個己量化的頻譜數據。一共有13個有效的碼本,1個用於scalefactor解碼,其余的12個用於量化譜線的解碼。
霍夫曼編碼利用一個霍夫曼碼表示4量化系數或2個量化譜線系數。編碼時,一共使用了12本碼書。利用這些碼書,對系數的模值進行霍夫曼編碼,而非零系數的符號位添加到碼字中去。
為了使格組量化頻譜系數所需的比特數最少,無噪聲編碼把一組1024個量化頻譜系數分成一些區(section),每個區由一個或多個比例系數頻段組成,使用同一本霍夫曼碼書。這樣,對比例系數頻段的每個分區都必須傳輸該區的長度以及該區所用碼書的序號。
對scalefactor的解碼,第一個比例系數代表了量化器的全局步長,稱為全局增益值,也就是公共比例因子。它被編碼為一個8位無符號整數PCM值,全局增益的動態范圍足以表示一個24位PCM音源的所有值。后面所有的比例因子都利用一個特殊的霍夫曼碼書(對第一個比例系數,則與全局增益)進行差分編碼。為了提高壓縮比,系統不傳輸系數值全為零的比例系數頻段的比例系數。
標准區別 |
對SCF |
碼本組織 |
碼本個數 |
MP3 |
查分定長編碼 |
低頻2個系數一組,中頻4個系數一組 |
有效碼本8個 |
AAC |
Huffman編碼 |
低頻4個系數一組,中頻2個系數一組 |
有效碼本10個 |
2 Huffman的解碼優化設計
Huffman的解碼實現主要有以下幾種:
- 線性搜索法
線性搜索法按碼字非減的順序間碼本排成一個表,每次讀取一個比特,然后看排序的表中是否有完全匹配,如有則找到索引,沒有則繼續尋找。它的優點是所用的表比較小,但是搜索較長碼表的時候所需的時間太長,且不易擴展.
- 二叉樹搜索法
二叉樹搜索法要根據碼表建立一個二叉樹,葉節點表示相應的索引,左右子樹分別用1 ,0表示,如圖3-10(b)所示。進行搜索時,每次讀入一個比特,當讀入的值為1時進入左子樹,為0時進入右子樹,直到找到葉子節點。
- 直接查表法
直接查表法就是根據碼字逆向建表,解碼時每次讀入碼表中碼字的最大長度個比特,查表后便可找到相應的索引。這種算法只需一次查表即可完成,是所有算法中速度最快的,但是因為需要建立龐大的碼表而變得不可取。
- 分步搜索法等。
分步查表法避免了直接查表法中占用內存大的缺點,它靈活地把查表分為幾次完成。這樣就需要建幾個表,前一個表相當於后一個表的索引,最后的表記錄了相應碼字的索引,如圖3-10(a)所示的是兩步查表法。它實際上是二叉樹搜索法與直接查表法的折衷,當各個表的位寬為1時就是二叉樹搜索法,當位寬為最長碼長的長度時就變成直接查表法。所以分布查表法是各種解碼方法中最靈活的,可以根據不同的應用限制制定相應的表的組織形式。
3 huffman解碼算法模塊在不同參考軟件中的實現方法
Mp3
參考軟件1:11172-5_1998(E)_Software_Simulation
頂層函數
III_hufman_decode
子函數1
initialize_huffman
從文件中讀入huffman表
子函數2
huffman_decoder,解碼big value和count1
解碼一個碼字的算法
/* 查找huffman樹的方法. */
do {
if (h->val[point][0]==0) { /*end of tree*/
*x = h->val[point][1] >> 4;
*y = h->val[point][1] & 0xf;
error = 0;
break;
}
if (hget1bit()) {
while (h->val[point][1] >= MXOFF) point += h->val[point][1];
point += h->val[point][1];
}
else {
while (h->val[point][0] >= MXOFF) point += h->val[point][0];
point += h->val[point][0];
}
level >>= 1;
} while (level || (point < ht->treelen) );
參考軟件2:libmp3dec
Libmp3dec的解碼函數是從ffmpeg中提取出來,原意是想適應多個標准的huffman解碼,所以書寫比較復雜,但是效率較高.
有初始化表
init_vlc->調用build_table建立huffman表,其目的是通過建立huffman表減少huffman表在rom中的存儲空間,一個huffman表最少有3個部分組成,碼字,數據,碼長.但是libmp3dec的方法省略了數據的存儲,靜態表只有碼字和碼長,這樣的好處是省略了大量的數據的rom存儲空間,而改用ram存儲,這樣多個標准存儲的情況換成了1個標准執行存儲ram的情況.尤其對音頻這種組碼的情況更加有效.
解碼頂層
huffman_decode中
if (code_table) {
code = get_vlc(&s->gb, vlc);
if (code < 0)
return -1;
y = code_table[code];
x = y >> 4;
y = y & 0x0f;
} else {
x = 0;
y = 0;
}
這段代碼用來解碼一個碼字.其中調用函數get_vlc解碼, get_vlc函數設計的十分巧妙.但是這種巧妙主要用於多標准適應(可能包括視頻),是來自ffmpeg的一個函數.里邊調用一個關鍵的宏定義GET_VLC解碼一個碼字
#define GET_VLC(code, name, gb, table, bits, max_depth)\
{\
int n, index, nb_bits;\
\
index= SHOW_UBITS(name, gb, bits);\ //bits=8(一般的情況下),step1,提取8位碼流
code = table[index][0];\ //查找表
n = table[index][1];\ //
\
if(max_depth > 1 && n < 0){\ //n<0表示沒找到, max_depth表示最大查找步長
LAST_SKIP_BITS(name, gb, bits)\
UPDATE_CACHE(name, gb)\
\
nb_bits = -n;\
\
index= SHOW_UBITS(name, gb, nb_bits) + code;\ //再次取數據
code = table[index][0];\
n = table[index][1];\
if(max_depth > 2 && n < 0){\ //還沒找到,再查
LAST_SKIP_BITS(name, gb, nb_bits)\
UPDATE_CACHE(name, gb)\
\
nb_bits = -n;\
\
index= SHOW_UBITS(name, gb, nb_bits) + code;\
code = table[index][0];\
n = table[index][1];\
}\
}\
SKIP_BITS(name, gb, n)\
}
三步法完成查找.
參考軟件3 Melo
使用與faad相似的2步法完成查表。
AAC
參考軟件1: 13818-5_2005_Reference_Software
也是用分布查找法,沒有init表,表是靜態全局變量.
與mp3不同的是,scalefactor也是用的huffman解碼,mp3中scalefactor不是的.
程序中是按如下定義的
頂層huffdecode
核心子函數1
get_ics_info
核心子函數2
Getics
Getics中調用huffcb解碼section data
Getics中調用hufffac解碼scale factor data
Getics中調用huffspec解碼量化譜線系數
Hufffac和huffspec都調用了函數decode_huff_cw解碼一個碼字
其函數體是
i = h->len;
cw = getbits(i);
while (cw != h->cw) {
h++;
j = h->len-i;
i += j;
cw <<= j;
cw |= getbits(j);
}
Step1:解碼huffman表中的index值.
下面是13818 -7 ISO官方參考代碼中解碼一個huffman碼字的函數的流程圖與注釋:
int decode_huff_cw(Huffman *h)函數的流程圖與注釋
Step2:實現index值到量化譜線的映射
標准中的偽碼
unsigned = 數組unsigned_cb[i]的布爾值, 見表2的第二列.
當unsigned=0時表示有符號數, unsigned=1表示無符號數.
dim = 碼本的維數, 見表2的第三列.
lav = LAV,最大可編碼量化譜線系數的絕對值, 見表2的第四列..
idx = 碼字索引
if (unsigned) {
mod = lav + 1;
off = 0;
}
else {
mod = 2*lav + 1;
off = lav;
}
if (dim == 4) {
w = INT(idx/(mod*mod*mod)) - off;
idx -= (w+off)*(mod*mod*mod)
x = INT(idx/(mod*mod)) - off;
idx -= (x+off)*(mod*mod)
y = INT(idx/mod) - off;
idx -= (y+off)*mod
z = idx - off;
}
else {
y = INT(idx/mod) - off;
idx -= (y+off)*mod
z = idx - off;
}
注:這里的實際公式是
4-tuple : Idx = (w+off)*mod³ + (x+off)*mod² + (y+off)*mod + z + off
2-tuple: Idx = (y+off)*mod + z + off
之所以采取分組的整體huffman編碼的方法是為了進一步壓縮幀內的相關性節省碼字.其實在編碼端或或是解碼端完全可以直接選用(w,x,y,z)制表,查表,但這樣的查閱不便,加入了index的做法使思路清晰.但也給我們提出了優化的方向.
ISO參考代碼中的unpack_idx函數(注:程序初始化階段就計算出每個碼本的mod和off值)
if(dim == 4){
qp[0] = (idx/(mod*mod*mod)) - off;
idx -= (qp[0] + off)*(mod*mod*mod);
qp[1] = (idx/(mod*mod)) - off;
idx -= (qp[1] + off)*(mod*mod);
qp[2] = (idx/(mod)) - off;
idx -= (qp[2] + off)*(mod);
qp[3] = (idx) - off;
}
else {
qp[0] = (idx/(mod)) - off;
idx -= (qp[0] + off)*(mod);
qp[1] = (idx) - off;
}
Step3:獲取符號位
標准中偽碼
if (y != 0)
if (one_sign_bit == 1)
y = -y ;
if (z != 0)
if (one_sign_bit == 1)
z = -z;
ISO參考代碼
q:剛進行huffman解碼的量化譜線數據
n:碼本的維數
void get_sign_bits(int *q, int n)
{
while (n) {
if (*q) {
if (getbits(1)) { //1表示為負
*q = -*q;
}
}
n--;q++;
}
}
-----scale_factor_data部分的解碼
輸入:scale_factor_data部分碼流
輸出:差分scalefactor數據或差分intensity位置數據
Step1:
解碼scalefactor差分值, 同解碼量化譜線系數中的step1.index既是差分值.其他步驟參見scalefactor章節.
解碼intensity位置的差分值, 同解碼量化譜線系數中的step1.index既是差分值. 其他步驟參見intensity章節.
11.3 C參考代碼:
Step1:
獲得global_gain
Getics函數
*global_gain = getbits(LEN_SCL_PCM);
計算sf[g][sfb]
Hufffac函數
fac = global_gain;
t = decode_huff_cw(hcw);
fac += t - MIDFAC; /* 1.5 dB */
if(fac >= 2*maxfac || fac < 0)
return 0;
factors[i] = fac;
Step2 和 step3
來自huffspec函數
{
int sbk, nsbk, sfb, nsfb, fac, top;
Float *fp, scale;
i = 0;
fp = coef;
nsbk = info->nsbk;
for (sbk=0; sbk<nsbk; sbk++) {
nsfb = info->sfb_per_sbk[sbk];
k=0;
for (sfb=0; sfb<nsfb; sfb++) {
top = info->sbk_sfb_top[sbk][sfb];
fac = factors[i++]-SF_OFFSET;
//注釋:小於TEXP的使用查找表
if (fac >= 0 && fac < TEXP) {
scale = exptable[fac];
}
else {
if (fac == -SF_OFFSET) {
scale = 0;
}
else {
scale = pow( 2.0, 0.25*fac );
}
}
for ( ; k<top; k++) {
*fp++ *= scale;
}
}
}
}
參考軟件2:faad
Faad中調用huffman_scale_factor解碼scalefactor數據,使用的是二叉樹法
while (hcb_sf[offset][1])
{
uint8_t b = faad_get1bit(ld
DEBUGVAR(1,255,"huffman_scale_factor()"));
offset += hcb_sf[offset][b];
if (offset > 240)
{
// stop_huffman_timer();
/* printf("ERROR: offset into hcb_sf = %d >240!\n", offset); */
return -1;
}
}
調用huffman_spectral_data函數解碼譜線數據使用的是2步法解碼
總結
|
AAC |
Mp3 |
|||
|
官方 |
Faad or melo |
官方 |
Libmp3dec |
Lame |
解碼譜線 |
分步查表 |
碼字寬度小於12的用2步查表 碼字寬度大於等於12的用二叉樹 |
二叉樹 |
3步查表 |
二叉樹 |
解碼scalefactor |
分步查表 |
二叉樹 |
|
|
|
Huffman表狀態 |
靜態表 |
靜態表 |
靜態表 |
動態表 |
靜態表 |
參考文獻
【1】 基於risc的mpeg-4 aac編解碼研究
【2】 略