openal: Retry deleting a buffer until success

The rationale for this change is Apple's OpenAL implementation,
which needs a little time after the
`alSourcei(source, AL_BUFFER, 0);`
call before any buffers used by that source are free for deletion.

The defaults in the config variables are such that the OpenAL manager
will attempt to delete a buffer up to 6 times (that is, the original
attempt plus 5 reattempts), with delays of 1ms, 2ms, 4ms, 8ms, and 16ms
before each reattempt - which means it'll wait a grand total of 31ms for
a buffer to be free before assuming that some even greater problem must
be happening and giving up.
This commit is contained in:
Sam Edwards 2018-03-01 16:44:46 -07:00
parent 1862100bac
commit 6ca0a68042
5 changed files with 63 additions and 4 deletions

View File

@ -30,6 +30,23 @@ ConfigVariableString openal_device
PRC_DESC("Specify the OpenAL device string for audio playback (no quotes). If this "
"is not specified, the OpenAL default device is used."));
ConfigVariableInt openal_buffer_delete_reattempts
("openal-buffer-delete-reattempts", 5,
PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL "
"sound plugin will wait a moment and reattempt deletion, with an "
"exponentially-increasing delay for each attempt. This number "
"specifies how many repeat attempts (not counting the initial attempt) "
"should be made before giving up and raising an error."));
ConfigVariableDouble openal_buffer_delete_delay
("openal-buffer-delete-delay", 0.001,
PRC_DESC("If deleting a buffer fails due to still being in use, the OpenAL "
"sound plugin will wait a moment and reattempt deletion, with an "
"exponentially-increasing delay for each attempt. This number "
"specifies how long, in seconds, the OpenAL plugin will wait after "
"its first failed attempt. The second attempt will be double this "
"delay, the third quadruple, and so on."));
/**
* Initializes the library. This must be called at least once before any of

View File

@ -26,5 +26,7 @@ extern "C" EXPCL_OPENAL_AUDIO void init_libOpenALAudio();
extern "C" EXPCL_OPENAL_AUDIO Create_AudioManager_proc *get_audio_manager_func_openal_audio();
extern ConfigVariableString openal_device;
extern ConfigVariableInt openal_buffer_delete_reattempts;
extern ConfigVariableDouble openal_buffer_delete_delay;
#endif // CONFIG_OPENALAUDIO_H

View File

@ -1052,7 +1052,7 @@ OpenALAudioManager::SoundData::
if (_sample != 0) {
if (_manager->_is_valid) {
_manager->make_current();
alDeleteBuffers(1,&_sample);
_manager->delete_buffer(_sample);
}
_sample = 0;
}
@ -1128,3 +1128,42 @@ discard_excess_cache(int sample_limit) {
delete sd;
}
}
/**
* Deletes an OpenAL buffer. This is a special function because some
* implementations of OpenAL (e.g. Apple's) don't unlock the buffers
* immediately, due to needing to coordinate with another thread. If this is
* the case, the alDeleteBuffers call will error back with AL_INVALID_OPERATION
* as if trying to delete an actively-used buffer, which will tell us to wait a
* bit and try again.
*/
void OpenALAudioManager::
delete_buffer(ALuint buffer) {
ReMutexHolder holder(_lock);
int attempt = 0;
ALuint error;
// Keep trying until we succeed (or give up).
while (true) {
alDeleteBuffers(1, &buffer);
error = alGetError();
if (error == AL_NO_ERROR) {
// Success! This will happen right away 99% of the time.
return;
} else if (error != AL_INVALID_OPERATION) {
// We weren't expecting that. This should be reported.
break;
} else if (attempt >= openal_buffer_delete_reattempts.get_value()) {
// We ran out of reattempts. Give up.
break;
} else {
// Make another attempt after (delay * 2^n) seconds.
Thread::sleep(openal_buffer_delete_delay.get_value() * (1 << attempt));
attempt++;
}
}
// If we got here, one of the breaks above happened, indicating an error.
audio_error("failed to delete a buffer: " << alGetString(error) );
}

View File

@ -129,6 +129,8 @@ private:
void decrement_client_count(SoundData *sd);
void discard_excess_cache(int limit);
void delete_buffer(ALuint buffer);
void starting_sound(OpenALAudioSound* audio);
void stopping_sound(OpenALAudioSound* audio);

View File

@ -208,8 +208,7 @@ stop() {
for (int i=0; i<((int)(_stream_queued.size())); i++) {
ALuint buffer = _stream_queued[i]._buffer;
if (buffer != _sd->_sample) {
alDeleteBuffers(1, &buffer);
al_audio_errcheck("deleting a buffer");
_manager->delete_buffer(buffer);
}
}
_stream_queued.resize(0);
@ -472,7 +471,7 @@ pull_used_buffers() {
correct_calibrated_clock(rtc, al);
}
if (buffer != _sd->_sample) {
alDeleteBuffers(1,&buffer);
_manager->delete_buffer(buffer);
}
}
} else {