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

    }

  • 相关阅读:
    【HDOJ】1811 Rank of Tetris
    【HDOJ】1518 Square
    日期类 Date
    RunTime
    System 系统类
    StringBuffer
    获取联系人列表的时候contact_id出现null的值
    String类
    object类
    eclipse使用的步骤
  • 原文地址:https://www.cnblogs.com/gaozehua/p/2485613.html
Copyright © 2020-2023  润新知