You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
akode/akode/plugins/xiph_decoder/vorbis_decoder.cpp

275 lines
7.4 KiB
C++

/* aKode: Ogg Vorbis-Decoder
Copyright (C) 2004 Allan Sandfeld Jensen <kde@carewolf.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "akodelib.h"
#ifdef HAVE_OGG_VORBIS
#include <vorbis/vorbisfile.h>
#include "file.h"
#include "audioframe.h"
#include "decoder.h"
#include "vorbis_decoder.h"
//#include <iostream>
namespace aKode {
static size_t _read(void *ptr, size_t size, size_t nmemb, void *datasource) {
File *src = (File*)datasource;
return src->read((char*)ptr, size*nmemb);
}
static int _seek(void *datasource, ogg_int64_t offset, int whence) {
File *src = (File*)datasource;
return src->lseek(offset, whence);
}
static int _close(void *datasource) {
File *src = (File*)datasource;
src->close();
return 0;
}
static long _tell(void *datasource) {
File *src = (File*)datasource;
return src->position();
}
static ov_callbacks _callbacks = {_read, _seek, _close, _tell};
bool VorbisDecoderPlugin::canDecode(File* src) {
OggVorbis_File vf;
src->openRO();
int r = ov_test_callbacks(src, &vf, 0, 0, _callbacks);
ov_clear(&vf);
src->close();
return r==0;
}
extern "C" { VorbisDecoderPlugin vorbis_decoder; }
struct VorbisDecoder::private_data
{
private_data() : bitstream(0), eof(false), error(false), initialized(false), retries(0), big_endian(0) {};
OggVorbis_File *vf;
vorbis_comment *vc;
vorbis_info *vi;
File *src;
AudioConfiguration config;
int bitstream;
bool eof, error;
char buffer[8192];
bool initialized;
int retries;
int big_endian;
};
VorbisDecoder::VorbisDecoder(File *src) {
m_data = new private_data;
m_data->vf = new OggVorbis_File;
m_data->src = src;
m_data->src->openRO();
m_data->src->fadvise();
unsigned short endian_test = 1;
m_data->big_endian = 1 - (*((char *)(&endian_test)));
}
VorbisDecoder::~VorbisDecoder() {
if (m_data->initialized)
ov_clear(m_data->vf);
delete m_data->vf;
delete m_data;
}
static void setAudioConfiguration(AudioConfiguration *config, vorbis_info *vi)
{
config->channels = vi->channels;
config->sample_rate = vi->rate;
config->sample_width = 16;
if (config->channels <= 2) {
config->channel_config = MonoStereo;
config->surround_config = 0;
} else
if (config->channels <= 6) {
config->channel_config = Surround;
SurroundConfiguration surround_config;
switch (config->channels) {
case 3:
surround_config.front_channels = 3;
break;
case 4:
surround_config.front_channels = 2;
surround_config.rear_channels = 2;
break;
case 5:
surround_config.front_channels = 3;
surround_config.rear_channels = 2;
break;
case 6:
surround_config.front_channels = 3;
surround_config.rear_channels = 2;
surround_config.LFE_channel = 1;
break;
}
config->surround_config = surround_config;
}
else {
config->channel_config = MultiChannel;
config->surround_config = 0;
}
}
bool VorbisDecoder::openFile() {
int status;
status = ov_open_callbacks(m_data->src, m_data->vf, 0, 0, _callbacks);
if (status != 0) goto fault;
m_data->vi = ov_info(m_data->vf, -1);
//m_data->vc = ov_comment(m_data->vf, -1);
setAudioConfiguration(&m_data->config, m_data->vi);
m_data->initialized = true;
m_data->error = false;
m_data->retries = 0;
return true;
fault:
m_data->initialized = false;
m_data->error = true;
return false;
}
// translation from vorbis channel-layout to akodelib channel-layout
static int vorbis_channel[7][6] = {
{-1, -1, -1, -1, -1, -1},
{0, -1, -1, -1, -1, -1},
{0, 1, -1, -1, -1, -1},
{0, 2, 1, -1, -1, -1},
{0, 1, 2, 3, -1, -1},
{0, 2, 1, 3, 4, -1},
{0, 2, 1, 3, 4, 5}
};
bool VorbisDecoder::readFrame(AudioFrame* frame)
{
if (!m_data->initialized) {
if (!openFile()) return false;
}
int old_bitstream = m_data->bitstream;
long v = ov_read(m_data->vf, (char*)m_data->buffer, 8192, m_data->big_endian, 2, 1, &m_data->bitstream);
if (v == 0 || v == OV_EOF ) {
// vorbisfile sometimes return 0 even though EOF is not yet reached
if (m_data->src->eof() || m_data->src->error() || ++m_data->retries >= 16)
m_data->eof = true;
// std::cerr << "akode-vorbis: EOF\n";
}
else
if (v == OV_HOLE) {
if (++m_data->retries >= 16) m_data->error = true;
// std::cerr << "akode-vorbis: Hole\n";
}
else
if (v < 0) {
m_data->error = true;
// std::cerr << "akode-vorbis: Error\n";
}
if (v <= 0) return false;
m_data->retries = 0;
if (old_bitstream != m_data->bitstream) { // changing streams, update info
m_data->vi = ov_info(m_data->vf, -1);
//m_data->vc = ov_comment(m_data->vf, -1);
setAudioConfiguration(&m_data->config, m_data->vi);
}
int channels = m_data->config.channels;
long length = v/(channels*2);
frame->reserveSpace(&m_data->config, length);
// Demux into frame
int16_t* buffer = (int16_t*)m_data->buffer;
int16_t** data = (int16_t**)frame->data;
if (channels <= 6) {
int *trans = vorbis_channel[channels];
for(int i=0; i<length; i++)
for(int j=0; j<channels; j++)
data [trans[j]] [i] = buffer[i*channels+j];
}
else
for(int i=0; i<length; i++)
for(int j=0; j<channels; j++)
data[j][i] = buffer[i*channels+j];
frame->pos = position();
return true;
}
long VorbisDecoder::length() {
if (!m_data->initialized) return -1;
// -1 return total length of all bitstreams.
// Should we take them one at a time instead?
double ogglen = ov_time_total(m_data->vf,-1);
return (long)(ogglen*1000.0);
}
long VorbisDecoder::position() {
if (!m_data->initialized) return -1;
double oggpos = ov_time_tell(m_data->vf);
return (long)(oggpos*1000.0);
}
bool VorbisDecoder::eof() {
return m_data->eof;
}
bool VorbisDecoder::error() {
return m_data->error;
}
bool VorbisDecoder::seekable() {
return m_data->src->seekable();
}
bool VorbisDecoder::seek(long pos) {
if (!m_data->initialized) return false;
int r = ov_time_seek(m_data->vf, pos/1000.0);
return r == 0;
}
const AudioConfiguration* VorbisDecoder::audioConfiguration() {
if (!m_data->initialized) return 0;
return &m_data->config;
}
} // namespace
#endif //OGG_VORBIS