summaryrefslogtreecommitdiffstats
path: root/flow/gsl/gsldatahandle-vorbis.c
diff options
context:
space:
mode:
Diffstat (limited to 'flow/gsl/gsldatahandle-vorbis.c')
-rw-r--r--flow/gsl/gsldatahandle-vorbis.c376
1 files changed, 376 insertions, 0 deletions
diff --git a/flow/gsl/gsldatahandle-vorbis.c b/flow/gsl/gsldatahandle-vorbis.c
new file mode 100644
index 0000000..9b84ae0
--- /dev/null
+++ b/flow/gsl/gsldatahandle-vorbis.c
@@ -0,0 +1,376 @@
+/* GSL - Generic Sound Layer
+ * Copyright (C) 2001-2002 Tim Janik
+ *
+ * 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 "gsldatahandle-vorbis.h"
+
+#if GSL_HAVE_OGGVORBIS
+#include "gslfilehash.h"
+#include <ogg/ogg.h>
+#include <vorbis/vorbisfile.h>
+#include <errno.h>
+
+
+/* --- defines --- */
+#define MAX_CHANNELS (16) /* hard limit, eases our life somewhat */
+/* number of values to decode and throw away instead of seeking. since
+ * seeking can be quite time consuming, this should cover a good range
+ * of seek-ahead space
+ */
+#define SEEK_BY_READ_AHEAD(vhandle) (vhandle->max_block_size * 8)
+
+
+/* --- structure --- */
+typedef struct {
+ GslDataHandle dhandle;
+
+ guint stream;
+ guint n_streams;
+
+ /* live data */
+ gint64 soffset; /* our PCM start offset */
+ guint max_block_size;
+
+ /* pcm read out cache */
+ GslLong pcm_pos, pcm_length;
+ gfloat *pcm[MAX_CHANNELS];
+
+ OggVorbis_File ofile;
+} VorbisHandle;
+
+
+/* --- functions --- */
+static GslErrorType
+ov_errno_to_error (gint ov_errno,
+ GslErrorType fallback)
+{
+ switch (ov_errno)
+ {
+ case OV_EOF: return GSL_ERROR_EOF;
+ case OV_EBADLINK:
+ case OV_EBADPACKET:
+ case OV_HOLE: return GSL_ERROR_DATA_CORRUPT;
+ case OV_EREAD: return GSL_ERROR_READ_FAILED;
+ case OV_ENOSEEK: return GSL_ERROR_SEEK_FAILED;
+ case OV_EFAULT:
+ case OV_EIMPL: return GSL_ERROR_CODEC_FAILURE;
+ case OV_EINVAL: return GSL_ERROR_INTERNAL;
+ case OV_ENOTAUDIO:
+ case OV_EVERSION:
+ case OV_EBADHEADER:
+ case OV_ENOTVORBIS: return GSL_ERROR_FORMAT_INVALID;
+ case OV_FALSE:
+ default: return fallback;
+ }
+}
+
+static size_t
+rfile_read (void *ptr,
+ size_t size,
+ size_t nmemb,
+ void *datasource)
+{
+ GslRFile *rfile = datasource;
+ return gsl_rfile_read (rfile, size * nmemb, ptr);
+}
+
+static int
+rfile_seek (void *datasource,
+ ogg_int64_t offset,
+ int whence)
+{
+ GslRFile *rfile = datasource;
+ GslLong l;
+ switch (whence)
+ {
+ default:
+ case SEEK_SET:
+ l = gsl_rfile_seek_set (rfile, offset);
+ break;
+ case SEEK_CUR:
+ l = gsl_rfile_position (rfile);
+ l = gsl_rfile_seek_set (rfile, l + offset);
+ break;
+ case SEEK_END:
+ l = gsl_rfile_length (rfile);
+ l = gsl_rfile_seek_set (rfile, l + offset);
+ break;
+ }
+ return l;
+}
+
+static int
+rfile_close (void *datasource)
+{
+ GslRFile *rfile = datasource;
+ gsl_rfile_close (rfile);
+ return 0;
+}
+
+static long
+rfile_tell (void *datasource)
+{
+ GslRFile *rfile = datasource;
+ return gsl_rfile_position (rfile);
+}
+
+static ov_callbacks rfile_ov_callbacks = {
+ rfile_read,
+ rfile_seek,
+ rfile_close,
+ rfile_tell,
+};
+
+static GslErrorType
+dh_vorbis_open (GslDataHandle *data_handle,
+ GslDataHandleSetup *setup)
+{
+ VorbisHandle *vhandle = (VorbisHandle*) data_handle;
+ GslRFile *rfile;
+ vorbis_info *vi;
+ GslLong n, i;
+ gint err;
+
+#if 0
+ file = fopen (vhandle->dhandle.name, "r");
+ if (!file)
+ return gsl_error_from_errno (errno, GSL_ERROR_OPEN_FAILED);
+ err = ov_open (file, &vhandle->ofile, NULL, 0);
+ if (err < 0)
+ {
+ fclose (file);
+ return ov_errno_to_error (err, GSL_ERROR_OPEN_FAILED);
+ }
+#endif
+
+ rfile = gsl_rfile_open (vhandle->dhandle.name);
+ if (!rfile)
+ return gsl_error_from_errno (errno, GSL_ERROR_OPEN_FAILED);
+ err = ov_open_callbacks (rfile, &vhandle->ofile, NULL, 0, rfile_ov_callbacks);
+ if (err < 0)
+ {
+ gsl_rfile_close (rfile);
+ return ov_errno_to_error (err, GSL_ERROR_OPEN_FAILED);
+ }
+
+ n = ov_streams (&vhandle->ofile);
+ if (n > vhandle->stream)
+ vhandle->n_streams = n;
+ else
+ {
+ ov_clear (&vhandle->ofile); /* closes file */
+ return GSL_ERROR_OPEN_FAILED;
+ }
+
+ vhandle->soffset = 0;
+ for (i = 0; i < vhandle->stream; i++)
+ vhandle->soffset += ov_pcm_total (&vhandle->ofile, i);
+
+ n = ov_pcm_total (&vhandle->ofile, vhandle->stream);
+ vi = ov_info (&vhandle->ofile, vhandle->stream);
+ if (n > 0 && vi && vi->channels && ov_pcm_seek (&vhandle->ofile, vhandle->soffset) >= 0)
+ {
+ setup->n_channels = vi->channels;
+ setup->n_values = n * setup->n_channels;
+ setup->bit_depth = 24;
+ }
+ else
+ {
+ ov_clear (&vhandle->ofile); /* closes file */
+ return GSL_ERROR_OPEN_FAILED;
+ }
+
+ vhandle->max_block_size = vorbis_info_blocksize (vi, 0);
+ n = vorbis_info_blocksize (vi, 1);
+ vhandle->max_block_size = MAX (vhandle->max_block_size, n);
+ vhandle->pcm_pos = 0;
+ vhandle->pcm_length = 0;
+
+ return GSL_ERROR_NONE;
+}
+
+static GslLong
+dh_vorbis_coarse_seek (GslDataHandle *dhandle,
+ GslLong voffset)
+{
+ VorbisHandle *vhandle = (VorbisHandle*) dhandle;
+ GslLong opos = vhandle->pcm_pos, pos = voffset / dhandle->setup.n_channels;
+
+ if (voffset < 0)
+ return vhandle->pcm_pos * dhandle->setup.n_channels;
+
+ if (pos < vhandle->pcm_pos ||
+ pos >= vhandle->pcm_pos + vhandle->pcm_length + SEEK_BY_READ_AHEAD (vhandle))
+ {
+ gint err = ov_pcm_seek_page (&vhandle->ofile, vhandle->soffset + pos);
+
+ if (err) /* eek */
+ err = ov_pcm_seek_page (&vhandle->ofile, vhandle->soffset);
+ else
+ vhandle->pcm_pos = ov_pcm_tell (&vhandle->ofile) - vhandle->soffset;
+ if (err || vhandle->pcm_pos < 0) /* urg, we're completely screwed */
+ vhandle->pcm_pos = 0;
+ vhandle->pcm_length = 0;
+ }
+ g_printerr ("OggS-SEEK: at %lu want %lu got %lu (diff-requested %ld)\n",
+ opos, pos, vhandle->pcm_pos, pos - opos);
+
+ return vhandle->pcm_pos * dhandle->setup.n_channels;
+}
+
+static void
+read_packet (VorbisHandle *vhandle)
+{
+ gfloat **pcm = NULL;
+ gint stream_id, i;
+
+ vhandle->pcm_pos = ov_pcm_tell (&vhandle->ofile) - vhandle->soffset;
+#if GSL_HAVE_OGGVORBIS_RC3
+ vhandle->pcm_length = ov_read_float (&vhandle->ofile, &pcm, &stream_id);
+#else
+ vhandle->pcm_length = ov_read_float (&vhandle->ofile, &pcm, (~0U>>1), &stream_id);
+#endif
+ if (vhandle->pcm_pos < 0 || vhandle->pcm_length < 0 || stream_id != vhandle->stream)
+ {
+ /* urg, this is bad! */
+ dh_vorbis_coarse_seek (&vhandle->dhandle, 0);
+ }
+ else
+ for (i = 0; i < vhandle->dhandle.setup.n_channels; i++)
+ vhandle->pcm[i] = pcm[i];
+}
+
+static GslLong
+dh_vorbis_read (GslDataHandle *dhandle,
+ GslLong voffset, /* in values */
+ GslLong n_values,
+ gfloat *values)
+{
+ VorbisHandle *vhandle = (VorbisHandle*) dhandle;
+ GslLong pos = voffset / dhandle->setup.n_channels;
+
+ if (pos < vhandle->pcm_pos ||
+ pos >= vhandle->pcm_pos + vhandle->pcm_length + SEEK_BY_READ_AHEAD (vhandle))
+ {
+ GslLong tmp;
+
+ /* suckage, needs to seek in file, this takes ages */
+ tmp = dh_vorbis_coarse_seek (dhandle, voffset);
+ g_assert (tmp <= voffset);
+ }
+
+ while (pos >= vhandle->pcm_pos + vhandle->pcm_length)
+ read_packet (vhandle);
+
+ n_values = MIN (n_values, vhandle->pcm_length * dhandle->setup.n_channels);
+
+ /* interleave into output buffer */
+ if (pos >= vhandle->pcm_pos && pos < vhandle->pcm_pos + vhandle->pcm_length)
+ {
+ guint offset = voffset - vhandle->pcm_pos * dhandle->setup.n_channels;
+ guint align = offset % dhandle->setup.n_channels;
+ guint n_samples = MIN (n_values, vhandle->pcm_length * dhandle->setup.n_channels - offset);
+ gfloat *pcm[MAX_CHANNELS], *bound = values + n_samples;
+ guint i;
+
+ offset /= dhandle->setup.n_channels;
+ for (i = 0; i < dhandle->setup.n_channels; i++)
+ pcm[i] = vhandle->pcm[i] + offset + (i < align);
+
+ for (i = align; values < bound; values++)
+ {
+ gfloat f = *(pcm[i]++);
+
+ f = CLAMP (f, -1.0, 1.0);
+ *values = f;
+ if (++i >= dhandle->setup.n_channels)
+ i = 0;
+ }
+ return n_samples;
+ }
+ else /* something went wrong here, _badly_ */
+ return 0;
+}
+
+static void
+dh_vorbis_close (GslDataHandle *dhandle)
+{
+ VorbisHandle *vhandle = (VorbisHandle*) dhandle;
+
+ ov_clear (&vhandle->ofile);
+ vhandle->pcm_pos = 0;
+ vhandle->pcm_length = 0;
+}
+
+static void
+dh_vorbis_destroy (GslDataHandle *data_handle)
+{
+ VorbisHandle *vhandle = (VorbisHandle*) data_handle;
+
+ gsl_data_handle_common_free (data_handle);
+ gsl_delete_struct (VorbisHandle, vhandle);
+}
+
+static GslDataHandleFuncs dh_vorbis_vtable = {
+ dh_vorbis_open,
+ dh_vorbis_read,
+ dh_vorbis_close,
+ dh_vorbis_destroy,
+ dh_vorbis_coarse_seek,
+};
+
+GslDataHandle*
+gsl_data_handle_new_ogg_vorbis (const gchar *file_name,
+ guint lbitstream)
+{
+ VorbisHandle *vhandle;
+ gboolean success;
+
+ g_return_val_if_fail (file_name != NULL, NULL);
+
+ vhandle = gsl_new_struct0 (VorbisHandle, 1);
+ success = gsl_data_handle_common_init (&vhandle->dhandle, file_name);
+ if (success)
+ {
+ GslErrorType error;
+
+ vhandle->dhandle.vtable = &dh_vorbis_vtable;
+ vhandle->n_streams = 0;
+ vhandle->stream = lbitstream;
+
+ /* we can only check matters upon opening
+ */
+ error = gsl_data_handle_open (&vhandle->dhandle);
+ if (!error)
+ {
+ gsl_data_handle_close (&vhandle->dhandle);
+ return &vhandle->dhandle;
+ }
+ else
+ {
+ gsl_data_handle_unref (&vhandle->dhandle);
+ return NULL;
+ }
+ }
+ else
+ {
+ gsl_delete_struct (VorbisHandle, vhandle);
+ return NULL;
+ }
+}
+#endif /* GSL_HAVE_OGGVORBIS */
+