优化了内存使用,不再使用mmap映射整个文件到内存
/*
* 本程序是从 minimad 改进而来,如要更详细的说明请参看 minimad.c
*
* Gu Zhou, 2009/12/25, SiChuan University, China
*
* 系统: ubuntu 19.10
* 依赖: sudo apt install libmad0-dev libasound2-dev
* 编译: gcc -Wall -o madplayer madplayer.c -lmad -lasound
* 运行: ./madplayer filename.mp3
*
* 20191027, bugfix & improve
* 20191120, optimized for memory used
*/
#include <stdio.h>
#include <mad.h>
#include <alsa/asoundlib.h>
/* pcm device, eg. "plughw:0,0", use `aplay -l` */
const char *PCM_DEVICE = "default";
const size_t BUFFER_SIZE = 65536;
static snd_pcm_hw_params_t *hwparams = NULL;
static snd_pcm_t *pcm_handle = NULL;
static int init_alsa();
typedef struct {
FILE *file;
unsigned char *buffer;
} decoder_data;
static int decode_and_play(decoder_data *data);
int main(int argc, const char * const argv[])
{
decoder_data data;
data.file = fopen(argv[1], "r");
if (NULL == data.file)
{
fprintf(stderr, "open %s failed\n", argv[1]);
return -1;
}
data.buffer = malloc(BUFFER_SIZE);
if (init_alsa() == -1)
{
fprintf(stderr, "init_alsa() error\n");
return -1;
}
decode_and_play(&data);
fclose(data.file);
free(data.buffer);
return 0;
}
static int init_alsa()
{
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
char *pcm_name;
const unsigned int rate = 44100;
unsigned int exact_rate;
/*const int periods = 2;
const snd_pcm_uframes_t periodsize = 8192;*/
pcm_name = strdup(PCM_DEVICE);
snd_pcm_hw_params_alloca(&hwparams);
if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0)
{
fprintf (stderr, "Error opening PCM device %s\n", pcm_name);
return -1;
}
if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0)
{
fprintf (stderr, "Can not configure this PCM device.\n");
return -1;
}
if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
{
fprintf (stderr, "Error setting access.\n");
return -1;
}
if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0)
{
fprintf (stderr, "Error setting format.\n");
return -1;
}
exact_rate = rate;
if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0)
{
fprintf (stderr, "Error setting rate.\n");
return -1;
}
if (rate != exact_rate)
{
fprintf (stderr, "The rate %d Hz is not supported by your hardware.\n==> Using %d Hz instead.\n", rate, exact_rate);
}
if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0)
{
fprintf (stderr, "Error setting channels.\n");
return -1;
}
/* 设置periods报错,好像不用也行 :) */
/*if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0)
{
fprintf(stderr, "Error setting periods.\n");
return -1;
}
if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, (periodsize * periods)>>2) < 0)
{
fprintf(stderr, "Error setting buffersize.\n");
return -1;
}*/
if (snd_pcm_hw_params(pcm_handle, hwparams) < 0)
{
fprintf (stderr, "Error setting HW params.\n");
return -1;
}
return 0;
}
static enum mad_flow input(void *data, struct mad_stream *stream)
{
size_t length, bytes = 0;
decoder_data *dd = data;
if (feof(dd->file))
return MAD_FLOW_STOP;
if (NULL != stream->next_frame)
{
bytes = stream->bufend - stream->next_frame;
memcpy(dd->buffer, stream->next_frame, bytes);
}
length = fread(dd->buffer + bytes, 1, BUFFER_SIZE - bytes, dd->file);
mad_stream_buffer(stream, dd->buffer, length + bytes);
return MAD_FLOW_CONTINUE;
}
/* 缩放成16位的pcm音频 */
static short scale(mad_fixed_t sample)
{
sample += (1L << (MAD_F_FRACBITS - 16));
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
return sample >> (MAD_F_FRACBITS + 1 - 16);
}
static enum mad_flow output(void *data, struct mad_header const *header, struct mad_pcm *pcm)
{
unsigned int nchannels, nsamples, i;
const mad_fixed_t *channel[2];
short output[8192], *outputPtr;
short sample;
nchannels = pcm->channels;
nsamples = pcm->length;
for (i = 0; i < nchannels; i++)
channel[i] = pcm->samples[i];
outputPtr = output;
while (nsamples--)
{
for (i = 0; i < nchannels; i++)
{
sample = scale(*channel[i]++);
*(outputPtr++) = sample;
}
}
while (snd_pcm_writei(pcm_handle, output, pcm->length) < 0)
snd_pcm_prepare(pcm_handle);
return MAD_FLOW_CONTINUE;
}
static enum mad_flow error(void *data, struct mad_stream *stream, struct mad_frame *frame)
{
fprintf(stderr, "decoding error 0x%04x (%s)\n", stream->error, mad_stream_errorstr(stream));
return MAD_FLOW_CONTINUE;
}
static int decode_and_play(decoder_data *data)
{
struct mad_decoder decoder;
int result;
mad_decoder_init(&decoder, data, input, 0, 0, output, error, 0);
mad_decoder_options(&decoder, 0);
result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
mad_decoder_finish(&decoder);
snd_pcm_drain(pcm_handle);
return result;
}
再来个简化版,使用底层api
#include <stdio.h>
#include <mad.h>
#include <alsa/asoundlib.h>
#define BUFFER_SIZE 65536
static snd_pcm_hw_params_t *hwparams = NULL;
static snd_pcm_t *pcm_handle = NULL;
static int init_alsa();
static void play(struct mad_pcm *pcm);
int main(int argc, const char *argv[])
{
int ret;
struct mad_stream stream;
struct mad_synth synth;
struct mad_frame frame;
size_t length, bytes = 0;
unsigned char *buf = malloc(BUFFER_SIZE);
FILE *file = fopen(argv[1], "r");
init_alsa();
mad_stream_init(&stream);
mad_synth_init(&synth);
mad_frame_init(&frame);
stream.error = MAD_ERROR_NONE;
while (!feof(file))
{
if (NULL != stream.next_frame)
{
bytes = stream.bufend - stream.next_frame;
memcpy(buf, stream.next_frame, bytes);
}
length = fread(&buf[bytes], 1, BUFFER_SIZE - bytes, file);
mad_stream_buffer(&stream, buf, length + bytes);
bytes = 0;
for (;;)
{
ret = mad_frame_decode(&frame, &stream);
if (0 == ret)
{
mad_synth_frame(&synth, &frame);
play(&synth.pcm);
continue;
}
fprintf(stderr, "decoding error 0x%04x (%s)\n", stream.error, mad_stream_errorstr(&stream));
if (!MAD_RECOVERABLE(stream.error))
break;
}
}
free(buf);
fclose(file);
mad_synth_finish(&synth);
mad_frame_finish(&frame);
mad_stream_finish(&stream);
snd_pcm_drain(pcm_handle);
return 0;
}
static short scale(mad_fixed_t sample)
{
sample += (1L << (MAD_F_FRACBITS - 16));
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
return sample >> (MAD_F_FRACBITS + 1 - 16);
}
static void play(struct mad_pcm *pcm)
{
unsigned int nchannels, nsamples, i;
const mad_fixed_t *channel[2];
short output[8192], *outputPtr;
short sample;
nchannels = pcm->channels;
nsamples = pcm->length;
for (i = 0; i < nchannels; i++)
channel[i] = pcm->samples[i];
outputPtr = output;
while (nsamples--)
{
for (i = 0; i < nchannels; i++)
{
sample = scale(*channel[i]++);
*(outputPtr++) = sample;
}
}
while (snd_pcm_writei(pcm_handle, output, pcm->length) < 0)
snd_pcm_prepare(pcm_handle);
}
static int init_alsa()
{
snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
const char *pcm_name = "default";
const unsigned int rate = 44100;
unsigned int exact_rate;
/*const int periods = 2;
const snd_pcm_uframes_t periodsize = 8192;*/
snd_pcm_hw_params_alloca(&hwparams);
if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0)
{
fprintf (stderr, "Error opening PCM device %s\n", pcm_name);
return -1;
}
if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0)
{
fprintf (stderr, "Can not configure this PCM device.\n");
return -1;
}
if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
{
fprintf (stderr, "Error setting access.\n");
return -1;
}
if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, SND_PCM_FORMAT_S16_LE) < 0)
{
fprintf (stderr, "Error setting format.\n");
return -1;
}
exact_rate = rate;
if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0)
{
fprintf (stderr, "Error setting rate.\n");
return -1;
}
if (rate != exact_rate)
{
fprintf (stderr, "The rate %d Hz is not supported by your hardware.\n==> Using %d Hz instead.\n", rate, exact_rate);
}
if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2) < 0)
{
fprintf (stderr, "Error setting channels.\n");
return -1;
}
/* 设置periods报错,好像不用也行 :) */
/*if (snd_pcm_hw_params_set_periods(pcm_handle, hwparams, periods, 0) < 0)
{
fprintf(stderr, "Error setting periods.\n");
return -1;
}
if (snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, (periodsize * periods)>>2) < 0)
{
fprintf(stderr, "Error setting buffersize.\n");
return -1;
}*/
if (snd_pcm_hw_params(pcm_handle, hwparams) < 0)
{
fprintf (stderr, "Error setting HW params.\n");
return -1;
}
return 0;
}
来源:oschina
链接:https://my.oschina.net/guzhou/blog/3132065