应用libmad制作一款mp3播放器


MAD(libmad)是一款高质量的MPEG音频解码器,它支持MPEG-1,MPEG-2(LSF) 和所谓的MPEG-2.5格式(支持更低的采样率),并实现了各标准下所有三个层级的解码(Layer I, Layer II, and Layer III a.k.a. MP3).但是MAD不支持MPEG-2多声道和AAC.  MAD通过100%定点计算来输出24bit的PCM数据, 因此具有相当高的解析度,同时对没有FPU单元的嵌入式板子也是一大福音.MAD(libmad)通过GPL许可证发布,是一款应用广泛的开源软件.

调用libmad库来解码MP3文件也是相当方便的,主要涉及编写下面几个回调函数:
1. input回调函数:   在解码前执行,目的是将mp3数据段喂给解码器.
2. header回调函数:  在每次读取帧头后执行.我们在这里读取键盘指令和mp3格式,播放时间等信息.
3. header_preprocess回调函数:  在每次读取帧头后执行.这里作为前处理,将帧头位置和时间推入FILO索引.
4. output回调函数:  在每次完成一帧解码后执行.这里将PCM数据转化成S16L格式后进行播放.
5. error回调函数:   在每次完成mad_header(frame)_decode()后执行.主要用于打印错误信息.

通过对libmad自带的minimad.c做改进和修改,我们可以简单地实现一个mp3播放器,具备键盘输入指令,音量控制,前后选曲,快速进退等功能.(部分需要调用EGI)

059b4af2bf1efeb61870294b44013adf.png

具体源代码如下:

/* ----------------------------------------------------------------------------
 * libmad - MPEG audio decoder library
 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: minimad.c,v 1.4 2004/01/23 09:41:32 rob Exp $

 * 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.

 ----------------------------------------------------------
 Modified for ALSA PCM playback with SND_PCM_FORMAT_S16_LE.
 ----------------------------------------------------------

 Usage example:
	./minimad  /Music/ *.mp3

 KeyInput command:
	'q'	Quit			退出
	'n'	Next			下一首
	'p'	Prev			前一首
	'+'	Increase volume +5%	增加音量
	'-'	Decrease volume -5%	减小音量
	'>'	Fast forward		快进
	'<'	Fast backward		快退

 Midas Zhou
 midaszhou@yahoo.com
------------------------------------------------------------------------------- */
# include <stdio.h>
# include <unistd.h>
# include <sys/stat.h>
# include <sys/mman.h>
# include <egi_utils.h>
# include <egi_pcm.h>
# include <egi_input.h>
# include <egi_filo.h>
# include "mad.h"


const char *mad_mode_str[]={
	"Single channel",
	"Dual channel",
	"Joint Stereo",		// joint (MS/intensity) stereo */
	"Normal LR Stereo",
};


/* input回调函数.   在解码前执行.目的是将mp3数据喂给解码器.*/
static enum mad_flow input_all(void *data, struct mad_stream *stream);

/* header回调函数.  在每次读取帧头后执行.在这里读取键盘指令和mp3格式,播放时间等信息. */
static enum mad_flow header(void *data,  struct mad_header const *header);

/* header回调函数.  在每次读取帧头后执行.这里作为前处理,将帧头位置和时间推入FILO索引. */
static enum mad_flow header_preprocess(void *data,  struct mad_header const *header);

/* output回调函数.  在每次完成一帧解码后执行.将PCM数据转化成S16L格式后进行播放.*/
static enum mad_flow output(void *data, struct mad_header const *header, struct mad_pcm *pcm);

/* error回调函数.   在每次完成mad_header(frame)_decode()后执行.主要用于打印错误信息. */
static enum mad_flow error(void *data, struct mad_stream *stream, struct mad_frame *frame);

/* 前期解码函数,读取所有帧头,并将位置和时间推入FILO索引. */
static int decode_preprocess(unsigned char const *start, unsigned long length);

/* 解码函数MP3 */
static int decode(unsigned char const *, unsigned long);


static snd_pcm_t *pcm_handle;      /* PCM播放设备句柄 */
static int16_t  *data_s16l=NULL;   /* FORMAT_S16L 数据 */
static bool pcmdev_ready;	   /* PCM 设备ready */
static bool pcmparam_ready;	   /* PCM 参数ready */

