Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Simple mp3 player.  Derived from libmad's example minimad.c.
 */
#include <u.h>
#include <libc.h>
#include "mad.h"

/* Current input file */
char *name;
vlong offset;
int rate = 44100;

char *outfile;
int vfd; /* /dev/volume */

static enum mad_flow
input(void *data, struct mad_stream *stream)
{
        int fd, n, m;
        static uchar buf[32768];

        fd = (int)data;
        n = stream->bufend - stream->next_frame;
        memmove(buf, stream->next_frame, n);
        m = read(fd, buf+n, sizeof buf-n);
        offset += m;
        if(m < 0)
                sysfatal("reading input: %r");
        if(m == 0)
                return MAD_FLOW_STOP;
        n += m;
        mad_stream_buffer(stream, buf, n);
        return MAD_FLOW_CONTINUE;
}

/*
 * Dither 28-bit down to 16-bit.  From mpg321. 
 * I'm skeptical, but it's supposed to make the
 * samples sound better than just truncation.
 */
typedef struct Dither Dither;
struct Dither
{
        mad_fixed_t error[3];
        mad_fixed_t random;
};

#define PRNG(x) (((x)*0x19660dL + 0x3c6ef35fL) & 0xffffffffL)

enum
{
        FracBits = MAD_F_FRACBITS,
        OutBits = 16,
        Round = 1 << (FracBits+1-OutBits-1),    // sic
        ScaleBits = FracBits + 1 - OutBits,
        LowMask  = (1<<ScaleBits) - 1,
        Min = -MAD_F_ONE,
        Max = MAD_F_ONE - 1,
};

int
audiodither(mad_fixed_t v, Dither *d)
{
        int out;
        mad_fixed_t random;

        /* noise shape */
        v += d->error[0] - d->error[1] + d->error[2];
        d->error[2] = d->error[1];
        d->error[1] = d->error[0] / 2;
        
        /* bias */
        out = v + Round;
        
        /* dither */
        random = PRNG(d->random);
        out += (random & LowMask) - (d->random & LowMask);
        d->random = random;
        
        /* clip */
        if(out > Max){
                out = Max;
                if(v > Max)
                        v = Max;
        }else if(out < Min){
                out = Min;
                if(v < Min)
                        v = Min;
        }
        
        /* quantize */
        out &= ~LowMask;
        
        /* error feedback */
        d->error[0] = v - out;
        
        /* scale */
        return out >> ScaleBits;
}

static enum mad_flow
output(void *data, struct mad_header const* header, struct mad_pcm *pcm)
{
        int i, n, v;
        mad_fixed_t const *left, *right;
        static Dither d;
        static uchar buf[16384], *p;

        if(pcm->samplerate != rate){
                rate = pcm->samplerate;
                if(vfd < 0)
                        fprint(2, "warning: audio sample rate is %d Hz\n", rate);
                else
                        fprint(vfd, "speed %d", rate);
        }
        p = buf;
        memset(&d, 0, sizeof d);
        n = pcm->length;
        switch(pcm->channels){
        case 1:
                left = pcm->samples[0];
                for(i=0; i<n; i++){
                        v = audiodither(*left++, &d);
                        /* stereoize */
                        *p++ = v;
                        *p++ = v>>8;
                        *p++ = v;
                        *p++ = v>>8;
                }
                break;
        case 2:
                left = pcm->samples[0];
                right = pcm->samples[1];
                for(i=0; i<n; i++){
                        v = audiodither(*left++, &d);
                        *p++ = v;
                        *p++ = v>>8;
                        v = audiodither(*right++, &d);
                        *p++ = v;
                        *p++ = v>>8;
                }
                break;
        }
        assert(p<=buf+sizeof buf);
        write(1, buf, p-buf);
        return MAD_FLOW_CONTINUE;
}

static enum mad_flow
error(void *data, struct mad_stream *stream, struct mad_frame *frame)
{
        if(stream->error == MAD_ERROR_LOSTSYNC)
                return MAD_FLOW_CONTINUE;
        sysfatal("%s:#%lld: %s",
                name, offset-(stream->bufend-stream->next_frame),
                mad_stream_errorstr(stream));
        return 0;
}

void
play(int fd, char *nam)
{
        struct mad_decoder decoder;
        
        name = nam;
        mad_decoder_init(&decoder, (void*)fd, input, nil, nil, output, error, nil);
        mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
        mad_decoder_finish(&decoder);
}

void
usage(void)
{
        fprint(2, "usage: mp3dec [-o outfile] [file...]\n");
        exits("usage");
}

void
main(int argc, char **argv)
{
        int i, fd;
        char *p;

        ARGBEGIN{
        case 'o':
                outfile = EARGF(usage());
                break;
        default:
                usage();
        }ARGEND
        
        if(outfile){
                if((fd = create(outfile, OWRITE, 0666)) < 0)
                        sysfatal("create %s: %r", outfile);
        }else{
                if((fd = open("/dev/audio", OWRITE)) < 0)
                        sysfatal("open /dev/audio: %r");
                vfd = open("/dev/volume", OWRITE);
        }
        if(fd != 1){
                dup(fd, 1);
                close(fd);
        }

        if(argc == 0){
                play(0, "standard input");
        }else{
                for(i=0; i<argc; i++){
                        if((fd = open(argv[i], OREAD)) < 0)
                                sysfatal("open %s: %r", argv[i]);
                        play(fd, argv[i]);
                        close(fd);
                }
        }
        exits(0);
}