MP3 Player. You can change fwd/rev speed and skip. see: http://mbed.org/users/okini3939/notebook/lpc4088_madplayer/

Dependencies:   I2SSlave SDFileSystem TLV320 mbed

player.cpp

Committer:
okini3939
Date:
2014-02-18
Revision:
0:8ba6230eefbd

File content as of revision 0:8ba6230eefbd:

#include "mbed.h"
#include "player.h"
#include "SDFileSystem.h"
#include "sdram.h"

#define DACBUF_SIZE (1024 * 1024 * 8 - 1024)
SDFileSystem sd(p5, p6, p7, p8, "sd");
TLV320 audio(p32, p31, 0x34, p11, p12, p13, p14, p16); // I2S Codec / sda, scl, addr, tx_sda, tx_ws, clk, rx_sda, rx_ws

static struct mad_decoder decoder;
struct dacout_s *dacbuf;
volatile int dac_r, dac_w, dac_l;
FILE *fp;
volatile int player_busy = 0, cmd_stop = 0;
int dac_step = 1, dac_vol = 100;

extern DigitalOut led1, led2, led3, led4;
extern Serial pc;


bool isFull() {
    return (((dac_w + 1) % DACBUF_SIZE) == dac_r);
};
bool isEmpty() {
    return (dac_r == dac_w);
};
bool isEmpty2() {
    return (dac_r == dac_l);
};
uint32_t available() {
    return (dac_w >= dac_r) ? dac_w - dac_r : DACBUF_SIZE - dac_r + dac_w;
};
uint32_t available2() {
    return (dac_r >= dac_l) ? dac_r - dac_l : DACBUF_SIZE - dac_l + dac_r;
};


void isr_audio ()
{
    int i, j, a;
    static int buf[4] = {0,0,0,0};
    static int w = 0;
    static short l = 0, r = 0;

    for (i = 0; i < 4; i ++) {
        if (dac_step > 0 && !isEmpty()) {
            // fwd
            for (j = 0; j < dac_step; j ++) {
//              buf[i] = (dacbuf[dac_r].l << 16) | dacbuf[dac_r].r;
              l = dacbuf[dac_r].l;
              r = dacbuf[dac_r].r;
              buf[i] = (l << 16) | (r & 0xffff);
              dac_r = (dac_r + 1) % DACBUF_SIZE;
              if (isEmpty()) break;
            }
        } else
        if (dac_step < 0 && !isEmpty2()) {
            // rev
            for (j = 0; j < -dac_step; j ++) {
//              buf[i] = (dacbuf[dac_r].l << 16) | dacbuf[dac_r].r;
              l = dacbuf[dac_r].l;
              r = dacbuf[dac_r].r;
              buf[i] = (l << 16) | (r & 0xffff);
              dac_r = (dac_r - 1 + DACBUF_SIZE) % DACBUF_SIZE;
              if (isEmpty2()) break;
            }
        } else {
            // under flow
            if (l > 0) l --;
            if (l < 0) l ++;
            if (r > 0) r --;
            if (r < 0) r ++;
            buf[i] = (l << 16) | (r & 0xffff);
        }
    }
    audio.write(buf, 0, 4);
}

int init_audio () {
    if (sdram_init() == 1) {
        pc.printf("Failed to initialize SDRAM\n");
        return -1;
    }
    malloc(16); 
    dacbuf = (dacout_s*)malloc(sizeof(dacout_s) * DACBUF_SIZE);
    if (dacbuf == NULL) return -1;
    pc.printf("memory %08x\r\n", dacbuf);

    audio.power(0x02); // mic off
    audio.inputVolume(0, 0);
    audio.frequency(44100);
    audio.attach(&isr_audio);
    audio.start(TRANSMIT);
    NVIC_SetPriority(I2S_IRQn, 1);
    NVIC_SetPriority(TIMER3_IRQn, 10);
    return 0;
}

int play (char *filename) {
    int i;

    DBG("play: %s\r\n", filename);
    fp = fopen(filename, "rb");
    if(!fp) {
        pc.printf("file error\r\n");
        return -1;
    }
    player_busy = 1;
    dac_r = dac_w = dac_l = 0;
    dac_step = 1;
    cmd_stop = 0;
    led2 = 0;
    mad_decoder_init(&decoder, NULL, input, 0, 0, output, error_fn, 0);
    mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
    mad_decoder_finish(&decoder);
    fclose(fp);
    fp = 0;
    dac_r = dac_w = 0;
    led2 = 1;
    player_busy = 0;
    wait_ms(100);
    DBG("eof\r\n");
    return 0;
}

