Initial work on audio device hotplug support.

This fills in the core pieces and fully implements it for Mac OS X.

Most other platforms, at the moment, will report a disconnected device if
it fails to write audio, but don't notice if the system's device list changed
at all.
This commit is contained in:
Ryan C. Gordon 2015-03-16 02:11:39 -04:00
parent 338bf5d297
commit 0e02ce0856
19 changed files with 658 additions and 132 deletions

View File

@ -110,6 +110,10 @@ typedef enum
SDL_JOYDEVICEADDED, /**< A new joystick has been inserted into the system */
SDL_JOYDEVICEREMOVED, /**< An opened joystick has been removed */
/* Audio hotplug events */
SDL_AUDIODEVICEADDED = 0x700, /**< A new audio device is available */
SDL_AUDIODEVICEREMOVED, /**< An audio device has been removed. */
/* Game controller events */
SDL_CONTROLLERAXISMOTION = 0x650, /**< Game controller axis motion */
SDL_CONTROLLERBUTTONDOWN, /**< Game controller button pressed */
@ -382,6 +386,20 @@ typedef struct SDL_ControllerDeviceEvent
Sint32 which; /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */
} SDL_ControllerDeviceEvent;
/**
* \brief Audio device event structure (event.adevice.*)
*/
typedef struct SDL_AudioDeviceEvent
{
Uint32 type; /**< ::SDL_AUDIODEVICEADDED, or ::SDL_AUDIODEVICEREMOVED */
Uint32 timestamp;
Uint32 which; /**< The audio device index for the ADDED event (valid until next SDL_GetNumAudioDevices() call), SDL_AudioDeviceID for the REMOVED event */
Uint8 iscapture; /**< zero if an output device, non-zero if a capture device. */
Uint8 padding1;
Uint8 padding2;
Uint8 padding3;
} SDL_AudioDeviceEvent;
/**
* \brief Touch finger event structure (event.tfinger.*)
@ -516,6 +534,7 @@ typedef union SDL_Event
SDL_ControllerAxisEvent caxis; /**< Game Controller axis event data */
SDL_ControllerButtonEvent cbutton; /**< Game Controller button event data */
SDL_ControllerDeviceEvent cdevice; /**< Game Controller device event data */
SDL_AudioDeviceEvent adevice; /**< Audio device event data */
SDL_QuitEvent quit; /**< Quit request event data */
SDL_UserEvent user; /**< Custom event data */
SDL_SysWMEvent syswm; /**< System dependent window event data */

View File

