Skip to content

Commit

Permalink
alsa: Implemented hotplug support, cleaned up device names.
Browse files Browse the repository at this point in the history
  • Loading branch information
icculus committed Aug 28, 2016
1 parent 35e564a commit 850da32
Showing 1 changed file with 159 additions and 56 deletions.
215 changes: 159 additions & 56 deletions src/audio/alsa/SDL_alsa_audio.c
Expand Up @@ -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"
Expand Down Expand Up @@ -660,92 +661,195 @@ ALSA_OpenDevice(_THIS, void *handle, const char *devname, int iscapture)
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;

if (!name || !_desc) {
return; /* nothing we can do with this...? */
}
char *ptr;

desc = SDL_strdup(_desc);
if (!desc) {
return; /* oh well, out of memory. Skip it. */
SDL_free(dev);
return;
} else if (!dev) {
free(desc);
return;
}

/* some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output" */
for (ptr = strchr(desc, '\n'); ptr; ptr = strchr(ptr + 1, '\n')) {
*ptr = ' ';
SDL_assert(name != NULL);

/* 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 != NULL) {
SDL_AddAudioDevice(iscapture, desc, handle);
if (!handle) {
free(desc);
SDL_free(dev);
return;
}

SDL_free(desc);
SDL_AddAudioDevice(iscapture, desc, handle);
free(desc);

dev->name = handle;
dev->iscapture = iscapture;
dev->next = *pSeen;
*pSeen = dev;
}

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) {
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");
static SDL_atomic_t ALSA_hotplug_shutdown;
static SDL_Thread *ALSA_hotplug_thread;

/* 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);
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);
}

if ((ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0)) {
add_device(SDL_TRUE, name, desc);
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);
}
}

free(name);
free(desc);
free(ioid);
/* 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);
}
}

ALSA_snd_device_name_free_hint(hints);
#else
#error Fill in udev support here.
#endif
/* 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);
}

return 0;
}

static void
ALSA_FreeDeviceHandle(void *handle)
ALSA_DetectDevices(void)
{
#if 1 /*!SDL_USE_LIBUDEV*/
SDL_free(handle);
#else
#error Fill in udev support here.
#endif
/* Start the device detection thread here, wait for an initial iteration to complete. */
SDL_sem *semaphore = SDL_CreateSemaphore(0);
if (!semaphore) {
return; /* oh well. */
}

SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);

ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", semaphore);
if (ALSA_hotplug_thread) {
SDL_SemWait(semaphore); /* wait for the first iteration to finish. */
}

SDL_DestroySemaphore(semaphore);
}

static void
ALSA_Deinitialize(void)
{
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)
Expand All @@ -762,7 +866,6 @@ ALSA_Init(SDL_AudioDriverImpl * impl)
impl->PlayDevice = ALSA_PlayDevice;
impl->CloseDevice = ALSA_CloseDevice;
impl->Deinitialize = ALSA_Deinitialize;
impl->FreeDeviceHandle = ALSA_FreeDeviceHandle;

return 1; /* this audio target is available. */
}
Expand Down

0 comments on commit 850da32

Please sign in to comment.