int command (char *cmd) {
    int i, r = -1;

    pc.printf("command %s\r\n", cmd);
    switch (cmd[0]) {
    case 'P':
        if (!player_busy) {
            char buf[40];
            strcpy(buf, "/sd/");
            strcat(buf, &cmd[1]);
            r = play(buf);
        }
        break;
    case 'S':
        cmd_stop = 1;
        r = 0;
        break;
    case 'T':
        i = atoi(&cmd[1]);
        if (i < -10 || i > 10) break;
        dac_step = i;
        DBG("dac_step %d\r\n", dac_step);
        r = 0;
        break;
    case 'Q':
        if (cmd[1] == '+' || cmd[1] == '-') {
            i = atof(&cmd[1]) * 44100;
            if (i < 0 && i < - available2()) break;
            if (i > 0 && i > available()) break;
            dac_r += i;
            DBG("skip %+d\r\n", i);
        } else {
            i = atof(&cmd[1]) * 44100;
            if (i < dac_l || i > dac_r + available()) break;
            dac_r = i;
            DBG("skip %d\r\n", i);
        }
        r = 0;
        break;
    case 'V':
        i = atoi(&cmd[1]);
        if (i < 0 || i > 200) break;
        dac_vol = i;
        DBG("volume %d\r\n", i);
        r = 0;
        break;
    }

    return r;
}


/*
 * This is the input callback. The purpose of this callback is to (re)fill
 * the stream buffer which is to be decoded.
 */

enum mad_flow input(void *data,
                    struct mad_stream *stream)
{
    static unsigned char strmbuff[2100];
    int ret;
    int rsz;
    unsigned char *bp;

    /* the remaining bytes from incomplete frames must be copied
    to the beginning of the new buffer !
    */
    bp = strmbuff;
    rsz = 0;
    if(stream->error == MAD_ERROR_BUFLEN||stream->buffer==NULL) {
        if(stream->next_frame!=NULL) {
            rsz = stream->bufend-stream->next_frame;
            memmove(strmbuff,stream->next_frame,rsz);
            bp = strmbuff+rsz;
        }
    }

    if (feof(fp)) {
        if (isEmpty()) {
            return MAD_FLOW_STOP;
        } else {
            return MAD_FLOW_CONTINUE;
        }
    }

    led4 = 1;
    ret = fread(bp,1,sizeof(strmbuff) - rsz,fp);

    if (!ret) {
        DBG("input stop\r\n");
        return MAD_FLOW_STOP;
    }

    mad_stream_buffer(stream, strmbuff, ret + rsz);

    return MAD_FLOW_CONTINUE;
}


/*
 * 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);
}

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

enum mad_flow output(void *data,
                     struct mad_header const *header,
                     struct mad_pcm *pcm)
{
    unsigned int nchannels, nsamples;
    mad_fixed_t const *left_ch, *right_ch;

    /* pcm->samplerate contains the sampling frequency */
    nchannels = pcm->channels;
    nsamples  = pcm->length;
    left_ch   = pcm->samples[0];
    right_ch  = pcm->samples[1];

    poll();
    while (nsamples--) {
        while (isFull() || available() >= (44100 * FWDBUF)) {
            poll();
            if (cmd_stop) break;
        }
        __disable_irq();
        dacbuf[dac_w].l = scale(*left_ch);
        dacbuf[dac_w].r = scale(*right_ch);
        dac_w = (dac_w + 1) % DACBUF_SIZE;
        if (dac_w == 0 || dac_l) dac_l ++;
        __enable_irq();
        left_ch++;
        right_ch++;
    }

    if (cmd_stop) {
        DBG("output stop o\r\n");
        cmd_stop = 0;
        return MAD_FLOW_STOP;
    }
    return MAD_FLOW_CONTINUE;
}

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

enum mad_flow error_fn(void *data,
                       struct mad_stream *stream,
                       struct mad_frame *frame)
{
    /* ID3 tags will cause warnings and short noise, ignore it for the moment*/
    /*
      fprintf(stderr, "decoding error 0x%04x (%s)\n",
          stream->error, mad_stream_errorstr(stream));
    */

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

    return MAD_FLOW_CONTINUE;
}