@ -333,6 +333,144 @@ SDL_StreamDeinit(SDL_AudioStreamer * stream)
}
#endif
/* device hotplug support... */
/* this function expects its caller to hold current_audio.detection_lock */
static int
add_audio_device(const char *_name, char ***_devices, int *_devCount)
{
char *name = SDL_strdup(_name);
int retval = -1;
if (name != NULL) {
char **devices = *_devices;
int devCount = *_devCount;
void *ptr = SDL_realloc(devices, (devCount+1) * sizeof(char*));
if (ptr == NULL) {
SDL_free(name);
} else {
retval = devCount;
devices = (char **) ptr;
devices[devCount++] = name;
*_devices = devices;
*_devCount = devCount;
}
}
return retval;
}
static int
add_capture_device(const char *name)
{
/* !!! FIXME: add this later. SDL_assert(current_audio.impl.HasCaptureSupport);*/
return add_audio_device(name, &current_audio.inputDevices, &current_audio.inputDeviceCount);
}
static int
add_output_device(const char *name)
{
return add_audio_device(name, &current_audio.outputDevices, &current_audio.outputDeviceCount);
}
static void
free_device_list(char ***devices, int *devCount)
{
int i = *devCount;
if ((i > 0) && (*devices != NULL)) {
while (i--) {
SDL_free((*devices)[i]);
}
}
SDL_free(*devices);
*devices = NULL;
*devCount = 0;
}
static void
perform_full_device_redetect(const int iscapture)
{
SDL_LockMutex(current_audio.detection_lock);
if (iscapture) {
if (!current_audio.impl.OnlyHasDefaultOutputDevice) {
free_device_list(&current_audio.outputDevices, &current_audio.outputDeviceCount);
current_audio.impl.DetectDevices(SDL_FALSE, add_output_device);
}
} else {
if ((current_audio.impl.HasCaptureSupport) && (!current_audio.impl.OnlyHasDefaultInputDevice)) {
free_device_list(&current_audio.inputDevices, &current_audio.inputDeviceCount);
current_audio.impl.DetectDevices(SDL_TRUE, add_capture_device);
}
}
SDL_UnlockMutex(current_audio.detection_lock);
}
/* The audio backends call this when a new device is plugged in. */
void
SDL_AudioDeviceConnected(const int iscapture, const char *name)
{
int device_index = -1;
SDL_LockMutex(current_audio.detection_lock);
if (iscapture) {
device_index = add_capture_device(name);
} else {
device_index = add_output_device(name);
}
SDL_UnlockMutex(current_audio.detection_lock);
if (device_index != -1) {
/* Post the event, if desired */
if (SDL_GetEventState(SDL_AUDIODEVICEADDED) == SDL_ENABLE) {
SDL_Event event;
event.adevice.type = SDL_AUDIODEVICEADDED;
event.adevice.which = device_index;
event.adevice.iscapture = iscapture;
SDL_PushEvent(&event);
}
}
}
/* The audio backends call this when a device is unplugged. */
void
SDL_AudioDeviceDisconnected(const int iscapture, SDL_AudioDevice *device)
{
/* device==NULL means an unopened device was lost; do the redetect only. */
if (device != NULL) {
SDL_assert(get_audio_device(device->id) == device);
SDL_assert(device->enabled); /* called more than once?! */
/* Ends the audio callback and mark the device as STOPPED, but the
app still needs to close the device to free resources. */
current_audio.impl.LockDevice(device);
device->enabled = 0;
current_audio.impl.UnlockDevice(device);
/* Post the event, if desired */
if (SDL_GetEventState(SDL_AUDIODEVICEREMOVED) == SDL_ENABLE) {
SDL_Event event;
event.adevice.type = SDL_AUDIODEVICEREMOVED;
event.adevice.which = device->id;
event.adevice.iscapture = device->iscapture ? 1 : 0;
SDL_PushEvent(&event);
}
}
/* we don't really know which name (if any) was associated with this
device in the device list, so drop the entire list and rebuild it.
(we should probably change the API in 2.1 to make this more clear?) */
if (iscapture) {
current_audio.need_capture_device_redetect = SDL_TRUE;
} else {
current_audio.need_output_device_redetect = SDL_TRUE;
}
}
/* buffer queueing support... */
@ -690,6 +828,13 @@ SDL_RunAudio(void *devicep)
/* !!! FIXME: this should be LockDevice. */
SDL_LockMutex(device->mixer_lock);
/* Check again, in case device was removed while a lock was held. */
if (!device->enabled) {
SDL_UnlockMutex(device->mixer_lock);
break;
}
if (device->paused) {
SDL_memset(stream, silence, stream_len);
} else {
@ -821,8 +966,34 @@ SDL_AudioInit(const char *driver_name)
return -1; /* No driver was available, so fail. */
}
current_audio.detection_lock = SDL_CreateMutex();
finalize_audio_entry_points();
/* Make sure we have a list of devices available at startup. */
perform_full_device_redetect(SDL_TRUE);
perform_full_device_redetect(SDL_FALSE);
/* Post an add event for each initial device, if desired */
if (SDL_GetEventState(SDL_AUDIODEVICEADDED) == SDL_ENABLE) {
SDL_Event event;
SDL_zero(event);
event.adevice.type = SDL_AUDIODEVICEADDED;
event.adevice.iscapture = 0;
for (i = 0; i < current_audio.outputDeviceCount; i++) {
event.adevice.which = i;
SDL_PushEvent(&event);
}
event.adevice.iscapture = 1;
for (i = 0; i < current_audio.inputDeviceCount; i++) {
event.adevice.which = i;
SDL_PushEvent(&event);
}
}
return 0;
}
@ -835,53 +1006,6 @@ SDL_GetCurrentAudioDriver()
return current_audio.name;
}
static void
free_device_list(char ***devices, int *devCount)
{
int i = *devCount;
if ((i > 0) && (*devices != NULL)) {
while (i--) {
SDL_free((*devices)[i]);
}
}
SDL_free(*devices);
*devices = NULL;
*devCount = 0;
}
static
void SDL_AddCaptureAudioDevice(const char *_name)
{
char *name = NULL;
void *ptr = SDL_realloc(current_audio.inputDevices,
(current_audio.inputDeviceCount+1) * sizeof(char*));
if (ptr == NULL) {
return; /* oh well. */
}
current_audio.inputDevices = (char **) ptr;
name = SDL_strdup(_name); /* if this returns NULL, that's okay. */
current_audio.inputDevices[current_audio.inputDeviceCount++] = name;
}
static
void SDL_AddOutputAudioDevice(const char *_name)
{
char *name = NULL;
void *ptr = SDL_realloc(current_audio.outputDevices,
(current_audio.outputDeviceCount+1) * sizeof(char*));
if (ptr == NULL) {
return; /* oh well. */
}
current_audio.outputDevices = (char **) ptr;
name = SDL_strdup(_name); /* if this returns NULL, that's okay. */
current_audio.outputDevices[current_audio.outputDeviceCount++] = name;
}
int
SDL_GetNumAudioDevices(int iscapture)
{
@ -903,18 +1027,20 @@ SDL_GetNumAudioDevices(int iscapture)
return 1;
}
if (iscapture) {
free_device_list(&current_audio.inputDevices,
&current_audio.inputDeviceCount);
current_audio.impl.DetectDevices(iscapture, SDL_AddCaptureAudioDevice);
retval = current_audio.inputDeviceCount;
} else {
free_device_list(&current_audio.outputDevices,
&current_audio.outputDeviceCount);
current_audio.impl.DetectDevices(iscapture, SDL_AddOutputAudioDevice);
retval = current_audio.outputDeviceCount;
if (current_audio.need_capture_device_redetect) {
current_audio.need_capture_device_redetect = SDL_FALSE;
perform_full_device_redetect(SDL_TRUE);
}
if (current_audio.need_output_device_redetect) {
current_audio.need_output_device_redetect = SDL_FALSE;
perform_full_device_redetect(SDL_FALSE);
}
SDL_LockMutex(current_audio.detection_lock);
retval = iscapture ? current_audio.inputDeviceCount : current_audio.outputDeviceCount;
SDL_UnlockMutex(current_audio.detection_lock);
return retval;
}
@ -922,6 +1048,8 @@ SDL_GetNumAudioDevices(int iscapture)
const char *
SDL_GetAudioDeviceName(int index, int iscapture)
{
const char *retval = NULL;
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
SDL_SetError("Audio subsystem is not initialized");
return NULL;
@ -950,16 +1078,18 @@ SDL_GetAudioDeviceName(int index, int iscapture)
return DEFAULT_OUTPUT_DEVNAME;
}
if (iscapture) {
if (index >= current_audio.inputDeviceCount) {
goto no_such_device;
SDL_LockMutex(current_audio.detection_lock);
if (iscapture && (index < current_audio.inputDeviceCount)) {
retval = current_audio.inputDevices[index];
} else if (!iscapture && (index < current_audio.outputDeviceCount)) {
retval = current_audio.outputDevices[index];
}
return current_audio.inputDevices[index];
} else {
if (index >= current_audio.outputDeviceCount) {
goto no_such_device;
}
return current_audio.outputDevices[index];
SDL_UnlockMutex(current_audio.detection_lock);
/* !!! FIXME: a device could be removed after being returned here, freeing retval's pointer. */
if (retval != NULL) {
return retval;
}
no_such_device:
@ -1077,6 +1207,18 @@ open_audio_device(const char *devname, int iscapture,
return 0;
}
/* Find an available device ID... */
for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) {
if (open_devices[id] == NULL) {
break;
}
}
if (id == SDL_arraysize(open_devices)) {
SDL_SetError("Too many open audio devices");
return 0;
}
if (!obtained) {
obtained = &_obtained;
}
@ -1135,6 +1277,7 @@ open_audio_device(const char *devname, int iscapture,
return 0;
}
SDL_zerop(device);
device->id = id + 1;
device->spec = *obtained;
device->enabled = 1;
device->paused = 1;
@ -1150,12 +1293,6 @@ open_audio_device(const char *devname, int iscapture,
}
}
/* force a device detection if we haven't done one yet. */
if ( ((iscapture) && (current_audio.inputDevices == NULL)) ||
((!iscapture) && (current_audio.outputDevices == NULL)) ) {
SDL_GetNumAudioDevices(iscapture);
}
if (current_audio.impl.OpenDevice(device, devname, iscapture) < 0) {
close_audio_device(device);
return 0;
@ -1247,25 +1384,14 @@ open_audio_device(const char *devname, int iscapture,
device->spec.userdata = device;
}
/* Find an available device ID and store the structure... */
for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) {
if (open_devices[id] == NULL) {
/* add it to our list of open devices. */
open_devices[id] = device;
break;
}
}
if (id == SDL_arraysize(open_devices)) {
SDL_SetError("Too many open audio devices");
close_audio_device(device);
return 0;
}
/* Start the audio thread if necessary */
if (!current_audio.impl.ProvidesOwnCallbackThread) {
/* Start the audio thread */
char name[64];
SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) (id + 1));
SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) device->id);
/* !!! FIXME: this is nasty. */
#if defined(__WIN32__) && !defined(HAVE_LIBC)
#undef SDL_CreateThread
@ -1278,13 +1404,13 @@ open_audio_device(const char *devname, int iscapture,
device->thread = SDL_CreateThread(SDL_RunAudio, name, device);
#endif
if (device->thread == NULL) {
SDL_CloseAudioDevice(id + 1);
SDL_CloseAudioDevice(device->id);
SDL_SetError("Couldn't create audio thread");
return 0;
}
}
return id + 1;
return device->id;
}
@ -1431,12 +1557,16 @@ SDL_AudioQuit(void)
/* Free the driver data */
current_audio.impl.Deinitialize();
free_device_list(&current_audio.outputDevices,
&current_audio.outputDeviceCount);
free_device_list(&current_audio.inputDevices,
&current_audio.inputDeviceCount);
SDL_memset(&current_audio, '\0', sizeof(current_audio));
SDL_memset(open_devices, '\0', sizeof(open_devices));
SDL_DestroyMutex(current_audio.detection_lock);
SDL_zero(current_audio);
SDL_zero(open_devices);
}
#define NUM_FORMATS 10

