X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/ce6c36be6c2df99afd01a7a602debb321322e113..28dc2d220aee492d34c35e3e0756248a16d55b5a:/server/decode.c diff --git a/server/decode.c b/server/decode.c index d1be348..c693c75 100644 --- a/server/decode.c +++ b/server/decode.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder - * Copyright (C) 2007 Richard Kettlewell + * Copyright (C) 2007, 2008 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 @@ -34,12 +34,23 @@ #include #include #include +#include + +/* libFLAC has had an API change and stupidly taken away the old API */ +#if HAVE_FLAC_FILE_DECODER_H +# include +#else +# include +#define FLAC__FileDecoder FLAC__StreamDecoder +#define FLAC__FileDecoderState FLAC__StreamDecoderState +#endif #include "log.h" #include "syscalls.h" #include "defs.h" #include "wav.h" #include "speaker-protocol.h" +#include "version.h" /** @brief Encoding lookup table type */ struct decoder { @@ -59,35 +70,50 @@ static FILE *outputfp; static const char *path; /** @brief Input buffer */ -static char buffer[1048576]; - -/** @brief Open the input file */ -static void open_input(void) { - if((inputfd = open(path, O_RDONLY)) < 0) - fatal(errno, "opening %s", path); -} +static char input_buffer[1048576]; -/** @brief Fill the buffer - * @return Number of bytes read - */ -static size_t fill(void) { - int n = read(inputfd, buffer, sizeof buffer); +/** @brief Number of bytes read into buffer */ +static int input_count; - if(n < 0) - fatal(errno, "reading from %s", path); - return n; +/** @brief Write an 8-bit word */ +static inline void output_8(int n) { + if(putc(n, outputfp) < 0) + fatal(errno, "decoding %s: output error", path); } /** @brief Write a 16-bit word in bigendian format */ static inline void output_16(uint16_t n) { if(putc(n >> 8, outputfp) < 0 - || putc(n & 0xFF, outputfp) < 0) + || putc(n, outputfp) < 0) fatal(errno, "decoding %s: output error", path); } -/** @brief Write the header - * If called more than once, either does nothing (if you kept the same - * output encoding) or fails (if you changed it). +/** @brief Write a 24-bit word in bigendian format */ +static inline void output_24(uint32_t n) { + if(putc(n >> 16, outputfp) < 0 + || putc(n >> 8, outputfp) < 0 + || putc(n, outputfp) < 0) + fatal(errno, "decoding %s: output error", path); +} + +/** @brief Write a 32-bit word in bigendian format */ +static inline void output_32(uint32_t n) { + if(putc(n >> 24, outputfp) < 0 + || putc(n >> 16, outputfp) < 0 + || putc(n >> 8, outputfp) < 0 + || putc(n, outputfp) < 0) + fatal(errno, "decoding %s: output error", path); +} + +/** @brief Write a block header + * @param rate Sample rate in Hz + * @param channels Channel count (currently only 1 or 2 supported) + * @param bits Bits per sample (must be a multiple of 8, no more than 64) + * @param nbytes Total number of data bytes + * @param endian @ref ENDIAN_BIG or @ref ENDIAN_LITTLE + * + * Checks that the sample format is a supported one (so other calls do not have + * to) and calls fatal() on error. */ static void output_header(int rate, int channels, @@ -96,6 +122,12 @@ static void output_header(int rate, int endian) { struct stream_header header; + if(bits <= 0 || bits % 8 || bits > 64) + fatal(0, "decoding %s: unsupported sample size %d bits", path, bits); + if(channels <= 0 || channels > 2) + fatal(0, "decoding %s: unsupported channel count %d", path, channels); + if(rate <= 0) + fatal(0, "decoding %s: nonsensical sample rate %dHz", path, rate); header.rate = rate; header.bits = bits; header.channels = channels; @@ -121,11 +153,11 @@ static inline unsigned long prng(unsigned long state) /** @brief Generic linear sample quantize and dither routine * Filched from mpg321, which credits it to Robert Leslie */ -#define bits 16 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, @@ -173,7 +205,6 @@ static long audio_linear_dither(mad_fixed_t sample, /* scale */ return output >> scalebits; } -#undef bits /** @brief MP3 output callback */ static enum mad_flow mp3_output(void attribute((unused)) *data, @@ -199,8 +230,6 @@ static enum mad_flow mp3_output(void attribute((unused)) *data, output_16(audio_linear_dither(*r++, rd)); } break; - default: - fatal(0, "decoding %s: unsupported channel count %d", path, pcm->channels); } return MAD_FLOW_CONTINUE; } @@ -208,15 +237,34 @@ static enum mad_flow mp3_output(void attribute((unused)) *data, /** @brief MP3 input callback */ static enum mad_flow mp3_input(void attribute((unused)) *data, struct mad_stream *stream) { - const size_t n = fill(); - - if(!n) + 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 = read(inputfd, input_buffer + remain, (sizeof input_buffer) - remain); + if(n < 0) + 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; - mad_stream_buffer(stream, (unsigned char *)buffer, n); - return MAD_FLOW_CONTINUE; } - /** @brief MP3 error callback */ static enum mad_flow mp3_error(void attribute((unused)) *data, struct mad_stream *stream, @@ -232,7 +280,8 @@ static enum mad_flow mp3_error(void attribute((unused)) *data, static void decode_mp3(void) { struct mad_decoder mad[1]; - open_input(); + if((inputfd = open(path, O_RDONLY)) < 0) + 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)) @@ -257,14 +306,14 @@ static void decode_ogg(void) { fatal(0, "ov_fopen %s: %d", path, err); if(!(vi = ov_info(vf, 0/*link*/))) fatal(0, "ov_info %s: failed", path); - while((n = ov_read(vf, buffer, sizeof buffer, 1/*bigendianp*/, + while((n = ov_read(vf, input_buffer, sizeof input_buffer, 1/*bigendianp*/, 2/*bytes/word*/, 1/*signed*/, &bitstream))) { if(n < 0) fatal(0, "ov_read %s: %ld", path, n); if(bitstream > 0) fatal(0, "only single-bitstream ogg files are supported"); output_header(vi->rate, vi->channels, 16/*bits*/, n, ENDIAN_BIG); - if(fwrite(buffer, 1, n, outputfp) < (size_t)n) + if(fwrite(input_buffer, 1, n, outputfp) < (size_t)n) fatal(errno, "decoding %s: writing sample data", path); } } @@ -286,19 +335,91 @@ static void decode_wav(void) { if((err = wav_init(f, path))) fatal(err, "opening %s", path); - if(f->bits % 8) - fatal(err, "%s: unsupported byte size %d", path, f->bits); output_header(f->rate, f->channels, f->bits, f->datasize, ENDIAN_LITTLE); if((err = wav_data(f, wav_write, 0))) fatal(err, "error decoding %s", path); } +/** @brief Metadata callback for FLAC decoder + * + * This is a no-op here. + */ +static void flac_metadata(const FLAC__FileDecoder attribute((unused)) *decoder, + const FLAC__StreamMetadata attribute((unused)) *metadata, + void attribute((unused)) *client_data) { +} + +/** @brief Error callback for FLAC decoder */ +static void flac_error(const FLAC__FileDecoder attribute((unused)) *decoder, + FLAC__StreamDecoderErrorStatus status, + void attribute((unused)) *client_data) { + fatal(0, "error decoding %s: %s", path, + FLAC__StreamDecoderErrorStatusString[status]); +} + +/** @brief Write callback for FLAC decoder */ +static FLAC__StreamDecoderWriteStatus flac_write + (const FLAC__FileDecoder attribute((unused)) *decoder, + const FLAC__Frame *frame, + const FLAC__int32 *const buffer[], + void attribute((unused)) *client_data) { + size_t n, c; + + output_header(frame->header.sample_rate, + frame->header.channels, + frame->header.bits_per_sample, + (frame->header.channels * frame->header.blocksize + * frame->header.bits_per_sample) / 8, + ENDIAN_BIG); + for(n = 0; n < frame->header.blocksize; ++n) { + for(c = 0; c < frame->header.channels; ++c) { + switch(frame->header.bits_per_sample) { + case 8: output_8(buffer[c][n]); break; + case 16: output_16(buffer[c][n]); break; + case 24: output_24(buffer[c][n]); break; + case 32: output_32(buffer[c][n]); break; + } + } + } + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + + +/** @brief FLAC file decoder */ +static void decode_flac(void) { +#if HAVE_FLAC_FILE_DECODER_H + FLAC__FileDecoder *fd = 0; + FLAC__FileDecoderState fs; + + if(!(fd = FLAC__file_decoder_new())) + fatal(0, "FLAC__file_decoder_new failed"); + if(!(FLAC__file_decoder_set_filename(fd, path))) + fatal(0, "FLAC__file_set_filename failed"); + FLAC__file_decoder_set_metadata_callback(fd, flac_metadata); + FLAC__file_decoder_set_error_callback(fd, flac_error); + FLAC__file_decoder_set_write_callback(fd, flac_write); + if((fs = FLAC__file_decoder_init(fd))) + fatal(0, "FLAC__file_decoder_init: %s", FLAC__FileDecoderStateString[fs]); + FLAC__file_decoder_process_until_end_of_file(fd); +#else + FLAC__StreamDecoder *sd = 0; + FLAC__StreamDecoderInitStatus is; + + if((is = FLAC__stream_decoder_init_file(sd, path, flac_write, flac_metadata, + flac_error, 0))) + fatal(0, "FLAC__stream_decoder_init_file %s: %s", + path, FLAC__StreamDecoderInitStatusString[is]); +#endif +} + /** @brief Lookup table of decoders */ static const struct decoder decoders[] = { { "*.mp3", decode_mp3 }, { "*.MP3", decode_mp3 }, { "*.ogg", decode_ogg }, { "*.OGG", decode_ogg }, + { "*.flac", decode_flac }, + { "*.FLAC", decode_flac }, { "*.wav", decode_wav }, { "*.WAV", decode_wav }, { 0, 0 } @@ -324,13 +445,6 @@ static void help(void) { exit(0); } -/* Display version number and terminate. */ -static void version(void) { - xprintf("disorder-decode version %s\n", disorder_version_string); - xfclose(stdout); - exit(0); -} - int main(int argc, char **argv) { int n; const char *e; @@ -340,7 +454,7 @@ int main(int argc, char **argv) { while((n = getopt_long(argc, argv, "hV", options, 0)) >= 0) { switch(n) { case 'h': help(); - case 'V': version(); + case 'V': version("disorder-decode"); default: fatal(0, "invalid option"); } }