warzone2100/lib/sound/oggvorbis.c

265 lines
6.8 KiB
C
Raw Normal View History

/*
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 <physfs.h>
#ifndef WZ_NOOGG
#include <vorbis/vorbisfile.h>
#endif
#ifdef __BIG_ENDIAN__
#define OGG_ENDIAN 1
#else
#define OGG_ENDIAN 0
#endif
typedef struct
{
// Internal identifier towards PhysicsFS
PHYSFS_file* fileHandle;
// Wether to allow seeking or not
BOOL allowSeeking;
// Internal identifier towards libVorbisFile
OggVorbis_File oggVorbis_stream;
// Internal meta data
vorbis_info* VorbisInfo;
} OggVorbisDecoderState;
#define _LIBSOUND_OGGVORBIS_C_
#include "oggvorbis.h"
static size_t wz_oggVorbis_read(void *ptr, size_t size, size_t nmemb, void *datasource)
{
PHYSFS_file* fileHandle = ((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 = ((OggVorbisDecoderState*)datasource)->fileHandle;
BOOL allowSeeking = ((OggVorbisDecoderState*)datasource)->allowSeeking;
int curPos, fileSize, newPos;
if (!allowSeeking)
return -1;
switch (whence)
{
// Seek to absolute position
case SEEK_SET:
newPos = offset;
break;
// Seek `offset` ahead
case SEEK_CUR:
curPos = PHYSFS_tell(fileHandle);
if (curPos == -1)
return -1;
newPos = curPos + offset;
break;
// Seek backwards from the end of the file
case SEEK_END:
fileSize = PHYSFS_fileLength(fileHandle);
if (fileSize == -1)
return -1;
newPos = fileSize - 1 - offset;
break;
}
// 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 = ((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
};
OggVorbisDecoderState* sound_CreateOggVorbisDecoder(PHYSFS_file* PHYSFS_fileHandle, BOOL allowSeeking)
{
int error;
OggVorbisDecoderState* decoder = malloc(sizeof(OggVorbisDecoderState));
if (decoder == NULL)
{
debug(LOG_ERROR, "sound_CreateOggVorbisDecoder: couldn't allocate memory\n");
return NULL;
}
decoder->fileHandle = PHYSFS_fileHandle;
decoder->allowSeeking = allowSeeking;
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\n", error);
free(decoder);
return NULL;
}
// Aquire some info about the sound data
decoder->VorbisInfo = ov_info(&decoder->oggVorbis_stream, -1);
return decoder;
}
void sound_DestroyOggVorbisDecoder(OggVorbisDecoderState* decoder)
{
// Close the OggVorbis decoding stream
ov_clear(&decoder->oggVorbis_stream);
free(decoder);
}
static inline unsigned int getSampleCount(OggVorbisDecoderState* decoder)
{
int numSamples = ov_pcm_total(&decoder->oggVorbis_stream, -1);
if (numSamples == OV_EINVAL)
return 0;
return numSamples;
}
static inline unsigned int getCurrentSample(OggVorbisDecoderState* decoder)
{
int samplePos = ov_pcm_tell(&decoder->oggVorbis_stream);
if (samplePos == OV_EINVAL)
return 0;
return samplePos;
}
soundDataBuffer* sound_DecodeOggVorbis(OggVorbisDecoderState* decoder, size_t bufferSize, char* targetBuffer)
{
size_t size = 0;
int result;
soundDataBuffer* buffer;
if (targetBuffer == NULL)
{
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;
}
buffer = malloc(bufferSize + sizeof(soundDataBuffer));
if (buffer == NULL)
{
debug(LOG_ERROR, "sound_DecodeOggVorbis: couldn't allocate memory (%u bytes requested)\n", bufferSize + sizeof(soundDataBuffer));
return NULL;
}
}
else
{
if (bufferSize <= sizeof(soundDataBuffer))
{
free(targetBuffer);
return NULL;
}
buffer = (soundDataBuffer*)targetBuffer;
bufferSize -= sizeof(soundDataBuffer);
}
buffer->data = (char*)(buffer + 1);
buffer->bufferSize = bufferSize + sizeof(soundDataBuffer);
buffer->bitsPerSample = 16;
buffer->channelCount = decoder->VorbisInfo->channels;
buffer->frequency = decoder->VorbisInfo->rate;
// Decode PCM data into the buffer until there is nothing to decode left
do
{
// If the PCM buffer has become to small increase it by reallocating double its previous size
if (size == bufferSize && targetBuffer != NULL)
{
void* newBuffer;
bufferSize *= 2;
newBuffer = realloc(buffer, bufferSize + sizeof(soundDataBuffer));
if (newBuffer == NULL)
{
debug(LOG_ERROR, "sound_DecodeOggVorbis: realloc failed, bailing out\n");
return buffer;
}
buffer = newBuffer;
buffer->data = (char*)(buffer + 1);
buffer->bufferSize = bufferSize + sizeof(soundDataBuffer);
}
// Decode
int section;
result = ov_read(&decoder->oggVorbis_stream, &buffer->data[size], bufferSize - size, OGG_ENDIAN, 2, 1, &section);
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) || (size == bufferSize && targetBuffer != NULL));
buffer->size = size;
return buffer;
}