Mercurial > almixer_isolated
diff Isolated/SoundDecoder.c @ 38:71b465ff0622
Added support files.
author | Eric Wing <ewing@anscamobile.com> |
---|---|
date | Thu, 28 Apr 2011 16:22:30 -0700 |
parents | |
children | 5c9eaf0cc385 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Isolated/SoundDecoder.c Thu Apr 28 16:22:30 2011 -0700 @@ -0,0 +1,711 @@ +#ifndef ALMIXER_COMPILED_WITH_SDL + +#include "SoundDecoder.h" +#include "SoundDecoder_Internal.h" +#include "tErrorLib.h" +#include "LinkedList.h" +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#ifdef ANDROID_NDK +#include <android/log.h> +#endif + +/* A partial shim reimplementation of SDL_sound to work around the LGPL issues. + * This implementation is more limited than SDL_sound. + * For example, there is no generic software conversion routines. + * Functions are also not necessarily thread safe. + * This implementation relies on the back-end decoder much more heavily + * than SDL_sound. (For example, I bypass the internal->buffer.) + */ + +static LinkedList* s_listOfLoadedSamples = NULL; + +static signed char s_isInitialized = 0; +static TErrorPool* s_errorPool = NULL; + +static const SoundDecoder_DecoderInfo** s_availableDecoders = NULL; + +#ifdef __APPLE__ +//extern const SoundDecoder_DecoderFunctions __SoundDecoder_DecoderFunctions_CoreAudio; +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_CoreAudio; +#endif +#ifdef ANDROID_NDK +#ifdef SOUND_SUPPORTS_WAV +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_WAV; +#endif +#ifdef SOUND_SUPPORTS_MPG123 +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MPG123; +#endif +#ifdef SOUND_SUPPORTS_OGG +extern const Sound_DecoderFunctions __Sound_DecoderFunctions_OGG; +#endif +#endif + +typedef struct +{ + int available; + const SoundDecoder_DecoderFunctions* funcs; +} SoundElement; + +static SoundElement s_linkedDecoders[] = +{ +#if defined(__APPLE__) + { 0, &__Sound_DecoderFunctions_CoreAudio }, +#endif +#if defined(ANDROID_NDK) +#ifdef SOUND_SUPPORTS_WAV + { 0, &__Sound_DecoderFunctions_WAV }, +#endif +#ifdef SOUND_SUPPORTS_MPG123 + { 0, &__Sound_DecoderFunctions_MPG123 }, +#endif +#ifdef SOUND_SUPPORTS_OGG + { 0, &__Sound_DecoderFunctions_OGG }, +#endif +#endif + { 0, NULL } +}; + + +#include <ctype.h> +int SoundDecoder_strcasecmp(const char* str1, const char* str2) +{ + int the_char1; + int the_char2; + int i = 0; + if(str1 == str2) + { + return 0; + } + if(NULL == str1) + { + return -1; + } + if(NULL == str2) + { + return 1; + } + + do + { + the_char1 = tolower(str1[i]); + the_char2 = tolower(str2[i]); + if(the_char1 < the_char2) + { + return -1; + } + else if(the_char1 > the_char2) + { + return 1; + } + i++; + } while( (0 != the_char1) && (0 != the_char2) ); + + return 0; +} + + +#ifdef ANDROID_NDK +#include <stdarg.h> +#include <android/log.h> +int SoundDecoder_DebugPrint(const char* format, ...) +{ + va_list arg_list; + int ret_val; + + va_start(arg_list, format); + ret_val = __android_log_vprint(ANDROID_LOG_INFO, "SoundDecoder", format, arg_list); + va_end(arg_list); + return ret_val; +} +#endif + +const char* SoundDecoder_GetError() +{ + const char* error_string = NULL; + if(NULL == s_errorPool) + { + return "Error: You should not call SoundDecoder_GetError while Sound is not initialized"; + } + error_string = TError_GetLastErrorStr(s_errorPool); + /* SDL returns empty strings instead of NULL */ + if(NULL == error_string) + { + return ""; + } + else + { + return error_string; + } +} + +void SoundDecoder_ClearError() +{ + if(NULL == s_errorPool) + { + return; + } + TError_SetError(s_errorPool, 0, NULL); +} + +void SoundDecoder_SetError(const char* err_str, ...) +{ + if(NULL == s_errorPool) + { + fprintf(stderr, "Error: You should not call SoundDecoder_SetError while Sound is not initialized\n"); + return; + } + va_list argp; + va_start(argp, err_str); + // SDL_SetError which I'm emulating has no number parameter. + TError_SetErrorv(s_errorPool, 1, err_str, argp); + va_end(argp); +#ifdef ANDROID_NDK + __android_log_print(ANDROID_LOG_INFO, "SoundDecoder_SetError", TError_GetLastErrorStr(s_errorPool)); +#endif +} + + +void SoundDecoder_GetLinkedVersion(SoundDecoder_Version* the_version) +{ + if(NULL != the_version) + { + the_version->major = SOUNDDECODER_VER_MAJOR; + the_version->minor = SOUNDDECODER_VER_MINOR; + the_version->patch = SOUNDDECODER_VER_PATCH; + } +} + + +const SoundDecoder_DecoderInfo** SoundDecoder_AvailableDecoders() +{ + return(s_availableDecoders); +} + +int SoundDecoder_Init() +{ + size_t total_number_of_decoders; + size_t i; + size_t current_pos = 0; + if(1 == s_isInitialized) + { + return 1; + } + if(NULL == s_errorPool) + { + s_errorPool = TError_CreateErrorPool(); + } + if(NULL == s_errorPool) + { + return 0; + } + + total_number_of_decoders = sizeof(s_linkedDecoders) / sizeof(s_linkedDecoders[0]); + s_availableDecoders = (const SoundDecoder_DecoderInfo **)malloc((total_number_of_decoders) * sizeof(SoundDecoder_DecoderInfo*)); + if(NULL == s_availableDecoders) + { + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return 0; + } + + /* Allocate memory for linked list of sound samples. */ + s_listOfLoadedSamples = LinkedList_Create(); + if(NULL == s_listOfLoadedSamples) + { + free(s_availableDecoders); + s_availableDecoders = NULL; + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return 0; + } + + for(i = 0; s_linkedDecoders[i].funcs != NULL; i++) + { + s_linkedDecoders[i].available = s_linkedDecoders[i].funcs->init(); + if(s_linkedDecoders[i].available) + { + s_availableDecoders[current_pos] = &(s_linkedDecoders[i].funcs->info); + current_pos++; + } + } + + s_availableDecoders[current_pos] = NULL; + s_isInitialized = 1; + return 1; +} + +void SoundDecoder_Quit() +{ + size_t i; + if(0 == s_isInitialized) + { + return; + } + + /* + * SDL_sound actually embeds the linked list in the internal data structure. + * So any sample can potentially reach any other sample. + * But I'm keeping my own separate list. + */ + while(LinkedList_Size(s_listOfLoadedSamples) > 0) + { + SoundDecoder_Sample* sound_sample = (SoundDecoder_Sample*)LinkedList_PopBack(s_listOfLoadedSamples); + SoundDecoder_FreeSample(sound_sample); + } + LinkedList_Free(s_listOfLoadedSamples); + s_listOfLoadedSamples = NULL; + + + for(i = 0; s_linkedDecoders[i].funcs != NULL; i++) + { + if (s_linkedDecoders[i].available) + { + s_linkedDecoders[i].funcs->quit(); + s_linkedDecoders[i].available = 0; + } + } + + if(NULL != s_availableDecoders) + { + free(s_availableDecoders); + } + s_availableDecoders = NULL; + + + /* Remember: ALmixer_SetError/GetError calls will not work while this is gone. */ + TError_FreeErrorPool(s_errorPool); + s_errorPool = NULL; + + s_isInitialized = 0; +} + + +void SoundDecoder_FreeSample(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* sample_internal; + + /* Quit unloads all samples, so it is not possible to free a sample + * when not initialized. + */ + if(0 == s_isInitialized) + { + return; + } + + if(sound_sample == NULL) + { + return; + } + + /* SDL_sound keeps a linked list of all the loaded samples. + * We want to remove the current sample from that list. + */ + LinkedListNode* the_node = LinkedList_Find(s_listOfLoadedSamples, sound_sample, NULL); + if(NULL == the_node) + { + SoundDecoder_SetError("SoundDecoder_FreeSample: Internal Error, sample does not exist in linked list."); + return; + } + LinkedList_Remove(s_listOfLoadedSamples, the_node); + + sample_internal = (SoundDecoder_SampleInternal*)sound_sample->opaque; + + /* Ugh...SDL_sound has a lot of pointers. + * I hope I didn't miss any dynamic memory. + */ + + /* Call close on the decoder */ + sample_internal->funcs->close(sound_sample); + + if(NULL != sample_internal->rw) + { + sample_internal->rw->close(sample_internal->rw); + } + + /* Ooops. The public buffer might be shared with the internal buffer. + * Make sure to not accidentally double delete. + */ + if((NULL != sample_internal->buffer) + && (sample_internal->buffer != sound_sample->buffer) + ) + { + free(sample_internal->buffer); + } + free(sample_internal); + + if(NULL != sound_sample->buffer) + { + free(sound_sample->buffer); + } + free(sound_sample); +} + + + +static int Internal_LoadSample(const SoundDecoder_DecoderFunctions *funcs, + SoundDecoder_Sample* sound_sample, const char *ext +) +{ + SoundDecoder_SampleInternal* internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + long current_file_position = internal_sample->rw->seek(internal_sample->rw, 0, SEEK_CUR); + + /* fill in the funcs for this decoder... */ + sound_sample->decoder = &funcs->info; + internal_sample->funcs = funcs; + if (!funcs->open(sound_sample, ext)) + { + internal_sample->rw->seek(internal_sample->rw, current_file_position, SEEK_SET); + return 0; + } + + /* we found a compatible decoder */ + + /* SDL_sound normally goes on to setup a bunch of things to + * support format conversion via SDL APIs. + * I am not porting any of that stuff. + * My goal is to simply setup the struct properties to values + * that will not cause any confusion with other parts of the implementation. + */ + + + if(0 == sound_sample->desired.format) + { + sound_sample->desired.format = sound_sample->actual.format; + } + if(0 == sound_sample->desired.channels) + { + sound_sample->desired.channels = sound_sample->actual.channels; + } + if(0 == sound_sample->desired.rate) + { + sound_sample->desired.rate = sound_sample->actual.rate; + } + + /* I'm a little confused at the difference between the internal + * public buffer. I am going to make them the same. + * I assume I already allocated the public buffer. + */ + internal_sample->buffer = sound_sample->buffer; + internal_sample->buffer_size = sound_sample->buffer_size; + + /* Insert the new sample into the linked list of samples. */ + LinkedList_PushBack(s_listOfLoadedSamples, sound_sample); + + return 1; +} + + + +SoundDecoder_Sample* SoundDecoder_NewSampleFromFile(const char* file_name, + SoundDecoder_AudioInfo* desired_format, + size_t buffer_size) +{ + + const char* file_extension; + ALmixer_RWops* rw_ops; + + + if(0 == s_isInitialized) + { + SoundDecoder_SetError(ERR_NOT_INITIALIZED); + return NULL; + } + if(NULL == file_name) + { + SoundDecoder_SetError("No file specified"); + return NULL; + } + + file_extension = strrchr(file_name, '.'); + if(NULL != file_extension) + { + file_extension++; + } + + rw_ops = ALmixer_RWFromFile(file_name, "rb"); + + return SoundDecoder_NewSample(rw_ops, file_extension, desired_format, buffer_size); +} + + +SoundDecoder_Sample* SoundDecoder_NewSample(ALmixer_RWops* rw_ops, const char* file_extension, SoundDecoder_AudioInfo* desired_format, size_t buffer_size) +{ + SoundDecoder_Sample* new_sample; + SoundDecoder_SampleInternal* internal_sample; + SoundElement* current_decoder; + + if(0 == s_isInitialized) + { + SoundDecoder_SetError(ERR_NOT_INITIALIZED); + return NULL; + } + if(NULL == rw_ops) + { + SoundDecoder_SetError("No file specified"); + return NULL; + } + + new_sample = (SoundDecoder_Sample*)calloc(1, sizeof(SoundDecoder_Sample)); + if(NULL == new_sample) + { + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return NULL; + } + internal_sample = (SoundDecoder_SampleInternal*)calloc(1, sizeof(SoundDecoder_SampleInternal)); + if(NULL == internal_sample) + { + free(new_sample); + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return NULL; + } + new_sample->buffer = calloc(1, buffer_size); + if(NULL == new_sample->buffer) + { + free(internal_sample); + free(new_sample); + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + return NULL; + } + + new_sample->buffer_size = buffer_size; + + if(NULL != desired_format) + { + memcpy(&new_sample->desired, desired_format, sizeof(SoundDecoder_AudioInfo)); + } + + internal_sample->rw = rw_ops; + new_sample->opaque = internal_sample; + + + if(NULL != file_extension) + { + for(current_decoder = &s_linkedDecoders[0]; current_decoder->funcs != NULL; current_decoder++) + { + if(current_decoder->available) + { + const char** decoder_file_extension = current_decoder->funcs->info.extensions; + while(*decoder_file_extension) + { + if(0 == (SoundDecoder_strcasecmp(*decoder_file_extension, file_extension))) + { + if(Internal_LoadSample(current_decoder->funcs, new_sample, file_extension)) + { + return(new_sample); + } + break; /* go to the next decoder */ + } + decoder_file_extension++; + } + } + } + } + + /* no direct file_extensionension match? Try everything we've got... */ + for(current_decoder = &s_linkedDecoders[0]; current_decoder->funcs != NULL; current_decoder++) + { + if(current_decoder->available) + { + int already_tried_decoder = 0; + const char** decoder_file_extension = current_decoder->funcs->info.extensions; + + /* skip decoders we already tried from the above loop */ + while(*decoder_file_extension) + { + if(SoundDecoder_strcasecmp(*decoder_file_extension, file_extension) == 0) + { + already_tried_decoder = 1; + break; + } + decoder_file_extension++; + } + + if(0 == already_tried_decoder) + { + if (Internal_LoadSample(current_decoder->funcs, new_sample, file_extension)) + { + return new_sample; + } + } + } + } + + /* could not find a decoder */ + SoundDecoder_SetError(ERR_UNSUPPORTED_FORMAT); + /* clean up the memory */ + free(new_sample->opaque); + if(NULL != new_sample->buffer) + { + free(new_sample->buffer); + } + free(new_sample); + + rw_ops->close(rw_ops); + return NULL; +} + + +int SoundDecoder_SetBufferSize(SoundDecoder_Sample* sound_sample, size_t new_buffer_size) +{ + SoundDecoder_SampleInternal* internal_sample = NULL; + void* new_buffer_ptr = NULL; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + internal_sample = ((SoundDecoder_SampleInternal*)sound_sample->opaque); + + + new_buffer_ptr = realloc(sound_sample->buffer, new_buffer_size); + BAIL_IF_MACRO(NULL == new_buffer_ptr, ERR_OUT_OF_MEMORY, 0); + + sound_sample->buffer = new_buffer_ptr; + sound_sample->buffer_size = new_buffer_size; + internal_sample->buffer = sound_sample->buffer; + internal_sample->buffer_size = sound_sample->buffer_size; + + return 1; +} + + +size_t SoundDecoder_Decode(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample = NULL; + size_t bytes_read = 0; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + BAIL_IF_MACRO(sound_sample->flags & SOUND_SAMPLEFLAG_ERROR, ERR_PREVIOUS_SAMPLE_ERROR, 0); + BAIL_IF_MACRO(sound_sample->flags & SOUND_SAMPLEFLAG_EOF, ERR_ALREADY_AT_EOF_ERROR, 0); + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + + assert(sound_sample->buffer != NULL); + assert(sound_sample->buffer_size > 0); + assert(internal_sample->buffer != NULL); + assert(internal_sample->buffer_size > 0); + + /* reset EAGAIN. Decoder can flip it back on if it needs to. */ + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; + bytes_read = internal_sample->funcs->read(sound_sample); + return bytes_read; +} + + +size_t SoundDecoder_DecodeAll(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample = NULL; + void* data_buffer = NULL; + size_t updated_buffer_size = 0; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + /* My original thought was to call SetBufferSize and resize to + * the size needed to hold the entire decoded file utilizing total_time, + * but it appears SDL_sound simply loops on SoundDecoder_Decode. + * I suppose it is possible to partially decode or seek a file, and then + * call DecodeAll so you won't have the whole thing in which case + * my idea would waste memory. + */ + while( (0 == (sound_sample->flags & SOUND_SAMPLEFLAG_EOF) ) + && (0 == (sound_sample->flags & SOUND_SAMPLEFLAG_ERROR)) + ) + { + size_t bytes_decoded = SoundDecoder_Decode(sound_sample); + void* realloced_ptr = realloc(data_buffer, updated_buffer_size + bytes_decoded); + if(NULL == realloced_ptr) + { + sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR; + SoundDecoder_SetError(ERR_OUT_OF_MEMORY); + if(NULL != data_buffer) + { + free(data_buffer); + } + return bytes_decoded; + } + data_buffer = realloced_ptr; + /* copy the chunk of decoded PCM to the end of our new data buffer */ + memcpy( ((char*)data_buffer) + updated_buffer_size, sound_sample->buffer, bytes_decoded ); + updated_buffer_size += bytes_decoded; + } + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + if(internal_sample->buffer != sound_sample->buffer) + { + free(internal_sample->buffer); + } + free(sound_sample->buffer); + + sound_sample->buffer = data_buffer; + sound_sample->buffer_size = updated_buffer_size; + internal_sample->buffer = sound_sample->buffer; + internal_sample->buffer_size = sound_sample->buffer_size; + + return sound_sample->buffer_size; +} + + +int SoundDecoder_Rewind(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample; + int ret_val; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + ret_val = internal_sample->funcs->rewind(sound_sample); + if(0 == ret_val) + { + sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR; + SoundDecoder_SetError("Rewind failed"); + return 0; + } + /* reset flags */ + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_ERROR; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EOF; + + return 1; +} + + +int SoundDecoder_Seek(SoundDecoder_Sample* sound_sample, size_t ms) +{ + SoundDecoder_SampleInternal* internal_sample; + int ret_val; + + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, 0); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + + BAIL_IF_MACRO(!(sound_sample->flags & SOUND_SAMPLEFLAG_CANSEEK), "Sound sample is not seekable", 0); + + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + ret_val = internal_sample->funcs->seek(sound_sample, ms); + if(0 == ret_val) + { + sound_sample->flags |= SOUND_SAMPLEFLAG_ERROR; + SoundDecoder_SetError("Seek failed"); + return 0; + } + /* reset flags */ + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_ERROR; + sound_sample->flags &= ~SOUND_SAMPLEFLAG_EOF; + + return 1; +} + + +ptrdiff_t SoundDecoder_GetDuration(SoundDecoder_Sample* sound_sample) +{ + SoundDecoder_SampleInternal* internal_sample; + BAIL_IF_MACRO(!s_isInitialized, ERR_NOT_INITIALIZED, -1); + BAIL_IF_MACRO(NULL == sound_sample, ERR_NULL_SAMPLE, 0); + internal_sample = (SoundDecoder_SampleInternal*)sound_sample->opaque; + return internal_sample->total_time; +} + +#endif