[原创]桓泽学音频编解码(6):MP3 无损解码模块算法分析
之前的链接
[置顶]研究音频编解码要看什么书
[原创]桓泽学音频编解码(1):MPEG1 MP3 系统算法分析
[原创]桓泽学音频编解码(2):AC3/Dolby Digital 系统算法分析
[原创]桓泽学音频编解码(3):AAC 系统算法分析
[原创]桓泽学音频编解码(4):MP3 和 AAC 中反量化原理,优化设计与参考代码中实现
[原创]桓泽学音频编解码(5):MP3 和 AAC 中IMDCT算法的原理,优化设计与参考代码中实现
1. 概述
在MPEG1 音频标准编码算法中,输入这个模块的数据是scalefactor数据和经过量化和scaler的残差谱线数据。MPEG1音频标准使用huffman编码算法对参差谱线进行编码。对scalefactor数据使用差分编码。解码时这个模块输入的是huffman码流数据,输出的是残差谱线数据。
由于Huffman编码是一种广泛使用的基础算法,所以就不在这里介绍了。
2. Huffman编解码算法
2.1 算法原理
图2 main_data的数据结构和Huffman code的数据结构
在MPEG1音频层3标准中,无噪声编码模块的对输入的是一组576个残差谱线数据使用huffman编码。为了更好的压缩数据,编码器首先对残差谱线数据进行分区,把一组576个量化频谱系数分成3个region。由低频到高频分别为big_value区,count1区,rzero区,big_value区一个huffman码字表示2个残差谱线,每个残差谱线的幅值范围是0~15(含15),当残差谱线的幅值大于15时不再使用huffman编码,xxxxxx,使用32个huffman表,count1区一个huffman码字表示4个残差谱线。nzero区不用解码,表示余下子带谱线值全为0。并且对低频的big_value区继续分割成3各子区。标准中一共使用了34个码本。2个码本供count1区选择,32个码本供big_value的3各子区选择。count1区和big_value的3各子区分别可以使用各自的码本。在huffman码字后是每个非零残差谱线的符号位。
如图所示
大于15的残差谱线的格式
2.2 解码过程
总的解码框图
解码bigvalue区
对于scalefactor的解码,在按照码流信息中提供的scalefactor长度读出差分的scalefactor值以后,与前一个scalefactor相加得到真正的scalefactor值。第一个差分scalefactor值和码流中的global_gain相加得到第一个scalefactor值。
当使用强度立体声编码时,按照预解码scalefactor相似的方式解码is_possition值。与解码scalefactor唯一不同的是,第一个差分is_possition和0相加得到第一个is_possition值
2.3 优化算法
Huffman解码算法的实现主要有以下几种:
A.线性搜索法
线性搜索法按码字非减的顺序间码本排成一个表,每次读取一个比特,然后看排序的表中是否有完全匹配,如有则找到索引,没有则继续寻找。它的优点是所用的表比较小,但是搜索较长码表的时候所需的时间太长,且不易扩展.
B.二叉树搜索法
二叉树搜索法要根据码表建立一个二叉树,叶节点表示相应的索引,左右子树分别用1 ,0表示,如图3-10(b)所示。进行搜索时,每次读入一个比特,当读入的值为1时进入左子树,为0时进入右子树,直到找到叶子节点。
C.直接查表法
直接查表法就是根据码字逆向建表,解码时每次读入码表中码字的最大长度个比特,查表后便可找到相应的索引。这种算法只需一次查表即可完成,是所有算法中速度最快的,但是因为需要建立庞大的码表而变得不可取。
D.分步搜索法等。
分步查表法避免了直接查表法中占用内存大的缺点,它灵活地把查表分为几次完成。这样就需要建几个表,前一个表相当于后一个表的索引,最后的表记录了相应码字的索引,如图3-10(a)所示的是两步查表法。它实际上是二叉树搜索法与直接查表法的折衷,当各个表的位宽为1时就是二叉树搜索法,当位宽为最长码长的长度时就变成直接查表法。所以分布查表法是各种解码方法中最灵活的,可以根据不同的应用限制制定相应的表的组织形式。
在参考代码里会给出3步搜索法的详细说明。
2.4 算法性能
2.5 参考代码
参考软件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
Mp3有变长码表32个,码字最长位宽19位,大部分码本码字在16位以下,libmp3采用3步查表法进行huffman解码。
Libmp3dec 3步法建表算法概述
一个huffman数据结构由如下3个组分组成:码字,码字长度,码字代表的数据。
在libmp3dec中,码字长度和码字代表的数据组成一个子类数据类型。码字是索引,也就是子类数据类型在码本内的地址。的3步法建表中以8位码宽建表。
实际代码实现中,通过函数init_vlc调用build_table建立huffman表,其目的是通过建立huffman表减少huffman表在rom中的存储空间,一个huffman表最少有3个部分组成,码字,数据,码长.但是libmp3dec的方法省略了数据的存储,静态表只有码字和码长,这样的好处是省略了大量的数据的rom存储空间,而改用ram存储,这样多个标准存储的情况换成了1个标准执行存储ram的情况.尤其对音频这种组码的情况更加有效.
Libmp3dec 3步法解码算法概述
不超过8位的码字,一次从码流中取8位数据,对比码本查表,查找到后,返回码字长度和码字代表的数据。
超过8位的码字小于16位的码字,一次从码流中取8位数据,对比码本查表,查找到后,该码字所指示的码字长度为负数值,码字长度的绝对值指示着下一步要在码流中获取数据的长度,返回的码字代表的数据指示着码本的偏移。再在码流中取码字长度的绝对值位的码流继续查表。第二次查表查找到后,返回码字长度和码字代表的数据。
超过16位的码字小于24位的码字,前两次从码流中取8位数据,对比码本查表,查找到后,该码字所指示的码字长度为负数值,码字长度的绝对值指示着下一步要在码流中获取数据的长度,返回的码字代表的数据指示着码本的偏移。的三次在码流中取码字长度的绝对值位的码流继续查表。的三次查表查找到后,返回码字长度和码字代表的数据。
1 算法流程图
2 码本静态数据结构
3 码本动态数据结构
3 子算法Huffman init 结构图
Libmp3dec的初始化执行的是把静态表变成动态表。构建3步查找表结构。Build_table是huffman init的主要函数。分2步收敛的进行。其流程如下
Pass 1 流程图
Huffman Init 部分详细注释
int build_table(VLC *vlc, int table_nb_bits,
int nb_codes,
const void *bits, int bits_wrap, int bits_size,
const void *codes, int codes_wrap, int codes_size,
uint32_t code_prefix, int n_prefix)
{
int i, j, k, n, table_size, table_index, nb, n1, index;
uint32_t code;
VLC_TYPE (*table)[2];
table_size = 1 << table_nb_bits;
//the table_size is the size of table that need to creat
//if table_nb_bits=8 indicate there are 256 tables that need to creat
table_index = alloc_table(vlc, table_size);
//allocate the memory by the table_size
//table_index is the
if (table_index < 0)
return -1;
table = &vlc->table[table_index];
//refers to the vlc table
/*init table*/
for(i=0;i<table_size;i++) {
table[i][1] = 0; //bits
table[i][0] = -1; //codes
}
/* first pass: map codes and compute auxillary table sizes */
for(i=0;i<nb_codes;i++) {
GET_DATA(n, bits, i, bits_wrap, bits_size);
GET_DATA(code, codes, i, codes_wrap, codes_size);
/* we accept tables with holes */
if (n <= 0)
continue;
/* if code matches the prefix, it is in the table */
n -= n_prefix;
if (n > 0 && (code >> n) == code_prefix) {
if (n <= table_nb_bits) {
/* no need to add another table, add the code and the code length to the table */
j = (code << (table_nb_bits - n)) & (table_size - 1);
//count the code word
nb = 1 << (table_nb_bits - n);
// count then number of the code word that need to feed the table
for(k=0;k<nb;k++) {
if (table[j][1] /*bits*/ != 0)
{
// this if is no use
printf("Panic\n");
return 0;
}
table[j][1] = n; //bits
// put the huffman code length in the table[j][1]
table[j][0] = i; //code
// put the huffman code in the table[j][0]
j++;
}
}
/*need auxillary table and compute the auxillary table size */
else {
n -= table_nb_bits;
j = (code >> n) & ((1 << table_nb_bits) - 1);
/* compute table size */
n1 = -table[j][1]; //bits
if (n > n1)
n1 = n;
table[j][1] = -n1; //bits
}
}
}
/* second pass : fill auxillary tables recursively */
for(i=0;i<table_size;i++)
{
n = table[i][1]; //bits
if (n < 0) {
n = -n;
if (n > table_nb_bits) {
n = table_nb_bits;
table[i][1] = -n; //bits
}
index = build_table(vlc, n, nb_codes,
bits, bits_wrap, bits_size,
codes, codes_wrap, codes_size,
(code_prefix << table_nb_bits) | i,
n_prefix + table_nb_bits);
if (index < 0)
return -1;
/* note: realloc has been done, so reload tables */
table = &vlc->table[table_index];
table[i][0] = index; //code
}
}
return table_index;
}
解码一个码字的算法
函数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)\
}