解码一帧Layer3第3步:哈夫曼解码 -- huffmanDecoder方法
1.哈夫曼码表 共有33张码表,从ISO/IEC 11172-3复制一张码表出来,看看码表的庐山真面目,下面说到的哈夫曼树的构造及解码过程都以这一张表为例。码表如下所示:
Huffman code table 6
x y hlen hcod No.
0 0 3 111 0
0 1 3 011 1
0 2 5 00101 2
0 3 7 0000001 3
1 0 3 110 4
1 1 2 10 5
1 2 4 0011 6
1 3 5 00010 7
2 0 4 0101 8
2 1 4 0100 9
2 2 5 00100 10
2 3 6 000001 11
3 0 6 000011 12
3 1 5 00011 13
3 2 6 000010 14
3 3 7 0000000 15
码表的每一行由4个元素x、y、hlen、hcod组成,其最后一列是为了后文描述问题方便,我加上去的。解码大值区,一个码字(hcod)解码得到x、y两个值,hlen是码字的长度。33张码表中这几个值的取值范围是:x=0..15,y=0..15,hlen=0..19。其实33张码表只有象“Huffman code table 6”这样的表15张,码表有“共用”的情况,但是“共用”码表的linbits不同,看一看HuffmanBits的构造方法就明白这一点了。
2.哈夫曼树 哈夫曼解码用位流索引查表法,无论你采用何种数据结构存储码表,码表在逻辑是仍然是树形结构。
(1).2叉树 可以构造一棵2叉树装入这张码表,从位流中读入一位,根据是0还是1确定指向左子树还是右子树,直至到达叶结点,叶结点保存x、y,这样就可以由位流解得一个码字的2个码值的x和y了。
(2).N叉树 采用2叉树这种数据结构一次只能从位流处理1位,效率比较低。如果一次处理位流中的n位,采用N(N=2^n)叉树这种数据结构。以一次处理2位的4叉树为例,上面的码表“Huffman code table 6”构造出的4叉树如下:
private final static short htbv6[] = { //32 -4, -24, 529, -28, -8, -16, -20,1042, -12,1571,1586,1584,1843,1843,1795,1795, 1299,1299,1329,1329,1314,1314,1282,1282,1057,1056, 769, 769, 784, 784, 768, 768};
htbv6被初始化到解码用的码表htBV[6],见下文给出的源代码 HuffmanBits.java的第52行,这一行中的0表示该码表的linbits=0。
3.哈夫曼解码 下文给出的源代码 HuffmanBits.java的第135行是选择码表,假设当前选中的是 htBV[6],即htCur= htBV[6],htCur.table=htbv6:
主信息的第二部分内容是以哈夫曼编码的主数据(main_data)。MP3编码器在将PCM编码过程中先是用有损压缩使数据长度大大缩短,然后用哈夫曼编码这种无损压缩方式进一步减小数据流长度。每个粒度组内的频谱值使用不同的哈夫曼码表编码,全频率段从0到奈奎斯特频率(Nyquist frequency,等于采样率的二分之一)被划分为几个按不同码表编码的区段,区段是根据最大量化值来划分的。对高频率低幅值的信号不编码,即被压缩掉了。为了提高编码过程中哈夫曼编码效率,主数据被划分为三部分:大值区、小值区、零值区。示意图如下:
大值区 一个码字(hcod)解码得到两个值,码表的成员变量linbits的值为0..13,如果linbits不为0并且x(或y)=15,还需要从位流中读入linbits位得到一个整数值与x(或y)相加,可以计算出解码大值区得到的最大值(绝对值)为15+(2^13-1)=8206。解码大值区最多可能用到三张不同的码表。怎么知道当前的码流该用哪一张码表呢?GRInfo内定义了两个成员变量:region1Start和region2Start,它表示一张码表解码得到的值的个数,解得指定个数的值之后更换另一张码表。该更换到哪一张码表呢?解码帧边信息时已经初始化了GRInfo的成员变量table_select,当前可能用到的码表(最多3张)编号就存储在其中。
region1Start和region2Start 这两个变量在 Layer3的huffmanDecoder方法内被初始化,根据MPEG的版本(1.0/2.0/2.5)和“块”的类型初始化为不同的值。不同的块,子带的宽度不同。块和子带这些概念在后文再作交待。
供解码大值区使用的共有31张码表,其中第0张表hlen=0(其实不会被使用吧),第4、14码表不用。大值区中的一个码字(hcod)解得两个值,由帧边信息结构中的big_values可以计算出解码出大值区得到的值的个数 。
小值区 又称为count1区 ,解码得到的值为0,1或-1,整个小值区解码只用一张码表。供解码小值区使用的共有两张码表。小值区中的一个码字解得4个值。
零值区 未编码,所以不用解码,直接置为缺省值0。
位流中一帧主数据的末尾可能填充了几位而使之凑足一个字节,解码时对填充位作舍弃处理。调试程序时发现有填充位超过一字节的情况。
class Layer3内的huffmanDecoder方法源码如下:
//3. //>>>>HUFFMAN BITS========================================================= /* * nozero_index[]: 调用objHuffBits.decode()方法赋值;在Requantizer()方法内被修正; * 在hybird方法内使用. */ private static int[] rzero_index = new int[2]; private static int[] is; //[32 * 18 + 4]; private static HuffmanBits objHuffBits; private void huffmanDecoder(final int ch, final int gr) { GRInfo s = objSI.ch[ch].gr[gr]; int r1, r2; if (s.window_switching_flag != 0) { int v = objHeader.getVersion(); if(v == Header.MPEG1 || (v == Header.MPEG2 && s.block_type == 2)){ s.region1Start = 36; s.region2Start = 576; } else { if(v == Header.MPEG25) { if(s.block_type == 2 && s.mixed_block_flag == 0) s.region1Start = intSfbIdxLong[6]; else s.region1Start = intSfbIdxLong[8]; s.region2Start = 576; } else { s.region1Start = 54; s.region2Start = 576; } } } else { r1 = s.region0_count + 1; r2 = r1 + s.region1_count + 1; if (r2 > intSfbIdxLong.length - 1) { r2 = intSfbIdxLong.length - 1; } s.region1Start = intSfbIdxLong[r1]; s.region2Start = intSfbIdxLong[r2]; } rzero_index[ch] = objHuffBits.decode(ch, s, is); // 哈夫曼解码 } //<<<<HUFFMAN BITS=========================================================
实例化class HuffmanBits类型变量objHuffBits,用语句objHuffBits.decode()调用HuffmanBits的decode方法就完成了解码一个粒度组中的一个声道的哈夫曼解码。解码输出的结果暂存到is[]供下一步使用。
进一步了解哈夫曼解码细节 哈夫曼解码是MP3的关键模块之一,对解码器的效率(速度和内存开销)有较大影响。怎样设计出高效的哈夫曼解码器,至今仍有不少人在探索。我的哈夫曼解码方法只是一个尝试。我的另一篇贴子《MP3解码之哈夫曼解码快速算法》也是讲哈夫曼解码的,