View File

@ -31,7 +31,16 @@ typedef struct SDL_AudioDevice SDL_AudioDevice;
#define _THIS SDL_AudioDevice *_this
/* Used by audio targets during DetectDevices() */
typedef void (*SDL_AddAudioDevice)(const char *name);
typedef int (*SDL_AddAudioDevice)(const char *name);
/* Audio targets should call this as devices are hotplugged. Don't call
during DetectDevices(), this is for hotplugging a device later. */
extern void SDL_AudioDeviceConnected(const int iscapture, const char *name);
/* Audio targets should call this as devices are unplugged.
(device) can be NULL if an unopened device is lost. */
extern void SDL_AudioDeviceDisconnected(const int iscapture, SDL_AudioDevice *device);
/* This is the size of a packet when using SDL_QueueAudio(). We allocate
these as necessary and pool them, under the assumption that we'll
@ -92,6 +101,12 @@ typedef struct SDL_AudioDriver
SDL_AudioDriverImpl impl;
/* A mutex for device detection */
SDL_mutex *detection_lock;
SDL_bool need_capture_device_redetect;
SDL_bool need_output_device_redetect;
char **outputDevices;
int outputDeviceCount;
@ -114,6 +129,7 @@ struct SDL_AudioDevice
{
/* * * */
/* Data common to all devices */
SDL_AudioDeviceID id;
/* The current audio specification (shared with audio thread) */
SDL_AudioSpec spec;

View File

@ -320,7 +320,7 @@ ALSA_PlayDevice(_THIS)
/* Hmm, not much we can do - abort */
fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
ALSA_snd_strerror(status));
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
return;
}
continue;

