linux下使用libmad庫實現mp3文件的解碼、播放

據說這個更新到2004年2月的libmad是一種高品質的MPEG音頻解碼器,支持24-bit輸出,優點多多。

對其的詳細介紹請參考主頁:http://www.underbit.com/products/mad/

準備工作

x86_64平臺的編譯可直接運行configure,arm下

libmad: ./configure --host=arm-xxx(arm-xxx爲交叉編譯工具的前綴) 

解碼流程

libmad將mp3文件解碼後生成pcm數據。libmad庫自帶一個非常簡潔的實例:minimad.c,分析其中的decode函數可看到其解碼流程非常簡單:

1、配置輸入回調函數、輸出回調函數、用戶數據、篩選回調函數、錯誤回調函數、消息回調函數,參考初始化函數源代碼。

回調函數中,輸入(讀取原始音頻數據)、輸出(對解碼後的音頻數據進行處理)是必需的。

void mad_decoder_init(struct mad_decoder *decoder, void *data,
					  enum mad_flow(*input_func)(void *, struct mad_stream *),
					  enum mad_flow(*header_func)(void *, struct mad_header const *),
					  enum mad_flow(*filter_func)(void *,  struct mad_stream const *,
										struct mad_frame *),
					  enum mad_flow(*output_func)(void *,
									        struct mad_header const *,
									        struct mad_pcm *),
					  enum mad_flow(*error_func)(void *,
										struct mad_stream *,
										struct mad_frame *),
					  enum mad_flow(*message_func)(void *,
										void *, unsigned int *))
{
	decoder->mode         = -1;

	decoder->options      = 0;

	decoder->async.pid    = 0;
	decoder->async.in     = -1;
	decoder->async.out    = -1;

	decoder->sync         = 0;

	decoder->cb_data      = data;

	decoder->input_func   = input_func;
	decoder->header_func  = header_func;
	decoder->filter_func  = filter_func;
	decoder->output_func  = output_func;
	decoder->error_func   = error_func;
	decoder->message_func = message_func;
}

其中的data參數是用戶需要傳給回調函數的自定義數據結構。比如,解碼一個文件,並且採用系統函數open函數打開,那麼可以定義一個對此描述的數據結構: 

typedef struct _mp3_file
{
	int *fd;//open("xx.mp3",O_RDONLY)
	uint32_t flen;//mp3文件的長度
	uint32_t fpos;//當前文件指針位置

	uint8_t  buf[BUFSIZE];
	uint32_t buf_size;

} mp3_file;

數據成員buf、buf_size傳遞給mad_stream_buffer,用來設置流緩衝區指針。 

錯誤處理在minimad中被忽略了,可參考madplay中處理方法(player.c:1776),此處貼出來大概的流程

/*
 * NAME:	decode->error()
 * DESCRIPTION:	handle a decoding error
 */
static
enum mad_flow decode_error(void *data, struct mad_stream *stream,
						   struct mad_frame *frame)
{
	struct player *player = data;
	signed long tagsize;

	switch (stream->error)
	{
		case MAD_ERROR_BADDATAPTR:
                        /*do something*/
			return MAD_FLOW_CONTINUE;

		case MAD_ERROR_LOSTSYNC:
			
			/* todo*/

		default:
			/*todo*/

	}

	if (stream->error == MAD_ERROR_BADCRC)
	{
		return MAD_FLOW_IGNORE;
	}

	return MAD_FLOW_CONTINUE;
}

錯誤處理的返回值定義如下:

enum mad_flow {
  MAD_FLOW_CONTINUE = 0x0000,	/* continue normally */
  MAD_FLOW_STOP     = 0x0010,	/* stop decoding normally */
  MAD_FLOW_BREAK    = 0x0011,	/* stop decoding and signal an error */
  MAD_FLOW_IGNORE   = 0x0020	/* ignore the current frame */
};


可視情況選擇繼續或者終止解碼。
 

2、調用mad_decoder_run開始解碼,支持兩種同步、異步兩種運行方式

enum mad_decoder_mode {
  MAD_DECODER_MODE_SYNC  = 0,
  MAD_DECODER_MODE_ASYNC
};

3、解碼結束後調用mad_decoder_finish釋放解碼器


 播放

在輸出回調函數中,將解碼數據直接寫入“/dev/dsp”即可實現mp3文件的播放,也可以生成pcm文件,供支持pcm解碼的音頻芯片使用。在稍微不算太古董的Linux系統中,都可以使用alsa來播放解碼後的數據。關於alsa請參考:

https://wiki.archlinux.org/index.php/Advanced_Linux_Sound_Architecture

http://www.equalarea.com/paul/alsa-audio.html (這是一篇alsa導學)

https://www.linuxjournal.com/article/6735 (這是另一篇導學)

以下代碼示範使用libmad解碼MP3,並使用alsa接口來播放:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <alsa/asoundlib.h>
#include <stdint.h>
#include <mad.h>
#include <id3tag.h>



