/* This file is part of Warzone 2100. Copyright (C) 2005-2007 Warzone Resurrection Project Warzone 2100 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. Warzone 2100 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 Warzone 2100; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "lib/framework/frame.h" #include #ifndef WZ_NOSOUND # include # if defined(OV_CALLBACKS_DEFAULT) && defined(OV_CALLBACKS_NOCLOSE) && defined(OV_CALLBACKS_STREAMONLY) && defined(OV_CALLBACKS_STREAMONLY_NOCLOSE) /* HACK: Dummy reference vorbisfile.h symbols to prevent warnings */ static WZ_DECL_UNUSED void MKID(dummy)(void) { (void)OV_CALLBACKS_DEFAULT; (void)OV_CALLBACKS_NOCLOSE; (void)OV_CALLBACKS_STREAMONLY; (void)OV_CALLBACKS_STREAMONLY_NOCLOSE; } # endif #endif #ifdef __BIG_ENDIAN__ #define OGG_ENDIAN 1 #else #define OGG_ENDIAN 0 #endif #include "oggvorbis.h" struct OggVorbisDecoderState { // Internal identifier towards PhysicsFS PHYSFS_file* fileHandle; // Wether to allow seeking or not BOOL allowSeeking; #ifndef WZ_NOSOUND // Internal identifier towards libVorbisFile OggVorbis_File oggVorbis_stream; // Internal meta data vorbis_info* VorbisInfo; #endif }; #ifndef WZ_NOSOUND static size_t wz_oggVorbis_read(void *ptr, size_t size, size_t nmemb, void *datasource) { PHYSFS_file* fileHandle = ((struct OggVorbisDecoderState*)datasource)->fileHandle; return PHYSFS_read(fileHandle, ptr, 1, size*nmemb); } static int wz_oggVorbis_seek(void *datasource, ogg_int64_t offset, int whence) { PHYSFS_file* fileHandle = ((struct OggVorbisDecoderState*)datasource)->fileHandle; BOOL allowSeeking = ((struct OggVorbisDecoderState*)datasource)->allowSeeking; int newPos; if (!allowSeeking) return -1; switch (whence) { // Seek to absolute position case SEEK_SET: newPos = offset; break; // Seek `offset` ahead case SEEK_CUR: { int curPos = PHYSFS_tell(fileHandle); if (curPos == -1) return -1; newPos = curPos + offset; break; } // Seek backwards from the end of the file case SEEK_END: { int fileSize = PHYSFS_fileLength(fileHandle); if (fileSize == -1) return -1; newPos = fileSize - 1 - offset; break; } // unrecognized seek instruction default: // indicate failure return -1; } // PHYSFS_seek return value of non-zero means success if (PHYSFS_seek(fileHandle, newPos) != 0) return newPos; // success else return -1; // failure } static int wz_oggVorbis_close(void *datasource) { return 0; } static long wz_oggVorbis_tell(void *datasource) { PHYSFS_file* fileHandle = ((struct OggVorbisDecoderState*)datasource)->fileHandle; return PHYSFS_tell(fileHandle); } static const ov_callbacks wz_oggVorbis_callbacks = { wz_oggVorbis_read, wz_oggVorbis_seek, wz_oggVorbis_close, wz_oggVorbis_tell }; #endif struct OggVorbisDecoderState* sound_CreateOggVorbisDecoder(PHYSFS_file* PHYSFS_fileHandle, BOOL allowSeeking) { #ifndef WZ_NOSOUND int error; #endif struct OggVorbisDecoderState* decoder = malloc(sizeof(struct OggVorbisDecoderState)); if (decoder == NULL) { debug(LOG_ERROR, "sound_CreateOggVorbisDecoder: Out of memory"); abort(); return NULL; } decoder->fileHandle = PHYSFS_fileHandle; decoder->allowSeeking = allowSeeking; #ifndef WZ_NOSOUND error = ov_open_callbacks(decoder, &decoder->oggVorbis_stream, NULL, 0, wz_oggVorbis_callbacks); if (error < 0) { debug(LOG_ERROR, "sound_CreateOggVorbisDecoder: ov_open_callbacks failed with errorcode %d", error); free(decoder); return NULL; } // Aquire some info about the sound data decoder->VorbisInfo = ov_info(&decoder->oggVorbis_stream, -1); #endif return decoder; } void sound_DestroyOggVorbisDecoder(struct OggVorbisDecoderState* decoder) { #ifndef WZ_NOSOUND // Close the OggVorbis decoding stream ov_clear(&decoder->oggVorbis_stream); #endif free(decoder); } static inline unsigned int getSampleCount(struct OggVorbisDecoderState* decoder) { #ifndef WZ_NOSOUND int numSamples = ov_pcm_total(&decoder->oggVorbis_stream, -1); if (numSamples == OV_EINVAL) return 0; return numSamples; #else return 0; #endif } static inline unsigned int getCurrentSample(struct OggVorbisDecoderState* decoder) { #ifndef WZ_NOSOUND int samplePos = ov_pcm_tell(&decoder->oggVorbis_stream); if (samplePos == OV_EINVAL) return 0; return samplePos; #else return 0; #endif } soundDataBuffer* sound_DecodeOggVorbis(struct OggVorbisDecoderState* decoder, size_t bufferSize) { size_t size = 0; #ifndef WZ_NOSOUND int result; #endif soundDataBuffer* buffer; #ifndef WZ_NOSOUND if (decoder->allowSeeking) { unsigned int sampleCount = getSampleCount(decoder); unsigned int sizeEstimate = sampleCount * decoder->VorbisInfo->channels * 2; if (((bufferSize == 0) || (bufferSize > sizeEstimate)) && (sizeEstimate != 0)) { bufferSize = (sampleCount - getCurrentSample(decoder)) * decoder->VorbisInfo->channels * 2; } } // If we can't seek nor receive any suggested size for our buffer, just quit if (bufferSize == 0) { debug(LOG_ERROR, "sound_DecodeOggVorbis: can't find a proper buffer size\n"); return NULL; } #else bufferSize = 0; #endif buffer = malloc(bufferSize + sizeof(soundDataBuffer)); if (buffer == NULL) { debug(LOG_ERROR, "sound_DecodeOggVorbis: couldn't allocate memory (%zu bytes requested)\n", bufferSize + sizeof(soundDataBuffer)); return NULL; } buffer->data = (char*)(buffer + 1); buffer->bufferSize = bufferSize; buffer->bitsPerSample = 16; #ifndef WZ_NOSOUND buffer->channelCount = decoder->VorbisInfo->channels; buffer->frequency = decoder->VorbisInfo->rate; // Decode PCM data into the buffer until there is nothing to decode left do { // Decode int section; result = ov_read(&decoder->oggVorbis_stream, &buffer->data[size], bufferSize - size, OGG_ENDIAN, 2, 1, §ion); if (result < 0) { debug(LOG_ERROR, "sound_DecodeOggVorbis: error decoding from OggVorbis file; errorcode from ov_read: %d\n", result); free(buffer); return NULL; } else { size += result; } } while ((result != 0 && size < bufferSize)); #endif buffer->size = size; return buffer; }