audio: threading and device hang improvements.
This tries to make SDL robust against device drivers that have hung up, apps don't freeze in catastrophic (but not necessarily uncommon) conditions. Now we detach the audio thread and let it clean up and don't care if it never actually runs to completion.
parent
3b0c79363d
commit
76f48acf63
|
@ -259,15 +259,28 @@ SDL_AudioUnlockDevice_Default(SDL_AudioDevice * device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
SDL_AudioLockOrUnlockDeviceWithNoMixerLock(SDL_AudioDevice * device)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
finalize_audio_entry_points(void)
|
finish_audio_entry_points_init(void)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Fill in stub functions for unused driver entry points. This lets us
|
* Fill in stub functions for unused driver entry points. This lets us
|
||||||
* blindly call them without having to check for validity first.
|
* blindly call them without having to check for validity first.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if (current_audio.impl.SkipMixerLock) {
|
||||||
|
if (current_audio.impl.LockDevice == NULL) {
|
||||||
|
current_audio.impl.LockDevice = SDL_AudioLockOrUnlockDeviceWithNoMixerLock;
|
||||||
|
}
|
||||||
|
if (current_audio.impl.UnlockDevice == NULL) {
|
||||||
|
current_audio.impl.UnlockDevice = SDL_AudioLockOrUnlockDeviceWithNoMixerLock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#define FILL_STUB(x) \
|
#define FILL_STUB(x) \
|
||||||
if (current_audio.impl.x == NULL) { \
|
if (current_audio.impl.x == NULL) { \
|
||||||
current_audio.impl.x = SDL_Audio##x##_Default; \
|
current_audio.impl.x = SDL_Audio##x##_Default; \
|
||||||
|
@ -695,6 +708,35 @@ SDL_ClearQueuedAudio(SDL_AudioDeviceID devid)
|
||||||
free_audio_queue(packet);
|
free_audio_queue(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SDL_FinalizeAudioDevice(SDL_AudioDevice *device)
|
||||||
|
{
|
||||||
|
if (!device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lock/unlock here so we don't race if the audio thread saw the shutdown
|
||||||
|
var without locking, and the thread that requested shutdown is now
|
||||||
|
trying to unlock the mutex while we destroy it. Threading is hard. */
|
||||||
|
current_audio.impl.LockDevice(device);
|
||||||
|
current_audio.impl.UnlockDevice(device);
|
||||||
|
|
||||||
|
if (device->mixer_lock != NULL) {
|
||||||
|
SDL_DestroyMutex(device->mixer_lock);
|
||||||
|
}
|
||||||
|
SDL_free(device->fake_stream);
|
||||||
|
if (device->convert.needed) {
|
||||||
|
SDL_free(device->convert.buf);
|
||||||
|
}
|
||||||
|
if (device->hidden != NULL) {
|
||||||
|
current_audio.impl.CloseDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_audio_queue(device->buffer_queue_head);
|
||||||
|
free_audio_queue(device->buffer_queue_pool);
|
||||||
|
|
||||||
|
SDL_free(device);
|
||||||
|
}
|
||||||
|
|
||||||
/* The general mixing thread function */
|
/* The general mixing thread function */
|
||||||
static int SDLCALL
|
static int SDLCALL
|
||||||
|
@ -773,6 +815,8 @@ SDL_RunAudio(void *devicep)
|
||||||
/* !!! FIXME: can we rename this WaitDrain? */
|
/* !!! FIXME: can we rename this WaitDrain? */
|
||||||
current_audio.impl.WaitDone(device);
|
current_audio.impl.WaitDone(device);
|
||||||
|
|
||||||
|
SDL_FinalizeAudioDevice(device);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,6 +898,8 @@ SDL_CaptureAudio(void *devicep)
|
||||||
|
|
||||||
current_audio.impl.FlushCapture(device);
|
current_audio.impl.FlushCapture(device);
|
||||||
|
|
||||||
|
SDL_FinalizeAudioDevice(device);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -949,7 +995,7 @@ SDL_AudioInit(const char *driver_name)
|
||||||
|
|
||||||
current_audio.detectionLock = SDL_CreateMutex();
|
current_audio.detectionLock = SDL_CreateMutex();
|
||||||
|
|
||||||
finalize_audio_entry_points();
|
finish_audio_entry_points_init();
|
||||||
|
|
||||||
/* Make sure we have a list of devices available at startup. */
|
/* Make sure we have a list of devices available at startup. */
|
||||||
current_audio.impl.DetectDevices();
|
current_audio.impl.DetectDevices();
|
||||||
|
@ -1064,26 +1110,70 @@ SDL_GetAudioDeviceName(int index, int iscapture)
|
||||||
static void
|
static void
|
||||||
close_audio_device(SDL_AudioDevice * device)
|
close_audio_device(SDL_AudioDevice * device)
|
||||||
{
|
{
|
||||||
SDL_AtomicSet(&device->shutdown, 1);
|
if (!device) {
|
||||||
SDL_AtomicSet(&device->enabled, 0);
|
return;
|
||||||
if (device->thread != NULL) {
|
|
||||||
SDL_WaitThread(device->thread, NULL);
|
|
||||||
}
|
|
||||||
if (device->mixer_lock != NULL) {
|
|
||||||
SDL_DestroyMutex(device->mixer_lock);
|
|
||||||
}
|
|
||||||
SDL_free(device->fake_stream);
|
|
||||||
if (device->convert.needed) {
|
|
||||||
SDL_free(device->convert.buf);
|
|
||||||
}
|
|
||||||
if (device->hidden != NULL) {
|
|
||||||
current_audio.impl.CloseDevice(device);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free_audio_queue(device->buffer_queue_head);
|
/* It's possible the audio device can hang at the OS level for
|
||||||
free_audio_queue(device->buffer_queue_pool);
|
several reasons (buggy drivers, etc), so if we've got a thread in
|
||||||
|
flight, we mark the device as ready to shutdown and return
|
||||||
|
immediately. The thread will either notice this and clean everything
|
||||||
|
up when it can, or it's frozen and helpless, but since we've already
|
||||||
|
detached the thread, it's none of our concern. Otherwise, we might
|
||||||
|
hang too. */
|
||||||
|
|
||||||
SDL_free(device);
|
/* Note this can still hang if we initialized the device but failed
|
||||||
|
to finish setting up, forcing _this_ thread to do the cleanup, but
|
||||||
|
oh well. */
|
||||||
|
|
||||||
|
/* take it out of our open list now, though, even if device hangs. */
|
||||||
|
if (device->id > 0) {
|
||||||
|
SDL_AudioDevice *opendev = open_devices[device->id - 1];
|
||||||
|
SDL_assert((opendev == device) || (opendev == NULL));
|
||||||
|
if (opendev == device) {
|
||||||
|
open_devices[device->id - 1] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current_audio.impl.ProvidesOwnCallbackThread && !device->thread) {
|
||||||
|
/* no thread...maybe we're cleaning up a half-opened failure. */
|
||||||
|
SDL_FinalizeAudioDevice(device); /* do it ourselves. */
|
||||||
|
} else if (current_audio.impl.ProvidesOwnCallbackThread) {
|
||||||
|
/* !!! FIXME: this is sort of a mess, because we _should_ treat this
|
||||||
|
!!! FIXME: the same as our internal threads, but those targets
|
||||||
|
!!! FIXME: need refactoring first: they need to call
|
||||||
|
!!! FIXME: SDL_FinalizeAudioDevice() themselves and also have
|
||||||
|
!!! FIXME: their CloseDevice() code deal with cleaning up
|
||||||
|
!!! FIXME: half-initialized opens _and_ normal runs. So for now,
|
||||||
|
!!! FIXME: nothing to do here but pray this doesn't hang.
|
||||||
|
!!! FIXME: (the TODO list: coreaudio, emscripten, nacl, haiku) */
|
||||||
|
SDL_FinalizeAudioDevice(device);
|
||||||
|
} else {
|
||||||
|
Uint32 delay = 0;
|
||||||
|
|
||||||
|
if (!device->iscapture) {
|
||||||
|
/* x2000: x1000 (cvt to ms) and x2 (alternating DMA buffers). */
|
||||||
|
const SDL_AudioSpec *spec = &device->spec;
|
||||||
|
delay = (Uint32) ((((float)spec->samples) / ((float)spec->freq)) * 2000.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lock to make sure an audio callback doesn't fire after we return.
|
||||||
|
Presumably, if a device hangs, it'll not be holding this mutex,
|
||||||
|
since it should only be held during the app's audio callback. */
|
||||||
|
current_audio.impl.LockDevice(device);
|
||||||
|
SDL_AtomicSet(&device->shutdown, 1); /* let the thread do it. */
|
||||||
|
SDL_AtomicSet(&device->enabled, 0);
|
||||||
|
current_audio.impl.UnlockDevice(device);
|
||||||
|
|
||||||
|
/* device is no longer safe to touch at this point. */
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
/* Block the amount that is roughly pending for playback, so we
|
||||||
|
don't drop audio if the process exits right after this call. */
|
||||||
|
printf("audio close: delay for %u ms\n", (unsigned int) delay);
|
||||||
|
SDL_Delay(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1172,6 +1262,7 @@ open_audio_device(const char *devname, int iscapture,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* !!! FIXME: there is a race condition here if two devices open from two threads at once. */
|
||||||
/* Find an available device ID... */
|
/* Find an available device ID... */
|
||||||
for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) {
|
for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) {
|
||||||
if (open_devices[id] == NULL) {
|
if (open_devices[id] == NULL) {
|
||||||
|
@ -1397,10 +1488,13 @@ open_audio_device(const char *devname, int iscapture,
|
||||||
device->thread = SDL_CreateThreadInternal(iscapture ? SDL_CaptureAudio : SDL_RunAudio, threadname, stacksize, device);
|
device->thread = SDL_CreateThreadInternal(iscapture ? SDL_CaptureAudio : SDL_RunAudio, threadname, stacksize, device);
|
||||||
|
|
||||||
if (device->thread == NULL) {
|
if (device->thread == NULL) {
|
||||||
SDL_CloseAudioDevice(device->id);
|
close_audio_device(device);
|
||||||
SDL_SetError("Couldn't create audio thread");
|
SDL_SetError("Couldn't create audio thread");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* don't ever join on this thread; it will clean itself up. */
|
||||||
|
SDL_DetachThread(device->thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
return device->id;
|
return device->id;
|
||||||
|
@ -1520,11 +1614,7 @@ SDL_UnlockAudio(void)
|
||||||
void
|
void
|
||||||
SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
|
SDL_CloseAudioDevice(SDL_AudioDeviceID devid)
|
||||||
{
|
{
|
||||||
SDL_AudioDevice *device = get_audio_device(devid);
|
close_audio_device(get_audio_device(devid));
|
||||||
if (device) {
|
|
||||||
close_audio_device(device);
|
|
||||||
open_devices[devid - 1] = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1543,9 +1633,7 @@ SDL_AudioQuit(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < SDL_arraysize(open_devices); i++) {
|
for (i = 0; i < SDL_arraysize(open_devices); i++) {
|
||||||
if (open_devices[i] != NULL) {
|
close_audio_device(open_devices[i]);
|
||||||
SDL_CloseAudioDevice(i+1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
free_device_list(¤t_audio.outputDevices, ¤t_audio.outputDeviceCount);
|
free_device_list(¤t_audio.outputDevices, ¤t_audio.outputDeviceCount);
|
||||||
|
|
|
@ -93,7 +93,7 @@ typedef struct SDL_AudioDriverImpl
|
||||||
/* Some flags to push duplicate code into the core and reduce #ifdefs. */
|
/* Some flags to push duplicate code into the core and reduce #ifdefs. */
|
||||||
/* !!! FIXME: these should be SDL_bool */
|
/* !!! FIXME: these should be SDL_bool */
|
||||||
int ProvidesOwnCallbackThread;
|
int ProvidesOwnCallbackThread;
|
||||||
int SkipMixerLock; /* !!! FIXME: do we need this anymore? */
|
int SkipMixerLock;
|
||||||
int HasCaptureSupport;
|
int HasCaptureSupport;
|
||||||
int OnlyHasDefaultOutputDevice;
|
int OnlyHasDefaultOutputDevice;
|
||||||
int OnlyHasDefaultCaptureDevice;
|
int OnlyHasDefaultCaptureDevice;
|
||||||
|
|
Loading…
Reference in New Issue