Libmad音频解码库的编译和使用

本文详细介绍了libmad,一个开源的MP3解码库,以及如何在Ubuntu环境下进行交叉编译。通过实例展示了如何使用libmad编译minimad.c,并实现从文件读取mp3数据,解码后播放。同时,讨论了如何修改minimad以实现边解码边读入数据,减少内存开销,适合嵌入式系统。最后,给出了一个简单的mp3播放器设计示例,将解码后的数据直接输出到音频设备进行播放。
摘要由CSDN通过智能技术生成

一、libmad的介绍

什么是libmad?

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

libmad的编译

环境

ubuntu20.04

交叉编译工具 arm-linux-gnueabihf-gcc 8.2.1

下载

下载地址 https://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz

在ubuntu下可通过wget直接下载

wget https://downloads.sourceforge.net/mad/libmad-0.15.1b.tar.gz

解压、配置编译

tar -zxvf libmad-0.15.1b.tar.gz
cd libmad-0.15.1b/
mkdir libmad
//先执行下面的命令:这条命令是为了适配高版本的gcc,因为高版本的gcc已经将-fforce-mem去除了:
sed -i '/-fforce-mem/d' configure
./configure CC=arm-linux-gnueabihf-gcc --host=arm-linux --prefix=$PWD/libmad
make -j8
make install 

方法二:添加脚本build.sh

#!/bin/bash
current_path=$(cd "$(dirname $0)";pwd)
rm -rf host
mkdir -p $current_path/host
install_path=$current_path/host
export CC=arm-linux-gnueabihf-gcc
export CXX=arm-linux-gnueabihf-g++
./configure --prefix=$install_path --enable-shared --host=arm-linux
make clean
make
make install
cp -rf $install_path/* ../libmad/

编译完成后将libmad复制到你的项目中,别忘了链接动态库

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./libmad/lib/

二、libmad的应用

1.minimad.c怎么编译?编译后怎么运行?运行时的输入输出分别是什么,或者说运行时什么效果?

Linux下(我前面说了,本文所有的工作都是在Linux进行)先安装libmad,说白了就是把libmad库导入C标准库,安装方法见libmad-0.15.1b中的READMEINSTALL文件。 安装libmad后,新建一个文件夹,将libmad-0.15.1b中的minimad.cmad.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 <mad.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <pthread.h>
 
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

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 <mad.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <pthread.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;
}
  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值