int  alsa_device_open(snd_pcm_t** ppcm, const char* dev)
{
   
	int err;
	if ((err = snd_pcm_open(ppcm, dev == 0?"default":dev, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
	{
		fprintf(stderr, "cannot open audio device %s (%s)\n",
				  dev,
				  snd_strerror(err));
		return -1;
	}

	snd_pcm_t *pcm = *ppcm;
	snd_pcm_hw_params_t *hw_params = 0;
	if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0)
	{
		fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",
				  snd_strerror(err));
		return -1;
	}

	if ((err = snd_pcm_hw_params_any(pcm, hw_params)) < 0)
	{
		fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",
				  snd_strerror(err));
		return -1;
	}

	if ((err = snd_pcm_hw_params_set_access(pcm, hw_params,
								SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
	{
		fprintf(stderr, "cannot set access type (%s)\n",
				  snd_strerror(err));
		return -1;
	}

	if ((err = snd_pcm_hw_params_set_format(pcm, hw_params,
					SND_PCM_FORMAT_S16/*SND_PCM_FORMAT_S16_LE*/)) < 0)
	{
		fprintf(stderr, "cannot set sample format (%s)\n",
				  snd_strerror(err));
		return -1;
	}


	int rate = 44100;

	if ((err = snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, 0)) < 0)
	{
		fprintf(stderr, "cannot set sample rate (%s)\n",
				  snd_strerror(err));
		return -1;
	}


	if ((err = snd_pcm_hw_params_set_channels(pcm, hw_params, 2)) < 0)
	{
		fprintf(stderr, "cannot set channel count (%s)\n",
				  snd_strerror(err));
		return -1;
	}


   snd_pcm_uframes_t periodsize = 1024;//frag_size / 4;
   err = snd_pcm_hw_params_set_period_size_near(pcm, hw_params,
                                                &periodsize, 0);
   if (err < 0)
   {
      printf("error on set_period_size (%d)\n", (int)periodsize);
      return -1;
   }

   uint frag_count = 8;

   err = snd_pcm_hw_params_set_periods_near(pcm, hw_params,
                                            &frag_count, 0);
   if (err < 0)
   {
      printf("error on set_periods (%d)\n", frag_count);
      return -1;
   }


	if ((err = snd_pcm_hw_params(pcm, hw_params)) < 0)
	{
		fprintf(stderr, "cannot set parameters (%s)\n",
				  snd_strerror(err));
		return -1;
	}

	snd_pcm_hw_params_free(hw_params);

	if ((err = snd_pcm_prepare(pcm)) < 0)
	{
		fprintf(stderr, "cannot prepare audio interface for use (%s)\n",
				  snd_strerror(err));
		return -1;
	}

	return 0;

}


int  alsa_device_write(snd_pcm_t* pcm, uint8_t* data, int data_len)
{
	int remain = data_len;
	while (remain > 0)
	{
		//之所以用/4,是因爲雙聲道,16位,所以每個frame爲4byte
		int ret = snd_pcm_writei(pcm, data, remain / 4);

		if (ret  == -EAGAIN)
		{
			snd_pcm_prepare(pcm);
			continue;
		}

		if (ret != remain / 4)
		{
			printf("pcm write %u frame ret %d frames\n", remain / 4, ret);
		}

		if (ret > 0)
		{
			remain -= (ret * 4);
		}
		else
		{
			fprintf(stderr,
					"error from writei: %s\n",
					snd_strerror(ret));

			return MAD_FLOW_STOP;
		}

		
	}

	return data_len - remain;
}


int alsa_device_close(snd_pcm_t* pcm)
{
	snd_pcm_drain(pcm);
	return snd_pcm_close(pcm);
}

static snd_pcm_t* pcm =0;

int output_decode_data(uint8_t* data, int data_len)
{
	if (pcm == 0)
	{
		return -1;
	}

	int write_count =  alsa_device_write(pcm,data,data_len);

#ifdef _OUT_TEST
//如果沒有聲音,可以打開該開關,用sox工具play播放該數據文件,驗證解碼的數據是否正確。
	static int fd = -1;

	if (fd == -1)
	{
		fd = open("/tmp/t.pcm", O_WRONLY | O_CREAT);
	}

	if (fd != -1)
	{
		int ret = write(fd, data, write_count);

		if (ret <= 0)
		{
			return MAD_FLOW_STOP;
		}
		//fsync(fd);
	}
#endif

	return write_count;
}


#define MAX_BUFF_SIZE 4096*10

typedef struct _DecodeData
{
	int fd; //open("xx.mp3",O_RDONLY)

	uint8_t  *buf;
	uint32_t buf_size;

	uint8_t *decode_buf;
	uint  decode_buf_size;

} DecodeData;


static enum mad_flow decode_input(void *data, struct mad_stream *stream)
{
	DecodeData *mp3 = (DecodeData *)data;


	size_t remaining = 0;
	if (stream->next_frame != 0)
	{
		remaining  = stream->bufend - stream->next_frame;
		if (remaining != 0)
		{
			memmove(mp3->buf, stream->next_frame, remaining);
			printf("remaining %lu\n", remaining);
		}
	}

	ssize_t ret  = read(mp3->fd, mp3->buf + remaining, MAX_BUFF_SIZE - remaining);

	if (ret <= 0)
	{
		return MAD_FLOW_STOP;
	}

	mp3->buf_size = ret + remaining;
	mad_stream_buffer(stream, mp3->buf, mp3->buf_size);	

	return MAD_FLOW_CONTINUE;
}


static ssize_t 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);
}