/* KeyIn command 定义键盘输入命令 */
static int cmd_keyin;
enum {
	CMD_NONE=0,
	CMD_NEXT,
	CMD_PREV,
	CMD_FFSEEK,	/* Fast forward seek */
	CMD_FBSEEK,	/* Fast backward seek */
	CMD_LOOP,
	CMD_PAUSE,
};

/* Frame counter 帧头计数 */
static unsigned long   	  header_cnt;

/* Time duration for a MP3 file 已播放时长 */
static unsigned long long timelapse_frac;
static unsigned int	  timelapse_sec;
static float		  timelapse;
static float		  lapfSec;
static int 		  lapHour, lapMin;

static float		  preset_timelapse;

/* Time duration for a MP3 file 总时长 */
static unsigned long long duration_frac;
static unsigned int	  duration_sec;
static float		  duration;
static float		  durfSec;
static int		  durHour, durMin;

static unsigned long long poff;		/* Offset to buff of mp3 file */
static unsigned long long fsize;	/* file size */

/*  Frame header index 帧头位置和时间,推入FILO作为mp3帧头位置和时间索引 */
typedef struct mp3_header_pos
{
	off_t poff;		/* Offset */
	float timelapse;	/* Timelapse at start of the frame */
} MP3_HEADER_POS;
EGI_FILO *filo_headpos;		/* Array/index of frame header pos */




/*-----------------------------
	     MAIN
------------------------------*/
int main(int argc, char *argv[])
{
  EGI_FILEMMAP *fmap=NULL;
  int i;
  int files;	/* Total inpu files */

  if (argc <2 )
  	return 1;
  files=argc-1;

  /* Set termI/O 设置终端为直接读取单个字符方式 */
  egi_set_termios();

  /* Prepare vol 启动系统声音调整设备 */
  egi_getset_pcm_volume(NULL,NULL);

  /* Open pcm captrue device 打开PCM播放设备 */
  if( snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0) <0 ) {  /* SND_PCM_NONBLOCK,SND_PCM_ASYNC */
  	printf("Fail to open PCM playback device!\n");
        return 1;
  }

/* Play all files 逐个播放MP3文件 */
for(i=0; i<files; i++) {

	/* Init filo 建立一个FILO用于存放 */
  	filo_headpos=egi_malloc_filo(1024, sizeof(MP3_HEADER_POS), FILOMEM_AUTO_DOUBLE);
  	if(filo_headpos==NULL) {
		printf("Fail to malloc headpos FILO!\n");
		return 2;
  	}

	/* Reset 重置参数 */
	duration_frac=0;
	duration_sec=0;
	timelapse_frac=0;
	timelapse_sec=0;
	header_cnt=0;
	pcmparam_ready=false;
	pcmdev_ready=false;
	poff=0;

  	/* Mmap input file 映射当前文件 */
	fmap=egi_fmap_create(argv[i+1]);
	if(fmap==NULL)
	return 1;

	/* To fill filo_headpos and calculation time duration. */
	/* 前期解码处理,仅快速读取所有帧头,并建立索引 */
 	decode_preprocess((const unsigned char *)fmap->fp, fmap->fsize);

	/* Calculate duration 计算总时长 */
	duration=(1.0*duration_frac/MAD_TIMER_RESOLUTION) + duration_sec;
  	printf("\rDuration: %.1f seconds\n", duration);
	durHour=duration/3600; durMin=(duration-durHour*3600)/60;
	durfSec=duration-durHour*3600-durMin*60;

	/* Decoding 解码播放 */
  	printf(" Start playing...\n %s\n size=%lldk\n", argv[i+1], fmap->fsize>>10);
 	decode((const unsigned char *)fmap->fp, fmap->fsize);
	poff=0;
	fsize=fmap->fsize;

	/* Command parse, after jumping out of decode. 键盘输入命令解释 */
	switch(cmd_keyin) {
		case CMD_PREV:
			if(i==0) i=-1;
			else i-=2;
			break;
		case CMD_LOOP:
			break;

	}
	cmd_keyin=CMD_NONE;

  	/* Release source 释放相关资源 */
  	free(data_s16l); data_s16l=NULL;
  	egi_fmap_free(&fmap);
	egi_filo_free(&filo_headpos);
}

  /* Close pcm handle 关闭PCM播放设备 */
  snd_pcm_close(pcm_handle);

  /* Reset termI/O 设置终端 */
  egi_reset_termios();

  return 0;
}

