MyMinimad ── Linux下用libmad写的mp3解码播放程序(四)

|▌冷眼眸甩不掉的悲伤 提交于 2019-11-25 22:37:01

优化了内存使用,不再使用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;
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!