parent
0340cc2baa
commit
df96445284
323
source/soundmanager/data/ogg.cpp
Normal file
323
source/soundmanager/data/ogg.cpp
Normal file
@ -0,0 +1,323 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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/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 = (VorbisFileAdapter*)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 = (VorbisFileAdapter*)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 = (VorbisFileAdapter*)context;
|
||||
adapter->file.reset();
|
||||
return 0; // return value is ignored
|
||||
}
|
||||
|
||||
static long Tell(void* context)
|
||||
{
|
||||
VorbisFileAdapter* adapter = (VorbisFileAdapter*)context;
|
||||
return adapter->offset;
|
||||
}
|
||||
|
||||
private:
|
||||
PFile file;
|
||||
off_t size;
|
||||
off_t offset;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
class VorbisBufferAdapter
|
||||
{
|
||||
public:
|
||||
VorbisBufferAdapter(const shared_ptr<u8>& 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 = (VorbisBufferAdapter*)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 = (VorbisBufferAdapter*)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 = (VorbisBufferAdapter*)context;
|
||||
adapter->buffer.reset();
|
||||
return 0; // return value is ignored
|
||||
}
|
||||
|
||||
static long Tell(void* context)
|
||||
{
|
||||
VorbisBufferAdapter* adapter = (VorbisBufferAdapter*)context;
|
||||
return adapter->offset;
|
||||
}
|
||||
|
||||
private:
|
||||
shared_ptr<u8> buffer;
|
||||
off_t size;
|
||||
off_t offset;
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template <typename Adapter>
|
||||
class OggStreamImpl : public OggStream
|
||||
{
|
||||
public:
|
||||
OggStreamImpl(const Adapter& adapter)
|
||||
: adapter(adapter)
|
||||
{
|
||||
m_fileEOF = false;
|
||||
}
|
||||
|
||||
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<OggStreamImpl<VorbisFileAdapter> > tmp(new OggStreamImpl<VorbisFileAdapter>(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<u8> contents;
|
||||
size_t size;
|
||||
RETURN_STATUS_IF_ERR(vfs->LoadFile(pathname, contents, size));
|
||||
|
||||
shared_ptr<OggStreamImpl<VorbisBufferAdapter> > tmp(new OggStreamImpl<VorbisBufferAdapter>(VorbisBufferAdapter(contents, size)));
|
||||
RETURN_STATUS_IF_ERR(tmp->Open());
|
||||
stream = tmp;
|
||||
return INFO::OK;
|
||||
}
|
||||
|
||||
#endif // CONFIG2_AUDIO
|
||||
|
54
source/soundmanager/data/ogg.h
Normal file
54
source/soundmanager/data/ogg.h
Normal file
@ -0,0 +1,54 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef INCLUDED_OGG
|
||||
#define INCLUDED_OGG
|
||||
|
||||
#include "lib/config2.h"
|
||||
|
||||
#if CONFIG2_AUDIO
|
||||
|
||||
#include "lib/external_libraries/openal.h"
|
||||
#include "lib/file/vfs/vfs.h"
|
||||
|
||||
class OggStream
|
||||
{
|
||||
public:
|
||||
virtual ~OggStream() { }
|
||||
virtual ALenum Format() = 0;
|
||||
virtual ALsizei SamplingRate() = 0;
|
||||
virtual bool atFileEOF() = 0;
|
||||
virtual Status ResetFile() = 0;
|
||||
|
||||
/**
|
||||
* @return bytes read (<= size) or a (negative) Status
|
||||
**/
|
||||
virtual Status GetNextChunk(u8* buffer, size_t size) = 0;
|
||||
};
|
||||
|
||||
typedef shared_ptr<OggStream> OggStreamPtr;
|
||||
|
||||
extern Status OpenOggStream(const OsPath& pathname, OggStreamPtr& stream);
|
||||
|
||||
/**
|
||||
* A non-streaming OggStream (reading the whole file in advance)
|
||||
* that can cope with archived/compressed files.
|
||||
*/
|
||||
extern Status OpenOggNonstream(const PIVFS& vfs, const VfsPath& pathname, OggStreamPtr& stream);
|
||||
|
||||
#endif // CONFIG2_AUDIO
|
||||
|
||||
#endif // INCLUDED_OGG
|
Loading…
Reference in New Issue
Block a user