View File

@ -151,7 +151,7 @@ ARTS_WaitDevice(_THIS)
/* Check every 10 loops */
if (this->hidden->parent && (((++cnt) % 10) == 0)) {
if (kill(this->hidden->parent, 0) < 0 && errno == ESRCH) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
}
}
@ -179,7 +179,7 @@ ARTS_PlayDevice(_THIS)
/* If we couldn't write, assume fatal error for now */
if (written < 0) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);

View File

@ -150,7 +150,7 @@ BSDAUDIO_WaitDevice(_THIS)
the user know what happened.
*/
fprintf(stderr, "SDL: %s\n", message);
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
/* Don't try to close - may hang */
this->hidden->audio_fd = -1;
#ifdef DEBUG_AUDIO
@ -195,7 +195,7 @@ BSDAUDIO_PlayDevice(_THIS)
/* If we couldn't write, assume fatal error for now */
if (written < 0) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);

View File

@ -40,13 +40,50 @@ static void COREAUDIO_CloseDevice(_THIS);
}
#if MACOSX_COREAUDIO
typedef void (*addDevFn)(const char *name, AudioDeviceID devId, void *data);
static const AudioObjectPropertyAddress devlist_address = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
typedef struct AudioDeviceList
{
AudioDeviceID devid;
SDL_bool alive;
struct AudioDeviceList *next;
} AudioDeviceList;
static AudioDeviceList *output_devs = NULL;
static AudioDeviceList *capture_devs = NULL;
static SDL_bool
add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
{
AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList));
if (item == NULL) {
return SDL_FALSE;
}
item->devid = devId;
item->alive = SDL_TRUE;
item->next = iscapture ? capture_devs : output_devs;
if (iscapture) {
capture_devs = item;
} else {
output_devs = item;
}
return SDL_TRUE;
}
static void
addToDevList(const char *name, AudioDeviceID devId, void *data)
addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
{
SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data;
if (add_to_internal_dev_list(iscapture, devId)) {
addfn(name);
}
}
typedef struct
@ -57,7 +94,7 @@ typedef struct
} FindDevIdData;
static void
findDevId(const char *name, AudioDeviceID devId, void *_data)
findDevId(const char *name, const int iscapture, AudioDeviceID devId, void *_data)
{
FindDevIdData *data = (FindDevIdData *) _data;
if (!data->found) {
@ -77,14 +114,8 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
UInt32 i = 0;
UInt32 max = 0;
AudioObjectPropertyAddress addr = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
0, NULL, &size);
result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
&devlist_address, 0, NULL, &size);
if (result != kAudioHardwareNoError)
return;
@ -92,8 +123,8 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
if (devs == NULL)
return;
result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
0, NULL, &size, devs);
result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
&devlist_address, 0, NULL, &size, devs);
if (result != kAudioHardwareNoError)
return;
@ -105,10 +136,17 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
AudioBufferList *buflist = NULL;
int usable = 0;
CFIndex len = 0;
const AudioObjectPropertyAddress addr = {
kAudioDevicePropertyStreamConfiguration,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster
};
addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
kAudioDevicePropertyScopeOutput;
addr.mSelector = kAudioDevicePropertyStreamConfiguration;
const AudioObjectPropertyAddress nameaddr = {
kAudioObjectPropertyName,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMaster
};
result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
if (result != noErr)
@ -136,9 +174,9 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
if (!usable)
continue;
addr.mSelector = kAudioObjectPropertyName;
size = sizeof (CFStringRef);
result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, &cfstr);
result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
if (result != kAudioHardwareNoError)
continue;
@ -169,18 +207,96 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
((iscapture) ? "capture" : "output"),
(int) *devCount, ptr, (int) dev);
#endif
addfn(ptr, dev, addfndata);
addfn(ptr, iscapture, dev, addfndata);
}
SDL_free(ptr); /* addfn() would have copied the string. */
}
}
static void
free_audio_device_list(AudioDeviceList **list)
{
AudioDeviceList *item = *list;
while (item) {
AudioDeviceList *next = item->next;
SDL_free(item);
item = next;
}
*list = NULL;
}
static void
COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
{
free_audio_device_list(iscapture ? &capture_devs : &output_devs);
build_device_list(iscapture, addToDevList, addfn);
}
static void
build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
{
AudioDeviceList **list = (AudioDeviceList **) data;
AudioDeviceList *item;
for (item = *list; item != NULL; item = item->next) {
if (item->devid == devId) {
item->alive = SDL_TRUE;
return;
}
}
add_to_internal_dev_list(iscapture, devId); /* new device, add it. */
SDL_AudioDeviceConnected(iscapture, name);
}
static SDL_bool
reprocess_device_list(const int iscapture, AudioDeviceList **list)
{
SDL_bool was_disconnect = SDL_FALSE;
AudioDeviceList *item;
AudioDeviceList *prev = NULL;
for (item = *list; item != NULL; item = item->next) {
item->alive = SDL_FALSE;
}
build_device_list(iscapture, build_device_change_list, list);
/* free items in the list that aren't still alive. */
item = *list;
while (item != NULL) {
AudioDeviceList *next = item->next;
if (item->alive) {
prev = item;
} else {
was_disconnect = SDL_TRUE;
if (prev) {
prev->next = item->next;
} else {
*list = item->next;
}
SDL_free(item);
}
item = next;
}
return was_disconnect;
}
/* this is called when the system's list of available audio devices changes. */
static OSStatus
device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
{
if (reprocess_device_list(SDL_TRUE, &capture_devs)) {
SDL_AudioDeviceDisconnected(SDL_TRUE, NULL);
}
if (reprocess_device_list(SDL_FALSE, &output_devs)) {
SDL_AudioDeviceDisconnected(SDL_FALSE, NULL);
}
return 0;
}
static int
find_device_by_name(_THIS, const char *devname, int iscapture)
{
@ -317,11 +433,54 @@ inputCallback(void *inRefCon,
}
#if MACOSX_COREAUDIO
static const AudioObjectPropertyAddress alive_address =
{
kAudioDevicePropertyDeviceIsAlive,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
static OSStatus
device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
{
SDL_AudioDevice *this = (SDL_AudioDevice *) data;
SDL_bool dead = SDL_FALSE;
UInt32 isAlive = 1;
UInt32 size = sizeof (isAlive);
OSStatus error;
if (!this->enabled) {
return 0; /* already known to be dead. */
}
error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
0, NULL, &size, &isAlive);
if (error == kAudioHardwareBadDeviceError) {
dead = SDL_TRUE; /* device was unplugged. */
} else if ((error == kAudioHardwareNoError) && (!isAlive)) {
dead = SDL_TRUE; /* device died in some other way. */
}
if (dead) {
SDL_AudioDeviceDisconnected(this->iscapture, this);
}
return 0;
}
#endif
static void
COREAUDIO_CloseDevice(_THIS)
{
if (this->hidden != NULL) {
if (this->hidden->audioUnitOpened) {
#if MACOSX_COREAUDIO
/* Unregister our disconnect callback. */
AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
#endif
AURenderCallbackStruct callback;
const AudioUnitElement output_bus = 0;
const AudioUnitElement input_bus = 1;
@ -355,7 +514,6 @@ COREAUDIO_CloseDevice(_THIS)
}
}
static int
prepare_audiounit(_THIS, const char *devname, int iscapture,
const AudioStreamBasicDescription * strdesc)
@ -454,6 +612,11 @@ prepare_audiounit(_THIS, const char *devname, int iscapture,
result = AudioOutputUnitStart(this->hidden->audioUnit);
CHECK_RESULT("AudioOutputUnitStart");
#if MACOSX_COREAUDIO
/* Fire a callback if the device stops being "alive" (disconnected, etc). */
AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
#endif
/* We're running! */
return 1;
}
@ -527,15 +690,27 @@ COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
return 0; /* good to go. */
}
static void
COREAUDIO_Deinitialize(void)
{
#if MACOSX_COREAUDIO
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
free_audio_device_list(&capture_devs);
free_audio_device_list(&output_devs);
#endif
}
static int
COREAUDIO_Init(SDL_AudioDriverImpl * impl)
{
/* Set the function pointers */
impl->OpenDevice = COREAUDIO_OpenDevice;
impl->CloseDevice = COREAUDIO_CloseDevice;
impl->Deinitialize = COREAUDIO_Deinitialize;
#if MACOSX_COREAUDIO
impl->DetectDevices = COREAUDIO_DetectDevices;
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
#else
impl->OnlyHasDefaultOutputDevice = 1;

View File

@ -71,7 +71,7 @@ DISKAUD_PlayDevice(_THIS)
/* If we couldn't write, assume fatal error for now */
if (written != this->hidden->mixlen) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);

View File

@ -270,7 +270,7 @@ DSP_PlayDevice(_THIS)
const int mixlen = this->hidden->mixlen;
if (write(this->hidden->audio_fd, mixbuf, mixlen) == -1) {
perror("Audio write");
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", mixlen);

View File

@ -129,7 +129,7 @@ ESD_WaitDevice(_THIS)
/* Check every 10 loops */
if (this->hidden->parent && (((++cnt) % 10) == 0)) {
if (kill(this->hidden->parent, 0) < 0 && errno == ESRCH) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
}
}
@ -161,7 +161,7 @@ ESD_PlayDevice(_THIS)
/* If we couldn't write, assume fatal error for now */
if (written < 0) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
}

