Mercurial > almixer_isolated
changeset 25:46e82b415520
merged
author | Eric Wing <ewing . public |-at-| gmail . com> |
---|---|
date | Fri, 24 Dec 2010 03:33:31 -0800 |
parents | e085cbc573cf (diff) 9365e714fc4b (current diff) |
children | 884cce2515eb |
files | ALmixer.c ALmixer.h |
diffstat | 3 files changed, 269 insertions(+), 152 deletions(-) [+] |
line wrap: on
line diff
--- a/ALmixer.c Mon Nov 08 22:19:47 2010 -0800 +++ b/ALmixer.c Fri Dec 24 03:33:31 2010 -0800 @@ -8,19 +8,14 @@ #include "ALmixer.h" #ifdef ALMIXER_COMPILE_WITHOUT_SDL - #include "ALmixer_rwops.h" + #include "ALmixer_RWops.h" #include "SoundDecoder.h" #else #include "SDL_sound.h" #endif -#ifdef ANDROID_NDK - #include <AL/al.h> - #include <AL/alc.h> -#else - #include "al.h" /* OpenAL */ - #include "alc.h" /* For creating OpenAL contexts */ -#endif +#include "al.h" /* OpenAL */ +#include "alc.h" /* For creating OpenAL contexts */ #ifdef __APPLE__ /* For performance things like ALC_CONVERT_DATA_UPON_LOADING */ @@ -29,15 +24,14 @@ * define was moved to a new header file and renamed to * ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING. */ + #include <TargetConditionals.h> /* - #include <TargetConditionals.h> #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) - + #include <AudioToolbox/AudioToolbox.h> #else #include <OpenAL/MacOSX_OALExtensions.h> #endif */ - #endif /* For malloc, bsearch, qsort */ @@ -235,6 +229,8 @@ static LinkedList* s_listOfALmixerData = NULL; +/* Special stuff for iOS interruption handling */ +static ALCcontext* s_interruptionContext = NULL; #ifdef __APPLE__ @@ -323,8 +319,6 @@ LARGE_INTEGER current_time; QueryPerformanceCounter(¤t_time); return (ALuint)((current_time.QuadPart - s_ticksBaseTime.QuadPart) * 1000 * s_hiResSecondsPerTick); - #elif ANDROID_NDK - #else /* assuming POSIX */ /* clock_gettime is POSIX.1-2001 */ struct timespec current_time; @@ -346,7 +340,39 @@ #define ALmixer_Delay SDL_Delay #endif - +/* On iOS, usleep() of small numbers (say less than 100, very pronounced from 0-50) + * seems to be sucking up quite a bit of CPU time and causing performance problems. + * Instead of increasing the sleep time, I started changing the thread priority. + * This seemed to help the problem. + * Experimentally, the default priority seems to be 31. According to the docs, + * valid ranges are from 0 to 31. 6 was still giving me some hiccups so setting to + * 0 (PTHREAD_MIN_PRIORITY) seems to be the best value so far. + * Mac also reports 31 as the default. However, I have not noticed the same + * performance problems and cannot get audio to show up as a significant percentage + * of the CPU time in Shark/Instruments. + */ +#if defined(__APPLE__) && !defined(ALMIXER_COMPILE_WITHOUT_SDL) && ( (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) ) +#include <pthread.h> +#endif +static void Internal_LowerThreadPriority(SDL_Thread* simple_thread) +{ + /* Might open to other platforms as needed */ +#if defined(__APPLE__) && ( (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) ) + #ifdef ALMIXER_COMPILE_WITHOUT_SDL + SimpleThread_SetThreadPriority(Stream_Thread_global, 0); + #else + struct sched_param schedule_param; + int sched_policy; + int ret_val; + schedule_param.sched_priority = 0; /* PTHREAD_MIN_PRIORITY, max=31 */ + /* EVIL! This will break if the SDL_Thread structure layout changes. */ + pthread_t* native_thread_ptr_hack = (pthread_t*)(((char*)(Stream_Thread_global))+sizeof(unsigned long)); + ret_val = pthread_setschedparam(*native_thread_ptr_hack, SCHED_OTHER, &schedule_param); + #endif +#else + /* No-Op */ +#endif +} /* If ENABLE_PARANOID_SIGNEDNESS_CHECK is used, * these values will be reset on Init() @@ -374,7 +400,7 @@ struct ALmixer_Data { ALboolean decoded_all; /* dictates different behaviors */ - ALint total_time; /* total playing time of sample (msec) */ + ALint total_time; /* total playing time of sample (msec), obsolete now that we pushed our changes to SDL_sound */ ALuint in_use; /* needed to prevent sharing for streams */ ALboolean eof; /* flag for eof, only used for streams */ @@ -609,43 +635,6 @@ ALmixer_Channel_List[channel].almixer_data = NULL; } - -#if 0 -/* Not needed anymore because not doing any fileext checks. - * - * Unfortunately, strcasecmp isn't portable so here's a - * reimplementation of it (taken from SDL_sound) - */ -static int ALmixer_strcasecmp(const char* x, const char* y) -{ - int ux, uy; - - if (x == y) /* same pointer? Both NULL? */ - return(0); - - if (x == NULL) - return(-1); - - if (y == NULL) - return(1); - - do - { - ux = toupper((int) *x); - uy = toupper((int) *y); - if (ux > uy) - return(1); - else if (ux < uy) - return(-1); - x++; - y++; - } while ((ux) && (uy)); - - return(0); -} -#endif - - /* What shoud this return? * 127 for signed, 255 for unsigned */ @@ -798,6 +787,7 @@ } /* End Compute_Total_Time */ +#ifdef ALMIXER_DISABLE_PREDECODED_PRECOMPUTE_BUFFER_SIZE_OPTIMIZATION static size_t Compute_Total_Bytes_Decomposed(ALuint bytes_per_sample, ALuint frequency, ALubyte channels, ALuint total_msec) { double total_sec; @@ -845,6 +835,7 @@ return Compute_Total_Bytes_Decomposed(bytes_per_sample, info->rate, info->channels, total_msec); } + /* The back-end decoders seem to need to decode in quantized frame sizes. * So if I can pad the bytes to the next quanta, things might go more smoothly. */ @@ -884,9 +875,8 @@ fprintf(stderr, "remainder_frames=%d, padded_total_bytes=%d\n", remainder_frames, return_bytes); */ return return_bytes; - -} - +} +#endif /* ALMIXER_DISABLE_PREDECODED_PRECOMPUTE_BUFFER_SIZE_OPTIMIZATION */ @@ -1141,21 +1131,30 @@ /* Converts milliseconds to byte positions. * This is needed for seeking on predecoded samples */ -static ALuint Convert_Msec_To_Byte_Pos(Sound_AudioInfo *info, ALuint ms) -{ - float frames_per_ms; - ALuint frame_offset; - ALuint frame_size; - if(info == NULL) +static ALuint Convert_Msec_To_Byte_Pos(Sound_AudioInfo* audio_info, ALuint number_of_milliseconds) +{ + ALuint bytes_per_sample; + ALuint bytes_per_frame; + float bytes_per_millisecond; + float byte_position; + + if(audio_info == NULL) { fprintf(stderr, "Error, info is NULL\n"); } - /* "frames" == "sample frames" */ - frames_per_ms = ((float) info->rate) / 1000.0f; - frame_offset = (ALuint) (frames_per_ms * ((float) ms)); - frame_size = (ALuint) ((info->format & 0xFF) / 8) * info->channels; - return frame_offset * frame_size; + /* SDL has a mask trick I was not aware of. Mask the upper bits + * of the format, and you get 8 or 16 which is the bits per sample. + * Divide by 8bits_per_bytes and you get bytes_per_sample + * I tested this under 32-bit and 64-bit and big and little endian + * to make sure this still works since I have since moved from + * Uint32 to unspecified size types like ALuint. + */ + bytes_per_sample = (ALuint) ((audio_info->format & 0xFF) / 8); + bytes_per_frame = bytes_per_sample * audio_info->channels; + bytes_per_millisecond = (float)bytes_per_frame * (audio_info->rate / 1000.0f); + byte_position = ((float)(number_of_milliseconds)) * bytes_per_millisecond; + return (ALuint)(byte_position + 0.5); } static ALint Set_Predecoded_Seek_Position(ALmixer_Data* data, ALuint byte_position) @@ -3439,7 +3438,7 @@ ALfloat value; ALenum error; ALfloat original_value; - ALuint current_time = ALmixer_GetTicks(); +/* ALuint current_time = ALmixer_GetTicks(); */ ALint retval; @@ -4805,6 +4804,15 @@ #endif return 0; } + + /* Bypass if in interruption event */ + if(NULL == alcGetCurrentContext()) + { +#ifdef ENABLE_ALMIXER_THREADS + SDL_UnlockMutex(s_simpleLock); +#endif + return 0; + } /* Check the quick flag to see if anything needs updating */ /* If anything is playing, then we have to do work */ @@ -6208,7 +6216,7 @@ * the user will need to restart it if they need it after * OpenAL shuts down. */ -ALboolean ALmixer_Init(ALuint frequency, ALint num_sources, ALuint refresh) +ALboolean ALmixer_Init(ALuint frequency, ALuint num_sources, ALuint refresh) { ALCdevice* dev; ALCcontext* context; @@ -6576,42 +6584,48 @@ * This feature is toggled on/off by using the alDisable() & alEnable() APIs. This * setting will be applied to all subsequent * calls to alBufferData(). + * + * Update: Some people keep reporting that they see the enable fail on Mac, but I can't reproduce it myself. + * Rather than deal with it right now, I think I am going to make it an opt-in thing. */ -#ifdef __APPLE__ +#if defined(__APPLE__) && defined(ALMIXER_USE_OSX_CONVERT_DATA_UPON_LOADING) /* + iPhone is getting this enum, but is failing on the enable, so I guess I'll define it out. + */ #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) - + #else + /* iOS reports this enum exists, but loading it always fails, so make it Mac only. */ + ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING"); + /* + fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum); + */ + if(0 != convert_data_enum) + { + alEnable(convert_data_enum); + } + if( (AL_NO_ERROR != alGetError()) ) + { + fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); + ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); + } #endif -*/ - ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING"); - /* - fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum); - */ - if(0 != convert_data_enum) - { - alEnable(convert_data_enum); - } - if( (AL_NO_ERROR != alGetError()) ) - { - fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); - ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); - } - -#endif + +#endif /* __APPLE__ */ ALmixer_Initialized = 1; - if(num_sources <= 0) + if(num_sources == 0) { Number_of_Channels_global = ALMIXER_DEFAULT_NUM_CHANNELS; } else { - Number_of_Channels_global = num_sources; + /* probably should make Number_of_Channels_global an ALuint, but need to cast which_channel all the time */ + Number_of_Channels_global = (ALint)num_sources; } Number_of_Reserve_Channels_global = 0; Is_Playing_global = 0; @@ -6760,6 +6774,9 @@ Number_of_Channels_global = 0; return AL_FALSE; } + + /* Note: Only a few platforms change the priority. See implementation for notes. */ + Internal_LowerThreadPriority(Stream_Thread_global); /* fprintf(stderr, "Using threads\n"); @@ -7115,34 +7132,38 @@ * This feature is toggled on/off by using the alDisable() & alEnable() APIs. This * setting will be applied to all subsequent * calls to alBufferData(). + * Update: Some people keep reporting that they see the enable fail on Mac, but I can't reproduce it myself. + * Rather than deal with it right now, I think I am going to make it an opt-in thing. */ -#ifdef __APPLE__ - /* - #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) - - #else - #endif - */ - ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING"); +#if defined(__APPLE__) && defined(ALMIXER_USE_OSX_CONVERT_DATA_UPON_LOADING) /* - fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum); - */ - if(0 != convert_data_enum) - { - alEnable(convert_data_enum); - } - if( (AL_NO_ERROR != alGetError()) ) - { - fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); - ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); - } + iPhone is getting this enum, but is failing on the enable, so I guess I'll define it out. + */ + #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) + + #else + /* iOS reports this enum exists, but loading it always fails, so make it Mac only. */ + ALenum convert_data_enum = alcGetEnumValue(dev, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING"); + /* + fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING=0x%x", convert_data_enum); + */ + if(0 != convert_data_enum) + { + alEnable(convert_data_enum); + } + if( (AL_NO_ERROR != alGetError()) ) + { + fprintf(stderr, "ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); + ALmixer_SetError("ALC_MAC_OSX_CONVERT_DATA_UPON_LOADING attempted but failed"); + } + #endif #endif return AL_TRUE; } -ALboolean ALmixer_InitMixer(ALint num_sources) +ALboolean ALmixer_InitMixer(ALuint num_sources) { ALint i; ALenum error; @@ -7176,13 +7197,13 @@ */ #endif - if(num_sources <= 0) + if(num_sources == 0) { Number_of_Channels_global = ALMIXER_DEFAULT_NUM_CHANNELS; } else { - Number_of_Channels_global = num_sources; + Number_of_Channels_global = (ALint)num_sources; } Number_of_Reserve_Channels_global = 0; Is_Playing_global = 0; @@ -7306,7 +7327,10 @@ Number_of_Channels_global = 0; return AL_FALSE; } - + + /* Note: Only a few platforms change the priority. See implementation for notes. */ + Internal_LowerThreadPriority(Stream_Thread_global); + /* fprintf(stderr, "Using threads\n"); */ @@ -7319,7 +7343,54 @@ return AL_TRUE; } - +void ALmixer_BeginInterruption() +{ +#ifdef ENABLE_ALMIXER_THREADS + SDL_LockMutex(s_simpleLock); +#endif + s_interruptionContext = alcGetCurrentContext(); + if(NULL != s_interruptionContext) + { + /* iOS alcSuspendContext is a no-op */ + alcSuspendContext(s_interruptionContext); + alcMakeContextCurrent(NULL); + } +#ifdef ENABLE_ALMIXER_THREADS + SDL_UnlockMutex(s_simpleLock); +#endif +} + +void ALmixer_EndInterruption() +{ +#ifdef ENABLE_ALMIXER_THREADS + SDL_LockMutex(s_simpleLock); +#endif + + /* Note: iOS, you need to set the AudioSession active. + * But if the AudioSession is not initialized, this SetActive + * call fails. + * So this is probably better if calling app sets this. + */ +/* +#if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) + OSStatus the_error = AudioSessionSetActive(true); + if(noErr != the_error) + { + fprintf(stderr, "Error setting audio session active! %d\n", the_error); + } +#endif + */ + + if(NULL != s_interruptionContext) + { + alcMakeContextCurrent(s_interruptionContext); + alcProcessContext(s_interruptionContext); + s_interruptionContext = NULL; + } +#ifdef ENABLE_ALMIXER_THREADS + SDL_UnlockMutex(s_simpleLock); +#endif +} /* Keep the return value void to allow easy use with * atexit() @@ -7337,6 +7408,34 @@ #ifdef ENABLE_ALMIXER_THREADS SDL_LockMutex(s_simpleLock); #endif + + /* Several things we need to do: + First, we need to check if we are in an interruption. + If so, we need to reactivate the alcContext so we can call OpenAL functions. + Next, we should delete the OpenAL sources. + Next, we need to free all the sound data via ALmixer_FreeData(). + Finally, we can delete the OpenAL context. + */ + + context = alcGetCurrentContext(); + if(NULL == context) + { + /* We might have an interruption event where the current context is NULL */ + if(NULL == s_interruptionContext) + { + /* Nothing left to try. I think we're done. */ + fprintf(stderr, "ALmixer_Quit: Assertion Error. Expecting to find an OpenAL context, but could not find one.\n"); + return; + } + else + { + context = s_interruptionContext; + /* reactivate the context so we can call OpenAL functions */ + alcMakeContextCurrent(context); + s_interruptionContext = NULL; + } + } + /* Shutdown everything before closing context */ Internal_HaltChannel(-1, AL_FALSE); @@ -7348,7 +7447,7 @@ SDL_DestroyMutex(s_simpleLock); #endif - + /* Delete all the OpenAL sources */ for(i=0; i<Number_of_Channels_global; i++) { @@ -7357,7 +7456,7 @@ /* Delete all the channels */ free(ALmixer_Channel_List); free(Source_Map_List); - + /* Reset the Number_of_Channels just in case somebody * tries using a ALmixer function. * I probably should put "Initialized" checks everywhere, @@ -7365,21 +7464,7 @@ */ Number_of_Channels_global = 0; - context = alcGetCurrentContext(); - if(NULL == context) - { - return; - } - /* Need to get the device before I close the context */ - dev = alcGetContextsDevice(context); - alcDestroyContext(context); - - if(NULL == dev) - { - return; - } - alcCloseDevice(dev); - + /* Delete the list of ALmixerData's before Sound_Quit deletes * its own underlying information and I potentially have dangling pointers. */ @@ -7391,7 +7476,20 @@ } LinkedList_Free(s_listOfALmixerData); s_listOfALmixerData = NULL; - + + + /* Need to get the device before I close the context */ + dev = alcGetContextsDevice(context); + + alcMakeContextCurrent(NULL); + alcDestroyContext(context); + + if(NULL == dev) + { + return; + } + alcCloseDevice(dev); + Sound_Quit(); #ifdef ALMIXER_COMPILE_WITHOUT_SDL @@ -7841,6 +7939,8 @@ */ ret_data->total_bytes = 0; + ret_data->total_time = Sound_GetDuration(sample); + /* Create buffers for data */ ret_data->buffer = (ALuint*)malloc( sizeof(ALuint) * max_queue_buffers); @@ -8369,6 +8469,13 @@ { return; } + + /* Bypass if in interruption event */ + if(NULL == alcGetCurrentContext()) + { + fprintf(stderr, "ALmixer_FreeData: Programmer Error. You cannot delete data when the OpenAL content is currently NULL. You may have already called ALmixer_Quit() or are in an interruption event\n"); + return; + } if(data->decoded_all) { @@ -8380,12 +8487,10 @@ Sound_FreeSample(data->sample); } alDeleteBuffers(1, data->buffer); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "70Testing error: %s\n", - alGetString(error)); - } - + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "ALmixer_FreeData: alDeleteBuffers failed. %s\n", alGetString(error)); + } } else { @@ -8407,11 +8512,10 @@ Sound_FreeSample(data->sample); alDeleteBuffers(data->max_queue_buffers, data->buffer); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "71Testing error: %s\n", - alGetString(error)); - } + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "ALmixer_FreeData: alDeleteBuffers failed. %s\n", alGetString(error)); + } } free(data->buffer); @@ -8428,6 +8532,7 @@ { return -1; } + return data->total_time; }
--- a/ALmixer.h Mon Nov 08 22:19:47 2010 -0800 +++ b/ALmixer.h Fri Dec 24 03:33:31 2010 -0800 @@ -168,11 +168,7 @@ /* Needed for OpenAL types since altypes.h was removed in 1.1 */ -#ifdef ANDROID_NDK - #include <AL/al.h> -#else - #include "al.h" -#endif +#include "al.h" /* Set up for C function definitions, even when using C++ */ #ifdef __cplusplus @@ -203,7 +199,7 @@ /* Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL */ #define ALMIXER_MAJOR_VERSION 0 -#define ALMIXER_MINOR_VERSION 1 +#define ALMIXER_MINOR_VERSION 2 #define ALMIXER_PATCHLEVEL 0 @@ -300,7 +296,7 @@ #ifdef ALMIXER_COMPILE_WITHOUT_SDL - #include "ALmixer_rwops.h" + #include "ALmixer_RWops.h" #else #include "SDL_rwops.h" /** @@ -344,7 +340,7 @@ * Pass in 0 or ALMIXER_DEFAULT_REFRESH to use OpenAL default behaviors. * @return Returns AL_FALSE on a failure or AL_TRUE if successfully initialized. */ -extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_Init(ALuint playback_frequency, ALint num_sources, ALuint refresh_rate); +extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_Init(ALuint playback_frequency, ALuint num_sources, ALuint refresh_rate); /** * InitContext will only initialize the OpenAL context (and not the mixer part). @@ -371,9 +367,24 @@ * the mixer system separately. I strongly recommend avoiding these two functions * and use the normal Init() function. */ -extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_InitMixer(ALint num_sources); +extern ALMIXER_DECLSPEC ALboolean ALMIXER_CALL ALmixer_InitMixer(ALuint num_sources); + +/** + * (EXPERIMENTAL) Call to notify ALmixer that your device needs to handle an interruption. + * (EXPERIMENTAL) For devices like iOS that need special handling for interruption events like phone calls and alarms, + * this function will do the correct platform correct thing to handle the interruption w.r.t. OpenAL. + */ +extern ALMIXER_DECLSPEC void ALMIXER_CALL ALmixer_BeginInterruption(void); /** + * (EXPERIMENTAL) Call to notify ALmixer that your device needs to resume from an interruption. + * (EXPERIMENTAL) For devices like iOS that need special handling for interruption events like phone calls and alarms, + * this function will do the correct platform correct thing to resume from the interruption w.r.t. OpenAL. + */ +extern ALMIXER_DECLSPEC void ALMIXER_CALL ALmixer_EndInterruption(void); + + +/** * This shuts down ALmixer. Please remember to free your ALmixer_Data* instances * before calling this method. */
--- a/LinkedList.c Mon Nov 08 22:19:47 2010 -0800 +++ b/LinkedList.c Fri Dec 24 03:33:31 2010 -0800 @@ -251,13 +251,14 @@ void LinkedList_Clear(LinkedList* linked_list) { + LinkedListNode* current_node; + LinkedListNode* next_node; if(NULL == linked_list) { return; } - LinkedListNode* current_node = linked_list->headPtr; - LinkedListNode* next_node; + current_node = linked_list->headPtr; while(NULL != current_node) { next_node = current_node->nextNode;