1. 程式人生 > >MP3解碼之哈夫曼解碼快速演算法

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位,這樣比較好。