`
xitonga
  • 浏览: 587276 次
文章分类
社区版块
存档分类
最新评论

libmad使用一步步进阶

 
阅读更多

原文地址:http://my.oschina.net/mjRao/blog/57874

libmad是一个开源mp3解码库,其对mp3解码算法做了很多优化,性能较好,很多播放器如mplayer、xmms等都是使用这个开源库进行解码的;如果要设计mp3播放器而又不想研究mp3解码算法的话,libmad是个不错的选择,可是问题来了:

  • libmad配套的相关文档太少,可以说几乎没有,只有一个示例程序minimad.c,但没有一定经验的人根本不知道怎么编译这个minimad.c,就算是编译了也不知道怎么运行、怎么播放mp3;
  • 网上讲libmad和minimad.c的文章很多,但能解释清楚的少之又少,大家都是抄来抄去,要么是不懂装懂,要么是懂了一点就自以为精通了,这样一来的结果是:在网上搜两天也弄不明白libmad究竟怎么使用。

所幸手里有Altera公司的一个工程,借助对该工程的分析、minimad.c中少的可怜的注释和网上搜索的Linux音频方面的相关知识,反复思考编码,总算把libmad库用起来了,现记录一下其使用方法,在帮助别人的同时也方便自己回头查询。 在开始之前,最好先把mp3文件格式和Linux音频编程方面的知识先学习一下,不然后面有的东西可能听不懂,还有就是一定要熟悉Linux系统,后面的代码都是在linux系统中用gcc编译的,在Windows下不能用的。 首先看下面几个问题,这也是我一开始最迷惑的,弄明白这几个问题了,也就对libmad库的使用相当熟悉了:

  1. minimad.c怎么编译?编译后怎么运行?运行时的输入输出分别是什么,或者说运行时什么效果?
  2. 怎样播放minimad输出的数据?或者说怎么播放解码后的数据?
  3. minimad运行时,mp3数据来源是标准输入,能不能改为从文件中读入数据?该怎么改?
  4. minimad运行时首先要将整个mp3文件读入内存,能不能改成边解码边读入的形式,比如每次读入16K,解码完再读入16K,而又不影响播放的连贯性,这样可以节省内存开销,方便在嵌入式系统中使用;
  5. 怎样用libmad做一个简单的mp3播放器?

一个一个来讲吧。

  1. minimad.c怎么编译?编译后怎么运行?运行时的输入输出分别是什么,或者说运行时什么效果? 在Linux下(我前面说了,本文所有的工作都是在Linux进行)先安装libmad,说白了就是把libmad库导入C标准库,安装方法见libmad-0.15.1b中的README和INSTALL文件。 安装libmad后,新建一个文件夹,将libmad-0.15.1b中的minimad.c和mad.h复制过来,用gcc编译minimad.c,编译命令为(假设要生成的可执行程序为minimad):gcc -o minimad minimad.c -lmad minimad程序从标准输入读入mp3文件,然后将解码后的音频数据送到标准输出,我们可以用重定向的方式从文件中读入数据并将结果写至文件,命令如下:./minimad tmp.pcm
  2. 怎样播放minimad输出的数据?或者说怎么播放解码后的数据? 假设你有Linux音频编程方面的基础的话,这个应该不成问题,如果没有也没关系,在Linux的设计理念中,一切皆是文件,音频设备也是文件,只需要打 开/dev/dsp(音频设备)这个文件,然后将解码后的数据写入这个文件即可实现播放,新建pcmplay.c文件,拷入如下代码:
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        int  id, fd, i;
        char buf[1024];
        int  rate;      /*simple rate 44.1KHz*/
        int  format;    /*quatize args*/
        int  channels;  /*sound channel*/
    
        if(argc != 2)
        {
            fprintf(stderr, "usage : %s \n", argv[0]);
            exit(-1);
        }
    
        if((fd = open(argv[1], O_RDONLY)) < 0)
        {
            fprintf(stderr, "Can't open sound file!\n");
            exit(-2);
        }
    
        if((id = open("/dev/dsp", O_WRONLY)) 0)
        {
            write(id, buf, i);
            //printf("i=%d\n", i);
        }
    
        close(fd);
        close(id);
    
        exit(0);
    
    }
    编译pcmplay文件,然后就可以用生成的可执行程序播放第一步中声称的tmp.pcm文件,命令如下:
    gcc -o pcmplay pcmplay.c
    ./minimad tmp.pcm
    ./pcmplay  tmp.pcm
    播放时可能会变调,这是因为上面这段代码中将音频设备采样率固定设置为44.1k,而mp3文件不一定是这个采样率,解决方法后面会讲。
  3. minimad运行时,mp3数据来源是标准输入,能不能改为从文件中读入数据?该怎么改? 当然可以改,而且改起来相当的简单,如果不知道怎么改只能说明自己没仔细看minimad.c,你可能不知道struct stat是什么,也不清楚mmap()函数有什么用,但这些都可以在网上查到的,查了之后稍加分析就会发现原来就是把一片数据放入一块内存并得到它的长度 而已,那改成文件读入的方式也很容易,用fopen打开文件,计算一下文件的长度,然后用fread把数据全部读出来即可,这里就不贴代码了。
  4. minimad运行时首先要将整个mp3文件读入内存,能不能改成边解码边读入的形式,比如每次读入16K,解码完再读入16K,而又不影响播放的连贯性,这样可以节省内存开销,方便在嵌入式系统中使用; 修改input()函数,在调用libmad中的mad_decoder_run()实现播放时,首先检查待解码缓存区中有没有数据,有则 解码,没有则调用input()函数一次以填充数据(填充多少可以自己指定),然后开始解码,解码后的数据交给output()函数处理,解码过程中,一 旦待解码缓存区中的解码数据不够则再次调用input()函数…… 在这里还要提一下struct buffer这个结构体,这个结构体是在input、output和decoder之间传送数据的载体,可以自行定义,比如我的数据来源是文件,待解码数 据缓存区大小为4K,要传递的私有数据包括文件指针、当前的位置、数据缓冲区、缓冲区的实际大小、文件的总大小等,则我这里定义如下:
    struct buffer {
      FILE  *fp;                    /*file pointer*/
      unsigned int  flen;           /*file length*/
      unsigned int  fpos;           /*current position*/
      unsigned char fbuf[BUFSIZE];  /*buffer*/
      unsigned int  fbsize;         /*indeed size of buffer*/
    };
    typedef struct buffer mp3_file;
    修改input()函数为如下形式,则每次调用填充BUFSIZE字节的数据:
    static
    enum mad_flow input(void *data,
    		    struct mad_stream *stream)
    {
      mp3_file *mp3fp;
      int      ret_code;
      int      unproc_data_size;    /*the unprocessed data's size*/
      int      copy_size;
    
      mp3fp = (mp3_file *)data;
      if(mp3fp->fpos flen)
      {
          unproc_data_size = stream->bufend - stream->next_frame;
          memcpy(mp3fp->fbuf, mp3fp->fbuf+mp3fp->fbsize-unproc_data_size, unproc_data_size);
          copy_size = BUFSIZE - unproc_data_size;
          if(mp3fp->fpos + copy_size > mp3fp->flen)
          {
              copy_size = mp3fp->flen - mp3fp->fpos;
          }
          fread(mp3fp->fbuf+unproc_data_size, 1, copy_size, mp3fp->fp);
          mp3fp->fbsize = unproc_data_size + copy_size;
          mp3fp->fpos  += copy_size;
          
          /*Hand off the buffer to the mp3 input stream*/
          mad_stream_buffer(stream, mp3fp->fbuf, mp3fp->fbsize);
          ret_code = MAD_FLOW_CONTINUE;
      }
      else
      {
          ret_code = MAD_FLOW_STOP;
      }
    
      return ret_code;
    
    }
    注意:在上面的代码中涉及到了断桢问题,即一桢跨了两个BUFSIZE,这时候应该将缓冲区中的剩余数据先移至缓冲区头部,然后再从文件中读出数据填充缓冲区。
  5. 怎样用libmad设计一个简单的mp3播放器? 修改output()函数。 我在上面说过了,解码后的数据通过output()函数进行处理,在minimad.c中output()函数直接将解码后的数据送到标准输出,其实只要将这里修改为送到音频设备就可以实现播放了。 还有一点需要说明的是:mp3文件的采样率不是固定不变的,解码后的数据中包括采样率,在播放过程中,一旦采样率发生变化,要重新设置一下音频设备。 新建一个mp3player.c文件,然后将下面的代码复制进去,编译生成mp3player,这就是一个简单的mp3播放器了,可以用./mp3player 1.mp3命令来播放1.mp3文件。
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "mad.h"
    
    #define BUFSIZE 8192
    
    /*
     * This is a private message structure. A generic pointer to this structure
     * is passed to each of the callback functions. Put here any data you need
     * to access from within the callbacks.
     */
    struct buffer {
      FILE  *fp;                    /*file pointer*/
      unsigned int  flen;           /*file length*/
      unsigned int  fpos;           /*current position*/
      unsigned char fbuf[BUFSIZE];  /*buffer*/
      unsigned int  fbsize;         /*indeed size of buffer*/
    };
    typedef struct buffer mp3_file;
    
    int soundfd;                 /*soundcard file*/
    unsigned int prerate = 0;    /*the pre simple rate*/
    
    int writedsp(int c)
    {
        return write(soundfd, (char *)&c, 1);
    }
    
    void set_dsp()
    {
        int format = AFMT_S16_LE;
        int channels = 2;
    
        soundfd = open("/dev/dsp", O_WRONLY);
        ioctl(soundfd, SNDCTL_DSP_SETFMT, &format);
        ioctl(soundfd, SNDCTL_DSP_CHANNELS, &channels);
    }
    
    /*
     * This is perhaps the simplest example use of the MAD high-level API.
     * Standard input is mapped into memory via mmap(), then the high-level API
     * is invoked with three callbacks: input, output, and error. The output
     * callback converts MAD's high-resolution PCM samples to 16 bits, then
     * writes them to standard output in little-endian, stereo-interleaved
     * format.
     */
    
    static int decode(mp3_file *mp3fp);
    
    int main(int argc, char *argv[])
    {
      long flen, fsta, fend;
      int  dlen;
      mp3_file *mp3fp;
    
      if (argc != 2)
        return 1;
    
      mp3fp = (mp3_file *)malloc(sizeof(mp3_file));
      if((mp3fp->fp = fopen(argv[1], "r")) == NULL)
      {
          printf("can't open source file.\n");
          return 2;
      }
      fsta = ftell(mp3fp->fp);
      fseek(mp3fp->fp, 0, SEEK_END);
      fend = ftell(mp3fp->fp);
      flen = fend - fsta;
      if(flen fp, 0, SEEK_SET);
      fread(mp3fp->fbuf, 1, BUFSIZE, mp3fp->fp);
      mp3fp->fbsize = BUFSIZE;
      mp3fp->fpos   = BUFSIZE;
      mp3fp->flen   = flen;
    
      set_dsp();
    
      decode(mp3fp);
    
      close(soundfd);
      fclose(mp3fp->fp);
    
      return 0;
    }
    
    /*
     * This is the input callback. The purpose of this callback is to (re)fill
     * the stream buffer which is to be decoded. In this example, an entire file
     * has been mapped into memory, so we just call mad_stream_buffer() with the
     * address and length of the mapping. When this callback is called a second
     * time, we are finished decoding.
     */
    
    static
    enum mad_flow input(void *data,
    		    struct mad_stream *stream)
    {
      mp3_file *mp3fp;
      int      ret_code;
      int      unproc_data_size;    /*the unprocessed data's size*/
      int      copy_size;
    
      mp3fp = (mp3_file *)data;
      if(mp3fp->fpos flen)
      {
          unproc_data_size = stream->bufend - stream->next_frame;
          memcpy(mp3fp->fbuf, mp3fp->fbuf+mp3fp->fbsize-unproc_data_size, unproc_data_size);
          copy_size = BUFSIZE - unproc_data_size;
          if(mp3fp->fpos + copy_size > mp3fp->flen)
          {
              copy_size = mp3fp->flen - mp3fp->fpos;
          }
          fread(mp3fp->fbuf+unproc_data_size, 1, copy_size, mp3fp->fp);
          mp3fp->fbsize = unproc_data_size + copy_size;
          mp3fp->fpos  += copy_size;
          
          /*Hand off the buffer to the mp3 input stream*/
          mad_stream_buffer(stream, mp3fp->fbuf, mp3fp->fbsize);
          ret_code = MAD_FLOW_CONTINUE;
      }
      else
      {
          ret_code = MAD_FLOW_STOP;
      }
    
      return ret_code;
    
    }
    
    /*
     * The following utility routine performs simple rounding, clipping, and
     * scaling of MAD's high-resolution samples down to 16 bits. It does not
     * perform any dithering or noise shaping, which would be recommended to
     * obtain any exceptional audio quality. It is therefore not recommended to
     * use this routine if high-quality output is desired.
     */
    
    static inline
    signed int scale(mad_fixed_t sample)
    {
      /* round */
      sample += (1L <= MAD_F_ONE)
        sample = MAD_F_ONE - 1;
      else if (sample > (MAD_F_FRACBITS + 1 - 16);
    }
    
    /*
     * This is the output callback function. It is called after each frame of
     * MPEG audio data has been completely decoded. The purpose of this callback
     * is to output (or play) the decoded PCM audio.
     */
    
    static
    enum mad_flow output(void *data,
    		     struct mad_header const *header,
    		     struct mad_pcm *pcm)
    {
      unsigned int nchannels, nsamples;
      unsigned int rate;
      mad_fixed_t const *left_ch, *right_ch;
    
      /* pcm->samplerate contains the sampling frequency */
    
      rate= pcm->samplerate;
      nchannels = pcm->channels;
      nsamples  = pcm->length;
      left_ch   = pcm->samples[0];
      right_ch  = pcm->samples[1];
    
      /* update the sample rate of dsp*/
      if(rate != prerate)
      {
          ioctl(soundfd, SNDCTL_DSP_SPEED, &rate);
          prerate = rate;
      }
    
      while (nsamples--) {
        signed int sample;
    
        /* output sample(s) in 16-bit signed little-endian PCM */
    
        sample = scale(*left_ch++);
        writedsp((sample >> 0) & 0xff);
        writedsp((sample >> 8) & 0xff);
    
        if (nchannels == 2) {
          sample = scale(*right_ch++);
          writedsp((sample >> 0) & 0xff);
          writedsp((sample >> 8) & 0xff);
        }
      }
    
      return MAD_FLOW_CONTINUE;
    }
    
    /*
     * This is the error callback function. It is called whenever a decoding
     * error occurs. The error is indicated by stream->error; the list of
     * possible MAD_ERROR_* errors can be found in the mad.h (or stream.h)
     * header file.
     */
    
    static enum mad_flow error(void *data,
    		    struct mad_stream *stream,
    		    struct mad_frame *frame)
    {
      mp3_file *mp3fp = data;
    
      fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n",
    	  stream->error, mad_stream_errorstr(stream),
    	  stream->this_frame - mp3fp->fbuf);
    
      /* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */
    
      return MAD_FLOW_CONTINUE;
    }
    
    /*
     * This is the function called by main() above to perform all the decoding.
     * It instantiates a decoder object and configures it with the input,
     * output, and error callback functions above. A single call to
     * mad_decoder_run() continues until a callback function returns
     * MAD_FLOW_STOP (to stop decoding) or MAD_FLOW_BREAK (to stop decoding and
     * signal an error).
     */
    
    static int decode(mp3_file *mp3fp)
    {
      struct mad_decoder decoder;
      int result;
    
      /* configure input, output, and error functions */
      mad_decoder_init(&decoder, mp3fp,
    		   input, 0 /* header */, 0 /* filter */, output,
    		   error, 0 /* message */);
    
      /* start decoding */
      result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
    
      /* release the decoder */
      mad_decoder_finish(&decoder);
    
      return result;
    }
分享到:
评论

相关推荐

    madlld.c libmad使用参考

    madlld.c文件,libmad使用的参考文件,压缩文件中包含了libmad的详细使用方法,以及移植方法

    Libmad.rar_libmad_libmad mp3

    用libmad库来播放mp3音乐,内有音乐播放代码和libmad库的使用说明书。

    libmad.aMP3库请用rar解压该包成4个源文件包.rar

    libmad是一个开源的高精度 MPEG 音频解码库,...使用 libmad 提供的一系列 API,就可以非常简单地实现 MP3 数据解码工作。在 libmad 的源代码文件目录下的 mad.h 文件中,可以看到绝大部分该库的数据结构和 API 等。

    libmad_MP3解码_

    libmad 链接库

    libmad-0.15.1b.zip_full_libm_libmad_libmad-0.15_libmad源代码

    Full Mp3 decoder (GPL) with ARM sources

    libmad+libao解码播放音乐

    改实例实现了在mt7628平台编译并使用alsa播放mp3音频,并解决在mips平台编译libmad产生的编译问题

    windows mobile下用libmad播放MP3

    经过我一个多星期的仔细研究,总于成功在wm平台上用libmad播放mp3. 网上的大侠都说方法,没一个具体例子。我愿意与大家共同分享我的经验,期望认识更多朋友。

    mp3-libmad

    著名的libmad-mp3解码库,欢迎大家使用下载,好东西呀。-Famous libmad-mp3 decoder library, welcome everyone to use the download, you good things.

    libmad-0.15.0b

    MAD (libmad) is a high-quality MPEG audio decoder.

    基于stm32 libmad MP3软解码库移植教程

    我花了些时间、精力,研究了目前linux领域很常用的一个开源音频解码库——libmad的解码流程,并将其成功移植到裸奔的stm32平台上。本着资源共享的奉献精神,我将移植过程整理成手册,发布于网络,希望对大家有用。

    MPlayer + libmad 资源打包奉献

    MPlayer-1.0rc2.tar.bz2 libmad-0.15.1b.tar.gz

    libmad.rar_WINCE libmad _libmad_libmad wince_libmad.rar_mp3 deco

    wince下基于libmad的mp3解码库

    android音频解码libmad.so动态库

    ndroid音频解码库libmad的动态库so文件,不过需要用android的NDK开发环境和了解JNI接口编写才能重新封装需要的接口!

    基于alsa和libmad嵌入式mp3播放器

    可移植ok6410 的mp3播放器,基于alsa和libmad库,内含编译好的库和播放器以及源代码,解压后可直接运行,和以移植好的mplayer官方播放器(可快进 暂停 时间轴 音量控制等)

    stm32 libmad helix MP3软解移植

    STM32软解MP3的移植工程,艰难找到了两种解码库libmad 和 helix。资料里面也有具体的资料,请大家支持

    在Linux下基于libmad库的音乐播放器

    在Linux环境下移植libmad库,用C语言自制的能播放mp3歌曲的一个小测试程序,鼠标左击是上一首,鼠标右击是下一首,由于上传文件大小的限制,没有添加歌曲,需要在解压后的文件夹下添加三首歌,歌曲的名字改为:1.mp3...

    libmad-0.15.1b.rar

    mpeg1 layer123 的音频解码库(源代码) libmad-0.15.1b.rar 感觉相对来说资料要多一些,效率也还可以

    MP3等音频数据解码库libmad

    因为考虑到有时候需要直接播放内存流而不用频繁打开关闭文件等繁琐的操作, Android系统中要想直接播放MP3等数据流可以借助于第三方的音频解码库libmad,解码结果为PCM数据,再利用AudioTrack播放组件便可直接播放。

    Libmad音乐播放器

    简单的MP3播放器示例,libmad解码,支持专辑图片显示,支持FFT显示,支持Lrc歌词显示,只能播放本地mp3

Global site tag (gtag.js) - Google Analytics