X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/39492555cd1d64e2f8a3474038f61d28c5c56de5..9f28e855aad11d25c0a69a7e59825c07bdefcafe:/server/decode.c?ds=sidebyside diff --git a/server/decode.c b/server/decode.c index c63ca20..47227a2 100644 --- a/server/decode.c +++ b/server/decode.c @@ -33,10 +33,21 @@ #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" /** @brief Encoding lookup table type */ @@ -57,7 +68,7 @@ static FILE *outputfp; static const char *path; /** @brief Input buffer */ -static unsigned char buffer[1048576]; +static char input_buffer[1048576]; /** @brief Open the input file */ static void open_input(void) { @@ -69,34 +80,70 @@ static void open_input(void) { * @return Number of bytes read */ static size_t fill(void) { - int n = read(inputfd, buffer, sizeof buffer); + int n = read(inputfd, input_buffer, sizeof input_buffer); 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, int bits, - int nbytes) { + int nbytes, + 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; - header.endian = ENDIAN_BIG; + header.endian = endian; header.nbytes = nbytes; if(fwrite(&header, sizeof header, 1, outputfp) < 1) fatal(errno, "decoding %s: writing format header", path); @@ -183,7 +230,8 @@ static enum mad_flow mp3_output(void attribute((unused)) *data, output_header(header->samplerate, pcm->channels, 16, - 2 * pcm->channels * pcm->length); + 2 * pcm->channels * pcm->length, + ENDIAN_BIG); switch(pcm->channels) { case 1: while(n--) @@ -195,8 +243,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; } @@ -205,10 +251,10 @@ static enum mad_flow mp3_output(void attribute((unused)) *data, static enum mad_flow mp3_input(void attribute((unused)) *data, struct mad_stream *stream) { const size_t n = fill(); - fprintf(stderr, "n=%zu\n", n); + if(!n) return MAD_FLOW_STOP; - mad_stream_buffer(stream, buffer, n); + mad_stream_buffer(stream, (unsigned char *)input_buffer, n); return MAD_FLOW_CONTINUE; } @@ -236,10 +282,139 @@ static void decode_mp3(void) { mad_decoder_finish(mad); } +/** @brief OGG decoder */ +static void decode_ogg(void) { + FILE *fp; + OggVorbis_File vf[1]; + int err; + long n; + int bitstream; + vorbis_info *vi; + + if(!(fp = fopen(path, "rb"))) + fatal(errno, "cannot open %s", path); + /* There doesn't seem to be any standard function for mapping the error codes + * to strings l-( */ + if((err = ov_open(fp, vf, 0/*initial*/, 0/*ibytes*/))) + 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, 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(input_buffer, 1, n, outputfp) < (size_t)n) + fatal(errno, "decoding %s: writing sample data", path); + } +} + +/** @brief Sample data callback used by decode_wav() */ +static int wav_write(struct wavfile attribute((unused)) *f, + const char *data, + size_t nbytes, + void attribute((unused)) *u) { + if(fwrite(data, 1, nbytes, outputfp) < nbytes) + fatal(errno, "decoding %s: writing sample data", path); + return 0; +} + +/** @brief WAV file decoder */ +static void decode_wav(void) { + struct wavfile f[1]; + int err; + + if((err = wav_init(f, path))) + fatal(err, "opening %s", path); + 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 } };