static enum mad_flow decode_output(void *data, struct mad_header const *header, struct mad_pcm *pcm)
{

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

	uint nchannels = pcm->channels;

	uint nsamples  = pcm->length;
	mad_fixed_t const *left_ch   = pcm->samples[0];
	mad_fixed_t const *right_ch  = pcm->samples[1];

	DecodeData * dd = (DecodeData*)data;

	dd->decode_buf = realloc(dd->decode_buf, dd->decode_buf_size + nsamples * nchannels * 2); 
	dd->decode_buf_size += nsamples * nchannels * 2;
	int16_t *output = (int16_t *)dd->decode_buf;

	while (nsamples--)
	{
		*output++ = scale(*(left_ch++));
		*output++ = scale(*(right_ch++));
	}

	//將解碼後的數據通過alsa寫入聲音設備
	int out_count = output_decode_data(dd->decode_buf, dd->decode_buf_size);
	if (out_count > 0)
	{
		dd->decode_buf_size -= out_count;
	}

	return MAD_FLOW_CONTINUE;
}


static enum mad_flow decode_error(void *data, struct mad_stream *ms,   struct mad_frame *frame)
{
	if (ms->error == MAD_ERROR_LOSTSYNC)
	{
		signed long tagsize;
		tagsize = id3_tag_query(ms->this_frame,
								ms->bufend - ms->this_frame);
		if (tagsize > 0)
		{
			mad_stream_skip(ms, tagsize);
		}
	}

	return MAD_FLOW_CONTINUE;
}

DecodeData* new_decode_data(const char* file)
{
	DecodeData *decode_data = (DecodeData*)malloc(sizeof(DecodeData));
	decode_data->fd = open(file, O_RDONLY);

	if (decode_data->fd == -1)
	{
		free(decode_data);
		perror("open:");
		return 0;
	}

	decode_data->buf = (uint8_t *)calloc(MAX_BUFF_SIZE, 1);
	decode_data->buf_size = MAX_BUFF_SIZE;
	decode_data->decode_buf = 0;
	decode_data->decode_buf_size = 0;

	return decode_data;
}

void del_decode_data(DecodeData** pdd)
{
	DecodeData* dd = *pdd;
	free(dd->buf);
	free(dd->decode_buf);
	close(dd->fd);
	free(dd);
	*pdd = 0;
}



int main(int argc, char *argv[])
{
	if (argc != 3 && argc != 2)
	{
		printf("usage: app [file] [device](etc->pluginhw:1,0)\n");
		return -1;
	}
	const char *dev = argc == 3 ? argv[2] : "default"; //"pluginhw:1,0"
	int ret  = alsa_device_open(&pcm, dev);
	if (ret == -1)
	{
		return -1;
	}
	DecodeData *decode_data = new_decode_data(argv[1]);
	struct mad_decoder decoder;
	mad_decoder_init(&decoder, decode_data,
						  decode_input, 0 /* header */, 0 /* filter */, decode_output,
						  decode_error, 0 /* message */);

	mad_decoder_options(&decoder, MAD_OPTION_IGNORECRC);
	/* 運行解碼器,直到返回 MAD_FLOW_STOP, MAD_FLOW_BREAK */
	int result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
	mad_decoder_finish(&decoder);
	alsa_device_close(pcm);
	del_decode_data(&decode_data);


        return 0;
}

編譯命令:gcc -g  [源文件名].c  -lasound -lmad -lid3tag -o  [程序名]

 

 遇到的問題

1、解碼錯誤 (0x0101 lost synchronization)

對於這個錯誤的解釋可參考http://www.mars.org/pipermail/mad-dev/2004-January/000975.html

需要的兩個庫的下載地址以及編譯方法如下:

zlib

這個庫的configure腳本沒有提供編譯器選項。直接運行configure程序後,打開產生的Makefile,將CC=gcc改爲你要使用的編譯器的名字。

libid3tag

依賴於zlib,需要指定交叉編譯工具名稱,以及zlib庫的頭文件路徑(-I)、庫路徑(-L)

運行./configure  --host=arm-xxx CPPFLAGS=-I(zlib頭文件路徑) LDFLAGS=-L(zlib庫路徑)

 

2、找不到“/dev/dsp"

對於無法正常播放聲音的系統,可採用手動建立dsp設備的方式:

sudo mknod /dev/dsp c 14 3 (其中的設備號可通過linux源碼目錄下/Documentation/devices.txt文件中查找/dev/dsp得到)

sudo chmod 666 /dev/dsp (設置普通用戶可用)

對於棄用/dev/dsp方式的系統來說,不能採用上述方式,可使用padsp程序,如:

padsp madplay xxx.mp3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章