chiark / gitweb /
finish off FLAC support
authorrjk@greenend.org.uk <>
Sun, 30 Sep 2007 12:41:27 +0000 (13:41 +0100)
committerrjk@greenend.org.uk <>
Sun, 30 Sep 2007 12:41:27 +0000 (13:41 +0100)
CHANGES
README
debian/etc.disorder.config
doc/disorder-decode.8.in
examples/config.sample.in
server/Makefile.am
server/decode.c

diff --git a/CHANGES b/CHANGES
index 01af4e7e33405ac84db88443dc04c76eb0788058..92615e4a7dd972b6dd233f6e450ac5dac5e41c60 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -38,8 +38,8 @@ crash when random play was enabled has been fixed.
 A new configuration option 'queue_pad' allows the number of random
 tracks kept on the queue to be controlled.
 
-There is a new utility disorder-decode which can decode several common
-audio file formats.  The example config file uses it.
+There is a new utility disorder-decode which can decode OGG, MP3, WAV
+and FLAC.  The example config file uses it.
 
 ** Network Play
 
diff --git a/README b/README
index 778af370592d3b35346d9d8912983d8a07fa21a6..124524f7ca7c6a5aaf71b30910dfb4a4f6f68475 100644 (file)
--- a/README
+++ b/README
@@ -107,7 +107,7 @@ NOTE: If you are upgrading from an earlier version, see README.upgrades.
 
 4. Create /etc/disorder/config.  Start from examples/config.sample and adapt it
    to your own requirements.  In particular, you should:
-    * edit the 'player' commands to reflect the software you have installed.
+    * add 'player' commands for any file formats not supported natively
     * edit the 'collection' command to identify the location(s) of your own
       digital audio files.  These commands also specify the encoding of
       filenames, which you should be sure to get right as recovery from an
index 1301d2730768df3e98f922ef28e245da0e83d3be..3b10fe6228c995f06e3854f8be4598c2f479a08f 100644 (file)
@@ -2,6 +2,7 @@
 player *.mp3 execraw disorder-decode
 player *.ogg execraw disorder-decode
 player *.wav execraw disorder-decode
+player *.flac execraw disorder-decode
 
 # don't leave a gap between tracks
 gap 0
index 522dad26e6c238cce452209cc403a2f4448225c9..91c59968a3833344f7243c38c2fc5195c4e9a06a 100644 (file)
@@ -1,3 +1,4 @@
+.\" -*-nroff-*-
 .\"
 .\" Copyright (C) 2007 Richard Kettlewell
 .\"
@@ -24,7 +25,7 @@ disorder-decode \- DisOrder audio decoder
 .I PATH
 .SH DESCRIPTION
 .B disorder-decode
-converts MP3, OGG and WAV files into DisOrders "raw" format.  It is
+converts MP3, OGG, FLAC and WAV files into DisOrders "raw" format.  It is
 therefore suitable for use as an
 .B execraw
 player.
@@ -33,8 +34,9 @@ It is not intended to be used from the command line.
 .SH LIMITATIONS
 OGG files with multiple bitstreams are not supported.
 .PP
-WAV files with sample sizes that are not a multiple of 8 bits, or any
-kind of compression, are not supported.
+All sample sizes must be multiples of 8 bits (currently).
+.PP
+WAV files with any kind of compression are not supported.
 .SH "SEE ALSO"
 .BR disorderd (8),
 .BR disorder_config (5)
index 86e97bf399f7f272a8652bb1d40b900c98b91804..32089139d407c11b9b674ea5525c0cec15aa949f 100644 (file)
@@ -2,6 +2,7 @@
 player *.mp3 execraw disorder-decode
 player *.ogg execraw disorder-decode
 player *.wav execraw disorder-decode
+player *.flac execraw disorder-decode
 
 # use the fs module to list files under /export/mp3.  The encoding
 # is ISO-8859-1.
index 987b87971be57f7ebb44aab9fd99eb2eeb70a8c1..48cf540ba4d6536351ab46faf8feb7090f1a3d57 100644 (file)
@@ -54,7 +54,7 @@ disorder_speaker_DEPENDENCIES=../lib/libdisorder.a
 
 disorder_decode_SOURCES=decode.c
 disorder_decode_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
-       $(LIBMAD) $(LIBVORBISFILE)
+       $(LIBMAD) $(LIBVORBISFILE) $(LIBFLAC)
 disorder_decode_DEPENDENCIES=../lib/libdisorder.a
 
 disorder_normalize_SOURCES=normalize.c
index d1be3483d9e05de56c5f784f213de5b9e4fb3939..8161dbaf126b40c81b4cd46f78bd425d74a4db86 100644 (file)
@@ -34,6 +34,7 @@
 #include <fnmatch.h>
 #include <mad.h>
 #include <vorbis/vorbisfile.h>
+#include <FLAC/file_decoder.h>
 
 #include "log.h"
 #include "syscalls.h"
@@ -59,7 +60,7 @@ static FILE *outputfp;
 static const char *path;
 
 /** @brief Input buffer */
-static char buffer[1048576];
+static char input_buffer[1048576];
 
 /** @brief Open the input file */
 static void open_input(void) {
@@ -71,23 +72,52 @@ 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 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 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 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 +126,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;
@@ -199,8 +235,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;
 }
@@ -212,7 +246,7 @@ static enum mad_flow mp3_input(void attribute((unused)) *data,
 
   if(!n)
     return MAD_FLOW_STOP;
-  mad_stream_buffer(stream, (unsigned char *)buffer, n);
+  mad_stream_buffer(stream, (unsigned char *)input_buffer, n);
   return MAD_FLOW_CONTINUE;
 }
 
@@ -257,14 +291,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 +320,81 @@ 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) {
+  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);
+}
+
 /** @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 }