View File

@ -143,7 +143,7 @@ SDL_FS_PlayDevice(_THIS)
this->hidden->mixsamples);
/* If we couldn't write, assume fatal error for now */
if (ret) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", this->hidden->mixlen);

View File

@ -176,7 +176,7 @@ PAUDIO_WaitDevice(_THIS)
* the user know what happened.
*/
fprintf(stderr, "SDL: %s - %s\n", strerror(errno), message);
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
/* Don't try to close - may hang */
this->hidden->audio_fd = -1;
#ifdef DEBUG_AUDIO
@ -212,7 +212,7 @@ PAUDIO_PlayDevice(_THIS)
/* If we couldn't write, assume fatal error for now */
if (written < 0) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);

View File

@ -302,7 +302,7 @@ PULSEAUDIO_WaitDevice(_THIS)
if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
return;
}
if (PULSEAUDIO_pa_stream_writable_size(h->stream) >= h->mixlen) {
@ -318,7 +318,7 @@ PULSEAUDIO_PlayDevice(_THIS)
struct SDL_PrivateAudioData *h = this->hidden;
if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, h->mixlen, NULL, 0LL,
PA_SEEK_RELATIVE) < 0) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
}

View File

@ -300,7 +300,7 @@ QSA_PlayDevice(_THIS)
/* If we couldn't write, assume fatal error for now */
if (towrite != 0) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
}

