瀏覽代碼

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.
Ryan C. Gordon 10 年之前
父節點
當前提交
0e02ce0856

+ 19 - 0
include/SDL_events.h

@@ -110,6 +110,10 @@ typedef enum
     SDL_JOYDEVICEADDED,         /**< A new joystick has been inserted into the system */
     SDL_JOYDEVICEADDED,         /**< A new joystick has been inserted into the system */
     SDL_JOYDEVICEREMOVED,       /**< An opened joystick has been removed */
     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 */
     /* Game controller events */
     SDL_CONTROLLERAXISMOTION  = 0x650, /**< Game controller axis motion */
     SDL_CONTROLLERAXISMOTION  = 0x650, /**< Game controller axis motion */
     SDL_CONTROLLERBUTTONDOWN,          /**< Game controller button pressed */
     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 */
     Sint32 which;       /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */
 } SDL_ControllerDeviceEvent;
 } 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.*)
  *  \brief Touch finger event structure (event.tfinger.*)
@@ -516,6 +534,7 @@ typedef union SDL_Event
     SDL_ControllerAxisEvent caxis;      /**< Game Controller axis event data */
     SDL_ControllerAxisEvent caxis;      /**< Game Controller axis event data */
     SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
     SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
     SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device 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_QuitEvent quit;             /**< Quit request event data */
     SDL_UserEvent user;             /**< Custom event data */
     SDL_UserEvent user;             /**< Custom event data */
     SDL_SysWMEvent syswm;           /**< System dependent window event data */
     SDL_SysWMEvent syswm;           /**< System dependent window event data */

+ 221 - 91
src/audio/SDL_audio.c

@@ -333,6 +333,144 @@ SDL_StreamDeinit(SDL_AudioStreamer * stream)
 }
 }
 #endif
 #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... */
 /* buffer queueing support... */
 
 
@@ -690,6 +828,13 @@ SDL_RunAudio(void *devicep)
 
 
             /* !!! FIXME: this should be LockDevice. */
             /* !!! FIXME: this should be LockDevice. */
             SDL_LockMutex(device->mixer_lock);
             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) {
             if (device->paused) {
                 SDL_memset(stream, silence, stream_len);
                 SDL_memset(stream, silence, stream_len);
             } else {
             } else {
@@ -821,8 +966,34 @@ SDL_AudioInit(const char *driver_name)
         return -1;            /* No driver was available, so fail. */
         return -1;            /* No driver was available, so fail. */
     }
     }
 
 
+    current_audio.detection_lock = SDL_CreateMutex();
+
     finalize_audio_entry_points();
     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;
     return 0;
 }
 }
 
 
@@ -835,53 +1006,6 @@ SDL_GetCurrentAudioDriver()
     return current_audio.name;
     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
 int
 SDL_GetNumAudioDevices(int iscapture)
 SDL_GetNumAudioDevices(int iscapture)
 {
 {
@@ -903,18 +1027,20 @@ SDL_GetNumAudioDevices(int iscapture)
         return 1;
         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;
     return retval;
 }
 }
 
 
@@ -922,6 +1048,8 @@ SDL_GetNumAudioDevices(int iscapture)
 const char *
 const char *
 SDL_GetAudioDeviceName(int index, int iscapture)
 SDL_GetAudioDeviceName(int index, int iscapture)
 {
 {
+    const char *retval = NULL;
+
     if (!SDL_WasInit(SDL_INIT_AUDIO)) {
     if (!SDL_WasInit(SDL_INIT_AUDIO)) {
         SDL_SetError("Audio subsystem is not initialized");
         SDL_SetError("Audio subsystem is not initialized");
         return NULL;
         return NULL;
@@ -950,16 +1078,18 @@ SDL_GetAudioDeviceName(int index, int iscapture)
         return DEFAULT_OUTPUT_DEVNAME;
         return DEFAULT_OUTPUT_DEVNAME;
     }
     }
 
 
-    if (iscapture) {
-        if (index >= current_audio.inputDeviceCount) {
-            goto no_such_device;
-        }
-        return current_audio.inputDevices[index];
-    } else {
-        if (index >= current_audio.outputDeviceCount) {
-            goto no_such_device;
-        }
-        return current_audio.outputDevices[index];
+    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];
+    }
+    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:
 no_such_device:
@@ -1077,6 +1207,18 @@ open_audio_device(const char *devname, int iscapture,
         return 0;
         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) {
     if (!obtained) {
         obtained = &_obtained;
         obtained = &_obtained;
     }
     }
@@ -1135,6 +1277,7 @@ open_audio_device(const char *devname, int iscapture,
         return 0;
         return 0;
     }
     }
     SDL_zerop(device);
     SDL_zerop(device);
