X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/fdb4e27ac67437dd655dc4b70613b977eeabccb1..8d399b30271359fc8cea9992f1513a86b4db3348:/server/decode-mp3.c diff --git a/server/decode-mp3.c b/server/decode-mp3.c new file mode 100644 index 0000000..6837beb --- /dev/null +++ b/server/decode-mp3.c @@ -0,0 +1,187 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2007-2010 Richard Kettlewell + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/** @file server/decode-mp3.c + * @brief Decode MP3 files. + */ +#include "decode.h" +#include + +static struct hreader input[1]; + +/** @brief Dithering state + * Filched from mpg321, which credits it to Robert Leslie */ +struct audio_dither { + mad_fixed_t error[3]; + mad_fixed_t random; +}; + +/** @brief 32-bit PRNG + * Filched from mpg321, which credits it to Robert Leslie */ +static inline unsigned long prng(unsigned long state) +{ + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; +} + +/** @brief Generic linear sample quantize and dither routine + * Filched from mpg321, which credits it to Robert Leslie */ +static long audio_linear_dither(mad_fixed_t sample, + struct audio_dither *dither) { + unsigned int scalebits; + mad_fixed_t output, mask, rnd; + const int bits = 16; + + enum { + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + /* noise shape */ + sample += dither->error[0] - dither->error[1] + dither->error[2]; + + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0] / 2; + + /* bias */ + output = sample + (1L << (MAD_F_FRACBITS + 1 - bits - 1)); + + scalebits = MAD_F_FRACBITS + 1 - bits; + mask = (1L << scalebits) - 1; + + /* dither */ + rnd = prng(dither->random); + output += (rnd & mask) - (dither->random & mask); + + dither->random = rnd; + + /* clip */ + if (output > MAX) { + output = MAX; + + if (sample > MAX) + sample = MAX; + } + else if (output < MIN) { + output = MIN; + + if (sample < MIN) + sample = MIN; + } + + /* quantize */ + output &= ~mask; + + /* error feedback */ + dither->error[0] = sample - output; + + /* scale */ + return output >> scalebits; +} + +/** @brief MP3 output callback */ +static enum mad_flow mp3_output(void attribute((unused)) *data, + struct mad_header const *header, + struct mad_pcm *pcm) { + size_t n = pcm->length; + const mad_fixed_t *l = pcm->samples[0], *r = pcm->samples[1]; + static struct audio_dither ld[1], rd[1]; + + output_header(header->samplerate, + pcm->channels, + 16, + 2 * pcm->channels * pcm->length, + ENDIAN_BIG); + switch(pcm->channels) { + case 1: + while(n--) + output_16(audio_linear_dither(*l++, ld)); + break; + case 2: + while(n--) { + output_16(audio_linear_dither(*l++, ld)); + output_16(audio_linear_dither(*r++, rd)); + } + break; + } + return MAD_FLOW_CONTINUE; +} + +/** @brief MP3 input callback */ +static enum mad_flow mp3_input(void attribute((unused)) *data, + struct mad_stream *stream) { + int used, remain, n; + + /* libmad requires its caller to do ALL the buffering work, including coping + * with partial frames. Given that it appears to be completely undocumented + * you could perhaps be forgiven for not discovering this... */ + if(input_count) { + /* Compute total number of bytes consumed */ + used = (char *)stream->next_frame - input_buffer; + /* Compute number of bytes left to consume */ + remain = input_count - used; + memmove(input_buffer, input_buffer + used, remain); + } else { + remain = 0; + } + /* Read new data */ + n = hreader_read(input, + input_buffer + remain, + (sizeof input_buffer) - remain); + if(n < 0) + disorder_fatal(errno, "reading from %s", path); + /* Compute total number of bytes available */ + input_count = remain + n; + if(input_count) + mad_stream_buffer(stream, (unsigned char *)input_buffer, input_count); + if(n) + return MAD_FLOW_CONTINUE; + else + return MAD_FLOW_STOP; +} + +/** @brief MP3 error callback */ +static enum mad_flow mp3_error(void attribute((unused)) *data, + struct mad_stream *stream, + struct mad_frame attribute((unused)) *frame) { + if(0) + /* Just generates pointless verbosity l-( */ + disorder_error(0, "decoding %s: %s (%#04x)", + path, mad_stream_errorstr(stream), stream->error); + return MAD_FLOW_CONTINUE; +} + +/** @brief MP3 decoder */ +void decode_mp3(void) { + struct mad_decoder mad[1]; + + if(hreader_init(path, input)) + disorder_fatal(errno, "opening %s", path); + mad_decoder_init(mad, 0/*data*/, mp3_input, 0/*header*/, 0/*filter*/, + mp3_output, mp3_error, 0/*message*/); + if(mad_decoder_run(mad, MAD_DECODER_MODE_SYNC)) + exit(1); + mad_decoder_finish(mad); +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/