/* $Id: Decoder.xs,v 1.2 2004/07/18 03:40:10 daniel Exp $ */
/* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* Chunks of this code have been borrowed and influenced
* by flac/decode.c and the flac XMMS plugin.
*
*/
#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>
#include <FLAC/all.h>
#include "include/common.h"
#include "include/dither.h"
#include "include/replaygain_synthesis.h"
#ifdef _MSC_VER
# define alloca _alloca
#endif
/* strlen the length automatically */
#define my_hv_store(a,b,c) hv_store(a,b,strlen(b),c,0)
#define my_hv_fetch(a,b) hv_fetch(a,b,strlen(b),0)
/* Create some generic (and shorter) names for these types. */
typedef FLAC__StreamDecoder decoder_t;
typedef FLAC__StreamDecoderReadStatus read_status_t;
#define FLACdecoder_new() FLAC__stream_decoder_new()
#define FLACdecoder_init(a,b,c,d,e,f,g,h,i,j) FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j)
#define FLACdecoder_process_metadata(x) FLAC__stream_decoder_process_until_end_of_metadata(x)
#define FLACdecoder_process_single(x) FLAC__stream_decoder_process_single(x)
#define FLACdecoder_finish(x) FLAC__stream_decoder_finish(x)
#define FLACdecoder_delete(x) FLAC__stream_decoder_delete(x)
#define FLACdecoder_set_read_callback(x, y) FLAC__stream_decoder_set_read_callback(x, y)
#define FLACdecoder_set_write_callback(x, y) FLAC__stream_decoder_set_write_callback(x, y)
#define FLACdecoder_set_metadata_callback(x, y) FLAC__stream_decoder_set_metadata_callback(x, y)
#define FLACdecoder_set_error_callback(x, y) FLAC__stream_decoder_set_error_callback(x, y)
#define FLACdecoder_set_client_data(x, y) FLAC__stream_decoder_set_client_data(x, y)
#define FLACdecoder_set_seek_callback(x, y) FLAC__stream_decoder_set_seek_callback(x, y)
#define FLACdecoder_set_tell_callback(x, y) FLAC__stream_decoder_set_tell_callback(x, y)
#define FLACdecoder_set_length_callback(x, y) FLAC__stream_decoder_set_length_callback(x, y)
#define FLACdecoder_set_eof_callback(x, y) FLAC__stream_decoder_set_eof_callback(x, y)
#define FLACdecoder_seek_absolute(x, y) FLAC__stream_decoder_seek_absolute(x, y)
#define FLACdecoder_get_state(x) FLAC__stream_decoder_get_state(x)
#define FLACdecoder_get_channels(x) FLAC__stream_decoder_get_channels(x)
#define FLACdecoder_get_blocksize(x) FLAC__stream_decoder_get_blocksize(x)
#define FLACdecoder_get_sample_rate(x) FLAC__stream_decoder_get_sample_rate(x)
#define FLACdecoder_get_bits_per_sample(x) FLAC__stream_decoder_get_bits_per_sample(x)
#define FLACdecoder_get_decode_position(x, y) FLAC__stream_decoder_get_decode_position(x, y)
#define SAMPLES_PER_WRITE 512
typedef struct {
/* i.e. specification string started with + or - */
FLAC__bool is_relative;
FLAC__bool value_is_samples;
union {
double seconds;
FLAC__int64 samples;
} value;
} SkipUntilSpecification;
/* Allow multiple instances of the decoder object. Stuff each filehandle into (void*)stream */
typedef struct {
int abort_flag;
int bytes_streamed;
int is_streaming;
FLAC__uint64 stream_length;
void *buffer;
PerlIO *stream;
decoder_t *decoder;
FLAC__bool has_replaygain;
/* (24/8) for max bytes per sample */
FLAC__byte sample_buffer[SAMPLES_PER_WRITE * FLAC__MAX_SUPPORTED_CHANNELS * (24/8)];
FLAC__int32 reservoir[FLAC__MAX_BLOCK_SIZE * 2 * FLAC__MAX_SUPPORTED_CHANNELS];
FLAC__uint64 decode_position_last;
FLAC__uint64 decode_position_frame_last;
FLAC__uint64 decode_position_frame;
unsigned buffer_size;
FLAC__int64 total_samples;
unsigned bps;
unsigned channels;
FLAC__int64 sample_rate;
FLAC__int64 length_in_msec;
unsigned wide_samples_in_reservoir;
SkipUntilSpecification skip_specification;
SkipUntilSpecification until_specification;
} flac_datasource;
/* start all the callbacks here. */
static void meta_callback(
const decoder_t *decoder,
const FLAC__StreamMetadata *metadata, void *client_data) {
flac_datasource *datasource = (flac_datasource *)client_data;
if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
FLAC__uint64 skip, until;
/* flac__utils_canonicalize_skip_until_specification(decoder_session->skip_specification, decoder_session->sample_rate);
FLAC__ASSERT(datasource->skip_specification->value.samples >= 0); */
skip = (FLAC__uint64)datasource->skip_specification.value.samples;
/* remember, metadata->data.stream_info.total_samples can be 0, meaning 'unknown' */
if (metadata->data.stream_info.total_samples > 0 && skip >= metadata->data.stream_info.total_samples) {
warn("ERROR trying to skip more samples than in stream\n");
datasource->abort_flag = true;
return;
} else if (metadata->data.stream_info.total_samples == 0 && skip > 0) {
warn("ERROR, can't skip when FLAC metadata has total sample count of 0\n");
datasource->abort_flag = true;
return;
}
datasource->bps = metadata->data.stream_info.bits_per_sample;
datasource->channels = metadata->data.stream_info.channels;
datasource->sample_rate = metadata->data.stream_info.sample_rate;
datasource->total_samples = metadata->data.stream_info.total_samples - skip;
datasource->length_in_msec = datasource->total_samples * 10 / (datasource->sample_rate / 100);
/* if (!canonicalize_until_specification(
datasource->until_specification, datasource->inbasefilename,
datasource>n->sample_rate, skip, metadata->data.stream_info.total_samples)) {
datasource->abort_flag = true;
return;
} */
FLAC__ASSERT(datasource->until_specification.value.samples >= 0);
until = (FLAC__uint64)datasource->until_specification.value.samples;
if (until > 0) {
datasource->total_samples -= (metadata->data.stream_info.total_samples - until);
}
if (datasource->bps != 8 && datasource->bps != 16 && datasource->bps != 24) {
warn("ERROR: bits per sample is not 8/16/24\n");
datasource->abort_flag = true;
return;
}
}
}
static void error_callback(
const decoder_t *decoder,
FLAC__StreamDecoderErrorStatus status, void *client_data) {
/* flac_datasource *datasource = (flac_datasource *)client_data; */
warn("FLAC decoder error_callback: %s\n", status);
}
static FLAC__StreamDecoderSeekStatus seek_callback(
const decoder_t *decoder,
FLAC__uint64 absolute_byte_offset, void *client_data) {
flac_datasource *datasource = (flac_datasource *)client_data;
/* can't seek on a socket */
if (datasource->is_streaming) {
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
}
if (PerlIO_seek(datasource->stream, absolute_byte_offset, SEEK_SET) >= 0) {
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
}
static FLAC__StreamDecoderTellStatus tell_callback(
const decoder_t *decoder,
FLAC__uint64 *absolute_byte_offset, void *client_data) {
flac_datasource *datasource = (flac_datasource *)client_data;
FLAC__uint64 pos = -1;
/* can't tell on a socket */
if (datasource->is_streaming) {
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
}
pos = PerlIO_tell(datasource->stream);
if (pos < 0) {
return FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
}
*absolute_byte_offset = pos;
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
static FLAC__StreamDecoderLengthStatus length_callback(
const decoder_t *decoder,
FLAC__uint64 *stream_length, void *client_data) {
flac_datasource *datasource = (flac_datasource *)client_data;
/* can't find the total length of a socket */
if (datasource->is_streaming) {
return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
}
*stream_length = datasource->stream_length;
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
static FLAC__bool eof_callback(
const decoder_t *decoder, void *client_data) {
flac_datasource *datasource = (flac_datasource *)client_data;
FLAC__uint64 pos = 0;
if (datasource->is_streaming) {
return false;
}
pos = PerlIO_tell(datasource->stream);
if (pos >= 0 && pos >= datasource->stream_length) {
/* printf("stream length: %d pos: %d\n", datasource->stream_length, pos); */
return true;
}
return false;
}
static read_status_t read_callback(
const decoder_t *decoder,
FLAC__byte buffer[], size_t *bytes, void *client_data) {
flac_datasource *datasource = (flac_datasource *)client_data;
*bytes = PerlIO_read(datasource->stream, buffer, *bytes);
datasource->buffer_size = *bytes;
if (*bytes == 0)
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
if (*bytes < 0)
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
static FLAC__StreamDecoderWriteStatus write_callback(
const decoder_t *decoder,
const FLAC__Frame *frame, const FLAC__int32 * const buffer[],
void *client_data) {
flac_datasource *datasource = (flac_datasource *)client_data;
const unsigned channels = frame->header.channels;
const unsigned wide_samples = frame->header.blocksize;
unsigned wide_sample, sample, channel;
if (datasource->abort_flag) {
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
for (sample = datasource->wide_samples_in_reservoir * channels,
wide_sample = 0; wide_sample < wide_samples; wide_sample++) {
for (channel = 0; channel < channels; channel++, sample++) {
datasource->reservoir[sample] = buffer[channel][wide_sample];
}
}
datasource->wide_samples_in_reservoir += wide_samples;
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
MODULE = Audio::FLAC::Decoder PACKAGE = Audio::FLAC::Decoder
PROTOTYPES: DISABLE
SV*
open(class, path)
char *class;
SV *path;
CODE:
/* for setting the stream length */
FLAC__uint64 pos;
/* Create our new self and a ref to it - all of these are cleaned up in DESTROY */
HV *self = newHV();
SV *obj_ref = newRV_noinc((SV*) self);
/* our stash for streams */
flac_datasource *datasource = safemalloc(sizeof(flac_datasource));
/* holder for the decoder itself */
datasource->decoder = FLACdecoder_new();
/* 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) {
FLACdecoder_finish(datasource->decoder);
FLACdecoder_delete(datasource->decoder);
safefree(datasource);
warn("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 (!datasource->is_streaming) {
pos = PerlIO_tell(datasource->stream);
if (PerlIO_seek(datasource->stream, 0, SEEK_END) != -1) {
datasource->stream_length = PerlIO_tell(datasource->stream);
if (PerlIO_seek(datasource->stream, pos, SEEK_SET) == -1) {
FLACdecoder_finish(datasource->decoder);
FLACdecoder_delete(datasource->decoder);
safefree(datasource);
warn("failed on seek to beginning: [%d] - [%s]\n", errno, strerror(errno));
XSRETURN_UNDEF;
}
}
}
if (FLACdecoder_init(datasource->decoder,
read_callback,
seek_callback,
tell_callback,
length_callback,
eof_callback,
write_callback,
meta_callback,
error_callback,
datasource) != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
warn("Failed on initializing the decoder: [%d]\n", FLACdecoder_get_state(datasource->decoder));
FLACdecoder_finish(datasource->decoder);
FLACdecoder_delete(datasource->decoder);
safefree(datasource);
XSRETURN_UNDEF;
}
/* skip ahead to the pcm data */
FLACdecoder_process_metadata(datasource->decoder);
datasource->bytes_streamed = 0;
datasource->decode_position_last = 0;
datasource->decode_position_frame = 0;
datasource->decode_position_frame_last = 0;
/* initalize bitrate, channels, etc */
/*__read_info(self, vf); */
/* Values stored at base level */
my_hv_store(self, "PATH", newSVsv(path));
my_hv_store(self, "DATASOURCE", newSViv((IV) datasource));
/*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
sysread(obj, buffer, nbytes = 1024)
SV* obj;
SV* buffer;
int nbytes;
CODE:
{
int total_bytes_read = 0;
unsigned blocksize = 1;
char *readBuffer = alloca(nbytes);
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
if (!datasource) XSRETURN_UNDEF;
if (!datasource->decoder) XSRETURN_UNDEF;
while (datasource->wide_samples_in_reservoir < SAMPLES_PER_WRITE) {
unsigned s = datasource->wide_samples_in_reservoir;
if (FLACdecoder_get_state(datasource->decoder) == FLAC__STREAM_DECODER_END_OF_STREAM ) {
break;
} else if (!FLACdecoder_process_single(datasource->decoder)) {
warn("Audio::FLAC::Decoder - read error while processing frame.\n");
break;
}
blocksize = datasource->wide_samples_in_reservoir - s;
datasource->decode_position_frame_last = datasource->decode_position_frame;
if (!FLACdecoder_get_decode_position(datasource->decoder, &datasource->decode_position_frame)) {
datasource->decode_position_frame = 0;
}
}
while (nbytes > 0) {
if (datasource->wide_samples_in_reservoir <= 0) {
break;
} else {
const unsigned channels = FLACdecoder_get_channels(datasource->decoder);
const unsigned bps = FLACdecoder_get_bits_per_sample(datasource->decoder);
const unsigned n = min(datasource->wide_samples_in_reservoir, SAMPLES_PER_WRITE);
const unsigned delta = n * channels;
unsigned i;
int bytes = (int)pack_pcm_signed_little_endian(
datasource->sample_buffer, datasource->reservoir, n, channels, bps, bps
);
for (i = delta; i < datasource->wide_samples_in_reservoir * channels; i++) {
datasource->reservoir[i-delta] = datasource->reservoir[i];
}
datasource->wide_samples_in_reservoir -= n;
readBuffer = datasource->sample_buffer;
total_bytes_read += bytes;
readBuffer += bytes;
nbytes -= bytes;
datasource->decode_position_last =
datasource->decode_position_frame -
datasource->wide_samples_in_reservoir *
(datasource->decode_position_frame - datasource->decode_position_frame_last) /
blocksize;
}
}
/* copy the buffer into our passed SV* */
sv_setpvn(buffer, readBuffer-total_bytes_read, total_bytes_read);
if (total_bytes_read < 0) XSRETURN_UNDEF;
RETVAL = total_bytes_read;
}
OUTPUT:
RETVAL
void
DESTROY (obj)
SV* obj;
CODE:
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
FLACdecoder_finish(datasource->decoder);
FLACdecoder_delete(datasource->decoder);
safefree(datasource);
IV
channels (obj)
SV* obj;
CODE:
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
RETVAL = FLACdecoder_get_channels(datasource->decoder);
OUTPUT:
RETVAL
IV
bits_per_sample (obj)
SV* obj;
CODE:
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
RETVAL = FLACdecoder_get_bits_per_sample(datasource->decoder);
OUTPUT:
RETVAL
IV
sample_rate (obj)
SV* obj;
CODE:
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
RETVAL = FLACdecoder_get_sample_rate(datasource->decoder);
OUTPUT:
RETVAL
IV
raw_seek (obj, pos, whence)
SV* obj;
long pos;
int whence;
CODE:
{
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
/* can't seek on a socket. */
if (datasource->is_streaming) {
XSRETURN_UNDEF;
}
if (!FLAC__stream_decoder_reset(datasource->decoder)) {
XSRETURN_UNDEF;
}
RETVAL = PerlIO_seek(datasource->stream, pos, whence);
}
OUTPUT:
RETVAL
FLAC__uint64
raw_tell (obj)
SV* obj;
CODE:
{
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
FLAC__uint64 decode_position = 0;
/* this is effectively doing a ftell() */
if (!FLACdecoder_get_decode_position(datasource->decoder, &decode_position)) {
decode_position = -1;
}
RETVAL = decode_position;
}
OUTPUT:
RETVAL
IV
sample_seek (obj, sample)
SV* obj;
IV sample;
CODE:
{
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
RETVAL = FLACdecoder_seek_absolute(datasource->decoder, sample);
}
OUTPUT:
RETVAL
FLAC__uint64
time_seek (obj, seconds)
SV* obj;
IV seconds;
CODE:
{
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
const double distance = (double)seconds * 1000.0 / (double)datasource->length_in_msec;
unsigned target_sample = (unsigned)(distance * (double)datasource->total_samples);
if (FLACdecoder_seek_absolute(datasource->decoder, (FLAC__uint64)target_sample)) {
if (!FLACdecoder_get_decode_position(datasource->decoder, &datasource->decode_position_frame)) {
datasource->decode_position_frame = 0;
}
datasource->wide_samples_in_reservoir = 0;
}
RETVAL = datasource->decode_position_frame;
}
OUTPUT:
RETVAL
FLAC__uint64
time_tell (obj)
SV* obj;
CODE:
{
HV *self = (HV *) SvRV(obj);
flac_datasource *datasource = (flac_datasource *) SvIV(*(my_hv_fetch(self, "DATASOURCE")));
FLAC__uint64 decode_position = 0;
/* float time_position = 0; */
if (!FLACdecoder_get_decode_position(datasource->decoder, &decode_position)) {
decode_position = -1;
} else {
/* time_position = metadata->data.stream_info.total_samples * 10 /
(metadata->data.stream_info.sample_rate / 100);
*/
}
RETVAL = decode_position;
}
OUTPUT:
RETVAL