+    device->id = id + 1;
     device->spec = *obtained;
     device->spec = *obtained;
     device->enabled = 1;
     device->enabled = 1;
     device->paused = 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) {
     if (current_audio.impl.OpenDevice(device, devname, iscapture) < 0) {
         close_audio_device(device);
         close_audio_device(device);
         return 0;
         return 0;
@@ -1247,25 +1384,14 @@ open_audio_device(const char *devname, int iscapture,
         device->spec.userdata = device;
         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) {
-            open_devices[id] = device;
-            break;
-        }
-    }
-
-    if (id == SDL_arraysize(open_devices)) {
-        SDL_SetError("Too many open audio devices");
-        close_audio_device(device);
-        return 0;
-    }
+    /* add it to our list of open devices. */
+    open_devices[id] = device;
 
 
     /* Start the audio thread if necessary */
     /* Start the audio thread if necessary */
     if (!current_audio.impl.ProvidesOwnCallbackThread) {
     if (!current_audio.impl.ProvidesOwnCallbackThread) {
         /* Start the audio thread */
         /* Start the audio thread */
         char name[64];
         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. */
 /* !!! FIXME: this is nasty. */
 #if defined(__WIN32__) && !defined(HAVE_LIBC)
 #if defined(__WIN32__) && !defined(HAVE_LIBC)
 #undef SDL_CreateThread
 #undef SDL_CreateThread
@@ -1278,13 +1404,13 @@ open_audio_device(const char *devname, int iscapture,
         device->thread = SDL_CreateThread(SDL_RunAudio, name, device);
         device->thread = SDL_CreateThread(SDL_RunAudio, name, device);
 #endif
 #endif
         if (device->thread == NULL) {
         if (device->thread == NULL) {
-            SDL_CloseAudioDevice(id + 1);
+            SDL_CloseAudioDevice(device->id);
             SDL_SetError("Couldn't create audio thread");
             SDL_SetError("Couldn't create audio thread");
             return 0;
             return 0;
         }
         }
     }
     }
 
 
-    return id + 1;
+    return device->id;
 }
 }
 
 
 
 
@@ -1431,12 +1557,16 @@ SDL_AudioQuit(void)
 
 
     /* Free the driver data */
     /* Free the driver data */
     current_audio.impl.Deinitialize();
     current_audio.impl.Deinitialize();
