Mercurial > almixer_isolated
view Isolated/SoundDecoder.c @ 79:358b0bd5df43 tip
Added support for Apportable's alcSuspend()/alcResume() in BeginInterruption()/EndInterruption. You must define ALMIXER_USE_APPORTABLE_OPENAL_EXTENSIONS to compile in this support.
author | Eric Wing <ewing@coronalabs.com> |
---|---|
date | Tue, 30 Oct 2012 16:01:30 -0700 |
parents | be97ae4f30c0 |
children |
line wrap: on
line source
#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__ #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__ /* I'm making Apple use the Core Audio backend. */ //extern const SoundDecoder_DecoderFunctions __SoundDecoder_DecoderFunctions_CoreAudio; extern const Sound_DecoderFunctions __Sound_DecoderFunctions_CoreAudio; #else /* Not Apple */ #ifdef SOUND_SUPPORTS_WAV extern const Sound_DecoderFunctions __Sound_DecoderFunctions_WAV; #endif #ifdef SOUND_SUPPORTS_MPG123 extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MPG123; #endif #endif /* Note: Make sure to compile only Vorbis xor Tremor, not both. */ #ifdef SOUND_SUPPORTS_OGG extern const Sound_DecoderFunctions __Sound_DecoderFunctions_OGG; #endif typedef struct { int available; const SoundDecoder_DecoderFunctions* funcs; } SoundElement; static SoundElement s_linkedDecoders[] = { #if defined(__APPLE__) { 0, &__Sound_DecoderFunctions_CoreAudio }, #else /* Not Apple */ #ifdef SOUND_SUPPORTS_WAV { 0, &__Sound_DecoderFunctions_WAV }, #endif #ifdef SOUND_SUPPORTS_MPG123 { 0, &__Sound_DecoderFunctions_MPG123 }, #endif #endif /* Note: Make sure to link only Vorbis xor Tremor, not both. */ #ifdef SOUND_SUPPORTS_OGG { 0, &__Sound_DecoderFunctions_OGG }, #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__ #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__ __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