MP3解碼之哈夫曼解碼快速演算法
哈夫曼(huffman)解碼用查表法,資料組織採用樹形結構,若採用二叉樹,一次處理一位(bit),效率是比較低的。從一些雜誌上看到關於哈夫曼(huffman)解碼的快速演算法介紹,直接用位流索引一次處理N(4<N<=32)位,這種方法實際上是不可行的,原因是構造出的碼錶很長,如果一次處理8位,可以編寫程式構造出碼錶,不過可以肯定的是碼錶的長度會超過我們事先的想象,以至於沒有多大的實用價值。一次處理多位(一位以上)碼錶中冗餘度很大導致碼錶很長。
MP3解碼處理主資料(main_data)的第一步就是對主資料進行哈夫曼解碼。MP3編解碼用到的哈夫曼表由ISO/IEC 11172-3 的附錄B中給出,大值區用到31張碼錶(其中第0、4、14號碼錶未使用),小值區用到兩張碼錶。從ISO/IEC 11172-3複製出兩張原始的碼錶來分析,解碼大值區的碼錶:
Huffman code table 7
x y hlen hcod
0 0 1 1
0 1 3 010
0 2 6 001010
0 3 8 00010011
0 4 8 00010000
0 5 9 000001010
1 0 3 011
1 1 4 0011
1 2 6 000111
1 3 7 0001010
1 4 7 0000101
1 5 8 00000011
2 0 6 001011
2 1 5 00100
2 2 7 0001101
2 3 8 00010001
2 4 8 00001000
2 5 9 000000100
3 0 7 0001100
3 1 7 0001011
3 2 8 00010010
3 3 9 000001111
3 4 9 000001011
3 5 9 000000010
4 0 7 0000111
4 1 7 0000110
4 2 8 00001001
4 3 9 000001110
4 4 9 000000011
4 5 10 0000000001
5 0 8 00000110
5 1 8 00000100
5 2 9 000000101
5 3 10 0000000011
5 4 10 0000000010
5 5 10 0000000000
解碼小值區的碼錶:
Huffman code table 17
x y hlen hcod
0 0 1 1
0 1 4 0101
0 2 4 0100
0 3 5 00101
0 4 4 0110
0 5 6 000101
0 6 5 00100
0 7 6 000100
0 8 4 0111
0 9 5 00011
0 10 5 00110
0 11 6 000000
0 12 5 00111
0 13 6 000010
0 14 6 000011
0 15 6 000001
所有碼錶中,大值區的x=0..15,y=0..15,碼長hlen=1..19。
一次處理多位應該如何實現呢?首先構造出碼錶,如果每次處理2位元,構造碼錶暫存資料時用4叉樹;如果一次處理3位元,則用8叉樹,以此類推。程式設計從一個文字檔案中讀入如上所示的原始的哈夫曼表資料,將其插入到N叉樹中相應的位置。假如一次處理2位而碼字(hcod)是5位(hlen=5),那麼在hcod最後分別補上0和1湊足2的整數倍位數,這就導致了構造出的哈夫曼表冗餘度,即一個碼字對應多個碼錶中元素。一次處理的位數越多,構造出的碼錶的冗餘度越大。
根據自己解碼設計構造出碼比較費事,解碼過程挺簡單。
實際程式設計中,可以用陣列來儲存N叉樹,只需要將指標域的值改為元素在陣列中的下標值。以大值區解碼一次處理2位元為例,x和y取值範圍為0..15,只需佔用4位元;碼長hlen取值範圍為1..19,儲存hlen只需5位元。儲存一個碼值只需要13位元,可以將一個碼值儲存在16位整數中,低4位是y,接下來的4位是x,再接下來的5位是hlen。高3位全為零表示該陣列元素儲存的是一個碼值,最高位為1(表示負數)則其絕對什表示該陣列元素儲存的是陣列的下標值。經過這樣處理後解碼大值區的碼錶“Huffman code table 7”:
C程式碼:
static short htbv7[72] = {
-4, -68, 256, 256, -8, -48, -64,1041, -12, -28, -40, -44, -16, -20, -24,2069,
2645,2629,2644,2643,2357,2357,2372,2372,2341,2341,2386,2386,2129, -32,2128, -36,
2309,2309,2356,2356,2371,2371,2355,2355,2084,2114,1812,1812,1857,1857,1856,1856,
-52, -56, -60,1554,2052,2083,2098,2051,1811,1811,1841,1841,1840,1840,1826,1826,
1313,1313,1538,1568, 769, 769, 784, 784};
解碼小值區的碼錶“Huffman code table 17”一次處理4位元:
static short htc0[80] = {
-16, -32, -48, -64,1026,1025,1028,1032, 256, 256, 256, 256, 256, 256, 256, 256,
1547,1547,1547,1547,1551,1551,1551,1551,1549,1549,1549,1549,1550,1550,1550,1550,
1543,1543,1543,1543,1541,1541,1541,1541,1289,1289,1289,1289,1289,1289,1289,1289,
1286,1286,1286,1286,1286,1286,1286,1286,1283,1283,1283,1283,1283,1283,1283,1283,
1290,1290,1290,1290,1290,1290,1290,1290,1292,1292,1292,1292,1292,1292,1292,1292};
碼錶構造好了,如何解碼呢?從碼流中讀入4位元組暫存到unsigned int umask,解碼大值區得到x和y:
y = ptab[umask >> 30]; // 一次處理2位元,ptab指向碼錶(如ptab=htbv7)
while (y < 0) {
umask <<= 2;
y = ptab[(umask >> 30) - y];
}
x = y >> 8; // hlen
num -= x; // num為umask中剩餘的位元數
umask <<= x;
x = (y >> 4) & 0xf; // 得到x值
y &= 0xf; // 得到y值
解碼小值區得到y值的程式碼:
C程式碼y = ptab[umask >> 28]; // 一次處理4位元
while (y < 0) {
umask <<= 4;
y = ptab[(umask >> 28) - y];
}
x = y >> 8; // hlen
num -= x;
umask <<= x;
y &= 0xf; // 得到y值
每解碼完一個碼字,重新重新整理umask和num。以上解碼方法正確與否可以用“Huffman code table 7”或“Huffman code table 17”內的資料去檢查。
一次處理N位元肯定比一次處理1位元效率高。兼顧效率和儲存空間開銷,解碼大值區採用一次處理2位到4位,解碼小值區一次處理4位,這樣比較好。