• 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里可以修改

  • 原来一直在看的几个mpeg4解码包,有ffmpeg,xvid和divx的,各有特点。ffmpeg兼容性(

    指h263dec.c)好能解各种格式的,包括divx和msmpegv3的,但是也带来一定的复杂性,对

    于想做优化的人来说,拆解代码的过程比较痛苦。xvid则相对简单了许多,读取码流和核

    心代码部分分离的很清楚,只支持avi格式,1.0.1以前的版本只支持I帧和P帧的解码。从速

    度上来说ffmpeg要略快于xvid,原因是xvid在VLC解码上做的优化和设计相对较少,查

    huffman表的时候都是选取了比较大的表,没有进行分级处理。对于嵌入式应用只有8k,16k

    Cache的处理器,效率相当低。我拿到的divx的代码对VLC做了很好的优化,但是代码分格

    太差,一个函数能够传上10几个参数,不知道是不是真的是它们的商业版本?

    很早就下载下来了rmp4的包,这两天才有时间看它,发现它集合了divx和xvid的优点,代

    码可读性好,速度比xvid要快,用纯C代码,在某款RISC处理器上播放QVGA(320x240)码率

    500k的码流2000帧时,xvid耗时42ms,rmp4耗时37ms。

    于是,又重新到网上search了一下,可以在这个地方下载到

    http://www.sigmadesigns.com/products/RMP4_download.htm

    下载下来的代码是没有Makefile的,只有VC的工程,但是俺不会用VC啊:( .又读了读它的

    接口,大致明白了怎么回事,写了个调用函数,在linux下编译通过。


    代码封装如下,有三个关键函数,我喜欢将之拿出来分别调用。
    int RMP4_decoder(void * handle, int opt, void * param1)
    {
     switch (opt)
     {
     case MP4V_DEC_DECODE :
       return RMP4_dec_processing((Decoder *) handle, (MP4V_DEC_FRAME

    *) param1);

     case MP4V_DEC_CREATE :
       return RMP4_dec_open((MP4V_DEC_PARAM *) param1);
     
     case MP4V_DEC_DESTROY :
       return RMP4_dec_close((Decoder *) handle);

     default:
       return MP4V_ERR_FAIL;
        }
    }

    RMP4_dec_open:打开一个解码器
    传入参数 MP4V_DEC_PARAM mp4_param;
    主要是设置高和宽,就可以了
    mp4_param.width=width;
    mp4_param.height=height;
    RMP4_dec_open(&mp4_param);该函数会给mp4_param.handle返回一个指针,可以做为

    RMP4_dec_processing的第一个参数和RMP4_dec_close的参数,这个RMP4_dec_close当然是

    在退出程序的时候调用,不多解释。

    RMP4_dec_processing:这个函数是解码一帧图像
    除了Decoder *handle参数外的,第二个参数是MP4V_DEC_FRAME mp4_frame
    主要配置输入的码流地址,码流长度,输出图像地址,输出图像格式,还有输出图像的步

    长(stride,有的地方叫pitch)
    mp4_frame.bitstream=bitstream;
    mp4_frame.length=mp4_frame.length;
    mp4_frame.image=output_buf;
    mp4_frame.stride=stride;
    mp4_frame.colorspace=MP4V_CSP_I420;//这个颜色空间有很多宏,按需要选一个
    然后,调用
    RMP4_dec_processing(mp4_param.handle,&mp4_frame);
    当然,这个函数会调用很多次了,因为每次只能解码一帧:),所以必须自己来维护

    mp4_frame的位流指针和位流长度。要注意的是,每次调用完RMP4_dec_processing函数之

    后,mp4_frame的mp4_frame.length会被修改为解码一帧后用掉的字节数,所以下一帧解码

    应该从位流的这个字节数后接着往下解码。

    好困,没有其它代码了:(,俺家没有编译器。

  • 讨论讨论位流(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;
    }

    结束,呵呵。很简单。

  • lcd显示器是多少位色的啊?俺不知道,人家说都用16位色(rgb565),那俺就学学这个怎么转换来着。如果用公式的话,又是乘法,又是饱和,又是移位,又是或的,会把人累死的:(.不过SDL里有个查表的算法不错,分析一下。

    下面是一个表的初始化。

    swdata->pixels = (Uint8 *) malloc(width*height*2);
     swdata->colortab = (int *)malloc(4*256*sizeof(int));
     Cr_r_tab = &swdata->colortab[0*256];
     Cr_g_tab = &swdata->colortab[1*256];
     Cb_g_tab = &swdata->colortab[2*256];
     Cb_b_tab = &swdata->colortab[3*256];
     swdata->rgb_2_pix = (Uint32 *)malloc(3*768*sizeof(Uint32));
     r_2_pix_alloc = &swdata->rgb_2_pix[0*768];
     g_2_pix_alloc = &swdata->rgb_2_pix[1*768];
     b_2_pix_alloc = &swdata->rgb_2_pix[2*768];

     for (i=0; i<256; i++) {
      /* 这里的一个表是为乘法做的一个表*/
      CB = CR = (i-128);
      Cr_r_tab[i] = (int) ( (0.419/0.299) * CR);
      Cr_g_tab[i] = (int) (-(0.299/0.419) * CR);
      Cb_g_tab[i] = (int) (-(0.114/0.331) * CB);
      Cb_b_tab[i] = (int) ( (0.587/0.331) * CB);
     }

     Rmask = display->format->Rmask;
     Gmask = display->format->Gmask;
     Bmask = display->format->Bmask;
     for ( i=0; i<256; ++i ) {

    /*这个表是为饱和做的,并且已经做好了移位,到查表的时候只要将这几个rgb的值或起来即可,r被饱和到0~0xf800之间(高5位有值),g被饱和到0~0x07e0之间(中间6位有值),b被饱和到0~0x001f之间(低5位有值)*/
      r_2_pix_alloc[i+256] = i >> (8 - number_of_bits_set(Rmask));
      r_2_pix_alloc[i+256] <<= free_bits_at_bottom(Rmask);
      g_2_pix_alloc[i+256] = i >> (8 - number_of_bits_set(Gmask));
      g_2_pix_alloc[i+256] <<= free_bits_at_bottom(Gmask);
      b_2_pix_alloc[i+256] = i >> (8 - number_of_bits_set(Bmask));
      b_2_pix_alloc[i+256] <<= free_bits_at_bottom(Bmask);
     }

    下面是使用部分代码,也就是查表过程

    /*前面那个768的系数其实是算在第二个表的偏移上的,因为y部分数据要使用四次,所以提前到cb,cr里面*/
    cr_r   = 0*768+256 + colortab[ *cr + 0*256 ];
    crb_g  = 1*768+256 + colortab[ *cr + 1*256 ]
                       + colortab[ *cb + 2*256 ];
    cb_b   = 2*768+256 + colortab[ *cb + 3*256 ];
                ++cr; ++cb;

    L = *lum++;/*将3个值或起来,构成rgb565,没什么好说的。*/
    *row1++ = (rgb_2_pix[ L + cr_r ] |
              rgb_2_pix[ L + crb_g ] |
               rgb_2_pix[ L + cb_b ]);

     

  • 传统的视频解码程序一般都是将宏块解码的输出结果,送到一个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;
    }

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

  • 2004-10-09

    AVI文件格式

    来源:http://blog.csdn.net/happydeer

    小知识:AVI文件格式----摘自《DirectShow实务精选》 作者:陆其明

     

    AVI(Audio Video Interleaved的缩写)是一种RIFF(Resource Interchange File Format的缩写)文件格式,多用于音视频捕捉、编辑、回放等应用程序中。通常情况下,一个AVI文件可以包含多个不同类型的媒体流(典型的情况下有一个音频流和一个视频流),不过含有单一音频流或单一视频流的AVI文件也是合法的。AVI可以算是Windows操作系统上最基本的、也是最常用的一种媒体文件格式。


    先来介绍RIFF文件格式。RIFF文件使用四字符码FOURCC(four-character code)来表征数据类型,比如‘RIFF’、‘AVI ’、‘LIST’等。注意,Windows操作系统使用的字节顺序是little-endian,因此一个四字符码‘abcd’实际的DWORD值应为0x64636261。另外,四字符码中像‘AVI ’一样含有空格也是合法的。


    RIFF文件首先含有一个如图3.31的文件头结构。

     

    图3.31 RIFF文件结构

     

    最开始的4个字节是一个四字符码‘RIFF’,表示这是一个RIFF文件;紧跟着后面用4个字节表示此RIFF文件的大小;然后又是一个四字符码说明文件的具体类型(比如AVI、WAVE等);最后就是实际的数据。注意文件大小值的计算方法为:实际数据长度 + 4(文件类型域的大小);也就是说,文件大小的值不包括‘RIFF’域和“文件大小”域本身的大小。


    RIFF文件的实际数据中,通常还使用了列表(List)和块(Chunk)的形式来组织。列表可以嵌套子列表和块。其中,列表的结构为:‘LIST’ listSize listType listData ——‘LIST’是一个四字符码,表示这是一个列表;listSize占用4字节,记录了整个列表的大小;listType也是一个四字符码,表示本列表的具体类型;listData就是实际的列表数据。注意listSize值的计算方法为:实际的列表数据长度 + 4(listType域的大小);也就是说listSize值不包括‘LIST’域和listSize域本身的大小。再来看块的结构:ckID ckSize ckData ——ckID是一个表示块类型的四字符码;ckSize占用4字节,记录了整个块的大小;ckData为实际的块数据。注意ckSize值指的是实际的块数据长度,而不包括ckID域和ckSize域本身的大小。(注意:在下面的内容中,将以LIST ( listType ( listData ) )的形式来表示一个列表,以ckID ( ckData )的形式来表示一个块,如[ optional element ]中括号中的元素表示为可选项。)


    接下来介绍AVI文件格式。AVI文件类型用一个四字符码‘AVI ’来表示。整个AVI文件的结构为:一个RIFF头 + 两个列表(一个用于描述媒体流格式、一个用于保存媒体流数据) + 一个可选的索引块。AVI文件的展开结构大致如下:

     

    RIFF (‘AVI ’
          LIST (‘hdrl’
                ‘avih’(主AVI信息头数据)
                LIST (‘strl’
                      ‘strh’ (流的头信息数据)
                      ‘strf’ (流的格式信息数据)
                      [‘strd’ (可选的额外的头信息数据) ]
                      [‘strn’ (可选的流的名字) ]
                      ...
                     )
                 ...
               )
          LIST (‘movi’
                { SubChunk | LIST (‘rec ’
                                  SubChunk1
                                  SubChunk2
                                  ...
                                 )
                   ...
                }
                ...
               )
          [‘idx1’ (可选的AVI索引块数据) ]
         )

     

    首先,RIFF (‘AVI ’…)表征了AVI文件类型。然后就是AVI文件必需的第一个列表——‘hdrl’列表,用于描述AVI文件中各个流的格式信息(AVI文件中的每一路媒体数据都称为一个流)。‘hdrl’列表嵌套了一系列块和子列表——首先是一个‘avih’块,用于记录AVI文件的全局信息,比如流的数量、视频图像的宽和高等,可以使用一个AVIMAINHEADER数据结构来操作:

     

    typedef struct _avimainheader {
        FOURCC fcc;   // 必须为‘avih’
        DWORD  cb;    // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
        DWORD  dwMicroSecPerFrame;   // 视频帧间隔时间(以毫秒为单位)
        DWORD  dwMaxBytesPerSec;     // 这个AVI文件的最大数据率
        DWORD  dwPaddingGranularity; // 数据填充的粒度
        DWORD  dwFlags;         // AVI文件的全局标记,比如是否含有索引块等
        DWORD  dwTotalFrames;   // 总帧数
        DWORD  dwInitialFrames; // 为交互格式指定初始帧数(非交互格式应该指定为0)
        DWORD  dwStreams;       // 本文件包含的流的个数
        DWORD  dwSuggestedBufferSize; // 建议读取本文件的缓存大小(应能容纳最大的块)
        DWORD  dwWidth;         // 视频图像的宽(以像素为单位)
        DWORD  dwHeight;        // 视频图像的高(以像素为单位)
        DWORD  dwReserved[4];   // 保留
    } AVIMAINHEADER;

     

    然后,就是一个或多个‘strl’子列表。(文件中有多少个流,这里就对应有多少个‘strl’子列表。)每个‘strl’子列表至少包含一个‘strh’块和一个‘strf’块,而‘strd’块(保存编解码器需要的一些配置信息)和‘strn’块(保存流的名字)是可选的。首先是‘strh’块,用于说明这个流的头信息,可以使用一个AVISTREAMHEADER数据结构来操作:

     

    typedef struct _avistreamheader {
         FOURCC fcc;  // 必须为‘strh’
         DWORD  cb;   // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
    FOURCC fccType;    // 流的类型:‘auds’(音频流)、‘vids’(视频流)、
                       //‘mids’(MIDI流)、‘txts’(文字流)
         FOURCC fccHandler; // 指定流的处理者,对于音视频来说就是解码器
         DWORD  dwFlags;    // 标记:是否允许这个流输出?调色板是否变化?
         WORD   wPriority;  // 流的优先级(当有多个相同类型的流时优先级最高的为默认流)
         WORD   wLanguage;
         DWORD  dwInitialFrames; // 为交互格式指定初始帧数
         DWORD  dwScale;   // 这个流使用的时间尺度
         DWORD  dwRate;
         DWORD  dwStart;   // 流的开始时间
         DWORD  dwLength;  // 流的长度(单位与dwScale和dwRate的定义有关)
         DWORD  dwSuggestedBufferSize; // 读取这个流数据建议使用的缓存大小
         DWORD  dwQuality;    // 流数据的质量指标(0 ~ 10,000)
         DWORD  dwSampleSize; // Sample的大小
         struct {
             short int left;
             short int top;
             short int right;
             short int bottom;
    }  rcFrame;  // 指定这个流(视频流或文字流)在视频主窗口中的显示位置
                 // 视频主窗口由AVIMAINHEADER结构中的dwWidth和dwHeight决定
    } AVISTREAMHEADER;

     

    然后是‘strf’块,用于说明流的具体格式。如果是视频流,则使用一个BITMAPINFO数据结构来描述;如果是音频流,则使用一个WAVEFORMATEX数据结构来描述。


    当AVI文件中的所有流都使用一个‘strl’子列表说明了以后(注意:‘strl’子列表出现的顺序与媒体流的编号是对应的,比如第一个‘strl’子列表说明的是第一个流(Stream 0),第二个‘strl’子列表说明的是第二个流(Stream 1),以此类推),‘hdrl’列表的任务也就完成了,随后跟着的就是AVI文件必需的第二个列表——‘movi’列表,用于保存真正的媒体流数据(视频图像帧数据或音频采样数据等)。那么,怎么来组织这些数据呢?可以将数据块直接嵌在‘movi’列表里面,也可以将几个数据块分组成一个‘rec ’列表后再编排进‘movi’列表。(注意:在读取AVI文件内容时,建议将一个‘rec ’列表中的所有数据块一次性读出。)但是,当AVI文件中包含有多个流的时候,数据块与数据块之间如何来区别呢?于是数据块使用了一个四字符码来表征它的类型,这个四字符码由2个字节的类型码和2个字节的流编号组成。标准的类型码定义如下:‘db’(非压缩视频帧)、‘dc’(压缩视频帧)、‘pc’(改用新的调色板)、‘wb’(音缩视频)。比如第一个流(Stream 0)是音频,则表征音频数据块的四字符码为‘00wb’;第二个流(Stream 1)是视频,则表征视频数据块的四字符码为‘00db’或‘00dc’。对于视频数据来说,在AVI数据序列中间还可以定义一个新的调色板,每个改变的调色板数据块用‘xxpc’来表征,新的调色板使用一个数据结构AVIPALCHANGE来定义。(注意:如果一个流的调色办中途可能改变,则应在这个流格式的描述中,也就是AVISTREAMHEADER结构的dwFlags中包含一个AVISF_VIDEO_PALCHANGES标记。)另外,文字流数据块可以使用随意的类型码表征。


    最后,紧跟在‘hdrl’列表和‘movi’列表之后的,就是AVI文件可选的索引块。这个索引块为AVI文件中每一个媒体数据块进行索引,并且记录它们在文件中的偏移(可能相对于‘movi’列表,也可能相对于AVI文件开头)。索引块使用一个四字符码‘idx1’来表征,索引信息使用一个数据结构来AVIOLDINDEX定义。

     

    typedef struct _avioldindex {
       FOURCC  fcc;  // 必须为‘idx1’
       DWORD   cb;   // 本数据结构的大小,不包括最初的8个字节(fcc和cb两个域)
       struct _avioldindex_entry {
          DWORD   dwChunkId;   // 表征本数据块的四字符码
          DWORD   dwFlags;     // 说明本数据块是不是关键帧、是不是‘rec ’列表等信息
          DWORD   dwOffset;    // 本数据块在文件中的偏移量
          DWORD   dwSize;      // 本数据块的大小
      } aIndex[]; // 这是一个数组!为每个媒体数据块都定义一个索引信息
    } AVIOLDINDEX;

     

    注意:如果一个AVI文件包含有索引块,则应在主AVI信息头的描述中,也就是AVIMAINHEADER结构的dwFlags中包含一个AVIF_HASINDEX标记。


    还有一种特殊的数据块,用一个四字符码‘JUNK’来表征,它用于内部数据的队齐(填充),应用程序应该忽略这些数据块的实际意义。

  •  一个函数,如果代码量比较少的话,用 -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)