Mercurial > almixer_isolated
diff ALmixer.c @ 52:53ee4253c925
merged
author | Eric Wing <ewing@anscamobile.com> |
---|---|
date | Wed, 18 Jan 2012 12:20:54 -0800 |
parents | e2687188aea5 |
children | 8063b19bd40e |
line wrap: on
line diff
--- a/ALmixer.c Tue Sep 13 18:11:34 2011 -0700 +++ b/ALmixer.c Wed Jan 18 12:20:54 2012 -0800 @@ -1692,6 +1692,171 @@ } +static ALboolean Internal_DetachBuffersFromSource(ALuint source_id, ALboolean is_predecoded) +{ + ALboolean retval = AL_TRUE; + ALenum error; + /* Here's the situation. My old method of using + * alSourceUnqueueBuffers() seemed to be invalid in light + * of all the problems I suffered through with getting + * the CoreData backend to work with this code. + * As such, I'm changing all the code to set the buffer to + * AL_NONE. Furthermore, the queued vs. non-queued issue + * doesn't need to apply here. For non-queued, Loki, + * Creative Windows, and CoreAudio seem to leave the + * buffer queued (Old Mac didn't.) For queued, we need to + * remove the processed buffers and force remove the + * still-queued buffers. + * Update: This code is was changed agian due to a serious regression bug in iOS 5.0 + * Since the 1.1 spec, I think I can make some simplifying assumptions sans the iOS 5.0 bug. + * Before I looked at buffers_still_queued and buffers_processed. + * According to the spec, all buffers get marked processed when alSourceStop is called. + * Also, in addition, alSourcei(source, AL_BUFFER, AL_NONE) is supposed + * to detach buffers for both streamed and non-streamed so all the code + * should just go through that. + * Unfortunately, the iOS 5.0 bug doesn't detach/clear buffers with AL_NONE. + * So I need a special handler for iOS 5.0 which manually unqueues the buffers. + * Ironically, I think the original Mac version did not work reliably + * with unqueuing buffers which is why I moved the code to the AL_NONE + * solution in the first place. This means for safety, I need to + * conditionalize the workaround as not to risk breaking Mac or other * platforms. + */ +#ifdef __APPLE__ /* iOS 5.0 workaround */ + #if (TARGET_OS_IPHONE == 1) || (TARGET_IPHONE_SIMULATOR == 1) + /* + fprintf(stderr, "kCFCoreFoundationVersionNumber: %lf\n", kCFCoreFoundationVersionNumber); + */ + /* I needed a C way to get the iOS version at runtime. This is returning 674.0 iOS 5.0 Beta 7. Apple hasn't updated the headers + * for these constants since iOS 4.2, so I don't know if the value is also catching 4.3. + * iOS 5.0.1 final is returning 675.0. + * TODO: Once I learn which version Apple fixes the bug in, I need to update the range so this check is not run on fixed versions. + * iOS 5.1 Beta 1 is returning 690.0. + */ + if(kCFCoreFoundationVersionNumber >= 674.0 && kCFCoreFoundationVersionNumber < 690.0) + { + /* For OpenAL experts, this is contrary to what you know, but must be done because the OpenAL implementation is broken. + Instead of unqueuing buffers on only streaming sources, it appears that alSourcei(source, AL_BUFFER, AL_NONE) is not reliable at all. + In cases where I switch between stream and non-stream on the same source and then stream again, the bug breaks playback on the third playback + and only one buffer plays. + The workaround seems to be to always unqueue buffers regardless of whether the source is streamed or not. + And then avoid calling (source, AL_BUFFER, AL_NONE) + From past experience, I know it is a bad idea to try to unqueue buffers from a non-streamed source (which is the contrary to OpenAL part), + but this seems to work for this bug. + */ + ALint buffers_processed; + /* Crap. Another iOS 5.0 problem. It seems our loop workaround below can get in cases where the buffer never clears. + * It appears that in some cases (probably predecoded, but not always which makes it hard), that the buffer never can be unqueued. + * I think it may possibly happen if you never use a source for streaming, and it is also possible it happens after loading or using a certain number of sources. + * So to workaround, we need to abort after a certain amount of time to prevent an infinite loop check. + * Some testing on iPad 2, 122ms is the highest number I've seen so far. So maybe 200ms is the cap? + */ + ALuint timeout_counter = ALmixer_GetTicks(); + const ALuint MAX_NUMBER_OF_TICKS_TO_WAIT_IN_WORKAROUND = 200; + + /* Wow this is the bug that just keeps on sucking. There is even a race condition bug where the unqueue may not actually work. + * So I have to keep doing it until it does. + */ + do + { + ALint temp_count; + + /* Wow this is the bug that just keeps on sucking. There is even a race condition bug where the unqueue may not actually work. + * So I have to keep doing it until it does. + * Sleeping for 20ms seems to help. 10ms was not long enough. (iPad 2). + * Update: A user is hitting this in a tight loop and calling dozens/hundreds of times. The sleep some times seems to result in a stutter. + * My theory is that there is thread contention and sleeps may be queuing up. The workaround seems to be reduce the sleep or eliminate it. + * I prefer to reduce it because fundamentally I am waiting for the Core Audio/OpenAL thread to finish processing so yielding seems like a good idea. + * The flipside is that I hit a lot of OpenAL errors because the commands keep failing and I'm burning CPU unnecessarily. + * So far, the OpenAL errors seem harmless and there are no serious cascading failures I've encountered. + */ + ALmixer_Delay(0); + + alGetSourcei( + source_id, + AL_BUFFERS_PROCESSED, &buffers_processed + ); + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "17aTesting Error with buffers_processed on Halt. (You may be seeing this because of a bad Apple OpenAL iOS 5.0 regression bug): %s", + alGetString(error)); + ALmixer_SetError("Failed detecting still processed buffers: %s", + alGetString(error) ); + /* This whole iOS 5.0 bug is so messed up that returning an error value probably isn't helpful. + retval = AL_FALSE; + */ + } + /* + fprintf(stderr, "Going to unqueue %d buffers\n", buffers_processed); + */ + for(temp_count=0; temp_count<buffers_processed; temp_count++) + { + ALuint unqueued_buffer_id; + + alSourceUnqueueBuffers( + source_id, + 1, &unqueued_buffer_id + ); + if((error = alGetError()) != AL_NO_ERROR) + { + /* Disabling this print because we hit it way too much with the usleep(0) */ +/* fprintf(stderr, "17bTesting error with unqueuing buffers on Halt (You may be seeing this because of a bad Apple OpenAL iOS 5.0 regression bug): %s\n", alGetString(error)); + */ + /* This whole iOS 5.0 bug is so messed up that returning an error value probably isn't helpful. + retval = AL_FALSE; + */ + } + } + + alGetSourcei( + source_id, + AL_BUFFERS_PROCESSED, &buffers_processed + ); + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "17cTesting Error with buffers_processed on Halt. (You may be seeing this because of a bad Apple OpenAL iOS 5.0 regression bug): %s", alGetString(error)); + ALmixer_SetError("Failed detecting still processed buffers: %s", alGetString(error) ); + /* This whole iOS 5.0 bug is so messed up that returning an error value probably isn't helpful. + retval = AL_FALSE; + */ + } + /* + fprintf(stderr, "unqueued buffers should be 0. Actual value is %d\n", buffers_processed); + */ + /* Wow this is the bug that just keeps on sucking. There is an additional race condition bug where the unqueue may not actually work. + * So I have to keep doing it until it does. + * I hope this doesn't infinite loop. + */ + /* Disabling this print because we hit it way too much with the usleep(0) */ + /* + if(0 != buffers_processed) + { + fprintf(stderr, "Evil Apple OpenAL iOS 5.0 race condition. Buffers didn't actually unqueue. Repeating unqueue loop. %d, %d %d\n", ALmixer_GetTicks(), timeout_counter, ALmixer_GetTicks()-timeout_counter); + } + */ + } while(0 != buffers_processed && ( (ALmixer_GetTicks()-timeout_counter) < MAX_NUMBER_OF_TICKS_TO_WAIT_IN_WORKAROUND) ); + + /* Avoid calling the normal cleanup because part of this bug seems to be triggered by alSourcei(source_id, AL_BUFFER, AL_NONE); */ + return retval; + } + + #endif +#endif /* iOS 5.0 workaround */ + + /* According to the spec, this is the best way to clear a source. + * This is supposed to work for both streamed and non-streamed sources. + */ + alSourcei(source_id, AL_BUFFER, AL_NONE); + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "17dTesting Error with clearing buffer from source: %s", + alGetString(error)); + ALmixer_SetError("Failed to clear buffer from source: %s", + alGetString(error) ); + retval = AL_FALSE; + } + + return retval; +} /* Will return the number of channels halted * or 0 for error @@ -1701,8 +1866,7 @@ ALint retval = 0; ALint counter = 0; ALenum error; - ALint buffers_still_queued; - ALint buffers_processed; + ALboolean clear_succeeded; if(channel >= Number_of_Channels_global) { @@ -1721,59 +1885,13 @@ fprintf(stderr, "14Testing error: %s\n", alGetString(error)); } - /* Here's the situation. My old method of using - * alSourceUnqueueBuffers() seemed to be invalid in light - * of all the problems I suffered through with getting - * the CoreData backend to work with this code. - * As such, I'm changing all the code to set the buffer to - * AL_NONE. Furthermore, the queued vs. non-queued issue - * doesn't need to apply here. For non-queued, Loki, - * Creative Windows, and CoreAudio seem to leave the - * buffer queued (Old Mac didn't.) For queued, we need to - * remove the processed buffers and force remove the - * still-queued buffers. - */ - alGetSourcei( - ALmixer_Channel_List[channel].alsource, - AL_BUFFERS_QUEUED, &buffers_still_queued - ); - if((error = alGetError()) != AL_NO_ERROR) + + clear_succeeded = Internal_DetachBuffersFromSource(ALmixer_Channel_List[channel].alsource, ALmixer_Channel_List[channel].almixer_data->decoded_all); + if(AL_FALSE == clear_succeeded) { - fprintf(stderr, "17Testing Error with buffers_still_queued: %s", - alGetString(error)); - ALmixer_SetError("Failed detecting still queued buffers: %s", - alGetString(error) ); retval = -1; } - alGetSourcei( - ALmixer_Channel_List[channel].alsource, - AL_BUFFERS_PROCESSED, &buffers_processed - ); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "17Testing Error with buffers_processed: %s", - alGetString(error)); - ALmixer_SetError("Failed detecting still processed buffers: %s", - alGetString(error) ); - retval = -1; - } - /* If either of these is greater than 0, it means we need - * to clear the source - */ - if((buffers_still_queued > 0) || (buffers_processed > 0)) - { - alSourcei(ALmixer_Channel_List[channel].alsource, - AL_BUFFER, - AL_NONE); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "17Testing Error with clearing buffer from source: %s", - alGetString(error)); - ALmixer_SetError("Failed to clear buffer from source: %s", - alGetString(error) ); - retval = -1; - } - } + ALmixer_Channel_List[channel].almixer_data->num_buffers_in_use = 0; @@ -1801,60 +1919,12 @@ alGetString(error)); } - /* Here's the situation. My old method of using - * alSourceUnqueueBuffers() seemed to be invalid in light - * of all the problems I suffered through with getting - * the CoreData backend to work with this code. - * As such, I'm changing all the code to set the buffer to - * AL_NONE. Furthermore, the queued vs. non-queued issue - * doesn't need to apply here. For non-queued, Loki, - * Creative Windows, and CoreAudio seem to leave the - * buffer queued (Old Mac didn't.) For queued, we need to - * remove the processed buffers and force remove the - * still-queued buffers. - */ - alGetSourcei( - ALmixer_Channel_List[i].alsource, - AL_BUFFERS_QUEUED, &buffers_still_queued - ); - if((error = alGetError()) != AL_NO_ERROR) + clear_succeeded = Internal_DetachBuffersFromSource(ALmixer_Channel_List[i].alsource, ALmixer_Channel_List[i].almixer_data->decoded_all); + if(AL_FALSE == clear_succeeded) { - fprintf(stderr, "17Testing Error with buffers_still_queued: %s", - alGetString(error)); - ALmixer_SetError("Failed detecting still queued buffers: %s", - alGetString(error) ); retval = -1; } - alGetSourcei( - ALmixer_Channel_List[i].alsource, - AL_BUFFERS_PROCESSED, &buffers_processed - ); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "17Testing Error with buffers_processed: %s", - alGetString(error)); - ALmixer_SetError("Failed detecting still processed buffers: %s", - alGetString(error) ); - retval = -1; - } - /* If either of these is greater than 0, it means we need - * to clear the source - */ - if((buffers_still_queued > 0) || (buffers_processed > 0)) - { - alSourcei(ALmixer_Channel_List[i].alsource, - AL_BUFFER, - AL_NONE); - if((error = alGetError()) != AL_NO_ERROR) - { - fprintf(stderr, "17Testing Error with clearing buffer from source: %s", - alGetString(error)); - ALmixer_SetError("Failed to clear buffer from source: %s", - alGetString(error) ); - retval = -1; - } - } - + ALmixer_Channel_List[i].almixer_data->num_buffers_in_use = 0; /* Launch callback for consistency? */ @@ -2179,7 +2249,7 @@ /* only need to process channel if in use */ if(ALmixer_Channel_List[channel].channel_in_use) { - + running_count = 1; /* What should I do? Do I just rewind the channel * or also rewind the data? Since the data is * shared, let's make it the user's responsibility @@ -2243,7 +2313,10 @@ * much data is queued. Recommend users call Halt * before rewind if they want immediate results. */ - retval = Internal_RewindData(ALmixer_Channel_List[channel].almixer_data); + if(AL_FALSE == Internal_RewindData(ALmixer_Channel_List[channel].almixer_data)) + { + retval = -1; + } } } } @@ -2256,6 +2329,7 @@ /* only need to process channel if in use */ if(ALmixer_Channel_List[i].channel_in_use) { + running_count++; /* What should I do? Do I just rewind the channel * or also rewind the data? Since the data is * shared, let's make it the user's responsibility @@ -2319,7 +2393,10 @@ * much data is queued. Recommend users call Halt * before rewind if they want immediate results. */ - running_count += Internal_RewindData(ALmixer_Channel_List[i].almixer_data); + if(AL_FALSE == Internal_RewindData(ALmixer_Channel_List[i].almixer_data)) + { + retval = -1; + } } } } @@ -2689,7 +2766,7 @@ ALuint queue_ret_flag; for(k=0; k<data->num_buffers_in_use; k++) { -// fprintf(stderr, "56c: CircularQueue_PushBack.\n"); +/* fprintf(stderr, "56c: CircularQueue_PushBack.\n"); */ queue_ret_flag = CircularQueueUnsignedInt_PushBack(data->circular_buffer_queue, data->buffer[k]); if(0 == queue_ret_flag) { @@ -3291,18 +3368,8 @@ alGetString(error) ); retval = -1; } - /* Need to resume playback if it was originally playing */ - if(AL_PLAYING == state) - { - alSourcePlay(ALmixer_Channel_List[channel].alsource); - if((error = alGetError()) != AL_NO_ERROR) - { - ALmixer_SetError("%s", - alGetString(error) ); - retval = -1; - } - } - else if(AL_PAUSED == state) + /* OpenAL 1.1 spec says if this succeeds on a playing source, it will automatically jump */ + if(AL_PAUSED == state) { /* HACK: The problem is that when paused, after * the Rewind, I can't get it off the INITIAL @@ -3331,8 +3398,12 @@ * much data is queued. Recommend users call Halt * before rewind if they want immediate results. */ - retval = Internal_SeekData(ALmixer_Channel_List[channel].almixer_data, msec); + if(AL_FALSE == Internal_SeekData(ALmixer_Channel_List[channel].almixer_data, msec)) + { + retval = -1; + } } + running_count = 1; } } /* The user wants to rewind all channels */ @@ -3363,6 +3434,7 @@ alGetString(error)); } + /* OpenAL 1.1 spec says if this succeeds on a playing source, it will automatically jump */ alSourcef(ALmixer_Channel_List[channel].alsource, AL_SEC_OFFSET, sec_offset); if((error = alGetError()) != AL_NO_ERROR) { @@ -3370,18 +3442,7 @@ alGetString(error) ); retval = -1; } - /* Need to resume playback if it was originally playing */ - if(AL_PLAYING == state) - { - alSourcePlay(ALmixer_Channel_List[i].alsource); - if((error = alGetError()) != AL_NO_ERROR) - { - ALmixer_SetError("%s", - alGetString(error) ); - retval = -1; - } - } - else if(AL_PAUSED == state) + if(AL_PAUSED == state) { /* HACK: The problem is that when paused, after * the Rewind, I can't get it off the INITIAL @@ -3410,8 +3471,12 @@ * much data is queued. Recommend users call Halt * before rewind if they want immediate results. */ - running_count += Internal_SeekData(ALmixer_Channel_List[i].almixer_data, msec); + if(AL_FALSE == Internal_SeekData(ALmixer_Channel_List[i].almixer_data, msec)) + { + retval = -1; + } } + running_count++; } } } @@ -5173,36 +5238,11 @@ } if(buffers_still_queued > 0) { - -#if 0 /* This triggers an error in OS X Core Audio. */ - alSourceUnqueueBuffers( - ALmixer_Channel_List[i].alsource, - 1, - ALmixer_Channel_List[i].almixer_data->buffer - ); -#else -/* fprintf(stderr, "In the Bob Aron section...about to clear source\n"); - PrintQueueStatus(ALmixer_Channel_List[i].alsource); -*/ - /* Rather than force unqueuing the buffer, let's see if - * setting the buffer to none works (the OpenAL 1.0 - * Reference Annotation suggests this should work). - */ - alSourcei(ALmixer_Channel_List[i].alsource, - AL_BUFFER, AL_NONE); - /* - PrintQueueStatus(ALmixer_Channel_List[i].alsource); - */ -#endif - if((error = alGetError()) != AL_NO_ERROR) + ALboolean clear_succeeded = Internal_DetachBuffersFromSource(ALmixer_Channel_List[i].alsource, ALmixer_Channel_List[i].almixer_data->decoded_all); + if(AL_FALSE == clear_succeeded) { - fprintf(stderr, "Error with unqueue, after alSourceUnqueueBuffers, buffers_still_queued=%d, error is: %s", buffers_still_queued, - alGetString(error)); - ALmixer_SetError("Predecoded Unqueue buffer failed: %s", - alGetString(error) ); error_flag--; } - } /* Launch callback */ @@ -5276,7 +5316,7 @@ /* WARNING: It looks like Snow Leopard some times crashes on this call under x86_64 * typically when I suffer a lot of buffer underruns. */ -// fprintf(stderr, "calling AL_BUFFERS_PROCESSED on source:%d", ALmixer_Channel_List[i].alsource); +/* fprintf(stderr, "calling AL_BUFFERS_PROCESSED on source:%d", ALmixer_Channel_List[i].alsource); */ alGetSourcei( ALmixer_Channel_List[i].alsource, AL_BUFFERS_PROCESSED, &buffers_processed @@ -5286,7 +5326,7 @@ fprintf(stderr, "52Testing error: %s\n", alGetString(error)); } -// fprintf(stderr, "finished AL_BUFFERS_PROCESSED, buffers_processed=%d", buffers_processed); +/* fprintf(stderr, "finished AL_BUFFERS_PROCESSED, buffers_processed=%d", buffers_processed); */ /* WTF!!! The Nvidia distribution is failing on the alGetSourcei(source, AL_BUFFER, buf_id) call. * I need this call to figure out which buffer OpenAL is currently playing. @@ -5483,7 +5523,7 @@ if(AL_STOPPED == state) { number_of_buffers_to_queue_this_pass = ALmixer_Channel_List[i].almixer_data->num_startup_buffers; -// fprintf(stderr, "assuming underrun condition, using num_startup_buffers=%d\n", number_of_buffers_to_queue_this_pass); +/* fprintf(stderr, "assuming underrun condition, using num_startup_buffers=%d\n", number_of_buffers_to_queue_this_pass); */ } /* Don't bother to check to make sure the number_of_buffers_to_queue_this_pass does not exceed the maximum number of buffers because of the logic hack bug. */ @@ -5497,7 +5537,25 @@ } for(current_count_of_buffer_queue_passes=0; current_count_of_buffer_queue_passes<number_of_buffers_to_queue_this_pass; current_count_of_buffer_queue_passes++) { -// fprintf(stderr, "current_count_of_buffer_queue_passes:%d\n", current_count_of_buffer_queue_passes); +/* fprintf(stderr, "current_count_of_buffer_queue_passes:%d\n", current_count_of_buffer_queue_passes); */ + + /* Because I introduced this for-loop, I think I need to regrab the number of processed buffers because + * the number may now be stale from previous iterations. I suppose I could do it at the end of the loop, + * but the logic flow is already too complicated to ensure that the block is being hit. + */ + /* Get the number of buffers processed + */ + alGetSourcei( + ALmixer_Channel_List[i].alsource, + AL_BUFFERS_PROCESSED, + &buffers_processed + ); + if((error = alGetError()) != AL_NO_ERROR) + { + fprintf(stderr, "59aTestingError, Can't get buffers_processed: %s\n", + alGetString(error)); + } + /* For this to work, we must rely on EVERYTHING * else to unset the EOF if there is looping. @@ -5918,7 +5976,7 @@ if(ALmixer_Channel_List[i].almixer_data->circular_buffer_queue != NULL) { ALuint queue_ret_flag; - // fprintf(stderr, "56d: CircularQueue_PushBack.\n"); + /* fprintf(stderr, "56d: CircularQueue_PushBack.\n"); */ queue_ret_flag = CircularQueueUnsignedInt_PushBack( ALmixer_Channel_List[i].almixer_data->circular_buffer_queue, ALmixer_Channel_List[i].almixer_data->buffer[ALmixer_Channel_List[i].almixer_data->num_buffers_in_use] @@ -5966,7 +6024,7 @@ if(ALmixer_Channel_List[i].almixer_data->circular_buffer_queue != NULL) { ALuint queue_ret_flag; - // fprintf(stderr, "56e: CircularQueue_PushBack.\n"); + /* fprintf(stderr, "56e: CircularQueue_PushBack.\n"); */ queue_ret_flag = CircularQueueUnsignedInt_PushBack( ALmixer_Channel_List[i].almixer_data->circular_buffer_queue, unqueued_buffer_id @@ -6220,8 +6278,11 @@ * setting the buffer to none works (the OpenAL 1.0 * Reference Annotation suggests this should work). */ - alSourcei(ALmixer_Channel_List[i].alsource, - AL_BUFFER, AL_NONE); + ALboolean clear_succeeded = Internal_DetachBuffersFromSource(ALmixer_Channel_List[i].alsource, ALmixer_Channel_List[i].almixer_data->decoded_all); + if(AL_FALSE == clear_succeeded) + { + error_flag--; + } /* PrintQueueStatus(ALmixer_Channel_List[i].alsource); */ @@ -7737,7 +7798,7 @@ } va_list argp; va_start(argp, err_str); - // SDL_SetError which I'm emulating has no number parameter. + /* SDL_SetError which I'm emulating has no number parameter. */ TError_SetErrorv(s_ALmixerErrorPool, 1, err_str, argp); va_end(argp); } @@ -8268,7 +8329,7 @@ free(ret_data->buffer_map_list[j].data); j--; } - // Delete for j=0 because the while loop misses the last one + /* Delete for j=0 because the while loop misses the last one */ free(ret_data->buffer_map_list[j].data); free(ret_data->buffer_map_list);