--- a/src/audio/alsa/SDL_alsa_audio.c Fri Aug 26 15:46:29 2016 -0400
+++ b/src/audio/alsa/SDL_alsa_audio.c Sun Aug 28 08:50:26 2016 -0700
@@ -29,6 +29,7 @@
#include <errno.h>
#include <string.h>
+#include "SDL_assert.h"
#include "SDL_timer.h"
#include "SDL_audio.h"
#include "../SDL_audiomem.h"
@@ -660,93 +661,196 @@
return 0;
}
-static void
-ALSA_Deinitialize(void)
+typedef struct ALSA_Device
{
- UnloadALSALibrary();
-}
+ char *name;
+ SDL_bool iscapture;
+ struct ALSA_Device *next;
+} ALSA_Device;
static void
-add_device(const int iscapture, const char *name, const char *_desc)
+add_device(const int iscapture, const char *name, void *hint, ALSA_Device **pSeen)
{
- char *desc = NULL;
+ ALSA_Device *dev = SDL_malloc(sizeof (ALSA_Device));
+ char *desc = ALSA_snd_device_name_get_hint(hint, "DESC");
char *handle = NULL;
- char *ptr = NULL;
+ char *ptr;
+
+ if (!desc) {
+ SDL_free(dev);
+ return;
+ } else if (!dev) {
+ free(desc);
+ return;
+ }
+
+ SDL_assert(name != NULL);
- if (!name || !_desc) {
- return; /* nothing we can do with this...? */
+ /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
+ just chop the extra lines off, this seems to get a reasonable device
+ name without extra details. */
+ if ((ptr = strchr(desc, '\n')) != NULL) {
+ *ptr = '\0';
+ }
+
+ /*printf("ALSA: adding %s device '%s' (%s)\n", iscapture ? "capture" : "output", name, desc);*/
+
+ handle = SDL_strdup(name);
+ if (!handle) {
+ free(desc);
+ SDL_free(dev);
+ return;
}
- desc = SDL_strdup(_desc);
- if (!desc) {
- return; /* oh well, out of memory. Skip it. */
+ SDL_AddAudioDevice(iscapture, desc, handle);
+ free(desc);
+
+ dev->name = handle;
+ dev->iscapture = iscapture;
+ dev->next = *pSeen;
+ *pSeen = dev;
+}
+
+
+static SDL_atomic_t ALSA_hotplug_shutdown;
+static SDL_Thread *ALSA_hotplug_thread;
+
+static int SDLCALL
+ALSA_HotplugThread(void *arg)
+{
+ SDL_sem *first_run_semaphore = (SDL_sem *) arg;
+ ALSA_Device *devices = NULL;
+ ALSA_Device *next;
+ ALSA_Device *dev;
+ Uint32 ticks;
+
+ while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
+ void **hints = NULL;
+ if (ALSA_snd_device_name_hint(-1, "pcm", &hints) != -1) {
+ ALSA_Device *unseen = devices;
+ ALSA_Device *seen = NULL;
+ ALSA_Device *prev;
+ int i;
+
+ for (i = 0; hints[i]; i++) {
+ char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
+ if (!name) {
+ continue;
+ }
+
+ /* only want physical hardware interfaces */
+ if (SDL_strncmp(name, "hw:", 3) == 0) {
+ char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
+ const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
+ const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
+ SDL_bool have_output = SDL_FALSE;
+ SDL_bool have_input = SDL_FALSE;
+
+ free(ioid);
+
+ if (!isoutput && !isinput) {
+ free(name);
+ continue;
+ }
+
+ prev = NULL;
+ for (dev = unseen; dev; dev = next) {
+ next = dev->next;
+ if ( (SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture)) ) {
+ if (prev) {
+ prev->next = next;
+ } else {
+ unseen = next;
+ }
+ dev->next = seen;
+ seen = dev;
+ if (isinput) have_input = SDL_TRUE;
+ if (isoutput) have_output = SDL_TRUE;
+ } else {
+ prev = dev;
+ }
+ }
+
+ if (isinput && !have_input) {
+ add_device(SDL_TRUE, name, hints[i], &seen);
+ }
+ if (isoutput && !have_output) {
+ add_device(SDL_FALSE, name, hints[i], &seen);
+ }
+ }
+
+ free(name);
+ }
+
+ ALSA_snd_device_name_free_hint(hints);
+
+ devices = seen; /* now we have a known-good list of attached devices. */
+
+ /* report anything still in unseen as removed. */
+ for (dev = unseen; dev; dev = next) {
+ /*printf("ALSA: removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
+ next = dev->next;
+ SDL_RemoveAudioDevice(dev->iscapture, dev->name);
+ SDL_free(dev->name);
+ SDL_free(dev);
+ }
+ }
+
+ /* On first run, tell ALSA_DetectDevices() that we have a complete device list so it can return. */
+ if (first_run_semaphore) {
+ SDL_SemPost(first_run_semaphore);
+ first_run_semaphore = NULL; /* let other thread clean it up. */
+ }
+
+ /* Block awhile before checking again, unless we're told to stop. */
+ ticks = SDL_GetTicks() + 5000;
+ while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && !SDL_TICKS_PASSED(SDL_GetTicks(), ticks))
+ SDL_Delay(100);
+ }
}
- /* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output" */
- for (ptr = strchr(desc, '\n'); ptr; ptr = strchr(ptr + 1, '\n')) {
- *ptr = ' ';
+ /* Shutting down! Clean up any data we've gathered. */
+ for (dev = devices; dev; dev = next) {
+ /*printf("ALSA: at shutdown, removing %s device '%s'\n", dev->iscapture ? "capture" : "output", dev->name);*/
+ next = dev->next;
+ SDL_free(dev->name);
+ SDL_free(dev);
}
- handle = SDL_strdup(name);
- if (handle != NULL) {
- SDL_AddAudioDevice(iscapture, desc, handle);
- }
-
- SDL_free(desc);
+ return 0;
}
static void
ALSA_DetectDevices(void)
{
- void **hints = NULL;
- int i;
-
- /* !!! FIXME: use udev instead. */
- /* We won't deal with disconnects and hotplugs without udev, but at least
- you'll get a reasonable device list at startup. */
-#if 1 /*!SDL_USE_LIBUDEV */
- if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == -1) {
+ /* Start the device detection thread here, wait for an initial iteration to complete. */
+ SDL_sem *semaphore = SDL_CreateSemaphore(0);
+ if (!semaphore) {
return; /* oh well. */
}
- for (i = 0; hints[i]; i++) {
- char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
- char *desc = ALSA_snd_device_name_get_hint(hints[i], "DESC");
- char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
+ SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
- /* only want physical hardware interfaces */
- if (SDL_strncmp(name, "hw:", 3) == 0) {
- if ((ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0)) {
- add_device(SDL_FALSE, name, desc);
- }
-
- if ((ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0)) {
- add_device(SDL_TRUE, name, desc);
- }
- }
-
- free(name);
- free(desc);
- free(ioid);
+ ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", semaphore);
+ if (ALSA_hotplug_thread) {
+ SDL_SemWait(semaphore); /* wait for the first iteration to finish. */
}
- ALSA_snd_device_name_free_hint(hints);
-#else
-#error Fill in udev support here.
-#endif
+ SDL_DestroySemaphore(semaphore);
}
static void
-ALSA_FreeDeviceHandle(void *handle)
+ALSA_Deinitialize(void)
{
-#if 1 /*!SDL_USE_LIBUDEV*/
- SDL_free(handle);
-#else
-#error Fill in udev support here.
-#endif
+ if (ALSA_hotplug_thread != NULL) {
+ SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
+ SDL_WaitThread(ALSA_hotplug_thread, NULL);
+ ALSA_hotplug_thread = NULL;
+ }
+
+ UnloadALSALibrary();
}
-
static int
ALSA_Init(SDL_AudioDriverImpl * impl)
{
@@ -762,7 +866,6 @@
impl->PlayDevice = ALSA_PlayDevice;
impl->CloseDevice = ALSA_CloseDevice;
impl->Deinitialize = ALSA_Deinitialize;
- impl->FreeDeviceHandle = ALSA_FreeDeviceHandle;
return 1; /* this audio target is available. */
}