View File

@ -158,7 +158,7 @@ SNDIO_PlayDevice(_THIS)
/* If we couldn't write, assume fatal error for now */
if ( written == 0 ) {
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);

View File

@ -158,7 +158,7 @@ SUNAUDIO_PlayDevice(_THIS)
if (write(this->hidden->audio_fd, this->hidden->ulaw_buf,
this->hidden->fragsize) < 0) {
/* Assume fatal error, for now */
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
this->hidden->written += this->hidden->fragsize;
} else {
@ -168,7 +168,7 @@ SUNAUDIO_PlayDevice(_THIS)
if (write(this->hidden->audio_fd, this->hidden->mixbuf,
this->spec.size) < 0) {
/* Assume fatal error, for now */
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
this->hidden->written += this->hidden->fragsize;
}

View File

@ -221,7 +221,7 @@ XAUDIO2_PlayDevice(_THIS)
if (result != S_OK) { /* uhoh, panic! */
IXAudio2SourceVoice_FlushSourceBuffers(source);
this->enabled = 0;
SDL_AudioDeviceDisconnected(SDL_FALSE, this);
}
}

View File

@ -38,6 +38,7 @@ TARGETS = \
testloadso$(EXE) \
testlock$(EXE) \
testmultiaudio$(EXE) \
testaudiohotplug$(EXE) \
testnative$(EXE) \
testoverlay2$(EXE) \
testplatform$(EXE) \
@ -105,6 +106,9 @@ testautomation$(EXE): $(srcdir)/testautomation.c \
testmultiaudio$(EXE): $(srcdir)/testmultiaudio.c
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
testaudiohotplug$(EXE): $(srcdir)/testaudiohotplug.c
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
testatomic$(EXE): $(srcdir)/testatomic.c
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)

