#include <vorbis/vorbisfile.h>
#include "filter.h"
#include "log.h"
#include "misc.h"
#include "tag.h"
#include "vorbis.h"
static const char * errormsg(int code)
{
switch (code)
{
case OV_HOLE: return "missing or corrupt data encountered";
case OV_EREAD: return "read error";
case OV_EFAULT: return "internal decoder failure";
case OV_EIMPL: return "feature not implemented";
case OV_EINVAL: return "invalid or uninitialized argument";
case OV_ENOTVORBIS: return "not a Vorbis file";
case OV_EBADHEADER: return "corrupted or undecipherable Ogg header";
case OV_EVERSION: return "obsolete bitstream version";
case OV_ENOTAUDIO: return "packet not containing audio data found";
case OV_EBADPACKET: return "invalid packet found";
case OV_EBADLINK: return "corrupt bitstream";
case OV_ENOSEEK: return "bitstream not seekable";
case OV_FALSE:
default: return "unknown error";
}
}
void * vorbis_init(int fd, Decoder *dec)
{
const char *path = dec->tinfo->path;
OggVorbis_File *vf = xmalloc(sizeof(OggVorbis_File));
FILE *stream = fdopen(fd, "rb");
if (!stream)
{
log_append(LOG_ERR, "%s: couldn't fdopen", path);
return NULL;
}
const int ret = ov_open(stream, vf, NULL, 0);
if (ret)
{
log_append(LOG_WARNING, "%s: %s", path, errormsg(ret));
vorbis_free(vf);
return NULL;
}
const vorbis_info *vi = ov_info(vf, -1);
if (!vi)
{
log_append(LOG_WARNING, "%s: %s", path, errormsg(OV_EINVAL));
vorbis_free(vf);
return NULL;
}
if (vi->channels > 2)
{
log_append(LOG_ERR, "%s: only stereo and mono are supported",
path);
vorbis_free(vf);
return NULL;
}
dec->tinfo->format.bits = 0;
dec->tinfo->format.rate = vi->rate;
dec->tinfo->format.channels = vi->channels;
dec->tinfo->bitrate = vi->bitrate_nominal;
dec->tinfo->duration = duration_init(ov_pcm_total(vf, -1), vi->rate);
dec->tinfo->position = duration_init(0, vi->rate);
const vorbis_comment *vc = ov_comment(vf, -1);
for (int i = 0; i < vc->comments; ++i)
tag_vorbis_comment_parse(vc->user_comments[i], dec->tinfo->tags);
return vf;
}
void vorbis_free(void *vf)
{
ov_clear((OggVorbis_File *)vf);
free(vf);
}
static void vorbis_apply_gain(float **pcm, long channels, long samples,
void *arg)
{
const float gain = *(float *)arg;
if (gain == 1.f)
return;
switch (channels)
{
case 1:
for (long i = 0; i < samples; ++i)
pcm[0][i] *= gain;
break;
case 2:
for (long i = 0; i < samples; ++i)
{
pcm[0][i] *= gain;
pcm[1][i] *= gain;
}
break;
default: ; /* Only mono and stereo are accepted in vorbis_init() */
}
}
void * vorbis_routine(void *arg)
{
Decoder *dec = arg;
OggVorbis_File *vf = dec->ctx;
static char buf[BUFSIZ];
const bool endianness = isbigendian();
int bitstream;
long ret;
while ((ret = ov_read_filter(vf, buf, BUFSIZ, endianness, sizeof(int16_t),
1, &bitstream, vorbis_apply_gain, &dec->tinfo->gain)) > 0)
{
if (dec->cancel_flag)
break;
dec->tinfo->position->nb_samples += ret / sizeof(int16_t) /
dec->tinfo->format.channels;
write_pcm((int16_t *)buf, dec->pipefd_wr, ret / sizeof(int16_t));
}
if (ret != 0)
log_append(LOG_ERR, "%s: %s", dec->tinfo->path, errormsg(ret));
decoder_end(dec);
return NULL;
}