summaryrefslogtreecommitdiffstats
path: root/flow/gsl/gsldatahandle-mad.c
diff options
context:
space:
mode:
Diffstat (limited to 'flow/gsl/gsldatahandle-mad.c')
-rw-r--r--flow/gsl/gsldatahandle-mad.c711
1 files changed, 711 insertions, 0 deletions
diff --git a/flow/gsl/gsldatahandle-mad.c b/flow/gsl/gsldatahandle-mad.c
new file mode 100644
index 0000000..fcfdd7f
--- /dev/null
+++ b/flow/gsl/gsldatahandle-mad.c
@@ -0,0 +1,711 @@
+/* GSL - Generic Sound Layer
+ * Copyright (C) 2001-2002 Tim Janik and Stefan Westerfeld
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#include <gsl/gsldatahandle-mad.h>
+
+#include "gslfilehash.h"
+#include <gsl/gsldatautils.h>
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+
+#if GSL_HAVE_LIBMAD
+#include <mad.h>
+
+
+/* --- debugging and errors --- */
+#define MAD_DEBUG GSL_DEBUG_FUNCTION (GSL_MSG_DATA_HANDLE, "MAD")
+#define MAD_MSG GSL_MESSAGE_FUNCTION (GSL_MSG_DATA_HANDLE, "MAD")
+
+
+/* --- defines --- */
+#define FILE_BUFFER_SIZE (1024 * 44) /* approximately 1 second at 320 kbit */
+#define SEEK_BY_READ_AHEAD(h) (((h)->sample_rate / ((h)->frame_size * 2))) /* FIXME */
+#define MAX_CHANNELS (5)
+
+
+/* --- typedefs & structures --- */
+typedef struct
+{
+ GslDataHandle dhandle;
+
+ /* setup data */
+ guint sample_rate;
+ guint frame_size;
+ guint stream_options;
+ guint accumulate_state_frames;
+ guint skip_seek_table : 1;
+
+ /* file IO */
+ guint eof : 1;
+ GslHFile *hfile;
+ guint file_pos;
+ const gchar *error;
+
+ /* seek table */
+ GTime seek_mtime;
+ guint n_seeks;
+ guint *seeks;
+
+ /* file read buffer */
+ guint bfill;
+ guint8 buffer[FILE_BUFFER_SIZE + MAD_BUFFER_GUARD];
+
+ /* pcm housekeeping */
+ GslLong pcm_pos, pcm_length, next_pcm_pos;
+
+ /* libmad structures */
+ struct mad_stream stream;
+ struct mad_frame frame;
+ struct mad_synth synth;
+} MadHandle;
+
+
+/* --- prototypes --- */
+static GslLong dh_mad_coarse_seek (GslDataHandle *data_handle,
+ GslLong voffset);
+
+
+/* --- functions --- */
+static gboolean /* FALSE: handle->eof || errno != 0 */
+stream_read (MadHandle *handle)
+{
+ struct mad_stream *stream = &handle->stream;
+ guint l;
+
+ /* no further data to read (flag must be reset upon seeks) */
+ if (handle->eof)
+ return FALSE;
+
+ /* keep remaining data in buffer */
+ if (stream->next_frame && handle->bfill)
+ {
+ handle->bfill = handle->buffer + handle->bfill - stream->next_frame;
+ memmove (handle->buffer, stream->next_frame, handle->bfill);
+ }
+
+ /* fill buffer */
+ l = gsl_hfile_pread (handle->hfile, handle->file_pos, FILE_BUFFER_SIZE - handle->bfill, handle->buffer + handle->bfill);
+ if (l > 0)
+ {
+ handle->bfill += l;
+ handle->file_pos += l;
+ }
+ else if (l == 0)
+ {
+ handle->eof = TRUE;
+ memset (handle->buffer + handle->bfill, 0, MAD_BUFFER_GUARD);
+ handle->bfill += MAD_BUFFER_GUARD;
+ handle->file_pos += MAD_BUFFER_GUARD; /* bogus, but doesn't matter at eof */
+ }
+
+ mad_stream_buffer (stream, handle->buffer, handle->bfill);
+
+ return l < 0 ? FALSE : TRUE;
+}
+
+static gboolean
+check_frame_validity (MadHandle *handle,
+ struct mad_header *header)
+{
+ guint frame_size = MAD_NSBSAMPLES (header) * 32;
+ gchar *reason = NULL;
+
+ if (frame_size <= 0)
+ reason = "frame_size < 1";
+
+ if (handle->frame_size && handle->dhandle.setup.n_channels)
+ {
+#if 0
+ if (frame_size != handle->frame_size)
+ reason = "frame with non-standard size";
+#endif
+ if (MAD_NCHANNELS (header) != handle->dhandle.setup.n_channels)
+ reason = "frame with non-standard channel count";
+ }
+
+ if (reason)
+ {
+ MAD_DEBUG ("skipping frame: %s", reason);
+ return FALSE;
+ }
+ else
+ return TRUE;
+}
+
+static gboolean
+read_next_frame_header (MadHandle *handle)
+{
+ gboolean succeeded = TRUE;
+
+ /* fetch next frame header */
+ if (mad_header_decode (&handle->frame.header, &handle->stream) < 0)
+ {
+ if (!MAD_RECOVERABLE (handle->stream.error) ||
+ handle->stream.error == MAD_ERROR_LOSTSYNC)
+ {
+ /* read on */
+ if (!stream_read (handle))
+ {
+ handle->error = handle->eof ? NULL : g_strerror (errno);
+ return FALSE;
+ }
+ return read_next_frame_header (handle); /* retry */
+ }
+
+ if (!check_frame_validity (handle, &handle->frame.header))
+ return read_next_frame_header (handle); /* retry */
+
+ succeeded = FALSE;
+ }
+
+ handle->error = handle->stream.error ? mad_stream_errorstr (&handle->stream) : NULL;
+
+ return succeeded;
+}
+
+static gboolean /* FALSE: handle->eof || handle->error != NULL */
+pcm_frame_read (MadHandle *handle,
+ gboolean synth)
+{
+ gboolean succeeded = TRUE;
+
+ if (mad_frame_decode (&handle->frame, &handle->stream) < 0)
+ {
+ if (!MAD_RECOVERABLE (handle->stream.error) ||
+ handle->stream.error == MAD_ERROR_LOSTSYNC)
+ {
+ /* MAD_RECOVERABLE()==TRUE: frame was read, decoding failed (about to skip frame)
+ * MAD_RECOVERABLE()==FALSE: frame was not read, need data
+ * note: MAD_RECOVERABLE (MAD_ERROR_LOSTSYNC) == TRUE
+ */
+
+ /* read on */
+ if (!stream_read (handle))
+ {
+ handle->error = handle->eof ? NULL : g_strerror (errno);
+ return FALSE;
+ }
+ return pcm_frame_read (handle, synth); /* retry */
+ }
+
+ succeeded = FALSE;
+ if (synth)
+ mad_frame_mute (&handle->frame);
+ }
+
+ handle->pcm_pos = handle->next_pcm_pos;
+ handle->pcm_length = handle->frame_size;
+ handle->next_pcm_pos += handle->pcm_length;
+
+ if (synth)
+ mad_synth_frame (&handle->synth, &handle->frame);
+
+ handle->error = handle->stream.error && !succeeded ? mad_stream_errorstr (&handle->stream) : NULL;
+
+ return succeeded;
+}
+
+static guint*
+create_seek_table (MadHandle *handle,
+ guint *n_seeks_p)
+{
+ guint *seeks = NULL;
+ guint offs, n_seeks = 0;
+
+ *n_seeks_p = 0;
+ mad_synth_finish (&handle->synth);
+ mad_frame_finish (&handle->frame);
+ mad_stream_finish (&handle->stream);
+ mad_stream_init (&handle->stream);
+ mad_frame_init (&handle->frame);
+ mad_synth_init (&handle->synth);
+ mad_stream_options (&handle->stream, handle->stream_options);
+
+ offs = 0;
+ /* lseek (handle->hfile, offs, SEEK_SET) */
+ handle->eof = FALSE;
+ handle->bfill = 0;
+ handle->file_pos = 0;
+
+ do
+ {
+ while (read_next_frame_header (handle))
+ {
+ guint this_pos = handle->file_pos - handle->bfill + handle->stream.this_frame - handle->buffer;
+ guint i = n_seeks++;
+
+ if (n_seeks > 256 * 1024) /* FIXME: max_frames */
+ {
+ g_free (seeks);
+ return NULL; /* FIXME: ETOOBIG */
+ }
+
+ if (gsl_alloc_upper_power2 (n_seeks) > gsl_alloc_upper_power2 (i))
+ seeks = g_renew (guint, seeks, gsl_alloc_upper_power2 (n_seeks));
+ seeks[i] = this_pos;
+
+ if (0)
+ {
+ if (mad_frame_decode (&handle->frame, &handle->stream) < 0)
+ MAD_DEBUG ("seektable frame read failed: %s", mad_stream_errorstr (&handle->stream));
+ mad_synth_frame (&handle->synth, &handle->frame);
+ MAD_DEBUG ("frame(%u) PCM:%u => FILE:%u FDIFF:%d (%x %x %x) br:%lu time:%ld/%lu mode:%u ext:%u flags:0x%x phase:%u",
+ i, i * handle->frame_size, this_pos, this_pos - seeks[MAX (i, 1) - 1],
+ handle->stream.this_frame[0], handle->stream.this_frame[1],
+ (handle->stream.this_frame[1] >> 1) & 3,
+ handle->frame.header.bitrate,
+ handle->frame.header.duration.seconds,
+ handle->frame.header.duration.fraction,
+ handle->frame.header.mode,
+ handle->frame.header.mode_extension,
+ handle->frame.header.flags,
+ handle->synth.phase);
+ }
+ }
+
+ if (!handle->eof)
+ {
+ MAD_DEBUG ("reading seektable frame failed: %s", handle->error ? handle->error : "Unknown");
+
+ /* frame read failed for a reason other than eof */
+ g_free (seeks);
+ return NULL; /* FIXME: EIO/errno */
+ }
+ }
+ while (!handle->eof);
+
+ /* reset file offset */
+ offs = 0;
+ /* lseek (handle->hfile, offs, SEEK_SET) */
+ handle->eof = FALSE;
+ handle->file_pos = 0;
+ handle->bfill = 0;
+
+ /* shrink table */
+ seeks = g_renew (guint, seeks, n_seeks);
+ *n_seeks_p = n_seeks;
+
+ return seeks;
+}
+
+static GslErrorType
+dh_mad_open (GslDataHandle *dhandle,
+ GslDataHandleSetup *setup)
+{
+ MadHandle *handle = (MadHandle*) dhandle;
+ GslHFile *hfile;
+ GslLong n;
+ gboolean seek_invalidated = FALSE;
+
+ hfile = gsl_hfile_open (handle->dhandle.name);
+ if (!hfile)
+ return gsl_error_from_errno (errno, GSL_ERROR_OPEN_FAILED);
+ handle->hfile = hfile;
+
+ seek_invalidated |= handle->seek_mtime != hfile->mtime;
+ handle->bfill = 0;
+ handle->eof = FALSE;
+ handle->pcm_pos = 0;
+ handle->pcm_length = 0;
+ handle->next_pcm_pos = 0;
+ handle->file_pos = 0;
+ mad_stream_init (&handle->stream);
+ mad_frame_init (&handle->frame);
+ mad_synth_init (&handle->synth);
+ mad_stream_options (&handle->stream, handle->stream_options);
+
+ /* fetch first frame */
+ if (!read_next_frame_header (handle))
+ goto OPEN_FAILED;
+
+ /* get n_channels, frame size and sample rate */
+ setup->bit_depth = 24;
+ setup->n_channels = MAD_NCHANNELS (&handle->frame.header);
+ n = MAD_NSBSAMPLES (&handle->frame.header) * 32;
+ seek_invalidated |= n != handle->frame_size;
+ handle->frame_size = n;
+ handle->sample_rate = handle->frame.header.samplerate;
+ if (setup->n_channels < 1 ||
+ setup->n_channels > MAX_CHANNELS ||
+ handle->frame_size < 1 ||
+ handle->sample_rate < 1)
+ goto OPEN_FAILED;
+
+ /* seek through the stream to collect frame positions */
+ if (seek_invalidated || !handle->n_seeks)
+ {
+ handle->seek_mtime = hfile->mtime;
+ handle->n_seeks = 0;
+ g_free (handle->seeks);
+ handle->seeks = NULL;
+ if (handle->skip_seek_table)
+ {
+ /* fake seek table */
+ handle->n_seeks = 1;
+ handle->seeks = g_new (guint, handle->n_seeks);
+ handle->seeks[0] = 0;
+ }
+ else
+ {
+ handle->seeks = create_seek_table (handle, &handle->n_seeks);
+ if (!handle->seeks)
+ goto OPEN_FAILED;
+ MAD_DEBUG ("frames in seektable: %u", handle->n_seeks);
+ }
+ }
+
+ /* validate/setup handle length */
+ n = handle->n_seeks * handle->frame_size * setup->n_channels;
+ if (n > 0)
+ setup->n_values = n;
+ else
+ goto OPEN_FAILED;
+
+ if (dh_mad_coarse_seek (&handle->dhandle, 0) != 0)
+ goto OPEN_FAILED;
+
+ return GSL_ERROR_NONE;
+
+ OPEN_FAILED:
+ g_free (handle->seeks);
+ handle->seeks = NULL;
+ handle->n_seeks = 0;
+ handle->seek_mtime = -1;
+ handle->bfill = 0;
+ handle->eof = FALSE;
+ handle->pcm_pos = 0;
+ handle->pcm_length = 0;
+ handle->next_pcm_pos = 0;
+ handle->file_pos = 0;
+ mad_synth_finish (&handle->synth);
+ mad_frame_finish (&handle->frame);
+ mad_stream_finish (&handle->stream);
+ gsl_hfile_close (handle->hfile);
+ handle->hfile = NULL;
+
+ return GSL_ERROR_OPEN_FAILED;
+}
+
+static GslLong
+dh_mad_read (GslDataHandle *dhandle,
+ GslLong voffset, /* in values */
+ GslLong n_values,
+ gfloat *values)
+{
+ MadHandle *handle = (MadHandle*) dhandle;
+ GslLong pos = voffset / dhandle->setup.n_channels;
+ gboolean frame_read_ok = TRUE;
+
+ if (pos < handle->pcm_pos ||
+ pos >= handle->pcm_pos + handle->pcm_length + SEEK_BY_READ_AHEAD (handle) * handle->frame_size)
+ {
+ GslLong tmp;
+
+ /* suckage, need to do lengthy seek in file */
+ tmp = dh_mad_coarse_seek (dhandle, voffset);
+ g_assert (tmp <= voffset);
+ }
+
+ while (pos >= handle->pcm_pos + handle->pcm_length)
+ frame_read_ok = pcm_frame_read (handle, TRUE);
+
+ /* check if the last call to pcm_frame_read() failed */
+ if (!frame_read_ok)
+ {
+ if (handle->stream.error == MAD_ERROR_BADDATAPTR)
+ {
+ /* if we encounter that the inter-frame accumulated layer-III state
+ * is not complete now, we'll try to increase the amount of frames
+ * we accumulate
+ */
+ if (handle->accumulate_state_frames < 10)
+ {
+ handle->accumulate_state_frames++;
+ MAD_DEBUG ("retrying seek with accumulate_state_frames=%d",
+ handle->accumulate_state_frames);
+
+ /* force dh_mad_read to retry the seek */
+ dh_mad_coarse_seek (dhandle, 0);
+ return dh_mad_read (dhandle, voffset, n_values, values);
+ }
+ else
+ {
+ MAD_DEBUG ("synthesizing frame failed, accumulate_state_frames is already %u: %s",
+ handle->accumulate_state_frames, handle->error);
+ return -1;
+ }
+ }
+ else
+ {
+ MAD_DEBUG ("failed to synthesize frame: %s", handle->error);
+ return -1;
+ }
+ }
+
+ n_values = MIN (n_values, handle->pcm_length * dhandle->setup.n_channels);
+
+ /* interleave into output buffer */
+ if (pos >= handle->pcm_pos && pos < handle->pcm_pos + handle->pcm_length)
+ {
+ guint offset = voffset - handle->pcm_pos * dhandle->setup.n_channels;
+ guint align = offset % dhandle->setup.n_channels;
+ guint n_samples = MIN (n_values, handle->pcm_length * dhandle->setup.n_channels - offset);
+ mad_fixed_t *pcm[MAX_CHANNELS];
+ gfloat *bound = values + n_samples;
+ guint i;
+
+ offset /= dhandle->setup.n_channels;
+ for (i = 0; i < dhandle->setup.n_channels; i++)
+ pcm[i] = handle->synth.pcm.samples[i] + offset + (i < align);
+
+ for (i = align; values < bound; values++)
+ {
+ mad_fixed_t mf = *(pcm[i]++);
+
+ *values = CLAMP (mf, -MAD_F_ONE, MAD_F_ONE) * (1. / (double) MAD_F_ONE);
+ if (++i >= dhandle->setup.n_channels)
+ i = 0;
+ }
+ return n_samples;
+ }
+ else /* something went wrong here, _badly_ */
+ {
+ MAD_MSG (GSL_ERROR_READ_FAILED,
+ "pcm position screwed (pos: %lu, handle-pos: %lu), aborting read",
+ pos, handle->pcm_pos);
+ return -1;
+ }
+}
+
+static GslLong
+dh_mad_coarse_seek (GslDataHandle *dhandle,
+ GslLong voffset)
+{
+ MadHandle *handle = (MadHandle*) dhandle;
+ GslLong opos = handle->pcm_pos, pos = voffset / dhandle->setup.n_channels;
+
+ if (voffset < 0) /* pcm_tell() */
+ return handle->pcm_pos * dhandle->setup.n_channels;
+
+ if (pos < handle->pcm_pos ||
+ pos >= handle->pcm_pos + handle->pcm_length + SEEK_BY_READ_AHEAD (handle))
+ {
+ GslLong offs = pos;
+ guint i, file_pos;
+
+ /* reset decoder state */
+ mad_synth_finish (&handle->synth);
+ mad_frame_finish (&handle->frame);
+ mad_stream_finish (&handle->stream);
+ mad_stream_init (&handle->stream);
+ mad_frame_init (&handle->frame);
+ mad_synth_init (&handle->synth);
+ mad_stream_options (&handle->stream, handle->stream_options);
+
+ /* seek to some frames read ahead to accumulate layer III IDCMT state */
+ offs -= (gint) (handle->frame_size * handle->accumulate_state_frames);
+ offs = CLAMP (offs, 0, (gint) (handle->n_seeks * handle->frame_size));
+
+ /* get file position from seek table */
+ i = offs / handle->frame_size;
+ file_pos = handle->seeks[i];
+
+ /* perform file seek and adjust positions */
+ /* lseek (handle->hfile, file_pos, SEEK_SET) */
+ handle->eof = FALSE;
+ handle->bfill = 0;
+ handle->file_pos = file_pos;
+ handle->pcm_pos = i * handle->frame_size;
+ handle->pcm_length = 0;
+ handle->next_pcm_pos = handle->pcm_pos;
+
+#if 0
+ /* adapt synth phase */
+ handle->synth.phase = ((i + 1) * (handle->frame_size / 32)) % 16;
+#endif
+
+ /* accumulate state */
+ if (pos >= handle->accumulate_state_frames * handle->frame_size)
+ {
+ guint i;
+ for (i = 0; i < handle->accumulate_state_frames; i++)
+ {
+ gboolean synth = i + 1 == handle->accumulate_state_frames;
+
+ if (!pcm_frame_read (handle, synth) && handle->stream.error != MAD_ERROR_BADDATAPTR)
+ MAD_DEBUG ("COARSE-SEEK: frame read ahead (%u): failed: %s", i, handle->error);
+ }
+ }
+
+ MAD_DEBUG ("seek-done: at %lu (f:%lu) want %lu (f:%lu) got %lu (f:%lu) diff %ld (diff-requested %ld)",
+ opos, opos / handle->frame_size,
+ pos, pos / handle->frame_size,
+ handle->pcm_pos, handle->pcm_pos / handle->frame_size,
+ handle->pcm_pos - opos, pos - opos);
+ }
+
+ return handle->pcm_pos * dhandle->setup.n_channels;
+}
+
+static void
+dh_mad_close (GslDataHandle *data_handle)
+{
+ MadHandle *handle = (MadHandle*) data_handle;
+
+ handle->bfill = 0;
+ handle->eof = FALSE;
+ handle->pcm_pos = 0;
+ handle->pcm_length = 0;
+ handle->next_pcm_pos = 0;
+ handle->file_pos = 0;
+ mad_synth_finish (&handle->synth);
+ mad_frame_finish (&handle->frame);
+ mad_stream_finish (&handle->stream);
+ gsl_hfile_close (handle->hfile);
+ handle->hfile = NULL;
+}
+
+static void
+dh_mad_destroy (GslDataHandle *data_handle)
+{
+ MadHandle *handle = (MadHandle*) data_handle;
+
+ g_free (handle->seeks);
+ handle->seeks = NULL;
+ handle->n_seeks = 0;
+ gsl_data_handle_common_free (data_handle);
+ gsl_delete_struct (MadHandle, handle);
+}
+
+static GslDataHandleFuncs dh_mad_vtable = {
+ dh_mad_open,
+ dh_mad_read,
+ dh_mad_close,
+ dh_mad_destroy,
+ dh_mad_coarse_seek,
+};
+
+static GslDataHandle*
+dh_mad_new (const gchar *file_name,
+ gboolean skip_seek_keep_open)
+{
+ MadHandle *handle;
+ gboolean success;
+
+ handle = gsl_new_struct0 (MadHandle, 1);
+ success = gsl_data_handle_common_init (&handle->dhandle, file_name);
+ if (success)
+ {
+ GslErrorType error;
+
+ handle->dhandle.vtable = &dh_mad_vtable;
+ handle->sample_rate = 0;
+ handle->frame_size = 0;
+ handle->stream_options = MAD_OPTION_IGNORECRC;
+ handle->accumulate_state_frames = 0;
+ handle->eof = FALSE;
+ handle->hfile = NULL;
+ handle->file_pos = 0;
+ handle->error = NULL;
+ handle->n_seeks = 0;
+ handle->seeks = NULL;
+ handle->seek_mtime = -1;
+ handle->bfill = 0;
+ handle->pcm_pos = handle->pcm_length = handle->next_pcm_pos = 0;
+
+ /* we can only check matters upon opening
+ */
+ handle->skip_seek_table = skip_seek_keep_open != FALSE;
+ error = gsl_data_handle_open (&handle->dhandle);
+ if (!error)
+ {
+ if (!skip_seek_keep_open)
+ gsl_data_handle_close (&handle->dhandle);
+ return &handle->dhandle;
+ }
+ gsl_data_handle_unref (&handle->dhandle);
+ return NULL;
+ }
+ else
+ {
+ g_free (handle->seeks);
+ gsl_delete_struct (MadHandle, handle);
+ return NULL;
+ }
+}
+
+GslDataHandle*
+gsl_data_handle_new_mad (const gchar *file_name)
+{
+ g_return_val_if_fail (file_name != NULL, NULL);
+
+ return dh_mad_new (file_name, FALSE);
+}
+
+GslErrorType
+gsl_data_handle_mad_testopen (const gchar *file_name,
+ guint *n_channels,
+ gfloat *mix_freq)
+{
+ GslDataHandle *dhandle;
+ MadHandle *handle;
+
+ g_return_val_if_fail (file_name != NULL, GSL_ERROR_INTERNAL);
+
+ dhandle = dh_mad_new (file_name, TRUE);
+ if (!dhandle)
+ return GSL_ERROR_OPEN_FAILED;
+
+ handle = (MadHandle*) dhandle;
+ if (n_channels)
+ *n_channels = handle->dhandle.setup.n_channels;
+ if (mix_freq)
+ *mix_freq = handle->sample_rate;
+ gsl_data_handle_close (dhandle);
+ gsl_data_handle_unref (dhandle);
+
+ return GSL_ERROR_NONE;
+}
+
+#else /* !GSL_HAVE_LIBMAD */
+
+GslDataHandle*
+gsl_data_handle_new_mad (const gchar *file_name)
+{
+ return NULL;
+}
+
+GslErrorType
+gsl_data_handle_mad_testopen (const gchar *file_name,
+ guint *n_channels,
+ gfloat *mix_freq)
+{
+ return GSL_ERROR_FORMAT_UNKNOWN;
+}
+
+#endif /* !GSL_HAVE_LIBMAD */
+
+/* vim:set ts=8 sts=2 sw=2: */