• 1. idct.c中void idct_int32(short *const block)函数

    定义了几个中间变量 X0,X1,X2,X3,X4,X5...X8
    变量类型使用了 static long

    结果虽然不会出错,但是影响性能,因为static 变量在退出函数后还需要保留该变量的值,增加了运算量,而非static变量,则可以通过编译

    器优化,能使该变量为寄存器变量,减少内存访问的时间。本人测试了2000帧QVGA(320x240)图像,其中static变量的idct耗时8.6s,而非

    static的只要5.3s,可见对效率影响挺大。

    不过,俺刚看了xvid 1.03版本已经将这个代码改过来了,xvid 1.01和0.91都是static的。

    2.xvid_decraw.c中main函数中
            if (already_in_buffer > 0)
                    memcpy(mp4_buffer, mp4_ptr, already_in_buffer);

            /* Update mp4_ptr */
            mp4_ptr = mp4_buffer;

            /* read new data */
            if(feof(in_file))
                    break;
    这段代码将导致,码流没有结束而提前退出程序的情况

    因为在读文件的时候,文件结束并不代表,缓冲区中的数据也被读完了,缓冲区大小为
    #define BUFFER_SIZE (2*1024*1024)  //2兆啊
    所以,即使遇到文件结束,也应该继续解码,直道already_in_buffer的值为0

    3.bitstream.h中的static void __inline BitstreamInit()函数
            ptr_t adjbitstream = (ptr_t)bitstream;

            /*
             * Start the stream on a uint32_t boundary, by rounding down to the
             * previous uint32_t and skipping the intervening bytes.
             */
            bitpos = ((sizeof(uint32_t)-1) & (size_t)bitstream);
            adjbitstream = adjbitstream - bitpos;
            bs->start = bs->tail = (uint32_t *) adjbitstream;
    该段代码,直接将位流指针设置成整型,在x86处理器下可以正常运行,而有些处理器是不支持在非32位对齐的地址做字(32bits)读取的,除非编译器做额外处理。

    可以做改动的是,在传入参数时,就将其设置为字对齐的。如果是一个地址为addr,将其改为 addr&-4,低两位强制设0。这在xvid_decraw.c里可以修改

  • 讨论讨论位流(stream)的读取,其实很简单

    先看Bitstream的结构

    typedef struct
    {
     uint32_t curr;//位流访问中的当前字(32bits为一个字)
     uint32_t next;//要访问的下一个字,供跨字访问时使用
     uint32_t buf;//读bitstream的话,这个可不要
     uint32_t pos;//比特位置,在curr中读到哪一个位了
     uint32_t *tail; //位流缓冲区所指向的当前指针,读到哪一个字节
     uint32_t *start;//位流缓冲区的头
     uint32_t length;//缓冲区总的长度
    }Bitstream;

    //初始化位流结构
    static void __inline
    BitstreamInit(Bitstream * const bs,
         void *const bitstream,
         uint32_t length)
    {
     uint32_t tmp;

     bs->start = bs->tail = (uint32_t *) bitstream;

     tmp = *(uint32_t *) bitstream;
    #ifndef ARCH_IS_BIG_ENDIAN
     BSWAP(tmp);    //因为bitstream是bigendian的,所以在编译小尾程序时,需要swap一下
    #endif
     bs->curr = tmp;

     tmp = *((uint32_t *) bitstream + 1);
    #ifndef ARCH_IS_BIG_ENDIAN
     BSWAP(tmp);
    #endif
     bs->next = tmp;

     bs->buf = 0;
     bs->pos = 0; //因为还没开始读比特流,所以位置为0
     bs->length = length;
    }

    //取得将要读到的比特位数的指,不更改比特位置
    static uint32_t __inline
    BitstreamShowBits(Bitstream * const bs,
          const uint32_t bits)
    {
     //分析将要读出的比特位数,是否跨过一个字(32位)边界
     int nbit = (bits + bs->pos) - 32;

     if (nbit > 0) {//越界,则由curr和next的两部分拼出要读取的值
      return ((bs->curr & (0xffffffff >> bs->pos)) << nbit) | (bs->next >> (32 -nbit));
     } else {//没有越界,通过移位curr就能得到要取的值
      return (bs->curr & (0xffffffff >> bs->pos)) >> (32 - bs->pos - bits);
     }
    }

    //移动比特位位置,分越界和不越界两种情况
    static void __inline
    BitstreamSkip(Bitstream * const bs,
         const uint32_t bits)
    {
     bs->pos += bits;

     if (bs->pos >= 32) {
      uint32_t tmp;
      
      //如果越界了,自动修改curr和next
      bs->curr = bs->next;
      tmp = *((uint32_t *) bs->tail + 2);
    #ifndef ARCH_IS_BIG_ENDIAN
      BSWAP(tmp);
    #endif
      bs->next = tmp;
      bs->tail++;
      bs->pos -= 32;
     }
    }

    /* read n bits from bitstream */

    //将读值和修改位置,同时做
    static uint32_t __inline
    BitstreamGetBits(Bitstream * const bs,
         const uint32_t n)
    {
     uint32_t ret = BitstreamShowBits(bs, n);

     BitstreamSkip(bs, n);
     return ret;
    }

    结束,呵呵。很简单。

  • 传统的视频解码程序一般都是将宏块解码的输出结果,送到一个buffer中,然后统一将这一块内存区的内容送入到显存中,供播放使用。

    这样的话,对于嵌入式应用就会带来两个问题。1.就是中间经过了buffer,多了一遍内存拷贝的动作。2.就是到一帧解码结束后,这个buffer中的大部分数据都已经不在cache中了,会带来比较大的cache miss。

    所以,改进的方法就是宏块解码的结果直接送到输出显存,这时候宏块的数据都是在cache里的。对于destination则,可以用cache allocate (如果可cache的话,)加快拷贝速度。

  • mp3 和 mp2 的核心实现在 mpegaudiodec.c 里。

    如果不需要其它解码只要mpeg声音的解码的话,其接口相当简单。一个是初始化函数 decode_init,另一个是真正的帧解码函数 decode_frame.

    int decode_init(AVCodecContext * avctx)
    int decode_frame(AVCodecContext * avctx,void *data, int *data_size,uint8_t * buf, int buf_size)

    可以看得出来,用户只要定义一个 AVCodecContext 的变量,然后将指针传给这两个函数就可以了。另外特别值得注意的是 decode_init中第一行代码是
    MPADecodeContext *s = avctx->priv_data;

    由于AVCodecContext结构体的priv_data是一个数据空间的指针,如果没有分配空间那它是指向NULL的,所以你可以选择malloc一个空间,也可以定义一个全局变量MPADecodeContext MPDctx,然后将指针赋值给avctx->priv_data.我选择后者,对于管理内存,太麻烦,也不是俺的强项。

    decode_frame 的功能就是解码完一帧就返回,返回值表明使用了缓冲区中多少数据。当然如果传入的数据不够解码一帧也会返回,但是data_size为0。这里介绍一下decode_frame的后面几个参数,buf和buf_size是指传入的mpeg声音压缩码流的缓冲区地址和缓冲区大小,而data和是指解码出来的pcm数据要存放的地址,*data_size是解码出的pcm数据大小,一般为0或者1152x4. 除了data_size,decode_frame的另外四个参数都为输入型参数.

    据此,我们可以写一个main.c来实现一个播放器了。

    AVCodecContext t_avcc;
    MPADecodeContext t_mpadc;
    int main(int argc,char *argv[]){
         t_avcc.priv_data=&t_mpadc;

         decode_init(&t_avcc);

         此处加入打开mp3文件的指针file_mp3;

      while(!feof(file_mp3)){
        int len;
        unsigned char buf[4096],*buf_ptr;
        int buf_size;
        unsigned char data[1152*8];
        int data_size;
        /* 这个read函数不保证正确,凭感觉写的:(*/
        buf_size=read(buf,file_mp3,4096);
        while(buf_size>0){
          buf_ptr=buf;
          len=decode_frame(&t_avcc,data,&data_size,buf_ptr,buf_size);
          if(len<0)
            break;
          此处加入对data的处理代码,写入文件或者写到声卡,随便
          buf_optr+=len;
          buf_size-=len;
       }
      }
    }

  • 作者:肖文鹏 发文时间:2004.03.22
    在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux程序员面临的首要问题都是如何灵活运用C编译器。目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。GCC不仅功能非常强大,结构也异常灵活。最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等。

    开放、自由和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程。在使用GCC编译程序时,编译过程可以被细分为四个阶段:

    ◆ 预处理(Pre-Processing)

    ◆ 编译(Compiling)

    ◆ 汇编(Assembling)

    ◆ 链接(Linking)

    Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。

    GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。

    GCC起步

    在学习使用GCC之前,下面的这个例子能够帮助用户迅速理解GCC的工作原理,并将其立即运用到实际的项目开发中去。首先用熟悉的编辑器输入清单1所示的代码:

    清单1:hello.c

     #include <stdio.h>
     int main(void)
     {
      printf ("Hello world, Linux programming!\n");
      return 0;
     }
     
    然后执行下面的命令编译和运行这段程序:

     # gcc hello.c -o hello # ./hello Hello world, Linux programming!
     
    从程序员的角度看,只需简单地执行一条GCC命令就可以了,但从编译器的角度来看,却需要完成一系列非常繁杂的工作。首先,GCC需要调用预处理程序cpp,由它负责展开在源文件中定义的宏,并向其中插入“#include”语句所包含的内容;接着,GCC会调用ccl和as将处理后的源代码编译成目标代码;最后,GCC会调用链接程序ld,把生成的目标代码链接成一个可执行程序。

    为了更好地理解GCC的工作过程,可以把上述编译过程分成几个步骤单独进行,并观察每步的运行结果。第一步是进行预编译,使用-E参数可以让GCC在预处理结束后停止编译过程:

     # gcc -E hello.c -o hello.i
     
    此时若查看hello.cpp文件中的内容,会发现stdio.h的内容确实都插到文件里去了,而其它应当被预处理的宏定义也都做了相应的处理。下一步是将hello.i编译为目标代码,这可以通过使用-c参数来完成:

     # gcc -c hello.i -o hello.o
     
    GCC默认将.i文件看成是预处理后的C语言源代码,因此上述命令将自动跳过预处理步骤而开始执行编译过程,也可以使用-x参数让GCC从指定的步骤开始编译。最后一步是将生成的目标文件链接成可执行文件:

     # gcc hello.o -o hello
     
    在采用模块化的设计思想进行软件开发时,通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用GCC能够很好地管理这些编译单元。假设有一个由foo1.c和foo2.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序foo,可以使用下面这条命令:

     # gcc foo1.c foo2.c -o foo
     
    如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。如果深究起来,上面这条命令大致相当于依次执行如下三条命令:

     # gcc -c foo1.c -o foo1.o # gcc -c foo2.c -o foo2.o # gcc foo1.o foo2.o -o foo
     
    在编译一个包含许多源文件的工程时,若只用一条GCC命令来完成编译是非常浪费时间的。假设项目中有100个源文件需要编译,并且每个源文件中都包含10000行代码,如果像上面那样仅用一条GCC命令来完成编译工作,那么GCC需要将每个源文件都重新编译一遍,然后再全部连接起来。很显然,这样浪费的时间相当多,尤其是当用户只是修改了其中某一个文件的时候,完全没有必要将每个文件都重新编译一遍,因为很多已经生成的目标文件是不会改变的。要解决这个问题,关键是要灵活运用GCC,同时还要借助像Make这样的工具。

    警告提示功能

    GCC包含完整的出错检查和警告提示功能,它们可以帮助Linux程序员写出更加专业和优美的代码。先来读读清单2所示的程序,这段代码写得很糟糕,仔细检查一下不难挑出很多毛病:

    ◆main函数的返回值被声明为void,但实际上应该是int;

    ◆使用了GNU语法扩展,即使用long long来声明64位整数,不符合ANSI/ISO C语言标准;

    ◆main函数在终止前没有调用return语句。

    清单2:illcode.c

     #include <stdio.h>
     void main(void)
     {
      long long int var = 1;
      printf("It is not standard C code!\n");
     }

    下面来看看GCC是如何帮助程序员来发现这些错误的。当GCC在编译不符合ANSI/ISO C语言标准的源代码时,如果加上了-pedantic选项,那么使用了扩展语法的地方将产生相应的警告信息:

     # gcc -pedantic illcode.c -o illcode illcode.c: In function `main': illcode.c:9: ISO C89 does not support `long long' illcode.c:8: return type of `main' is not `int'

    需要注意的是,-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。或者换句话说,-pedantic选项能够帮助程序员发现一些不符合ANSI/ISO C标准的代码,但不是全部,事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。

    除了-pedantic之外,GCC还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall了,使用它能够使GCC产生尽可能多的警告信息:

     # gcc -Wall illcode.c -o illcode illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var'

    GCC给出的警告信息虽然从严格意义上说不能算作是错误,但却很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持简洁、优美和健壮的特性

    在处理警告方面,另一个常用的编译选项是-Werror,它要求GCC将所有的警告当成错误进行处理,这在使用自动编译工具(如Make等)时非常有用。如果编译时带上-Werror选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。只有当相应的警告信息消除时,才可能将编译过程继续朝前推进。执行情况如下:

     # gcc -Wall -Werror illcode.c -o illcode cc1: warnings being treated as errors illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var'

    对Linux程序员来讲,GCC给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用GCC编译源代码时始终带上-Wall选项,并把它逐渐培养成为一种习惯,这对找出常见的隐式编程错误很有帮助。

    库依赖

    在Linux下开发软件时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助一个或多个函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文件(.h)和库文件(.so或者.a)的集合。虽然Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下,但并不是所有的情况都是这样。正因如此,GCC在编译时必须有自己的办法来查找所需要的头文件和库文件。

    GCC采用搜索目录的办法来查找所需要的文件,-I选项可以向GCC的头文件搜索路径中添加新的目录。例如,如果在/home/xiaowp/include/目录下有编译时所需要的头文件,为了让GCC能够顺利地找到它们,就可以使用-I选项:

     # gcc foo.c -I /home/xiaowp/include -o foo

    同样,如果使用了不在标准位置的库文件,那么可以通过-L选项向GCC的库文件搜索路径中添加新的目录。例如,如果在/home/xiaowp/lib/目录下有链接时所需要的库文件libfoo.so,为了让GCC能够顺利地找到它,可以使用下面的命令:

     # gcc foo.c -L /home/xiaowp/lib -lfoo -o foo

    值得好好解释一下的是-l选项,它指示GCC去连接库文件libfoo.so。Linux下的库文件在命名时有一个约定,那就是应该以lib三个字母开头,由于所有的库文件都遵循了同样的规范,因此在用-l选项指定链接的库文件名时可以省去lib三个字母,也就是说GCC在对-lfoo进行处理时,会自动去链接名为libfoo.so的文件。

    Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),两者的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。默认情况下,GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。例如,如果在/home/xiaowp/lib/目录下有链接时所需要的库文件libfoo.so和libfoo.a,为了让GCC在链接时只用到静态链接库,可以使用下面的命令:

     # gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo
     
    代码优化

    代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,目的是改善程序的执行性能。GCC提供的代码优化功能非常强大,它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不同版本的GCC来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。

    编译时使用选项-O可以告诉GCC同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉GCC除了完成所有-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等级越高,同时也就意味着程序的运行速度越快。许多Linux程序员都喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。

    下面通过具体实例来感受一下GCC的代码优化功能,所用程序如清单3所示。

    清单3:optimize.c

     #include <stdio.h>
     int main(void)
     {
      double counter;
      double result;
      double temp;
      
      for (counter = 0; counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5 - 1) / 4)
      {
        temp = counter / 1979; result = counter;
      }
      printf("Result is %lf\n", result); return 0;
     }

    首先不加任何优化选项进行编译:

     # gcc -Wall optimize.c -o optimize
     
    借助Linux提供的time命令,可以大致统计出该程序在运行时所需要的时间:

     # time ./optimize Result is 400002019.000000 real 0m14.942s user 0m14.940s sys 0m0.000s
     
    接下去使用优化选项来对代码进行优化处理:

     # gcc -Wall -O optimize.c -o optimize
     
    在同样的条件下再次测试一下运行时间:

     # time ./optimize Result is 400002019.000000 real 0m3.256s user 0m3.240s sys 0m0.000s
     
    对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的14秒缩短到了3秒。这个例子是专门针对GCC的优化功能而设计的,因此优化前后程序的执行速度发生了很大的改变。尽管GCC的代码优化功能非常强大,但作为一名优秀的Linux程序员,首先还是要力求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工作,甚至根本用不着优化。

    优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码:

    ◆ 程序开发的时候 优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化。

    ◆ 资源受限的时候 一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的负面影响可能会产生非常严重的后果。

    ◆ 跟踪调试的时候 在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得更佳的性能而进行重组,从而使跟踪和调试变得异常困难。

    调试

    一个功能强大的调试器不仅为程序员提供了跟踪程序执行的手段,而且还可以帮助程序员找到解决问题的方法。对于Linux程序员来讲,GDB(GNU Debugger)通过与GCC的配合使用,为基于Linux的软件开发提供了一个完善的调试环境。

    默认情况下,GCC在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的大小。如果需要在编译时生成调试符号信息,可以使用GCC的-g或者-ggdb选项。GCC在产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g选项后附加数字1、2或3来指定在代码中加入调试信息的多少。默认的级别是2(-g2),此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。级别3(-g3)包含级别2中的所有调试信息,以及源代码中定义的宏。级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段。

    GCC产生的调试符号具有普遍的适应性,可以被许多调试器加以利用,但如果使用的是GDB,那么还可以通过-ggdb选项在生成的二进制代码中包含GDB专用的调试信息。这种做法的优点是可以方便GDB的调试工作,但缺点是可能导致其它调试器(如DBX)无法进行正常的调试。选项-ggdb能够接受的调试级别和-g是完全一样的,它们对输出的调试符号有着相同的影响。

    需要注意的是,使用任何一个调试选项都会使最终生成的二进制文件的大小急剧增加,同时增加程序在执行时的开销,因此调试选项通常仅在软件的开发和调试阶段使用。调试选项对生成代码大小的影响从下面的对比过程中可以看出来:

     # gcc optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 20 08:53 optimize (未加调试选项) # gcc -g optimize.c -o optimize # ls optimize -l -rwxrwxr-x 1 xiaowp xiaowp 15889 Nov 20 08:54 optimize (加入调试选项)
     
    虽然调试选项会增加文件的大小,但事实上Linux中的许多软件在测试版本甚至最终发行版本中仍然使用了调试选项来进行编译,这样做的目的是鼓励用户在发现问题时自己动手解决,是Linux的一个显著特色。

    下面还是通过一个具体的实例说明如何利用调试符号来分析错误,所用程序见清单4所示。

    清单4:crash.c

     #include <stdio.h>
     int main(void)
     {
      int input =0;
      
      printf("Input an integer:");
      scanf("%d", input);
      printf("The integer you input is %d\n", input);
      return 0;
     }
     
    编译并运行上述代码,会产生一个严重的段错误(Segmentation fault)如下:

     # gcc -g crash.c -o crash # ./crash Input an integer:10 Segmentation fault
     
    为了更快速地发现错误所在,可以使用GDB进行跟踪调试,方法如下:

     # gdb crash GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) …… (gdb)
     
    当GDB提示符出现的时候,表明GDB已经做好准备进行调试了,现在可以通过run命令让程序开始在GDB的监控下运行:

     (gdb) run Starting program: /home/xiaowp/thesis/gcc/code/crash Input an integer:10 Program received signal SIGSEGV, Segmentation fault. 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
     
    仔细分析一下GDB给出的输出结果不难看出,程序是由于段错误而导致异常中止的,说明内存操作出了问题,具体发生问题的地方是在调用_IO_vfscanf_internal ( )的时候。为了得到更加有价值的信息,可以使用GDB提供的回溯跟踪命令backtrace,执行结果如下:

     (gdb) backtrace #0 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6 #1 0xbffff0c0 in ?? () #2 0x4008e0ba in scanf () from /lib/libc.so.6 #3 0x08048393 in main () at crash.c:11 #4 0x40042917 in __libc_start_main () from /lib/libc.so.6
     
    跳过输出结果中的前面三行,从输出结果的第四行中不难看出,GDB已经将错误定位到crash.c中的第11行了。现在仔细检查一下:

     (gdb) frame 3 #3 0x08048393 in main () at crash.c:11 11 scanf("%d", input);
     
    使用GDB提供的frame命令可以定位到发生错误的代码段,该命令后面跟着的数值可以在backtrace命令输出结果中的行首找到。现在已经发现错误所在了,应该将

     scanf("%d", input); 改为 scanf("%d", &input);
     
    完成后就可以退出GDB了,命令如下:

     (gdb) quit
     
    GDB的功能远远不止如此,它还可以单步跟踪程序、检查内存变量和设置断点等。

    调试时可能会需要用到编译器产生的中间结果,这时可以使用-save-temps选项,让GCC将预处理代码、汇编代码和目标代码都作为文件保存起来。如果想检查生成的代码是否能够通过手工调整的办法来提高执行性能,在编译过程中生成的中间文件将会很有帮助,具体情况如下:

     # gcc -save-temps foo.c -o foo # ls foo* foo foo.c foo.i foo.s
     
    GCC支持的其它调试选项还包括-p和-pg,它们会将剖析(Profiling)信息加入到最终生成的二进制代码中。剖析信息对于找出程序的性能瓶颈很有帮助,是协助Linux程序员开发出高性能程序的有力工具。在编译时加入-p选项会在生成的代码中加入通用剖析工具(Prof)能够识别的统计信息,而-pg选项则生成只有GNU剖析工具(Gprof)才能识别的统计信息。

    最后提醒一点,虽然GCC允许在优化的同时加入调试符号信息,但优化后的代码对于调试本身而言将是一个很大的挑战。代码在经过优化之后,在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

    上次的培训园地中介绍了GCC的编译过程、警告提示功能、库依赖、代码优化和程序调试六个方面的内容。这期是最后的一部分内容。

    加速

    在将源代码变成可执行文件的过程中,需要经过许多中间步骤,包含预处理、编译、汇编和连接。这些过程实际上是由不同的程序负责完成的。大多数情况下GCC可以为Linux程序员完成所有的后台工作,自动调用相应程序进行处理。

    这样做有一个很明显的缺点,就是GCC在处理每一个源文件时,最终都需要生成好几个临时文件才能完成相应的工作,从而无形中导致处理速度变慢。例如,GCC在处理一个源文件时,可能需要一个临时文件来保存预处理的输出、一个临时文件来保存编译器的输出、一个临时文件来保存汇编器的输出,而读写这些临时文件显然需要耗费一定的时间。当软件项目变得非常庞大的时候,花费在这上面的代价可能会变得很沉重。

    解决的办法是,使用Linux提供的一种更加高效的通信方式—管道。它可以用来同时连接两个程序,其中一个程序的输出将被直接作为另一个程序的输入,这样就可以避免使用临时文件,但编译时却需要消耗更多的内存。

    在编译过程中使用管道是由GCC的-pipe选项决定的。下面的这条命令就是借助GCC的管道功能来提高编译速度的:

     # gcc -pipe foo.c -o foo
     
    在编译小型工程时使用管道,编译时间上的差异可能还不是很明显,但在源代码非常多的大型工程中,差异将变得非常明显。

    文件扩展名

    在使用GCC的过程中,用户对一些常用的扩展名一定要熟悉,并知道其含义。为了方便大家学习使用GCC,在此将这些扩展名罗列如下:

    .c C原始程序;

    .C C++原始程序;

    .cc C++原始程序;

    .cxx C++原始程序;

    .m Objective-C原始程序;

    .i 已经过预处理的C原始程序;

    .ii 已经过预处理之C++原始程序;

    .s 组合语言原始程序;

    .S 组合语言原始程序;

    .h 预处理文件(标头文件);

    .o 目标文件;

    .a 存档文件。

    GCC常用选项

    GCC作为Linux下C/C++重要的编译环境,功能强大,编译选项繁多。为了方便大家日后编译方便,在此将常用的选项及说明罗列出来如下:

    -c 通知GCC取消链接步骤,即编译源码并在最后生成目标文件;

    -Dmacro 定义指定的宏,使它能够通过源码中的#ifdef进行检验;

    -E 不经过编译预处理程序的输出而输送至标准输出;

    -g3 获得有关调试程序的详细信息,它不能与-o选项联合使用;

    -Idirectory 在包含文件搜索路径的起点处添加指定目录;

    -llibrary 提示链接程序在创建最终可执行文件时包含指定的库;

    -O、-O2、-O3 将优化状态打开,该选项不能与-g选项联合使用;

    -S 要求编译程序生成来自源代码的汇编程序输出;

    -v 启动所有警报;

    -Wall 在发生警报时取消编译操作,即将警报看作是错误;

    -Werror 在发生警报时取消编译操作,即把报警当作是错误;

    -w 禁止所有的报警。

    小结

    GCC是在Linux下开发程序时必须掌握的工具之一。本文对GCC做了一个简要的介绍,主要讲述了如何使用GCC编译程序、产生警告信息、调试程序和加快GCC的编译速度。对所有希望早日跨入Linux开发者行列的人来说,GCC就是成为一名优秀的Linux程序员的起跑线。

  • 就是在视频的dct域变换压缩中,经过dct转换后,数据大部分集中在左上角。所以数据的特性在行上就是,一个数后面跟7个0这种情况。所以在做idct逆运算的时候,可以很好的利用这个特性。

    就是:下面用data[0]....data[7]做示范
    short data[64];
    short a;
    int   b,c,d;
    a=data[1];b=((int *)data)[1];c=((int *)data)[2];d=((int *)data)[3];
    if(a|b|c|d){
    正常的idct运算}
    else{
     coef[0]=coef[1]....coef[6]=coef[7]=data[0]/8;
    }

    好像是这样的,效果很不错,实践证明的...

  •  一个函数,如果代码量比较少的话,用 -O3优化开关的话,gcc有可能将这个函数强制内联(inline)即使,你在函数前没有写inline助记符。

    如果是一个手写汇编的函数,那样的话很有可能破坏参数。gcc里有强制不内联的,用法如下

    void foo() __attribute__((noinline));

    但是有的gcc可能会忽略 noinline。

    那么你可以将你实现的这个函数写到调用函数之后,就不会被inline了。这是因为编译器gcc只内联当前函数之前可见(实现代码在前)的函数。

    今天刚学到的。

  •  libmpeg2中idct算法的mmx优化,俺看了一遍,发现并没有用快速idct算法,只不过是利用idct的定义综合了x86 MMX的非常好的simd特点,进行了一些顺序上的调整。

    简要介绍如下:

    目标:是将8点的X矢量变换到8点的Y矢量
         变换矩阵为8×8的C矩阵
    可以写为
         Y=X*C

    C矩阵本来是cos((2n+1)*k*pi/16)的形式

    经过化解得到如下矩阵:
    C4  C1  C2  C3  C4  C5  C6  C7
    C4  C3  C6 -C7 -C4 -C1 -C2 -C5
    C4  C5 -C6 -C1 -C4  C7  C2  C3
    C4  C7 -C2 -C5  C4  C3 -C6 -C1
    C4 -C7 -C2  C5  C4 -C3 -C6  C1
    C4 -C5 -C6  C1 -C4 -C7  C2 -C3
    C4 -C3  C6  C7 -C4  C1 -C2  C5
    C4 -C1  C2 -C3  C4 -C5  C6 -C7
    注:C##n=cos(pi*n/16)

    从上面矩阵可以看出两个特点:
        1。偶数列的上四行和下四行是对称的
        2。奇数列的上四行和下四行是反对称的
    有了这俩规律,下面的IDCT代码就顺理成章了

    a0=x[0]*C4+x[2]*C2+x[4]*C4+x[6]*C6
    a1=x[0]*C4+x[2]*C6-x[4]*C4-x[6]*C2
    a2=x[0]*C4-x[2]*C6-x[4]*C4+x[6]*C2
    a3=x[0]*C4-x[2]*C2+x[4]*C4-x[6]*C6
    b0=x[1]*C1+x[3]*C3+x[5]*C5+x[7]*C7
    b1=x[1]*C3-x[3]*C7-x[5]*C1-x[7]*C5
    b2=x[1]*C5-x[3]*C1+x[5]*C7+x[7]*C3
    b3=x[1]*C7-x[3]*C5+x[5]*C3-x[7]*C1
    y[0]=a0+b0;
    y[7]=a0-b0;
    y[1]=a1+b1;
    y[6]=a1-b1;
    y[2]=a2+b2;
    y[5]=a2-b2;
    y[3]=a3+b3;
    y[4]=a3-b3;

    这么规范的代码,对于MMX来说,正是它的拿手好戏
    这个代码转换成mmx应该不是难事了,其实是现成的,我把它整理一下,是希望读mmx代码的时候更容易理解而已

  •  因为在mpeg的解码过程中,在当前帧是B帧的情况下,如果同时用到了前向和后向预测的话,那么就会要用到前一帧和后一帧对应位置上的值作平均后才做运动补偿。

    对于没有SIMD指令的处理器来说,那么像象素值这样的8比特数据只能一个字节一个字节的做运算了?对于32位的处理器来说,岂不是要浪费掉24(or 23)比特?

    今天在ffmpeg的代码里看到两个宏,就很好的解决了这个问题,一下子可以算4个字节。

    #define avg_nornd(a,b)  (a&b)+(((a^b)&0xFEFEFEFE)>>1)
    #define avg_rnd(a,b)     ( a|b)- (((a^b)&0xFEFEFEFE)>>1)

    第一个是不带rounding的,第二个带rounding的,算法原理乃是这样

    1.不带rouding的那个,后面的异或运算,其实是加法的一种表示,但是1^1的这种情况有进行进位,却没有给进上,a&b的运算正是将丢掉的进位位给补足

    2.带rouding的那个,则是先假设除了0加0之外的运算都产生了进位,明显0和1相加是不应该有进位的,应该去掉,后面的那个减a^b正是做这个工作的

    至于两个rounding的区别,稍微在最后一位(bit24,bit26,bit8,bit0)上体会一下就能明白

  • 2004-10-03

    memcpy优化

    由于多媒体程序中有很多的数据拷贝,所以会用到数据量比较大的memcpy。

    因为我们知道,memcpy的时候我们不用去关心目标地址处数据原来的内容,只要直接覆盖过去就可以了。但是内存管理的特性是,如果你要访问的数据地址不在cache中,那么处理器会将内存中的数据导入cache,然后获得一个可以命中的地址。而在这种memcpy的过程中,不需要原来内存中的数据,可以直接利用内存的allocate特性,也就是直接得到一个命中地址,在cache中分配出一条cache line来,节省了从内存导入数据的时间(这部分时间相当客观,而且还占据总线资源)。

    优化方法就是,在要拷贝的数据大于两条cache line空间的时候,那么我们总能保证有一条cache line是要被完全覆盖的,可以用allocate的方法开出一个cache line 空间。剩余的数据还是用原来的memcpy实现。

  • 太麻烦了。

    新的方法。

    p_tmp=malloc(size+15+sizeof(void *));
    ptr=p_tmp+15+sizeof(void*);
    ptr&=-16;
    *((int *)ptr-1)=(int)p_tmp;

    释放的时候
    free(ptr-1);因为真正分配出来的空间地址在ptr-1这个地方。

    该代码来自于libmpeg2

     

  • 如果希望结构体中的某个数据保持某种对齐的话。那么你必须定义一个这样的全局结构体变量,并且在需要对齐的位置,写上这样的代码 __attribute__ ((alinged(32))).

    如果这个的一个结构体空间不是通过全局变量来定义的话,而是用malloc来分配的堆空间,或者栈空间,显然无法保证,你需要的那种对齐。

    一种折中的方法是分配一个大一点的空间(如果是32对齐的话,分配大于32的空间),然后在从对齐的地方开始使用。最后释放的时候还得使用不对齐的那个地址。

    p1=malloc(strucA+32);
    p2=(p1&-32)+32;
    p2将是我们使用的32对齐的指针。

    释放的时候,用 free(p1)

  • 运动补偿那一部分代码,也不知道到运算量会有多少。

    好像没起到应有的效果。

    put_pixels 和 avg_pixels 的代码要从参考帧读取一个宏块的内容,地址有可能是不连续的,所以原来的代码是一个字节一个字节的取,然后组织成为一个word,但是可以通过判断地址是0/1/2/3在什么字节上对齐,然后做相应的改进,改是改对了,有些微的提高。

  • dct32函数直接编译出来有1000多条指令,4k的cache就没掉了。

    之所以出现这种情况是因为中间访存太多。如果把同时需要用到的同一地址调度到一起,则能减少内存访问的指令。事实是调度后的指令数减少到700多条。

    #define BF(a, b, c)\
    {\
        tmp0 = tab[a] + tab[b];\
        tmp1 = tab[a] - tab[b];\
        tab[a] = tmp0;\
        tab[b] = MULL(tmp1, c);\
    }

    #define BF1(a, b, c, d)\
    {\
        BF(a, b, COS4_0);\
        BF(c, d, -COS4_0);\
        tab[c] += tab[d];\
    }

    #define BF2(a, b, c, d)\
    {\
        BF(a, b, COS4_0);\
        BF(c, d, -COS4_0);\
        tab[c] += tab[d];\
        tab[a] += tab[c];\
        tab[c] += tab[b];\
        tab[b] += tab[d];\
    }

    调度前
    /* DCT32 without 1/sqrt(2) coef zero scaling. */
    static void dct32(int32_t *out, int32_t *tab)
    {
        int tmp0, tmp1;

        /* pass 1 */
        BF(0, 31, COS0_0);
        BF(1, 30, COS0_1);
        BF(2, 29, COS0_2);
        BF(3, 28, COS0_3);
        BF(4, 27, COS0_4);
        BF(5, 26, COS0_5);
        BF(6, 25, COS0_6);
        BF(7, 24, COS0_7);
        BF(8, 23, COS0_8);
        BF(9, 22, COS0_9);
        BF(10, 21, COS0_10);
        BF(11, 20, COS0_11);
        BF(12, 19, COS0_12);
        BF(13, 18, COS0_13);
        BF(14, 17, COS0_14);
        BF(15, 16, COS0_15);

        /* pass 2 */
        BF(0, 15, COS1_0);
        BF(1, 14, COS1_1);
        BF(2, 13, COS1_2);
        BF(3, 12, COS1_3);
        BF(4, 11, COS1_4);
        BF(5, 10, COS1_5);
        BF(6,  9, COS1_6);
        BF(7,  8, COS1_7);
       
        BF(16, 31, -COS1_0);
        BF(17, 30, -COS1_1);
        BF(18, 29, -COS1_2);
        BF(19, 28, -COS1_3);
        BF(20, 27, -COS1_4);
        BF(21, 26, -COS1_5);
        BF(22, 25, -COS1_6);
        BF(23, 24, -COS1_7);
       
        /* pass 3 */
        BF(0, 7, COS2_0);
        BF(1, 6, COS2_1);
        BF(2, 5, COS2_2);
        BF(3, 4, COS2_3);
       
        BF(8, 15, -COS2_0);
        BF(9, 14, -COS2_1);
        BF(10, 13, -COS2_2);
        BF(11, 12, -COS2_3);
       
        BF(16, 23, COS2_0);
        BF(17, 22, COS2_1);
        BF(18, 21, COS2_2);
        BF(19, 20, COS2_3);
       
        BF(24, 31, -COS2_0);
        BF(25, 30, -COS2_1);
        BF(26, 29, -COS2_2);
        BF(27, 28, -COS2_3);

        /* pass 4 */
        BF(0, 3, COS3_0);
        BF(1, 2, COS3_1);
       
        BF(4, 7, -COS3_0);
        BF(5, 6, -COS3_1);
       
        BF(8, 11, COS3_0);
        BF(9, 10, COS3_1);
       
        BF(12, 15, -COS3_0);
        BF(13, 14, -COS3_1);
       
        BF(16, 19, COS3_0);
        BF(17, 18, COS3_1);
       
        BF(20, 23, -COS3_0);
        BF(21, 22, -COS3_1);
       
        BF(24, 27, COS3_0);
        BF(25, 26, COS3_1);
       
        BF(28, 31, -COS3_0);
        BF(29, 30, -COS3_1);
       
        /* pass 5 */
        BF1(0, 1, 2, 3);
        BF2(4, 5, 6, 7);
        BF1(8, 9, 10, 11);
        BF2(12, 13, 14, 15);
        BF1(16, 17, 18, 19);
        BF2(20, 21, 22, 23);
        BF1(24, 25, 26, 27);
        BF2(28, 29, 30, 31);
       
        /* pass 6 */
       
        ADD( 8, 12);
        ADD(12, 10);
        ADD(10, 14);
        ADD(14,  9);
        ADD( 9, 13);
        ADD(13, 11);
        ADD(11, 15);

        out[ 0] = tab[0];
        out[16] = tab[1];
        out[ 8] = tab[2];
        out[24] = tab[3];
        out[ 4] = tab[4];
        out[20] = tab[5];
        out[12] = tab[6];
        out[28] = tab[7];
        out[ 2] = tab[8];
        out[18] = tab[9];
        out[10] = tab[10];
        out[26] = tab[11];
        out[ 6] = tab[12];
        out[22] = tab[13];
        out[14] = tab[14];
        out[30] = tab[15];
       
        ADD(24, 28);
        ADD(28, 26);
        ADD(26, 30);
        ADD(30, 25);
        ADD(25, 29);
        ADD(29, 27);
        ADD(27, 31);

        out[ 1] = tab[16] + tab[24];
        out[17] = tab[17] + tab[25];
        out[ 9] = tab[18] + tab[26];
        out[25] = tab[19] + tab[27];
        out[ 5] = tab[20] + tab[28];
        out[21] = tab[21] + tab[29];
        out[13] = tab[22] + tab[30];
        out[29] = tab[23] + tab[31];
        out[ 3] = tab[24] + tab[20];
        out[19] = tab[25] + tab[21];
        out[11] = tab[26] + tab[22];
        out[27] = tab[27] + tab[23];
        out[ 7] = tab[28] + tab[18];
        out[23] = tab[29] + tab[19];
        out[15] = tab[30] + tab[17];
        out[31] = tab[31];
    }

     

    调度后:

    /* DCT32 without 1/sqrt(2) coef zero scaling. */
    static void dct32(int32_t *out, int32_t *tab)
    {
        int tmp0, tmp1;

        /* pass 1 */
        /* pass 2 */
        BF(0, 31, COS0_0);
        BF(15, 16, COS0_15);
        BF(0, 15, COS1_0);
        BF(16, 31, -COS1_0);

        BF(1, 30, COS0_1);
        BF(14, 17, COS0_14);
        BF(1, 14, COS1_1);
        BF(17, 30, -COS1_1);

        BF(2, 29, COS0_2);
        BF(13, 18, COS0_13);
        BF(2, 13, COS1_2);
        BF(18, 29, -COS1_2);


        BF(3, 28, COS0_3);
        BF(12, 19, COS0_12);
        BF(3, 12, COS1_3);
        BF(19, 28, -COS1_3);


        BF(4, 27, COS0_4);
        BF(11, 20, COS0_11);
        BF(4, 11, COS1_4);
        BF(20, 27, -COS1_4);

        BF(5, 26, COS0_5);
        BF(10, 21, COS0_10);
        BF(5, 10, COS1_5);
        BF(21, 26, -COS1_5);

        BF(6, 25, COS0_6);
        BF(9, 22, COS0_9);
        BF(6,  9, COS1_6);
        BF(22, 25, -COS1_6);

        BF(7, 24, COS0_7);
        BF(8, 23, COS0_8);
        BF(7,  8, COS1_7);
        BF(23, 24, -COS1_7);

       
       
        /* pass 3 */
        /* pass 4 */
        /* pass 5 */
        BF(0, 7, COS2_0);
        BF(1, 6, COS2_1);
        BF(2, 5, COS2_2);
        BF(3, 4, COS2_3);
        BF(0, 3, COS3_0);
        BF(1, 2, COS3_1);
        BF(4, 7, -COS3_0);
        BF(5, 6, -COS3_1);
        BF1(0, 1, 2, 3);
        BF2(4, 5, 6, 7);

        out[ 0] = tab[0];
        out[16] = tab[1];
        out[ 8] = tab[2];
        out[24] = tab[3];
        out[ 4] = tab[4];
        out[20] = tab[5];
        out[12] = tab[6];
        out[28] = tab[7];

       
        BF(8, 15, -COS2_0);
        BF(9, 14, -COS2_1);
        BF(10, 13, -COS2_2);
        BF(11, 12, -COS2_3);
        BF(8, 11, COS3_0);
        BF(9, 10, COS3_1);
        BF(12, 15, -COS3_0);
        BF(13, 14, -COS3_1);
        BF1(8, 9, 10, 11);
        BF2(12, 13, 14, 15);

        ADD( 8, 12);
        ADD(12, 10);
        ADD(10, 14);
        ADD(14,  9);
        ADD( 9, 13);
        ADD(13, 11);
        ADD(11, 15);

        out[ 2] = tab[8];
        out[18] = tab[9];
        out[10] = tab[10];
        out[26] = tab[11];
        out[ 6] = tab[12];
        out[22] = tab[13];
        out[14] = tab[14];
        out[30] = tab[15];

       
        BF(16, 23, COS2_0);
        BF(17, 22, COS2_1);
        BF(18, 21, COS2_2);
        BF(19, 20, COS2_3);
        BF(16, 19, COS3_0);
        BF(17, 18, COS3_1);
        BF(20, 23, -COS3_0);
        BF(21, 22, -COS3_1);
        BF1(16, 17, 18, 19);
        BF2(20, 21, 22, 23);

       
        BF(24, 31, -COS2_0);
        BF(25, 30, -COS2_1);
        BF(26, 29, -COS2_2);
        BF(27, 28, -COS2_3);
        BF(24, 27, COS3_0);
        BF(25, 26, COS3_1);
        BF(28, 31, -COS3_0);
        BF(29, 30, -COS3_1);
        BF1(24, 25, 26, 27);
        BF2(28, 29, 30, 31);
       
       
        /* pass 6 */
       
       
        ADD(24, 28);
        ADD(28, 26);
        ADD(26, 30);
        ADD(30, 25);
        ADD(25, 29);
        ADD(29, 27);
        ADD(27, 31);

        out[ 1] = tab[16] + tab[24];
        out[17] = tab[17] + tab[25];
        out[ 9] = tab[18] + tab[26];
        out[25] = tab[19] + tab[27];
        out[ 5] = tab[20] + tab[28];
        out[21] = tab[21] + tab[29];
        out[13] = tab[22] + tab[30];
        out[29] = tab[23] + tab[31];
        out[ 3] = tab[24] + tab[20];
        out[19] = tab[25] + tab[21];
        out[11] = tab[26] + tab[22];
        out[27] = tab[27] + tab[23];
        out[ 7] = tab[28] + tab[18];
        out[23] = tab[29] + tab[19];
        out[15] = tab[30] + tab[17];
        out[31] = tab[31];
    }

    此外,像这种代码
        BF(16, 31, -COS1_0);
    后面的一个负系数,可以变化成正的,只要将宏中的,(tab[a]-tab[b])×(-C)变成,(tab[b]-tab[a])×C即可,所以征服系数是可以共享的

     

  • #define SUM8(sum, op, w, p) \
    {                                               \
        sum op MULS((w)[0 * 64], p[0 * 64]);\
        sum op MULS((w)[1 * 64], p[1 * 64]);\
        sum op MULS((w)[2 * 64], p[2 * 64]);\
        sum op MULS((w)[3 * 64], p[3 * 64]);\
        sum op MULS((w)[4 * 64], p[4 * 64]);\
        sum op MULS((w)[5 * 64], p[5 * 64]);\
        sum op MULS((w)[6 * 64], p[6 * 64]);\
        sum op MULS((w)[7 * 64], p[7 * 64]);\
    }

    #define SUM8P2(sum1, op1, sum2, op2, w1, w2, p) \
    {                                               \
        int tmp;\
        tmp = p[0 * 64];\
        sum1 op1 MULS((w1)[0 * 64], tmp);\
        sum2 op2 MULS((w2)[0 * 64], tmp);\
        tmp = p[1 * 64];\
        sum1 op1 MULS((w1)[1 * 64], tmp);\
        sum2 op2 MULS((w2)[1 * 64], tmp);\
        tmp = p[2 * 64];\
        sum1 op1 MULS((w1)[2 * 64], tmp);\
        sum2 op2 MULS((w2)[2 * 64], tmp);\
        tmp = p[3 * 64];\
        sum1 op1 MULS((w1)[3 * 64], tmp);\
        sum2 op2 MULS((w2)[3 * 64], tmp);\
        tmp = p[4 * 64];\
        sum1 op1 MULS((w1)[4 * 64], tmp);\
        sum2 op2 MULS((w2)[4 * 64], tmp);\
        tmp = p[5 * 64];\
        sum1 op1 MULS((w1)[5 * 64], tmp);\
        sum2 op2 MULS((w2)[5 * 64], tmp);\
        tmp = p[6 * 64];\
        sum1 op1 MULS((w1)[6 * 64], tmp);\
        sum2 op2 MULS((w2)[6 * 64], tmp);\
        tmp = p[7 * 64];\
        sum1 op1 MULS((w1)[7 * 64], tmp);\
        sum2 op2 MULS((w2)[7 * 64], tmp);\
    }

    把这段代码,给换了一下

    /* 32 sub band synthesis filter. Input: 32 sub band samples, Output:
       32 samples. */
    /* XXX: optimize by avoiding ring buffer usage */
    static void synth_filter(MPADecodeContext *s1,
                             int ch, int16_t *samples, int incr,
                             int32_t sb_samples[SBLIMIT])
    {
        int32_t tmp[32];
        register MPA_INT *synth_buf;
        register const MPA_INT *w, *w2, *p;
        int j, offset, v;
        int16_t *samples2;
    #if FRAC_BITS <= 15
        int sum, sum2;
    #else
        int64_t sum, sum2;
    #endif
       
        dct32(tmp, sb_samples);
       
        offset = s1->synth_buf_offset[ch];
        synth_buf = s1->synth_buf[ch] + offset;

        for(j=0;j<32;j++) {
            v = tmp[j];
    #if FRAC_BITS <= 15
            /* NOTE: can cause a loss in precision if very high amplitude
               sound */
            if (v > 32767)
                v = 32767;
            else if (v < -32768)
                v = -32768;
    #endif
            synth_buf[j] = v;
        }
        /* copy to avoid wrap */
        memcpy(synth_buf + 512, synth_buf, 32 * sizeof(MPA_INT));

        samples2 = samples + 31 * incr;
        w = window;
        w2 = window + 31;

        sum = 0;
        p = synth_buf + 16;
        SUM8(sum, +=, w, p);
        p = synth_buf + 48;
        SUM8(sum, -=, w + 32, p);
        *samples = round_sample(sum);
        samples += incr;
        w++;

        /* we calculate two samples at the same time to avoid one memory
           access per two sample */
        for(j=1;j<16;j++) {
            sum = 0;
            sum2 = 0;
            p = synth_buf + 16 + j;
            SUM8P2(sum, +=, sum2, -=, w, w2, p);
            p = synth_buf + 48 - j;
            SUM8P2(sum, -=, sum2, -=, w + 32, w2 + 32, p);

            *samples = round_sample(sum);
            samples += incr;
            *samples2 = round_sample(sum2);
            samples2 -= incr;
            w++;
            w2--;
        }
       
        p = synth_buf + 32;
        sum = 0;
        SUM8(sum, -=, w + 32, p);
        *samples = round_sample(sum);

        offset = (offset - 32) & 511;
        s1->synth_buf_offset[ch] = offset;
    }

    求sum的那段代码是每隔64跳着取数的,把它改成连着取,可以降低data cache miss的比率