据说这个更新到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
#include
#include
#include
#include
#include
#include
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