/* 私有数据结构,用于在各callback函数间共享数据
 * 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;
  const struct mad_decoder *decoder;
};


/* 回调函数,在解码前执行.目的是将mp3数据喂给解码器.
 *  To feed whole mp3 file to the stream
 * 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_all(void *data, struct mad_stream *stream)
{
  struct buffer *buffer = data;

  if (!buffer->length)
    	return MAD_FLOW_STOP;

  /* set stream buffer pointers, pass user data to stream ... */
  mad_stream_buffer(stream, buffer->start, buffer->length);

  buffer->length = 0;

  return MAD_FLOW_CONTINUE;
}

/* 将解码后得到的PCM数据转化为FORMAT_S16L格式
 * 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);
}

/* 回调函数,在每次完成一帧解码后执行.将PCM数据转化成S16L格式后进行播放.
 * 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 i,k;
  unsigned int nchannels, nsamples, samplerate;
  mad_fixed_t const *left_ch, *right_ch;

  /* 声道数 采样率 PCM数据 */
  /* pcm->samplerate contains the sampling frequency */
  nchannels = pcm->channels;
  left_ch   = pcm->samples[0];
  right_ch  = pcm->samples[1];
  nsamples  = pcm->length;
  samplerate= pcm->samplerate;

  /* 为PCM播放设备设置相应参数 */
  /* Sep parameters for pcm_hanle */
  if(!pcmdev_ready) {
	//printf("nchanls=%d, sample_rate=%d\n", nchannels, samplerate);
	if( egi_pcmhnd_setParams(pcm_handle, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, nchannels, samplerate) <0 ) {
                printf("Fail to set params for PCM playback handle.!\n");
	} else {
		//printf("EGI set pcm params successfully!\n");
		pcmdev_ready=true;
	}
  }

  /* 转换成S16L格式 */
  /* Realloc data for S16 */
  data_s16l=(int16_t *)realloc(data_s16l, nchannels*nsamples*2);
  if(data_s16l==NULL)
	exit(1);
  /* Scale to S16L */
  for(k=0,i=0; i<nsamples; i++) {
	data_s16l[k++]=scale(*(left_ch+i));		/* Left */
	if(nchannels==2)
		data_s16l[k++]=scale(*(right_ch+i));	/* Right */
  }

  /* Playback 播放PCM数据 */
  if(pcmdev_ready)
	    egi_pcmhnd_playBuff(pcm_handle, true, (void *)data_s16l, nsamples);

  return MAD_FLOW_CONTINUE;
}


/* 回调函数,在每次读取帧头后执行.在这里读取键盘指令和mp3格式,播放时间等信息.
 * Parse keyinput and frame header
 *
 */
static enum mad_flow header(void *data,  struct mad_header const *header )
{
  int percent;
  char ch;
  ch=0;
  int mad_ret=MAD_FLOW_CONTINUE;
  struct buffer *buffer = data;
  MP3_HEADER_POS headpos;

  /* Count header frame */
  header_cnt++;

  /* Parse keyinput 读取键盘指令 */
  read(STDIN_FILENO, &ch,1);
  switch(ch) {
	case 'q': 	/* Quit */
	case 'Q':
		printf("\n\n");
		exit(0);
		break;
	case 'n':	/* Next */
	case 'N':
		printf("\n\n");
	  	return MAD_FLOW_STOP;
	case 'p':	/* Next */
	case 'P':
		printf("\n\n");
		cmd_keyin=CMD_PREV;
	  	return MAD_FLOW_STOP;
	case '+':
		egi_adjust_pcm_volume(5);
		egi_getset_pcm_volume(&percent,NULL);
		printf("\nVol: %d%%\n",percent);
		break;
	case '-':
		egi_adjust_pcm_volume(-5);
		egi_getset_pcm_volume(&percent,NULL);
		printf("\nVol: %d%%\n",percent);
		break;
	case '>':
		preset_timelapse=timelapse+5;  /* 5s */
		cmd_keyin=CMD_FFSEEK;
		mad_ret=MAD_FLOW_IGNORE;
		break;
	case '<':
		printf("headcnt:%ld \n", header_cnt);
		if(egi_filo_read(filo_headpos, header_cnt-50, &headpos)!=0)
			break;
		header_cnt-=50;

		/* Reload stream */
  		mad_stream_buffer(&(buffer->decoder->sync->stream), buffer->start+headpos.poff, fsize-headpos.poff);
		timelapse_sec=headpos.timelapse;
		timelapse_frac=headpos.timelapse-timelapse_sec;

		return MAD_FLOW_IGNORE;
		break;
  }

