哈夫曼(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”:
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值的代码:
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位,这样比较好。