chiark / gitweb /
Split up disorder-decode source into one file per format.
[disorder] / server / decode-mp3.c
diff --git a/server/decode-mp3.c b/server/decode-mp3.c
new file mode 100644 (file)
index 0000000..6837beb
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+ */
+/** @file server/decode-mp3.c
+ * @brief Decode MP3 files.
+ */
+#include "decode.h"
+#include <mad.h>
+
+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:
+*/