182
test/testaudiohotplug.c Normal file
View File

@ -0,0 +1,182 @@
/*
Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely.
*/
/* Program to test hotplugging of audio devices */
#include "SDL_config.h"
#include <stdio.h>
#include <stdlib.h>
#if HAVE_SIGNAL_H
#include <signal.h>
#endif
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
#include "SDL.h"
static SDL_AudioSpec spec;
static Uint8 *sound = NULL; /* Pointer to wave data */
static Uint32 soundlen = 0; /* Length of wave data */
static int posindex = 0;
static Uint32 positions[64];
/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
static void
quit(int rc)
{
SDL_Quit();
exit(rc);
}
void SDLCALL
fillerup(void *_pos, Uint8 * stream, int len)
{
Uint32 pos = *((Uint32 *) _pos);
Uint8 *waveptr;
int waveleft;
/* Set up the pointers */
waveptr = sound + pos;
waveleft = soundlen - pos;
/* Go! */
while (waveleft <= len) {
SDL_memcpy(stream, waveptr, waveleft);
stream += waveleft;
len -= waveleft;
waveptr = sound;
waveleft = soundlen;
pos = 0;
}
SDL_memcpy(stream, waveptr, len);
pos += len;
*((Uint32 *) _pos) = pos;
}
static int done = 0;
void
poked(int sig)
{
done = 1;
}
static void
iteration()
{
SDL_Event e;
SDL_AudioDeviceID dev;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
done = 1;
} else if (e.type == SDL_AUDIODEVICEADDED) {
const char *name = SDL_GetAudioDeviceName(e.adevice.which, 0);
SDL_Log("New %s audio device: %s\n", e.adevice.iscapture ? "capture" : "output", name);
if (!e.adevice.iscapture) {
positions[posindex] = 0;
spec.userdata = &positions[posindex++];
spec.callback = fillerup;
dev = SDL_OpenAudioDevice(name, 0, &spec, NULL, 0);
if (!dev) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open '%s': %s\n", name, SDL_GetError());
} else {
SDL_Log("Opened '%s' as %u\n", name, (unsigned int) dev);
SDL_PauseAudioDevice(dev, 0);
}
}
} else if (e.type == SDL_AUDIODEVICEREMOVED) {
dev = (SDL_AudioDeviceID) e.adevice.which;
SDL_Log("%s device %u removed.\n", e.adevice.iscapture ? "capture" : "output", (unsigned int) dev);
SDL_CloseAudioDevice(dev);
}
}
}
#ifdef __EMSCRIPTEN__
void
loop()
{
if(done)
emscripten_cancel_main_loop();
else
iteration();
}
#endif
int
main(int argc, char *argv[])
{
int i;
char filename[4096];
/* Enable standard application logging */
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
/* Load the SDL library */
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
return (1);
}
SDL_CreateWindow("testaudiohotplug", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
if (argc > 1) {
SDL_strlcpy(filename, argv[1], sizeof(filename));
} else {
SDL_strlcpy(filename, "sample.wav", sizeof(filename));
}
/* Load the wave file into memory */
if (SDL_LoadWAV(filename, &spec, &sound, &soundlen) == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError());
quit(1);
}
#if HAVE_SIGNAL_H
/* Set the signals */
#ifdef SIGHUP
signal(SIGHUP, poked);
#endif
signal(SIGINT, poked);
#ifdef SIGQUIT
signal(SIGQUIT, poked);
#endif
signal(SIGTERM, poked);
#endif /* HAVE_SIGNAL_H */
/* Show the list of available drivers */
SDL_Log("Available audio drivers:");
for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) {
SDL_Log("%i: %s", i, SDL_GetAudioDriver(i));
}
SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver());
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop(loop, 0, 1);
#else
while (!done) {
SDL_Delay(100);
iteration();
}
#endif
/* Clean up on signal */
SDL_Quit();
SDL_FreeWAV(sound);
return (0);
}
/* vi: set ts=4 sw=4 expandtab: */