+
     free_device_list(&current_audio.outputDevices,
     free_device_list(&current_audio.outputDevices,
                      &current_audio.outputDeviceCount);
                      &current_audio.outputDeviceCount);
     free_device_list(&current_audio.inputDevices,
     free_device_list(&current_audio.inputDevices,
                      &current_audio.inputDeviceCount);
                      &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
 #define NUM_FORMATS 10

+ 17 - 1
src/audio/SDL_sysaudio.h

@@ -31,7 +31,16 @@ typedef struct SDL_AudioDevice SDL_AudioDevice;
 #define _THIS   SDL_AudioDevice *_this
 #define _THIS   SDL_AudioDevice *_this
 
 
 /* Used by audio targets during DetectDevices() */
 /* 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
 /* 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
    these as necessary and pool them, under the assumption that we'll
@@ -92,6 +101,12 @@ typedef struct SDL_AudioDriver
 
 
     SDL_AudioDriverImpl impl;
     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;
     char **outputDevices;
     int outputDeviceCount;
     int outputDeviceCount;
 
 
@@ -114,6 +129,7 @@ struct SDL_AudioDevice
 {
 {
     /* * * */
     /* * * */
     /* Data common to all devices */
     /* Data common to all devices */
+    SDL_AudioDeviceID id;
 
 
     /* The current audio specification (shared with audio thread) */
     /* The current audio specification (shared with audio thread) */
     SDL_AudioSpec spec;
     SDL_AudioSpec spec;

+ 1 - 1
src/audio/alsa/SDL_alsa_audio.c

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

+ 2 - 2
src/audio/arts/SDL_artsaudio.c

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

+ 2 - 2
src/audio/bsd/SDL_bsdaudio.c

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

+ 197 - 22
src/audio/coreaudio/SDL_coreaudio.c

@@ -40,13 +40,50 @@ static void COREAUDIO_CloseDevice(_THIS);
     }
     }
 
 
 #if MACOSX_COREAUDIO
 #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
 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;
     SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data;
-    addfn(name);
+    if (add_to_internal_dev_list(iscapture, devId)) {
+        addfn(name);
+    }
 }
 }
 
 
 typedef struct
 typedef struct
@@ -57,7 +94,7 @@ typedef struct
 } FindDevIdData;
 } FindDevIdData;
 
 
 static void
 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;
     FindDevIdData *data = (FindDevIdData *) _data;
     if (!data->found) {
     if (!data->found) {
@@ -77,14 +114,8 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
     UInt32 i = 0;
     UInt32 i = 0;
     UInt32 max = 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)
     if (result != kAudioHardwareNoError)
         return;
         return;
 
 
@@ -92,8 +123,8 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
     if (devs == NULL)
     if (devs == NULL)
         return;
         return;
 
 
-    result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
-                                        0, NULL, &size, devs);
+    result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
+                                        &devlist_address, 0, NULL, &size, devs);
     if (result != kAudioHardwareNoError)
     if (result != kAudioHardwareNoError)
         return;
         return;
 
 
@@ -105,10 +136,17 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
         AudioBufferList *buflist = NULL;
         AudioBufferList *buflist = NULL;
         int usable = 0;
         int usable = 0;
         CFIndex len = 0;
         CFIndex len = 0;
-
-        addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
-                        kAudioDevicePropertyScopeOutput;
-        addr.mSelector = kAudioDevicePropertyStreamConfiguration;
+        const AudioObjectPropertyAddress addr = {
+            kAudioDevicePropertyStreamConfiguration,
+            iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+            kAudioObjectPropertyElementMaster
+        };
+
+        const AudioObjectPropertyAddress nameaddr = {
+            kAudioObjectPropertyName,
+            iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
+            kAudioObjectPropertyElementMaster
+        };
 
 
         result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
         result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
         if (result != noErr)
         if (result != noErr)
@@ -136,9 +174,9 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
         if (!usable)
         if (!usable)
             continue;
             continue;
 
 
-        addr.mSelector = kAudioObjectPropertyName;
+
         size = sizeof (CFStringRef);
         size = sizeof (CFStringRef);
-        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, &cfstr);
+        result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
         if (result != kAudioHardwareNoError)
         if (result != kAudioHardwareNoError)
             continue;
             continue;
 
 
@@ -169,18 +207,96 @@ build_device_list(int iscapture, addDevFn addfn, void *addfndata)
                    ((iscapture) ? "capture" : "output"),
                    ((iscapture) ? "capture" : "output"),
                    (int) *devCount, ptr, (int) dev);
                    (int) *devCount, ptr, (int) dev);
 #endif
 #endif