  /* Time elapsed 计算当前播放时间 ---or read FILO. */
  timelapse_frac += header->duration.fraction;
  timelapse_sec  += header->duration.seconds;
  timelapse=(1.0*timelapse_frac/MAD_TIMER_RESOLUTION) + timelapse_sec;

  /*** Print MP3 file info 输出播放信息到终端
   * Note:
   *	1. For some MP3 file, it should wait until the third header frame comes that it contains right layer and samplerate!
   */
  if(!pcmparam_ready ) {
	if( header_cnt>2 ) {
		printf(" -----------------------\n");
		printf(" Simple is the Best!\n");
		printf(" miniMAD +Libmad +EGI\n");
  		printf(" Layer_%d\n %s\n bitrate:    %.1fkbits\n samplerate: %dHz\n",
			header->layer, mad_mode_str[header->mode], 1.0*header->bitrate/1000, header->samplerate);
		printf(" -----------------------\n");
		pcmparam_ready=true; /* Set */
	}
	else
		return MAD_FLOW_CONTINUE;
  }
  lapHour=timelapse/3600;
  lapMin=(timelapse-lapHour*3600)/60;
  lapfSec=timelapse-3600*lapHour-60*lapMin;
  printf(" %02d:%02d:%.1f of [%02d:%02d:%.1f]   \r", lapHour, lapMin, lapfSec, durHour, durMin, durfSec);
  fflush(stdout);

  /* 当按下快进时,跳过中间帧 */
  /* Check timelapse, to skip/ignore if <preset_timelapse. */
  if(cmd_keyin==CMD_FFSEEK) {
	if(timelapse < preset_timelapse)
		return MAD_FLOW_IGNORE;
	else
		cmd_keyin=CMD_NONE;
  }

  return mad_ret;
}


/* 回调函数,在每次读取帧头后执行.这里作为前处理,将帧头位置和时间推入FILO索引
 * To calculate time duration only.
 */
static enum mad_flow header_preprocess(void *data,  struct mad_header const *header )
{
  MP3_HEADER_POS head_pos;
  struct buffer *buffer = data;

  /* Count duration */
  duration_frac += header->duration.fraction;
  duration_sec  += header->duration.seconds;

  /* Fill in filo_headpos */
  head_pos.poff= (buffer->decoder->sync->stream.this_frame)-(buffer->decoder->sync->stream.buffer);
  head_pos.timelapse=(1.0*duration_frac/MAD_TIMER_RESOLUTION) + duration_sec;
  egi_filo_push(filo_headpos, (void *)&head_pos);

  /* Do not decode */
  return MAD_FLOW_IGNORE;
}


/* 回调函数, 在每次完成mad_header(frame)_decode()后执行.
 * 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)
{
  #if 0
  struct buffer *buffer = data;

  fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n",
	  stream->error, mad_stream_errorstr(stream),
	  stream->this_frame - buffer->start);
  #endif

  /* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */
  return MAD_FLOW_CONTINUE;
}

/* MP3解码函数:	初开始化解码器,进行MP3解码.
 * 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_all, header /* 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;
}

/* MP3前处理解码函数:	初开始化解码器并执行解码. 这里作为前处理,将帧头位置和时间推入FILO索引
   Preprocess decoding. Read frame headers and fill into FILO.
 */
static int decode_preprocess(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;
  buffer.decoder = &decoder;

  /* configure input, output, and error functions */
  mad_decoder_init(&decoder, &buffer,
                   input_all, header_preprocess /* header */, 0 /* filter */, 0, /* output */
                   0,/* error */ 0 /* message */);

  /* start decoding */
  result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);

  /* release the decoder */
  mad_decoder_finish(&decoder);

  return result;
}

类似地,可以做成mp3流播放器等应用.

a49c0d1ad487e15b4350e2cd4f4d6bd1.jpeg

 5862d07bed605a02644fa0371fc25de9.jpeg

9df072cbb56840b5b15ad6e805fd4c4e.jpg

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Midas-Zhou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值