libmad+alsa 实现arm下mp3播放


2015-10-10 18:42:55
原文

libmad是一个开源的mp3解码库,轻便高效,mplayer也是用这个库来解码mp3的。ALSA(Advanced Linux Sound Architecture)是Linux中提供声音设备驱动的内核组件,用来代替原来的开放声音系统(Open Sound System,OSSv3)。除了声音设备驱动,ALSA还包含一个用户空间的函数库,以方便开发者通过高级API使用驱动功能,而不必直接与内核驱动交互。
有了这两个强大的工具,就可以在arm板子上面实现播放mp3功能了。当然,板子要有功放。

1.在http://www.linuxfromscratch.org/blfs/view/svn/multimedia/libmad.html下载libmad-0.15.1b.tar.gz。
http://www.alsa-project.org/main/index.php/Download下载alsa-lib-1.0.29.tar.bz2

2.解压出来
tar -xvz -f libmad-0.15.1b.tar.gz
tar -xvj -f alsa-lib-1.0.29.tar.bz2

3.交叉编译安装libmad
进入libmad解压之后的目录,进行配置,这里把库的安装目录设为/usr/local/libmab/mad ,编译工具用arm-linux-gcc
./configure CC=arm-linux-gcc –host=arm-linux –disable-shared –disable-debugging –prefix=/usr/local/libmab/mad

然后安装:
make
make install
如果make出现错误:error: unrecognized command line option “-fforce-mem”,就在Makefile中找到“-fforce-mem”字符串然后删除。
重新make&make install
然后在指定的安装目录/usr/local/libmab/mad 下会出现include和lib两个目录
把include目录下的mad.h复制到编译工具arm-linux-gcc目录下的include目录,我这里是/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/include/
把lib目录下的libmad.a和libmad.la文件复制到编译工具arm-linux-gcc目录下的lib目录,我这里是 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/lib/

这样部署好之后,在程序中添加头文件# include “mad.h”
编译时加上选项-lmad
就可以在程序中使用libmad解码了。

4.交叉编译安装alsa-lib
进入alsa-lib的解压之后的目录,进行配置,这里把库的安装目录设为/usr/local/arm-alsa,编译工具用arm-linux-gcc
./configure CC=arm-linux-gcc –host=arm-linux –prefix=/usr/local/arm-alsa
然后安装
make
make install
然后在指定的安装目录/usr/local/arm-alsa 下会出现include和lib等几个目录。

把include目录下的alsa文件夹复制到编译工具arm-linux-gcc目录的include目录下。

把lib目录下,除了pkgconfig文件夹之外的文件复制到编译工具arm-linux-gcc目录的lib目录下。

这样部署好之后,在程序中添加头文件# include “alsa/asoundlib.h”
编译时加上选项-lasound
就可以在程序中使用alsa驱动播放音频了。

5.接下来就是实战测试。
用libmad的API解码一个mp3的大概流程在网站http://m.baert.free.fr/contrib/docs/libmad/doxy/html/high-level.html上面讲的很清楚,原话是这样的:
First, you need a mad_decoder struct. This holds all information about how you want your stream decoded, such as input/output functions, error handling functions, etc.

mad_decoder_init() sets this structure up for you.

struct mad_decoder decoder;

struct my_playbuf playbuf;

mad_decoder_init(&decoder, &playbuf, input_func, header_func, /filter/ 0, output_func, /error/ 0, /* message */ 0);

In this example, the function called to get more data is set to input_callback, the function called after MPEG headers have been decoded is header_func, the function called after all sound data has been decoded to PCM (for output) is output_callback, and the filter, error, and message functions are unset.

Now, MAD runs in a constant decoding loop. It runs something along the following lines:

if I'm out of data
    call input_func
if input_func says there's no more data,
    quit
decode the header and call header_func
decode the mpeg audio data
call the filter function
call the output function
loop

Now, this is an oversimplification obviously. The important thing to realise is that at every step of the process you can tell MAD what to do.
大概意思是说:首先要建立一个mad_decoder结构体来装你想对这次解码的选项设置,比如你想用哪个输入函数,哪个输出函数等等。然后把把输入函数,输出函数等解码时需要用到的回调函数写好,作为API函数mad_decoder_init的参数传进去。mad_decoder_init会把这些信息存放到这个mad_decoder结构体中(至于这个结构体的构造可以到这个网站上面看看,里面的介绍很详细)。

