|
|
|
/* 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., 51 Franklin Street, Fifth Floor,
|
|
|
|
* Boston, MA 02110-1301, 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 */
|
|
|
|
|