You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
arts/flow/gsl/gsldatahandle-vorbis.c

377 lines
9.6 KiB

/* 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 */