设置好接下来就可以调用mad_decoder_run函数来解码了。流程就这么简单。
mad_decoder_run函数内部循环如下:

如果这段解码完了
调用输入函数,把下一段数据加载进来
如果全部解码完了
退出
解码头部,然后调用header函数,输出头部信息,里面包含声道数,采样率等信息
解码这一段数据
调用filter函数
调用output函数
继续循环

通过修改header,input,output等函数,你可以完全控制解码流程。
—–翻译完毕—–

当然实际应用中不会这么简化,还有一些初始化或者释放的工作要做,libmad官方给出一个minimad.c的例子,能够把mp3解码后输出。下面的alsa_minimad.c是在minimad.c的基础上加上alsa调用,让解码后的数据输出到功放,同时可以控制音量。
alsa_minimad.c:分为三部分,main函数,alsa设置,libmad设置

点击(此处)折叠或打开

# include <stdio.h>
# include <unistd.h>
# include <sys/stat.h>
# include <sys/mman.h>
# include <sys/fcntl.h>
# include "mad.h"
# include "alsa/asoundlib.h"


static int decode(unsigned char const *, unsigned long);
int set_pcm();
void set_volume(long volume);
snd_pcm_t* handle=NULL; //PCI设备句柄
snd_pcm_hw_params_t* params=NULL;//硬件信息和PCM流配置

int main(int argc, char *argv[])
{
  struct stat stat;
  void *fdm;

  if (argc != 3)
    {
    printf("Usage: minimad + mp3 file name + volume\n");
    return 1;
    }
  int fd;
  fd=open(argv[1],O_RDWR);
  if(fd<0)
  {
    perror("open file failed:");
    return 1;
  }

  if (fstat(fd, &stat) == -1 ||stat.st_size == 0)
  {
    printf("fstat failed:\n");
    return 2;
  }

  fdm = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0);
  if (fdm == MAP_FAILED)
    return 3;


  if(set_pcm()!=0) //设置pcm 参数
    {
        printf("set_pcm fialed:\n");
        return 1;
    }
    set_volume(atoi(argv[2]));
    decode(fdm, stat.st_size);

    if (munmap(fdm, stat.st_size) == -1)
      return 4;

    snd_pcm_drain(handle);
    snd_pcm_close(handle);

  return 0;
}


//---------------------------------------------------------------------------
//alsa设置相关函数

int set_pcm()
{
    int rc;
    int dir=0;
    int rate = 44100;; /* 采样频率 44.1KHz*/
    //int format = SND_PCM_FORMAT_S16_LE; /* 量化位数 16 */
    int channels = 2; /* 声道数 2 */

    rc=snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
    if(rc<0)
    {
      perror("\nopen PCM device failed:");
      exit(1);
    }
    snd_pcm_hw_params_alloca(&params); //分配params结构体

    rc=snd_pcm_hw_params_any(handle, params);//初始化params
    if(rc<0)
    {
      perror("\nsnd_pcm_hw_params_any:");
      exit(1);
    }
    rc=snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); //初始化访问权限
    if(rc<0)
    {
      perror("\nsed_pcm_hw_set_access:");
      exit(1);
    }
    rc=snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); //设置16位采样精度
    if(rc<0)
    {
      perror("snd_pcm_hw_params_set_format failed:");
      exit(1);
    }
    rc=snd_pcm_hw_params_set_channels(handle, params, channels); //设置声道,1表示单声>道,2表示立体声
    if(rc<0)
    {
      perror("\nsnd_pcm_hw_params_set_channels:");
      exit(1);
    }
    rc=snd_pcm_hw_params_set_rate_near(handle, params, &rate, &dir); //设置>频率
    if(rc<0)
    {
      perror("\nsnd_pcm_hw_params_set_rate_near:");
      exit(1);
    }
    rc = snd_pcm_hw_params(handle, params);
    if(rc<0)
    {
      perror("\nsnd_pcm_hw_params: ");
      exit(1);
    }

    return 0;
}


