diff --git a/source/soundmanager/data/ogg.cpp b/source/soundmanager/data/ogg.cpp new file mode 100644 index 0000000000..7c692c3ae5 --- /dev/null +++ b/source/soundmanager/data/ogg.cpp @@ -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 . + */ +#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& 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 buffer; + off_t size; + off_t offset; +}; + + +//----------------------------------------------------------------------------- + +template +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 > 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 + diff --git a/source/soundmanager/data/ogg.h b/source/soundmanager/data/ogg.h new file mode 100644 index 0000000000..c2e2e704d8 --- /dev/null +++ b/source/soundmanager/data/ogg.h @@ -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 . + */ +#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 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