-            addfn(ptr, dev, addfndata);
+            addfn(ptr, iscapture, dev, addfndata);
         }
         }
         SDL_free(ptr);  /* addfn() would have copied the string. */
         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
 static void
 COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
 COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
 {
 {
+    free_audio_device_list(iscapture ? &capture_devs : &output_devs);
     build_device_list(iscapture, addToDevList, addfn);
     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
 static int
 find_device_by_name(_THIS, const char *devname, int iscapture)
 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
 static void
 COREAUDIO_CloseDevice(_THIS)
 COREAUDIO_CloseDevice(_THIS)
 {
 {
     if (this->hidden != NULL) {
     if (this->hidden != NULL) {
         if (this->hidden->audioUnitOpened) {
         if (this->hidden->audioUnitOpened) {
+            #if MACOSX_COREAUDIO
+            /* Unregister our disconnect callback. */
+            AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
+            #endif
+
             AURenderCallbackStruct callback;
             AURenderCallbackStruct callback;
             const AudioUnitElement output_bus = 0;
             const AudioUnitElement output_bus = 0;
             const AudioUnitElement input_bus = 1;
             const AudioUnitElement input_bus = 1;
@@ -355,7 +514,6 @@ COREAUDIO_CloseDevice(_THIS)
     }
     }
 }
 }
 
 
-
 static int
 static int
 prepare_audiounit(_THIS, const char *devname, int iscapture,
 prepare_audiounit(_THIS, const char *devname, int iscapture,
                   const AudioStreamBasicDescription * strdesc)
                   const AudioStreamBasicDescription * strdesc)
@@ -454,6 +612,11 @@ prepare_audiounit(_THIS, const char *devname, int iscapture,
     result = AudioOutputUnitStart(this->hidden->audioUnit);
     result = AudioOutputUnitStart(this->hidden->audioUnit);
     CHECK_RESULT("AudioOutputUnitStart");
     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! */
     /* We're running! */
     return 1;
     return 1;
 }
 }
@@ -527,15 +690,27 @@ COREAUDIO_OpenDevice(_THIS, const char *devname, int iscapture)
     return 0;   /* good to go. */
     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
 static int
 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
 COREAUDIO_Init(SDL_AudioDriverImpl * impl)
 {
 {
     /* Set the function pointers */
     /* Set the function pointers */
     impl->OpenDevice = COREAUDIO_OpenDevice;
     impl->OpenDevice = COREAUDIO_OpenDevice;
     impl->CloseDevice = COREAUDIO_CloseDevice;
     impl->CloseDevice = COREAUDIO_CloseDevice;
+    impl->Deinitialize = COREAUDIO_Deinitialize;
 
 
 #if MACOSX_COREAUDIO
 #if MACOSX_COREAUDIO
     impl->DetectDevices = COREAUDIO_DetectDevices;
     impl->DetectDevices = COREAUDIO_DetectDevices;
+    AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
 #else
 #else
     impl->OnlyHasDefaultOutputDevice = 1;
     impl->OnlyHasDefaultOutputDevice = 1;
 
 

+ 1 - 1
src/audio/disk/SDL_diskaudio.c

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

+ 1 - 1
src/audio/dsp/SDL_dspaudio.c

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

+ 2 - 2
src/audio/esd/SDL_esdaudio.c

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

+ 1 - 1
src/audio/fusionsound/SDL_fsaudio.c

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

+ 2 - 2
src/audio/paudio/SDL_paudio.c

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

+ 2 - 2
src/audio/pulseaudio/SDL_pulseaudio.c

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

+ 1 - 1
src/audio/qsa/SDL_qsa_audio.c

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

+ 1 - 1
src/audio/sndio/SDL_sndioaudio.c

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

+ 2 - 2
src/audio/sun/SDL_sunaudio.c

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

+ 1 - 1
src/audio/xaudio2/SDL_xaudio2.c

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

+ 4 - 0
test/Makefile.in

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

+ 182 - 0
test/testaudiohotplug.c

@@ -0,0 +1,182 @@
+/*
+  Copyright (C) 1997-2014 Sam Lantinga <[email protected]>
+
+  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: */