void set_volume(long volume)
{
  snd_mixer_t *mixerFd;
  snd_mixer_elem_t *elem;
  long minVolume = 0,maxVolume = 100;
  int result;
  // 打开混音器
   if ((result = snd_mixer_open( &mixerFd, 0)) < 0)
   {
        printf("snd_mixer_open error!\n");
        mixerFd = NULL;
   }
  // Attach an HCTL to an opened mixer
   if ((result = snd_mixer_attach( mixerFd, "default")) < 0)
   {
        printf("snd_mixer_attach error!\n");
        snd_mixer_close(mixerFd);
        mixerFd = NULL;
   }
  // 注册混音器
   if ((result = snd_mixer_selem_register( mixerFd, NULL, NULL)) < 0)
  {
        printf("snd_mixer_selem_register error!\n");
        snd_mixer_close(mixerFd);
        mixerFd = NULL;
  }
  // 加载混音器
  if ((result = snd_mixer_load( mixerFd)) < 0)
  {
        printf("snd_mixer_load error!\n");
        snd_mixer_close(mixerFd);
        mixerFd = NULL;
  }

   // 遍历混音器元素
    for(elem=snd_mixer_first_elem(mixerFd); elem; elem=snd_mixer_elem_next(elem))
    {
        if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE &&
             snd_mixer_selem_is_active(elem)) // 找到可以用的, 激活的elem
        {
            snd_mixer_selem_get_playback_volume_range(elem, &minVolume, &maxVolume);
            snd_mixer_selem_set_playback_volume_all(elem, volume);
        }
    }
    snd_mixer_close(mixerFd);
}





//libmad设置相关函数
/*
 * 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 {
  unsigned char const *start;
  unsigned long length;
};

/*
 * 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)
{
  struct buffer *buffer = data;
  if (!buffer->length)
    return MAD_FLOW_STOP;

  mad_stream_buffer(stream, buffer->start, buffer->length);

  buffer->length = 0;

  return MAD_FLOW_CONTINUE;
}

/*
 * 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_FRACBITS - 16));

  /* clip */
  if (sample >= MAD_F_ONE)
    sample = MAD_F_ONE - 1;
  else if (sample < -MAD_F_ONE)
    sample = -MAD_F_ONE;

  /* quantize */
  return 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,n;
  mad_fixed_t const *left_ch, *right_ch;

  /* pcm->samplerate contains the sampling frequency */

  nchannels = pcm->channels;
  n=nsamples = pcm->length;
  left_ch = pcm->samples[0];
  right_ch = pcm->samples[1];

  unsigned char Output[6912], *OutputPtr;
  int fmt, wrote, speed, exact_rate, err, dir;


   OutputPtr = Output;

   while (nsamples--)
   {
    signed int sample;

    /* output sample(s) in 16-bit signed little-endian PCM */

    sample = scale(*left_ch++);

    *(OutputPtr++) = sample >> 0;
    *(OutputPtr++) = sample >> 8;
    if (nchannels == 2)
        {
            sample = scale (*right_ch++);
            *(OutputPtr++) = sample >> 0;
            *(OutputPtr++) = sample >> 8;
        }


  }

    OutputPtr = Output;
    snd_pcm_writei (handle, OutputPtr, n);
    OutputPtr = Output;

  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)
{
  struct buffer *buffer = data;
  printf("this is mad_flow error\n");
  fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n",
      stream->error, mad_stream_errorstr(stream),
      stream->this_frame - buffer->start);

  /* 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(unsigned char const *start, unsigned long length)
{
  struct buffer buffer;
  struct mad_decoder decoder;
  int result;

  /* initialize our private message structure */

  buffer.start = start;
  buffer.length = length;

  /* configure input, output, and error functions */

  mad_decoder_init(&decoder, &buffer,
           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;
}

用arm-linux-gcc编译:
arm-linux-gcc -o alsaplayer alsa_minimad.c -lmad -lasound
得到alsaplayer
使用方法:alsaplayer 文件名 音量
例如:alsaplayer voice.mp3 20

程序有个不足的地方,就是默认只能播放双声道的mp3,没有实现根据解码头文件变换单双声道来播放。

最后推荐一个介绍libmad构造的网站,讲解很详细,不知道是不是官网:http://m.baert.free.fr/contrib/docs/libmad/doxy/html/high-level.html

这里写链接内容

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值