[原创]桓泽学音频编解码(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)\

}

posted @ 2012-05-06 09:38  杭州桓泽  阅读(2055)  评论(0编辑  收藏  举报