/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. 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.
*
* 0 A.D. 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 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ogg.h"
#if CONFIG2_AUDIO
#include "lib/external_libraries/openal.h"
#include "lib/external_libraries/vorbis.h"
#include "lib/byte_order.h"
#include "lib/file/io/io.h"
#include "lib/file/file_system.h"
#include "lib/file/vfs/vfs_util.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
static Status LibErrorFromVorbis(int err)
{
switch(err)
{
case 0:
return INFO::OK;
case OV_HOLE:
return ERR::AGAIN;
case OV_EREAD:
return ERR::IO;
case OV_EFAULT:
return ERR::LOGIC;
case OV_EIMPL:
return ERR::NOT_SUPPORTED;
case OV_EINVAL:
return ERR::INVALID_PARAM;
case OV_ENOTVORBIS:
return ERR::NOT_SUPPORTED;
case OV_EBADHEADER:
return ERR::CORRUPTED;
case OV_EVERSION:
return ERR::INVALID_VERSION;
case OV_ENOTAUDIO:
return ERR::_1;
case OV_EBADPACKET:
return ERR::_2;
case OV_EBADLINK:
return ERR::_3;
case OV_ENOSEEK:
return ERR::_4;
default:
return ERR::FAIL;
}
}
//-----------------------------------------------------------------------------
class VorbisFileAdapter
{
public:
VorbisFileAdapter(const PFile& openedFile)
: file(openedFile)
, size(FileSize(openedFile->Pathname()))
, offset(0)
{
}
static size_t Read(void* bufferToFill, size_t itemSize, size_t numItems, void* context)
{
VorbisFileAdapter* adapter = static_cast(context);
const off_t sizeRequested = numItems*itemSize;
const off_t sizeRemaining = adapter->size - adapter->offset;
const size_t sizeToRead = (size_t)std::min(sizeRequested, sizeRemaining);
io::Operation op(*adapter->file.get(), bufferToFill, sizeToRead, adapter->offset);
if(io::Run(op) == INFO::OK)
{
adapter->offset += sizeToRead;
return sizeToRead;
}
errno = EIO;
return 0;
}
static int Seek(void* context, ogg_int64_t offset, int whence)
{
VorbisFileAdapter* adapter = static_cast(context);
off_t origin = 0;
switch(whence)
{
case SEEK_SET:
origin = 0;
break;
case SEEK_CUR:
origin = adapter->offset;
break;
case SEEK_END:
origin = adapter->size+1;
break;
NODEFAULT;
}
adapter->offset = Clamp(off_t(origin+offset), off_t(0), adapter->size);
return 0;
}
static int Close(void* context)
{
VorbisFileAdapter* adapter = static_cast(context);
adapter->file.reset();
return 0; // return value is ignored
}
static long Tell(void* context)
{
VorbisFileAdapter* adapter = static_cast(context);
return adapter->offset;
}
private:
PFile file;
off_t size;
off_t offset;
};
//-----------------------------------------------------------------------------
class VorbisBufferAdapter
{
public:
VorbisBufferAdapter(const shared_ptr& buffer, size_t size)
: buffer(buffer)
, size(size)
, offset(0)
{
}
static size_t Read(void* bufferToFill, size_t itemSize, size_t numItems, void* context)
{
VorbisBufferAdapter* adapter = static_cast(context);
const off_t sizeRequested = numItems*itemSize;
const off_t sizeRemaining = adapter->size - adapter->offset;
const size_t sizeToRead = (size_t)std::min(sizeRequested, sizeRemaining);
memcpy(bufferToFill, adapter->buffer.get() + adapter->offset, sizeToRead);
adapter->offset += sizeToRead;
return sizeToRead;
}
static int Seek(void* context, ogg_int64_t offset, int whence)
{
VorbisBufferAdapter* adapter = static_cast(context);
off_t origin = 0;
switch(whence)
{
case SEEK_SET:
origin = 0;
break;
case SEEK_CUR:
origin = adapter->offset;
break;
case SEEK_END:
origin = adapter->size+1;
break;
NODEFAULT;
}
adapter->offset = Clamp(off_t(origin+offset), off_t(0), adapter->size);
return 0;
}
static int Close(void* context)
{
VorbisBufferAdapter* adapter = static_cast(context);
adapter->buffer.reset();
return 0; // return value is ignored
}
static long Tell(void* context)
{
VorbisBufferAdapter* adapter = static_cast(context);
return adapter->offset;
}
private:
shared_ptr buffer;
off_t size;
off_t offset;
};
//-----------------------------------------------------------------------------
template
class OggStreamImpl : public OggStream
{
public:
OggStreamImpl(const Adapter& adapter)
: adapter(adapter)
{
m_fileEOF = false;
info = NULL;
}
Status Close()
{
ov_clear( &vf );
return 0;
}
Status Open()
{
ov_callbacks callbacks;
callbacks.read_func = Adapter::Read;
callbacks.close_func = Adapter::Close;
callbacks.seek_func = Adapter::Seek;
callbacks.tell_func = Adapter::Tell;
const int ret = ov_open_callbacks(&adapter, &vf, 0, 0, callbacks);
if(ret != 0)
WARN_RETURN(LibErrorFromVorbis(ret));
const int link = -1; // retrieve info for current bitstream
info = ov_info(&vf, link);
if(!info)
WARN_RETURN(ERR::INVALID_HANDLE);
return INFO::OK;
}
virtual ALenum Format()
{
return (info->channels == 1)? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
}
virtual ALsizei SamplingRate()
{
return info->rate;
}
virtual bool atFileEOF()
{
return m_fileEOF;
}
virtual Status ResetFile()
{
ov_time_seek( &vf, 0 );
m_fileEOF = false;
return INFO::OK;
}
virtual Status GetNextChunk(u8* buffer, size_t size)
{
// we may have to call ov_read multiple times because it
// treats the buffer size "as a limit and not a request"
size_t bytesRead = 0;
for(;;)
{
const int isBigEndian = (BYTE_ORDER == BIG_ENDIAN);
const int wordSize = sizeof(i16);
const int isSigned = 1;
int bitstream; // unused
const int ret = ov_read(&vf, (char*)buffer+bytesRead, int(size-bytesRead), isBigEndian, wordSize, isSigned, &bitstream);
if(ret == 0) { // EOF
m_fileEOF = true;
return (Status)bytesRead;
}
else if(ret < 0)
WARN_RETURN(LibErrorFromVorbis(ret));
else // success
{
bytesRead += ret;
if(bytesRead == size)
return (Status)bytesRead;
}
}
}
private:
Adapter adapter;
OggVorbis_File vf;
vorbis_info* info;
bool m_fileEOF;
};
//-----------------------------------------------------------------------------
Status OpenOggStream(const OsPath& pathname, OggStreamPtr& stream)
{
PFile file(new File);
RETURN_STATUS_IF_ERR(file->Open(pathname, L'r'));
shared_ptr > tmp(new OggStreamImpl(VorbisFileAdapter(file)));
RETURN_STATUS_IF_ERR(tmp->Open());
stream = tmp;
return INFO::OK;
}
Status OpenOggNonstream(const PIVFS& vfs, const VfsPath& pathname, OggStreamPtr& stream)
{
shared_ptr contents;
size_t size;
RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, contents, size));
shared_ptr > tmp(new OggStreamImpl(VorbisBufferAdapter(contents, size)));
RETURN_STATUS_IF_ERR(tmp->Open());
stream = tmp;
return INFO::OK;
}
#endif // CONFIG2_AUDIO