Ogg-Vorbis-Decoder
view release on metacpan or search on metacpan
/* $Id: Decoder.xs 348 2005-07-14 02:32:46Z dsully $ */
#ifdef __cplusplus
"C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#ifdef __cplusplus
}
#endif
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <stdlib.h>
#ifdef _MSC_VER
# define alloca _alloca
#endif
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
/* strlen the length automatically */
#define my_hv_store(a,b,c) (void)hv_store(a,b,strlen(b),c,0)
#define my_hv_fetch(a,b) hv_fetch(a,b,strlen(b),0)
#ifdef WORDS_BIGENDIAN
#define host_is_big_endian() TRUE
#else
#define host_is_big_endian() FALSE
#endif
int endian = host_is_big_endian();
static size_t ovcb_read(void *ptr, size_t size, size_t nmemb, void *datasource);
static int ovcb_seek(void *datasource, ogg_int64_t offset, int whence);
static int ovcb_close(void *datasource);
static long ovcb_tell(void *datasource);
/* http://www.xiph.org/ogg/vorbis/doc/vorbisfile/ov_callbacks.html */
ov_callbacks vorbis_callbacks = {
ovcb_read,
ovcb_seek,
ovcb_close,
ovcb_tell
};
/* Allow multiple instances of the decoder object. Stuff each filehandle into (void*)stream */
typedef struct {
int is_streaming;
int bytes_streamed;
int last_bitstream;
PerlIO *stream;
} ocvb_datasource;
/* useful items from XMMS */
static size_t ovcb_read(void *ptr, size_t size, size_t nmemb, void *vdatasource) {
size_t read_bytes = 0;
ocvb_datasource *datasource = vdatasource;
read_bytes = PerlIO_read(datasource->stream, ptr, size * nmemb);
datasource->bytes_streamed += read_bytes;
return read_bytes;
}
static int ovcb_seek(void *vdatasource, ogg_int64_t offset, int whence) {
ocvb_datasource *datasource = vdatasource;
if (datasource->is_streaming) {
return -1;
}
/* For some reason PerlIO_seek fails miserably here. < 5.8.1 works */
/* return PerlIO_seek(datasource->stream, offset, whence); */
return fseek(PerlIO_findFILE(datasource->stream), offset, whence);
}
static int ovcb_close(void *vdatasource) {
ocvb_datasource *datasource = vdatasource;
return PerlIO_close(datasource->stream);
}
static long ovcb_tell(void *vdatasource) {
ocvb_datasource *datasource = vdatasource;
if (datasource->is_streaming) {
return datasource->bytes_streamed;
}
return PerlIO_tell(datasource->stream);
}
/* Loads the commments from the stream and fills the object's hash */
void __read_comments(HV *self, OggVorbis_File *vf) {
int i;
char *half;
/* XXX - rename these */
HV *comments = newHV();
SV *ts;
AV *ta;
vorbis_comment *vc = ov_comment(vf, -1);
/* return early if there are no comments */
if (!vc) return;
for (i = 0; i < vc->comments; ++i) {
half = strchr(vc->user_comments[i], '=');
if (half == NULL) {
warn("Comment \"%s\" missing \'=\', skipping...\n", vc->user_comments[i]);
continue;
}
if (!hv_exists(comments, vc->user_comments[i], half - vc->user_comments[i])) {
ta = newAV();
ts = newRV_noinc((SV*) ta);
(void)hv_store(comments, vc->user_comments[i], half - vc->user_comments[i], ts, 0);
} else {
ta = (AV*) SvRV(*(hv_fetch(comments, vc->user_comments[i], half - vc->user_comments[i], 0)));
}
av_push(ta, newSVpv(half + 1, 0));
}
my_hv_store(self, "COMMENTS", newRV_noinc((SV*) comments));
}
void __read_info(HV *self, OggVorbis_File *vf) {
HV *info = newHV();
vorbis_info *vi = ov_info(vf, -1);
if (!vi) return;
my_hv_store(info, "version", newSViv(vi->version));
my_hv_store(info, "channels", newSViv(vi->channels));
my_hv_store(info, "rate", newSViv(vi->rate));
my_hv_store(info, "bitrate_upper", newSViv(vi->bitrate_upper));
my_hv_store(info, "bitrate_nominal", newSViv(vi->bitrate_nominal));
my_hv_store(info, "bitrate_lower", newSViv(vi->bitrate_lower));
my_hv_store(info, "bitrate_window", newSViv(vi->bitrate_window));
my_hv_store(info, "length", newSVnv(ov_time_total(vf, -1)));
my_hv_store(self, "INFO", newRV_noinc((SV*) info));
}
MODULE = Ogg::Vorbis::Decoder PACKAGE = Ogg::Vorbis::Decoder
SV*
open(class, path)
char *class;
SV *path;
CODE:
int ret;
/* Create our new self and a ref to it - all of these are cleaned up
* in DESTROY by ov_clear() and safefree() */
HV *self = newHV();
SV *obj_ref = newRV_noinc((SV*) self);
/* holder for the VF itself */
OggVorbis_File *vf = (OggVorbis_File *) safemalloc(sizeof(OggVorbis_File));
/* our stash for streams */
ocvb_datasource *datasource = (ocvb_datasource *) safemalloc(sizeof(ocvb_datasource));
memset(datasource, 0, sizeof(ocvb_datasource));
/* check and see if a pathname was passed in, otherwise it might be a
* IO::Socket subclass, or even a *FH Glob */
if (SvOK(path) && (SvTYPE(SvRV(path)) != SVt_PVGV)) {
if ((datasource->stream = PerlIO_open((char*)SvPV_nolen(path), "r")) == NULL) {
safefree(vf);
printf("failed on open: [%d] - [%s]\n", errno, strerror(errno));
XSRETURN_UNDEF;
}
datasource->is_streaming = 0;
} else if (SvOK(path)) {
/* Did we get a Glob, or a IO::Socket subclass?
*
* XXX This should really be a class method so the caller
* can tell us if it's streaming or not. But how to do this on
* a per object basis without changing open()s arugments. That
* may be the easiest/only way. XXX
*
*/
if (sv_isobject(path) && sv_derived_from(path, "IO::Socket")) {
datasource->is_streaming = 1;
} else {
datasource->is_streaming = 0;
}
/* dereference and get the SV* that contains the Magic & FH,
* then pull the fd from the PerlIO object */
datasource->stream = IoIFP(GvIOp(SvRV(path)));
} else {
XSRETURN_UNDEF;
}
if ((ret = ov_open_callbacks((void*)datasource, vf, NULL, 0, vorbis_callbacks)) < 0) {
warn("Failed on registering callbacks: [%d]\n", ret);
printf("failed on open: [%d] - [%s]\n", errno, strerror(errno));
ov_clear(vf);
XSRETURN_UNDEF;
}
datasource->bytes_streamed = 0;
datasource->last_bitstream = -1;
/* initalize bitrate, channels, etc */
__read_info(self, vf);
/* Values stored at base level */
my_hv_store(self, "PATH", newSVsv(path));
my_hv_store(self, "VFILE", newSViv((IV) vf));
my_hv_store(self, "BSTREAM", newSViv(0));
my_hv_store(self, "READCOMMENTS", newSViv(1));
/* Bless the hashref to create a class object */
sv_bless(obj_ref, gv_stashpv(class, FALSE));
RETVAL = obj_ref;
OUTPUT:
RETVAL
long
read(obj, buffer, nbytes = 4096, word = 2, sgned = 1)
SV* obj;
SV* buffer;
int nbytes;
int word;
int sgned;
ALIAS:
sysread = 1
CODE:
{
int bytes = 0;
int total_bytes_read = 0;
int read_comments = 0;
int old_bitstream, cur_bitstream;
char *readBuffer = alloca(nbytes);
/* for replay gain */
/* not yet.. */
int use_rg = 0;
float ***pcm = NULL;
HV *self = (HV *) SvRV(obj);
OggVorbis_File *vf = (OggVorbis_File *) SvIV(*(my_hv_fetch(self, "VFILE")));
if (!vf) XSRETURN_UNDEF;
if (ix) {
/* empty */
}
/* See http://www.xiph.org/ogg/vorbis/doc/vorbisfile/ov_read.html for
* a description of the bitstream parameter. This allows streaming
* without a hack like icy-metaint */
cur_bitstream = (int) SvIV(*(my_hv_fetch(self, "BSTREAM")));
old_bitstream = cur_bitstream;
/* When we get a new bitstream, re-read the comment fields */
read_comments = (int)SvIV(*(my_hv_fetch(self, "READCOMMENTS")));
/* The nbytes argument to ov_read is only a limit, not a request. So
* read until we hit the requested number of bytes */
while (nbytes > 0) {
if (use_rg) {
bytes = ov_read_float(vf, pcm, nbytes, &cur_bitstream);
if (bytes > 0) {
/* bytes = vorbis_process_replaygain(pcm, bytes, channels, readBuffer, rg_scale); */
}
} else {
bytes = ov_read(vf, readBuffer, nbytes, endian, word, sgned, &cur_bitstream);
}
if (bytes && read_comments != 0) {
__read_comments(self, vf);
read_comments = 0;
}
if (bytes == 0) {
/* eof */
break;
} else if (bytes == OV_HOLE || bytes == OV_EBADLINK) {
/* error in stream, but we don't care, move along */
} else if (bytes < 0 && errno == EINTR) {
/* try to re-read, same as above */
} else if (bytes < 0) {
/* error */
break;
} else {
total_bytes_read += bytes;
readBuffer += bytes;
nbytes -= bytes;
/* did we enter a new logical bitstream? */
if (old_bitstream != cur_bitstream && old_bitstream != -1) {
__read_info(self, vf);
read_comments = 1;
break;
}
}
}
/* update with our new bitstream */
( run in 0.691 second using v1.01-cache-2.11-cpan-39bf76dae61 )