Initial work on audio device hotplug support.
authorRyan C. Gordon <icculus@icculus.org>
Mon, 16 Mar 2015 02:11:39 -0400
changeset 9393ed79a66e57e5
parent 9392 92e23eff9b89
child 9394 bb28e5281770
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.
include/SDL_events.h
src/audio/SDL_audio.c
src/audio/SDL_sysaudio.h
src/audio/alsa/SDL_alsa_audio.c
src/audio/arts/SDL_artsaudio.c
src/audio/bsd/SDL_bsdaudio.c
src/audio/coreaudio/SDL_coreaudio.c
src/audio/disk/SDL_diskaudio.c
src/audio/dsp/SDL_dspaudio.c
src/audio/esd/SDL_esdaudio.c
src/audio/fusionsound/SDL_fsaudio.c
src/audio/paudio/SDL_paudio.c
src/audio/pulseaudio/SDL_pulseaudio.c
src/audio/qsa/SDL_qsa_audio.c
src/audio/sndio/SDL_sndioaudio.c
src/audio/sun/SDL_sunaudio.c
src/audio/xaudio2/SDL_xaudio2.c
test/Makefile.in
test/testaudiohotplug.c
     1.1 --- a/include/SDL_events.h	Wed Mar 18 10:09:39 2015 -0400
     1.2 +++ b/include/SDL_events.h	Mon Mar 16 02:11:39 2015 -0400
     1.3 @@ -110,6 +110,10 @@
     1.4      SDL_JOYDEVICEADDED,         /**< A new joystick has been inserted into the system */
     1.5      SDL_JOYDEVICEREMOVED,       /**< An opened joystick has been removed */
     1.6  
     1.7 +    /* Audio hotplug events */
     1.8 +    SDL_AUDIODEVICEADDED = 0x700,  /**< A new audio device is available */
     1.9 +    SDL_AUDIODEVICEREMOVED,        /**< An audio device has been removed. */
    1.10 +
    1.11      /* Game controller events */
    1.12      SDL_CONTROLLERAXISMOTION  = 0x650, /**< Game controller axis motion */
    1.13      SDL_CONTROLLERBUTTONDOWN,          /**< Game controller button pressed */
    1.14 @@ -382,6 +386,20 @@
    1.15      Sint32 which;       /**< The joystick device index for the ADDED event, instance id for the REMOVED or REMAPPED event */
    1.16  } SDL_ControllerDeviceEvent;
    1.17  
    1.18 +/**
    1.19 + *  \brief Audio device event structure (event.adevice.*)
    1.20 + */
    1.21 +typedef struct SDL_AudioDeviceEvent
    1.22 +{
    1.23 +    Uint32 type;        /**< ::SDL_AUDIODEVICEADDED, or ::SDL_AUDIODEVICEREMOVED */
    1.24 +    Uint32 timestamp;
    1.25 +    Uint32 which;       /**< The audio device index for the ADDED event (valid until next SDL_GetNumAudioDevices() call), SDL_AudioDeviceID for the REMOVED event */
    1.26 +    Uint8 iscapture;    /**< zero if an output device, non-zero if a capture device. */
    1.27 +    Uint8 padding1;
    1.28 +    Uint8 padding2;
    1.29 +    Uint8 padding3;
    1.30 +} SDL_AudioDeviceEvent;
    1.31 +
    1.32  
    1.33  /**
    1.34   *  \brief Touch finger event structure (event.tfinger.*)
    1.35 @@ -516,6 +534,7 @@
    1.36      SDL_ControllerAxisEvent caxis;      /**< Game Controller axis event data */
    1.37      SDL_ControllerButtonEvent cbutton;  /**< Game Controller button event data */
    1.38      SDL_ControllerDeviceEvent cdevice;  /**< Game Controller device event data */
    1.39 +    SDL_AudioDeviceEvent adevice;   /**< Audio device event data */
    1.40      SDL_QuitEvent quit;             /**< Quit request event data */
    1.41      SDL_UserEvent user;             /**< Custom event data */
    1.42      SDL_SysWMEvent syswm;           /**< System dependent window event data */
     2.1 --- a/src/audio/SDL_audio.c	Wed Mar 18 10:09:39 2015 -0400
     2.2 +++ b/src/audio/SDL_audio.c	Mon Mar 16 02:11:39 2015 -0400
     2.3 @@ -333,6 +333,144 @@
     2.4  }
     2.5  #endif
     2.6  
     2.7 +/* device hotplug support... */
     2.8 +
     2.9 +/* this function expects its caller to hold current_audio.detection_lock */
    2.10 +static int
    2.11 +add_audio_device(const char *_name, char ***_devices, int *_devCount)
    2.12 +{
    2.13 +    char *name = SDL_strdup(_name);
    2.14 +    int retval = -1;
    2.15 +
    2.16 +    if (name != NULL) {
    2.17 +        char **devices = *_devices;
    2.18 +        int devCount = *_devCount;
    2.19 +        void *ptr = SDL_realloc(devices, (devCount+1) * sizeof(char*));
    2.20 +        if (ptr == NULL) {
    2.21 +            SDL_free(name);
    2.22 +        } else {
    2.23 +            retval = devCount;
    2.24 +            devices = (char **) ptr;
    2.25 +            devices[devCount++] = name;
    2.26 +            *_devices = devices;
    2.27 +            *_devCount = devCount;
    2.28 +        }
    2.29 +    }
    2.30 +
    2.31 +    return retval;
    2.32 +}
    2.33 +
    2.34 +static int
    2.35 +add_capture_device(const char *name)
    2.36 +{
    2.37 +    /* !!! FIXME: add this later. SDL_assert(current_audio.impl.HasCaptureSupport);*/
    2.38 +    return add_audio_device(name, &current_audio.inputDevices, &current_audio.inputDeviceCount);
    2.39 +}
    2.40 +
    2.41 +static int
    2.42 +add_output_device(const char *name)
    2.43 +{
    2.44 +    return add_audio_device(name, &current_audio.outputDevices, &current_audio.outputDeviceCount);
    2.45 +}
    2.46 +
    2.47 +static void
    2.48 +free_device_list(char ***devices, int *devCount)
    2.49 +{
    2.50 +    int i = *devCount;
    2.51 +    if ((i > 0) && (*devices != NULL)) {
    2.52 +        while (i--) {
    2.53 +            SDL_free((*devices)[i]);
    2.54 +        }
    2.55 +    }
    2.56 +
    2.57 +    SDL_free(*devices);
    2.58 +
    2.59 +    *devices = NULL;
    2.60 +    *devCount = 0;
    2.61 +}
    2.62 +
    2.63 +static void
    2.64 +perform_full_device_redetect(const int iscapture)
    2.65 +{
    2.66 +    SDL_LockMutex(current_audio.detection_lock);
    2.67 +
    2.68 +    if (iscapture) {
    2.69 +        if (!current_audio.impl.OnlyHasDefaultOutputDevice) {
    2.70 +            free_device_list(&current_audio.outputDevices, &current_audio.outputDeviceCount);
    2.71 +            current_audio.impl.DetectDevices(SDL_FALSE, add_output_device);
    2.72 +        }
    2.73 +    } else {
    2.74 +        if ((current_audio.impl.HasCaptureSupport) && (!current_audio.impl.OnlyHasDefaultInputDevice)) {
    2.75 +            free_device_list(&current_audio.inputDevices, &current_audio.inputDeviceCount);
    2.76 +            current_audio.impl.DetectDevices(SDL_TRUE, add_capture_device);
    2.77 +        }
    2.78 +    }
    2.79 +
    2.80 +    SDL_UnlockMutex(current_audio.detection_lock);
    2.81 +}
    2.82 +
    2.83 +/* The audio backends call this when a new device is plugged in. */
    2.84 +void
    2.85 +SDL_AudioDeviceConnected(const int iscapture, const char *name)
    2.86 +{
    2.87 +    int device_index = -1;
    2.88 +
    2.89 +    SDL_LockMutex(current_audio.detection_lock);
    2.90 +    if (iscapture) {
    2.91 +        device_index = add_capture_device(name);
    2.92 +    } else {
    2.93 +        device_index = add_output_device(name);
    2.94 +    }
    2.95 +    SDL_UnlockMutex(current_audio.detection_lock);
    2.96 +
    2.97 +    if (device_index != -1) {
    2.98 +        /* Post the event, if desired */
    2.99 +        if (SDL_GetEventState(SDL_AUDIODEVICEADDED) == SDL_ENABLE) {
   2.100 +            SDL_Event event;
   2.101 +            event.adevice.type = SDL_AUDIODEVICEADDED;
   2.102 +            event.adevice.which = device_index;
   2.103 +            event.adevice.iscapture = iscapture;
   2.104 +            SDL_PushEvent(&event);
   2.105 +        }
   2.106 +    }
   2.107 +}
   2.108 +
   2.109 +/* The audio backends call this when a device is unplugged. */
   2.110 +void
   2.111 +SDL_AudioDeviceDisconnected(const int iscapture, SDL_AudioDevice *device)
   2.112 +{
   2.113 +    /* device==NULL means an unopened device was lost; do the redetect only. */
   2.114 +    if (device != NULL) {
   2.115 +        SDL_assert(get_audio_device(device->id) == device);
   2.116 +        SDL_assert(device->enabled);  /* called more than once?! */
   2.117 +
   2.118 +        /* Ends the audio callback and mark the device as STOPPED, but the
   2.119 +           app still needs to close the device to free resources. */
   2.120 +        current_audio.impl.LockDevice(device);
   2.121 +        device->enabled = 0;
   2.122 +        current_audio.impl.UnlockDevice(device);
   2.123 +
   2.124 +        /* Post the event, if desired */
   2.125 +        if (SDL_GetEventState(SDL_AUDIODEVICEREMOVED) == SDL_ENABLE) {
   2.126 +            SDL_Event event;
   2.127 +            event.adevice.type = SDL_AUDIODEVICEREMOVED;
   2.128 +            event.adevice.which = device->id;
   2.129 +            event.adevice.iscapture = device->iscapture ? 1 : 0;
   2.130 +            SDL_PushEvent(&event);
   2.131 +        }
   2.132 +    }
   2.133 +
   2.134 +    /* we don't really know which name (if any) was associated with this
   2.135 +       device in the device list, so drop the entire list and rebuild it.
   2.136 +       (we should probably change the API in 2.1 to make this more clear?) */
   2.137 +    if (iscapture) {
   2.138 +        current_audio.need_capture_device_redetect = SDL_TRUE;
   2.139 +    } else {
   2.140 +        current_audio.need_output_device_redetect = SDL_TRUE;
   2.141 +    }
   2.142 +}
   2.143 +
   2.144 +
   2.145  
   2.146  /* buffer queueing support... */
   2.147  
   2.148 @@ -690,6 +828,13 @@
   2.149  
   2.150              /* !!! FIXME: this should be LockDevice. */
   2.151              SDL_LockMutex(device->mixer_lock);
   2.152 +
   2.153 +            /* Check again, in case device was removed while a lock was held. */
   2.154 +            if (!device->enabled) {
   2.155 +                SDL_UnlockMutex(device->mixer_lock);
   2.156 +                break;
   2.157 +            }
   2.158 +
   2.159              if (device->paused) {
   2.160                  SDL_memset(stream, silence, stream_len);
   2.161              } else {
   2.162 @@ -821,8 +966,34 @@
   2.163          return -1;            /* No driver was available, so fail. */
   2.164      }
   2.165  
   2.166 +    current_audio.detection_lock = SDL_CreateMutex();
   2.167 +
   2.168      finalize_audio_entry_points();
   2.169  
   2.170 +    /* Make sure we have a list of devices available at startup. */
   2.171 +    perform_full_device_redetect(SDL_TRUE);
   2.172 +    perform_full_device_redetect(SDL_FALSE);
   2.173 +
   2.174 +    /* Post an add event for each initial device, if desired */
   2.175 +    if (SDL_GetEventState(SDL_AUDIODEVICEADDED) == SDL_ENABLE) {
   2.176 +        SDL_Event event;
   2.177 +
   2.178 +        SDL_zero(event);
   2.179 +        event.adevice.type = SDL_AUDIODEVICEADDED;
   2.180 +
   2.181 +        event.adevice.iscapture = 0;
   2.182 +        for (i = 0; i < current_audio.outputDeviceCount; i++) {
   2.183 +            event.adevice.which = i;
   2.184 +            SDL_PushEvent(&event);
   2.185 +        }
   2.186 +
   2.187 +        event.adevice.iscapture = 1;
   2.188 +        for (i = 0; i < current_audio.inputDeviceCount; i++) {
   2.189 +            event.adevice.which = i;
   2.190 +            SDL_PushEvent(&event);
   2.191 +        }
   2.192 +    }
   2.193 +
   2.194      return 0;
   2.195  }
   2.196  
   2.197 @@ -835,53 +1006,6 @@
   2.198      return current_audio.name;
   2.199  }
   2.200  
   2.201 -static void
   2.202 -free_device_list(char ***devices, int *devCount)
   2.203 -{
   2.204 -    int i = *devCount;
   2.205 -    if ((i > 0) && (*devices != NULL)) {
   2.206 -        while (i--) {
   2.207 -            SDL_free((*devices)[i]);
   2.208 -        }
   2.209 -    }
   2.210 -
   2.211 -    SDL_free(*devices);
   2.212 -
   2.213 -    *devices = NULL;
   2.214 -    *devCount = 0;
   2.215 -}
   2.216 -
   2.217 -static
   2.218 -void SDL_AddCaptureAudioDevice(const char *_name)
   2.219 -{
   2.220 -    char *name = NULL;
   2.221 -    void *ptr = SDL_realloc(current_audio.inputDevices,
   2.222 -                          (current_audio.inputDeviceCount+1) * sizeof(char*));
   2.223 -    if (ptr == NULL) {
   2.224 -        return;  /* oh well. */
   2.225 -    }
   2.226 -
   2.227 -    current_audio.inputDevices = (char **) ptr;
   2.228 -    name = SDL_strdup(_name);  /* if this returns NULL, that's okay. */
   2.229 -    current_audio.inputDevices[current_audio.inputDeviceCount++] = name;
   2.230 -}
   2.231 -
   2.232 -static
   2.233 -void SDL_AddOutputAudioDevice(const char *_name)
   2.234 -{
   2.235 -    char *name = NULL;
   2.236 -    void *ptr = SDL_realloc(current_audio.outputDevices,
   2.237 -                          (current_audio.outputDeviceCount+1) * sizeof(char*));
   2.238 -    if (ptr == NULL) {
   2.239 -        return;  /* oh well. */
   2.240 -    }
   2.241 -
   2.242 -    current_audio.outputDevices = (char **) ptr;
   2.243 -    name = SDL_strdup(_name);  /* if this returns NULL, that's okay. */
   2.244 -    current_audio.outputDevices[current_audio.outputDeviceCount++] = name;
   2.245 -}
   2.246 -
   2.247 -
   2.248  int
   2.249  SDL_GetNumAudioDevices(int iscapture)
   2.250  {
   2.251 @@ -903,18 +1027,20 @@
   2.252          return 1;
   2.253      }
   2.254  
   2.255 -    if (iscapture) {
   2.256 -        free_device_list(&current_audio.inputDevices,
   2.257 -                         &current_audio.inputDeviceCount);
   2.258 -        current_audio.impl.DetectDevices(iscapture, SDL_AddCaptureAudioDevice);
   2.259 -        retval = current_audio.inputDeviceCount;
   2.260 -    } else {
   2.261 -        free_device_list(&current_audio.outputDevices,
   2.262 -                         &current_audio.outputDeviceCount);
   2.263 -        current_audio.impl.DetectDevices(iscapture, SDL_AddOutputAudioDevice);
   2.264 -        retval = current_audio.outputDeviceCount;
   2.265 +    if (current_audio.need_capture_device_redetect) {
   2.266 +        current_audio.need_capture_device_redetect = SDL_FALSE;
   2.267 +        perform_full_device_redetect(SDL_TRUE);
   2.268      }
   2.269  
   2.270 +    if (current_audio.need_output_device_redetect) {
   2.271 +        current_audio.need_output_device_redetect = SDL_FALSE;
   2.272 +        perform_full_device_redetect(SDL_FALSE);
   2.273 +    }
   2.274 +
   2.275 +    SDL_LockMutex(current_audio.detection_lock);
   2.276 +    retval = iscapture ? current_audio.inputDeviceCount : current_audio.outputDeviceCount;
   2.277 +    SDL_UnlockMutex(current_audio.detection_lock);
   2.278 +
   2.279      return retval;
   2.280  }
   2.281  
   2.282 @@ -922,6 +1048,8 @@
   2.283  const char *
   2.284  SDL_GetAudioDeviceName(int index, int iscapture)
   2.285  {
   2.286 +    const char *retval = NULL;
   2.287 +
   2.288      if (!SDL_WasInit(SDL_INIT_AUDIO)) {
   2.289          SDL_SetError("Audio subsystem is not initialized");
   2.290          return NULL;
   2.291 @@ -950,16 +1078,18 @@
   2.292          return DEFAULT_OUTPUT_DEVNAME;
   2.293      }
   2.294  
   2.295 -    if (iscapture) {
   2.296 -        if (index >= current_audio.inputDeviceCount) {
   2.297 -            goto no_such_device;
   2.298 -        }
   2.299 -        return current_audio.inputDevices[index];
   2.300 -    } else {
   2.301 -        if (index >= current_audio.outputDeviceCount) {
   2.302 -            goto no_such_device;
   2.303 -        }
   2.304 -        return current_audio.outputDevices[index];
   2.305 +    SDL_LockMutex(current_audio.detection_lock);
   2.306 +    if (iscapture && (index < current_audio.inputDeviceCount)) {
   2.307 +        retval = current_audio.inputDevices[index];
   2.308 +    } else if (!iscapture && (index < current_audio.outputDeviceCount)) {
   2.309 +        retval = current_audio.outputDevices[index];
   2.310 +    }
   2.311 +    SDL_UnlockMutex(current_audio.detection_lock);
   2.312 +
   2.313 +    /* !!! FIXME: a device could be removed after being returned here, freeing retval's pointer. */
   2.314 +
   2.315 +    if (retval != NULL) {
   2.316 +        return retval;
   2.317      }
   2.318  
   2.319  no_such_device:
   2.320 @@ -1077,6 +1207,18 @@
   2.321          return 0;
   2.322      }
   2.323  
   2.324 +    /* Find an available device ID... */
   2.325 +    for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) {
   2.326 +        if (open_devices[id] == NULL) {
   2.327 +            break;
   2.328 +        }
   2.329 +    }
   2.330 +
   2.331 +    if (id == SDL_arraysize(open_devices)) {
   2.332 +        SDL_SetError("Too many open audio devices");
   2.333 +        return 0;
   2.334 +    }
   2.335 +
   2.336      if (!obtained) {
   2.337          obtained = &_obtained;
   2.338      }
   2.339 @@ -1135,6 +1277,7 @@
   2.340          return 0;
   2.341      }
   2.342      SDL_zerop(device);
   2.343 +    device->id = id + 1;
   2.344      device->spec = *obtained;
   2.345      device->enabled = 1;
   2.346      device->paused = 1;
   2.347 @@ -1150,12 +1293,6 @@
   2.348          }
   2.349      }
   2.350  
   2.351 -    /* force a device detection if we haven't done one yet. */
   2.352 -    if ( ((iscapture) && (current_audio.inputDevices == NULL)) ||
   2.353 -         ((!iscapture) && (current_audio.outputDevices == NULL)) ) {
   2.354 -        SDL_GetNumAudioDevices(iscapture);
   2.355 -    }
   2.356 -
   2.357      if (current_audio.impl.OpenDevice(device, devname, iscapture) < 0) {
   2.358          close_audio_device(device);
   2.359          return 0;
   2.360 @@ -1247,25 +1384,14 @@
   2.361          device->spec.userdata = device;
   2.362      }
   2.363  
   2.364 -    /* Find an available device ID and store the structure... */
   2.365 -    for (id = min_id - 1; id < SDL_arraysize(open_devices); id++) {
   2.366 -        if (open_devices[id] == NULL) {
   2.367 -            open_devices[id] = device;
   2.368 -            break;
   2.369 -        }
   2.370 -    }
   2.371 -
   2.372 -    if (id == SDL_arraysize(open_devices)) {
   2.373 -        SDL_SetError("Too many open audio devices");
   2.374 -        close_audio_device(device);
   2.375 -        return 0;
   2.376 -    }
   2.377 +    /* add it to our list of open devices. */
   2.378 +    open_devices[id] = device;
   2.379  
   2.380      /* Start the audio thread if necessary */
   2.381      if (!current_audio.impl.ProvidesOwnCallbackThread) {
   2.382          /* Start the audio thread */
   2.383          char name[64];
   2.384 -        SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) (id + 1));
   2.385 +        SDL_snprintf(name, sizeof (name), "SDLAudioDev%d", (int) device->id);
   2.386  /* !!! FIXME: this is nasty. */
   2.387  #if defined(__WIN32__) && !defined(HAVE_LIBC)
   2.388  #undef SDL_CreateThread
   2.389 @@ -1278,13 +1404,13 @@
   2.390          device->thread = SDL_CreateThread(SDL_RunAudio, name, device);
   2.391  #endif
   2.392          if (device->thread == NULL) {
   2.393 -            SDL_CloseAudioDevice(id + 1);
   2.394 +            SDL_CloseAudioDevice(device->id);
   2.395              SDL_SetError("Couldn't create audio thread");
   2.396              return 0;
   2.397          }
   2.398      }
   2.399  
   2.400 -    return id + 1;
   2.401 +    return device->id;
   2.402  }
   2.403  
   2.404  
   2.405 @@ -1431,12 +1557,16 @@
   2.406  
   2.407      /* Free the driver data */
   2.408      current_audio.impl.Deinitialize();
   2.409 +
   2.410      free_device_list(&current_audio.outputDevices,
   2.411                       &current_audio.outputDeviceCount);
   2.412      free_device_list(&current_audio.inputDevices,
   2.413                       &current_audio.inputDeviceCount);
   2.414 -    SDL_memset(&current_audio, '\0', sizeof(current_audio));
   2.415 -    SDL_memset(open_devices, '\0', sizeof(open_devices));
   2.416 +
   2.417 +    SDL_DestroyMutex(current_audio.detection_lock);
   2.418 +
   2.419 +    SDL_zero(current_audio);
   2.420 +    SDL_zero(open_devices);
   2.421  }
   2.422  
   2.423  #define NUM_FORMATS 10
     3.1 --- a/src/audio/SDL_sysaudio.h	Wed Mar 18 10:09:39 2015 -0400
     3.2 +++ b/src/audio/SDL_sysaudio.h	Mon Mar 16 02:11:39 2015 -0400
     3.3 @@ -31,7 +31,16 @@
     3.4  #define _THIS   SDL_AudioDevice *_this
     3.5  
     3.6  /* Used by audio targets during DetectDevices() */
     3.7 -typedef void (*SDL_AddAudioDevice)(const char *name);
     3.8 +typedef int (*SDL_AddAudioDevice)(const char *name);
     3.9 +
    3.10 +/* Audio targets should call this as devices are hotplugged. Don't call
    3.11 +   during DetectDevices(), this is for hotplugging a device later. */
    3.12 +extern void SDL_AudioDeviceConnected(const int iscapture, const char *name);
    3.13 +
    3.14 +/* Audio targets should call this as devices are unplugged.
    3.15 +  (device) can be NULL if an unopened device is lost. */
    3.16 +extern void SDL_AudioDeviceDisconnected(const int iscapture, SDL_AudioDevice *device);
    3.17 +
    3.18  
    3.19  /* This is the size of a packet when using SDL_QueueAudio(). We allocate
    3.20     these as necessary and pool them, under the assumption that we'll
    3.21 @@ -92,6 +101,12 @@
    3.22  
    3.23      SDL_AudioDriverImpl impl;
    3.24  
    3.25 +    /* A mutex for device detection */
    3.26 +    SDL_mutex *detection_lock;
    3.27 +
    3.28 +    SDL_bool need_capture_device_redetect;
    3.29 +    SDL_bool need_output_device_redetect;
    3.30 +
    3.31      char **outputDevices;
    3.32      int outputDeviceCount;
    3.33  
    3.34 @@ -114,6 +129,7 @@
    3.35  {
    3.36      /* * * */
    3.37      /* Data common to all devices */
    3.38 +    SDL_AudioDeviceID id;
    3.39  
    3.40      /* The current audio specification (shared with audio thread) */
    3.41      SDL_AudioSpec spec;
     4.1 --- a/src/audio/alsa/SDL_alsa_audio.c	Wed Mar 18 10:09:39 2015 -0400
     4.2 +++ b/src/audio/alsa/SDL_alsa_audio.c	Mon Mar 16 02:11:39 2015 -0400
     4.3 @@ -320,7 +320,7 @@
     4.4                  /* Hmm, not much we can do - abort */
     4.5                  fprintf(stderr, "ALSA write failed (unrecoverable): %s\n",
     4.6                          ALSA_snd_strerror(status));
     4.7 -                this->enabled = 0;
     4.8 +                SDL_AudioDeviceDisconnected(SDL_FALSE, this);
     4.9                  return;
    4.10              }
    4.11              continue;
     5.1 --- a/src/audio/arts/SDL_artsaudio.c	Wed Mar 18 10:09:39 2015 -0400
     5.2 +++ b/src/audio/arts/SDL_artsaudio.c	Mon Mar 16 02:11:39 2015 -0400
     5.3 @@ -151,7 +151,7 @@
     5.4          /* Check every 10 loops */
     5.5          if (this->hidden->parent && (((++cnt) % 10) == 0)) {
     5.6              if (kill(this->hidden->parent, 0) < 0 && errno == ESRCH) {
     5.7 -                this->enabled = 0;
     5.8 +                SDL_AudioDeviceDisconnected(SDL_FALSE, this);
     5.9              }
    5.10          }
    5.11      }
    5.12 @@ -179,7 +179,7 @@
    5.13  
    5.14      /* If we couldn't write, assume fatal error for now */
    5.15      if (written < 0) {
    5.16 -        this->enabled = 0;
    5.17 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    5.18      }
    5.19  #ifdef DEBUG_AUDIO
    5.20      fprintf(stderr, "Wrote %d bytes of audio data\n", written);
     6.1 --- a/src/audio/bsd/SDL_bsdaudio.c	Wed Mar 18 10:09:39 2015 -0400
     6.2 +++ b/src/audio/bsd/SDL_bsdaudio.c	Mon Mar 16 02:11:39 2015 -0400
     6.3 @@ -150,7 +150,7 @@
     6.4                 the user know what happened.
     6.5               */
     6.6              fprintf(stderr, "SDL: %s\n", message);
     6.7 -            this->enabled = 0;
     6.8 +            SDL_AudioDeviceDisconnected(SDL_FALSE, this);
     6.9              /* Don't try to close - may hang */
    6.10              this->hidden->audio_fd = -1;
    6.11  #ifdef DEBUG_AUDIO
    6.12 @@ -195,7 +195,7 @@
    6.13  
    6.14      /* If we couldn't write, assume fatal error for now */
    6.15      if (written < 0) {
    6.16 -        this->enabled = 0;
    6.17 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    6.18      }
    6.19  #ifdef DEBUG_AUDIO
    6.20      fprintf(stderr, "Wrote %d bytes of audio data\n", written);
     7.1 --- a/src/audio/coreaudio/SDL_coreaudio.c	Wed Mar 18 10:09:39 2015 -0400
     7.2 +++ b/src/audio/coreaudio/SDL_coreaudio.c	Mon Mar 16 02:11:39 2015 -0400
     7.3 @@ -40,13 +40,50 @@
     7.4      }
     7.5  
     7.6  #if MACOSX_COREAUDIO
     7.7 -typedef void (*addDevFn)(const char *name, AudioDeviceID devId, void *data);
     7.8 +static const AudioObjectPropertyAddress devlist_address = {
     7.9 +    kAudioHardwarePropertyDevices,
    7.10 +    kAudioObjectPropertyScopeGlobal,
    7.11 +    kAudioObjectPropertyElementMaster
    7.12 +};
    7.13 +
    7.14 +typedef void (*addDevFn)(const char *name, const int iscapture, AudioDeviceID devId, void *data);
    7.15 +
    7.16 +typedef struct AudioDeviceList
    7.17 +{
    7.18 +    AudioDeviceID devid;
    7.19 +    SDL_bool alive;
    7.20 +    struct AudioDeviceList *next;
    7.21 +} AudioDeviceList;
    7.22 +
    7.23 +static AudioDeviceList *output_devs = NULL;
    7.24 +static AudioDeviceList *capture_devs = NULL;
    7.25 +
    7.26 +static SDL_bool
    7.27 +add_to_internal_dev_list(const int iscapture, AudioDeviceID devId)
    7.28 +{
    7.29 +    AudioDeviceList *item = (AudioDeviceList *) SDL_malloc(sizeof (AudioDeviceList));
    7.30 +    if (item == NULL) {
    7.31 +        return SDL_FALSE;
    7.32 +    }
    7.33 +    item->devid = devId;
    7.34 +    item->alive = SDL_TRUE;
    7.35 +    item->next = iscapture ? capture_devs : output_devs;
    7.36 +    if (iscapture) {
    7.37 +        capture_devs = item;
    7.38 +    } else {
    7.39 +        output_devs = item;
    7.40 +    }
    7.41 +
    7.42 +    return SDL_TRUE;
    7.43 +}
    7.44  
    7.45  static void
    7.46 -addToDevList(const char *name, AudioDeviceID devId, void *data)
    7.47 +addToDevList(const char *name, const int iscapture, AudioDeviceID devId, void *data)
    7.48  {
    7.49      SDL_AddAudioDevice addfn = (SDL_AddAudioDevice) data;
    7.50 -    addfn(name);
    7.51 +    if (add_to_internal_dev_list(iscapture, devId)) {
    7.52 +        addfn(name);
    7.53 +    }
    7.54  }
    7.55  
    7.56  typedef struct
    7.57 @@ -57,7 +94,7 @@
    7.58  } FindDevIdData;
    7.59  
    7.60  static void
    7.61 -findDevId(const char *name, AudioDeviceID devId, void *_data)
    7.62 +findDevId(const char *name, const int iscapture, AudioDeviceID devId, void *_data)
    7.63  {
    7.64      FindDevIdData *data = (FindDevIdData *) _data;
    7.65      if (!data->found) {
    7.66 @@ -77,14 +114,8 @@
    7.67      UInt32 i = 0;
    7.68      UInt32 max = 0;
    7.69  
    7.70 -    AudioObjectPropertyAddress addr = {
    7.71 -        kAudioHardwarePropertyDevices,
    7.72 -        kAudioObjectPropertyScopeGlobal,
    7.73 -        kAudioObjectPropertyElementMaster
    7.74 -    };
    7.75 -
    7.76 -    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &addr,
    7.77 -                                            0, NULL, &size);
    7.78 +    result = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject,
    7.79 +                                            &devlist_address, 0, NULL, &size);
    7.80      if (result != kAudioHardwareNoError)
    7.81          return;
    7.82  
    7.83 @@ -92,8 +123,8 @@
    7.84      if (devs == NULL)
    7.85          return;
    7.86  
    7.87 -    result = AudioObjectGetPropertyData(kAudioObjectSystemObject, &addr,
    7.88 -                                        0, NULL, &size, devs);
    7.89 +    result = AudioObjectGetPropertyData(kAudioObjectSystemObject,
    7.90 +                                        &devlist_address, 0, NULL, &size, devs);
    7.91      if (result != kAudioHardwareNoError)
    7.92          return;
    7.93  
    7.94 @@ -105,10 +136,17 @@
    7.95          AudioBufferList *buflist = NULL;
    7.96          int usable = 0;
    7.97          CFIndex len = 0;
    7.98 +        const AudioObjectPropertyAddress addr = {
    7.99 +            kAudioDevicePropertyStreamConfiguration,
   7.100 +            iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   7.101 +            kAudioObjectPropertyElementMaster
   7.102 +        };
   7.103  
   7.104 -        addr.mScope = iscapture ? kAudioDevicePropertyScopeInput :
   7.105 -                        kAudioDevicePropertyScopeOutput;
   7.106 -        addr.mSelector = kAudioDevicePropertyStreamConfiguration;
   7.107 +        const AudioObjectPropertyAddress nameaddr = {
   7.108 +            kAudioObjectPropertyName,
   7.109 +            iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
   7.110 +            kAudioObjectPropertyElementMaster
   7.111 +        };
   7.112  
   7.113          result = AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size);
   7.114          if (result != noErr)
   7.115 @@ -136,9 +174,9 @@
   7.116          if (!usable)
   7.117              continue;
   7.118  
   7.119 -        addr.mSelector = kAudioObjectPropertyName;
   7.120 +
   7.121          size = sizeof (CFStringRef);
   7.122 -        result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, &cfstr);
   7.123 +        result = AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr);
   7.124          if (result != kAudioHardwareNoError)
   7.125              continue;
   7.126  
   7.127 @@ -169,18 +207,96 @@
   7.128                     ((iscapture) ? "capture" : "output"),
   7.129                     (int) *devCount, ptr, (int) dev);
   7.130  #endif
   7.131 -            addfn(ptr, dev, addfndata);
   7.132 +            addfn(ptr, iscapture, dev, addfndata);
   7.133          }
   7.134          SDL_free(ptr);  /* addfn() would have copied the string. */
   7.135      }
   7.136  }
   7.137  
   7.138  static void
   7.139 +free_audio_device_list(AudioDeviceList **list)
   7.140 +{
   7.141 +    AudioDeviceList *item = *list;
   7.142 +    while (item) {
   7.143 +        AudioDeviceList *next = item->next;
   7.144 +        SDL_free(item);
   7.145 +        item = next;
   7.146 +    }
   7.147 +    *list = NULL;
   7.148 +}
   7.149 +
   7.150 +static void
   7.151  COREAUDIO_DetectDevices(int iscapture, SDL_AddAudioDevice addfn)
   7.152  {
   7.153 +    free_audio_device_list(iscapture ? &capture_devs : &output_devs);
   7.154      build_device_list(iscapture, addToDevList, addfn);
   7.155  }
   7.156  
   7.157 +static void
   7.158 +build_device_change_list(const char *name, const int iscapture, AudioDeviceID devId, void *data)
   7.159 +{
   7.160 +    AudioDeviceList **list = (AudioDeviceList **) data;
   7.161 +    AudioDeviceList *item;
   7.162 +    for (item = *list; item != NULL; item = item->next) {
   7.163 +        if (item->devid == devId) {
   7.164 +            item->alive = SDL_TRUE;
   7.165 +            return;
   7.166 +        }
   7.167 +    }
   7.168 +
   7.169 +    add_to_internal_dev_list(iscapture, devId);  /* new device, add it. */
   7.170 +    SDL_AudioDeviceConnected(iscapture, name);
   7.171 +}
   7.172 +
   7.173 +static SDL_bool
   7.174 +reprocess_device_list(const int iscapture, AudioDeviceList **list)
   7.175 +{
   7.176 +    SDL_bool was_disconnect = SDL_FALSE;
   7.177 +    AudioDeviceList *item;
   7.178 +    AudioDeviceList *prev = NULL;
   7.179 +    for (item = *list; item != NULL; item = item->next) {
   7.180 +        item->alive = SDL_FALSE;
   7.181 +    }
   7.182 +
   7.183 +    build_device_list(iscapture, build_device_change_list, list);
   7.184 +
   7.185 +    /* free items in the list that aren't still alive. */
   7.186 +    item = *list;
   7.187 +    while (item != NULL) {
   7.188 +        AudioDeviceList *next = item->next;
   7.189 +        if (item->alive) {
   7.190 +            prev = item;
   7.191 +        } else {
   7.192 +            was_disconnect = SDL_TRUE;
   7.193 +            if (prev) {
   7.194 +                prev->next = item->next;
   7.195 +            } else {
   7.196 +                *list = item->next;
   7.197 +            }
   7.198 +            SDL_free(item);
   7.199 +        }
   7.200 +        item = next;
   7.201 +    }
   7.202 +
   7.203 +    return was_disconnect;
   7.204 +}
   7.205 +
   7.206 +
   7.207 +/* this is called when the system's list of available audio devices changes. */
   7.208 +static OSStatus
   7.209 +device_list_changed(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
   7.210 +{
   7.211 +    if (reprocess_device_list(SDL_TRUE, &capture_devs)) {
   7.212 +        SDL_AudioDeviceDisconnected(SDL_TRUE, NULL);
   7.213 +    }
   7.214 +
   7.215 +    if (reprocess_device_list(SDL_FALSE, &output_devs)) {
   7.216 +        SDL_AudioDeviceDisconnected(SDL_FALSE, NULL);
   7.217 +    }
   7.218 +
   7.219 +    return 0;
   7.220 +}
   7.221 +
   7.222  static int
   7.223  find_device_by_name(_THIS, const char *devname, int iscapture)
   7.224  {
   7.225 @@ -317,11 +433,54 @@
   7.226  }
   7.227  
   7.228  
   7.229 +#if MACOSX_COREAUDIO
   7.230 +static const AudioObjectPropertyAddress alive_address =
   7.231 +{
   7.232 +    kAudioDevicePropertyDeviceIsAlive,
   7.233 +    kAudioObjectPropertyScopeGlobal,
   7.234 +    kAudioObjectPropertyElementMaster
   7.235 +};
   7.236 +
   7.237 +static OSStatus
   7.238 +device_unplugged(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
   7.239 +{
   7.240 +    SDL_AudioDevice *this = (SDL_AudioDevice *) data;
   7.241 +    SDL_bool dead = SDL_FALSE;
   7.242 +    UInt32 isAlive = 1;
   7.243 +    UInt32 size = sizeof (isAlive);
   7.244 +    OSStatus error;
   7.245 +
   7.246 +    if (!this->enabled) {
   7.247 +        return 0;  /* already known to be dead. */
   7.248 +    }
   7.249 +
   7.250 +    error = AudioObjectGetPropertyData(this->hidden->deviceID, &alive_address,
   7.251 +                                       0, NULL, &size, &isAlive);
   7.252 +
   7.253 +    if (error == kAudioHardwareBadDeviceError) {
   7.254 +        dead = SDL_TRUE;  /* device was unplugged. */
   7.255 +    } else if ((error == kAudioHardwareNoError) && (!isAlive)) {
   7.256 +        dead = SDL_TRUE;  /* device died in some other way. */
   7.257 +    }
   7.258 +
   7.259 +    if (dead) {
   7.260 +        SDL_AudioDeviceDisconnected(this->iscapture, this);
   7.261 +    }
   7.262 +
   7.263 +    return 0;
   7.264 +}
   7.265 +#endif
   7.266 +
   7.267  static void
   7.268  COREAUDIO_CloseDevice(_THIS)
   7.269  {
   7.270      if (this->hidden != NULL) {
   7.271          if (this->hidden->audioUnitOpened) {
   7.272 +            #if MACOSX_COREAUDIO
   7.273 +            /* Unregister our disconnect callback. */
   7.274 +            AudioObjectRemovePropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
   7.275 +            #endif
   7.276 +
   7.277              AURenderCallbackStruct callback;
   7.278              const AudioUnitElement output_bus = 0;
   7.279              const AudioUnitElement input_bus = 1;
   7.280 @@ -355,7 +514,6 @@
   7.281      }
   7.282  }
   7.283  
   7.284 -
   7.285  static int
   7.286  prepare_audiounit(_THIS, const char *devname, int iscapture,
   7.287                    const AudioStreamBasicDescription * strdesc)
   7.288 @@ -454,6 +612,11 @@
   7.289      result = AudioOutputUnitStart(this->hidden->audioUnit);
   7.290      CHECK_RESULT("AudioOutputUnitStart");
   7.291  
   7.292 +#if MACOSX_COREAUDIO
   7.293 +    /* Fire a callback if the device stops being "alive" (disconnected, etc). */
   7.294 +    AudioObjectAddPropertyListener(this->hidden->deviceID, &alive_address, device_unplugged, this);
   7.295 +#endif
   7.296 +
   7.297      /* We're running! */
   7.298      return 1;
   7.299  }
   7.300 @@ -527,15 +690,27 @@
   7.301      return 0;   /* good to go. */
   7.302  }
   7.303  
   7.304 +static void
   7.305 +COREAUDIO_Deinitialize(void)
   7.306 +{
   7.307 +#if MACOSX_COREAUDIO
   7.308 +    AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   7.309 +    free_audio_device_list(&capture_devs);
   7.310 +    free_audio_device_list(&output_devs);
   7.311 +#endif
   7.312 +}
   7.313 +
   7.314  static int
   7.315  COREAUDIO_Init(SDL_AudioDriverImpl * impl)
   7.316  {
   7.317      /* Set the function pointers */
   7.318      impl->OpenDevice = COREAUDIO_OpenDevice;
   7.319      impl->CloseDevice = COREAUDIO_CloseDevice;
   7.320 +    impl->Deinitialize = COREAUDIO_Deinitialize;
   7.321  
   7.322  #if MACOSX_COREAUDIO
   7.323      impl->DetectDevices = COREAUDIO_DetectDevices;
   7.324 +    AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, device_list_changed, NULL);
   7.325  #else
   7.326      impl->OnlyHasDefaultOutputDevice = 1;
   7.327  
     8.1 --- a/src/audio/disk/SDL_diskaudio.c	Wed Mar 18 10:09:39 2015 -0400
     8.2 +++ b/src/audio/disk/SDL_diskaudio.c	Mon Mar 16 02:11:39 2015 -0400
     8.3 @@ -71,7 +71,7 @@
     8.4  
     8.5      /* If we couldn't write, assume fatal error for now */
     8.6      if (written != this->hidden->mixlen) {
     8.7 -        this->enabled = 0;
     8.8 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
     8.9      }
    8.10  #ifdef DEBUG_AUDIO
    8.11      fprintf(stderr, "Wrote %d bytes of audio data\n", written);
     9.1 --- a/src/audio/dsp/SDL_dspaudio.c	Wed Mar 18 10:09:39 2015 -0400
     9.2 +++ b/src/audio/dsp/SDL_dspaudio.c	Mon Mar 16 02:11:39 2015 -0400
     9.3 @@ -270,7 +270,7 @@
     9.4      const int mixlen = this->hidden->mixlen;
     9.5      if (write(this->hidden->audio_fd, mixbuf, mixlen) == -1) {
     9.6          perror("Audio write");
     9.7 -        this->enabled = 0;
     9.8 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
     9.9      }
    9.10  #ifdef DEBUG_AUDIO
    9.11      fprintf(stderr, "Wrote %d bytes of audio data\n", mixlen);
    10.1 --- a/src/audio/esd/SDL_esdaudio.c	Wed Mar 18 10:09:39 2015 -0400
    10.2 +++ b/src/audio/esd/SDL_esdaudio.c	Mon Mar 16 02:11:39 2015 -0400
    10.3 @@ -129,7 +129,7 @@
    10.4          /* Check every 10 loops */
    10.5          if (this->hidden->parent && (((++cnt) % 10) == 0)) {
    10.6              if (kill(this->hidden->parent, 0) < 0 && errno == ESRCH) {
    10.7 -                this->enabled = 0;
    10.8 +                SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    10.9              }
   10.10          }
   10.11      }
   10.12 @@ -161,7 +161,7 @@
   10.13  
   10.14      /* If we couldn't write, assume fatal error for now */
   10.15      if (written < 0) {
   10.16 -        this->enabled = 0;
   10.17 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
   10.18      }
   10.19  }
   10.20  
    11.1 --- a/src/audio/fusionsound/SDL_fsaudio.c	Wed Mar 18 10:09:39 2015 -0400
    11.2 +++ b/src/audio/fusionsound/SDL_fsaudio.c	Mon Mar 16 02:11:39 2015 -0400
    11.3 @@ -143,7 +143,7 @@
    11.4                                        this->hidden->mixsamples);
    11.5      /* If we couldn't write, assume fatal error for now */
    11.6      if (ret) {
    11.7 -        this->enabled = 0;
    11.8 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    11.9      }
   11.10  #ifdef DEBUG_AUDIO
   11.11      fprintf(stderr, "Wrote %d bytes of audio data\n", this->hidden->mixlen);
    12.1 --- a/src/audio/paudio/SDL_paudio.c	Wed Mar 18 10:09:39 2015 -0400
    12.2 +++ b/src/audio/paudio/SDL_paudio.c	Mon Mar 16 02:11:39 2015 -0400
    12.3 @@ -176,7 +176,7 @@
    12.4               * the user know what happened.
    12.5               */
    12.6              fprintf(stderr, "SDL: %s - %s\n", strerror(errno), message);
    12.7 -            this->enabled = 0;
    12.8 +            SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    12.9              /* Don't try to close - may hang */
   12.10              this->hidden->audio_fd = -1;
   12.11  #ifdef DEBUG_AUDIO
   12.12 @@ -212,7 +212,7 @@
   12.13  
   12.14      /* If we couldn't write, assume fatal error for now */
   12.15      if (written < 0) {
   12.16 -        this->enabled = 0;
   12.17 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
   12.18      }
   12.19  #ifdef DEBUG_AUDIO
   12.20      fprintf(stderr, "Wrote %d bytes of audio data\n", written);
    13.1 --- a/src/audio/pulseaudio/SDL_pulseaudio.c	Wed Mar 18 10:09:39 2015 -0400
    13.2 +++ b/src/audio/pulseaudio/SDL_pulseaudio.c	Mon Mar 16 02:11:39 2015 -0400
    13.3 @@ -302,7 +302,7 @@
    13.4          if (PULSEAUDIO_pa_context_get_state(h->context) != PA_CONTEXT_READY ||
    13.5              PULSEAUDIO_pa_stream_get_state(h->stream) != PA_STREAM_READY ||
    13.6              PULSEAUDIO_pa_mainloop_iterate(h->mainloop, 1, NULL) < 0) {
    13.7 -            this->enabled = 0;
    13.8 +            SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    13.9              return;
   13.10          }
   13.11          if (PULSEAUDIO_pa_stream_writable_size(h->stream) >= h->mixlen) {
   13.12 @@ -318,7 +318,7 @@
   13.13      struct SDL_PrivateAudioData *h = this->hidden;
   13.14      if (PULSEAUDIO_pa_stream_write(h->stream, h->mixbuf, h->mixlen, NULL, 0LL,
   13.15                                     PA_SEEK_RELATIVE) < 0) {
   13.16 -        this->enabled = 0;
   13.17 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
   13.18      }
   13.19  }
   13.20  
    14.1 --- a/src/audio/qsa/SDL_qsa_audio.c	Wed Mar 18 10:09:39 2015 -0400
    14.2 +++ b/src/audio/qsa/SDL_qsa_audio.c	Mon Mar 16 02:11:39 2015 -0400
    14.3 @@ -300,7 +300,7 @@
    14.4  
    14.5      /* If we couldn't write, assume fatal error for now */
    14.6      if (towrite != 0) {
    14.7 -        this->enabled = 0;
    14.8 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    14.9      }
   14.10  }
   14.11  
    15.1 --- a/src/audio/sndio/SDL_sndioaudio.c	Wed Mar 18 10:09:39 2015 -0400
    15.2 +++ b/src/audio/sndio/SDL_sndioaudio.c	Mon Mar 16 02:11:39 2015 -0400
    15.3 @@ -158,7 +158,7 @@
    15.4  
    15.5      /* If we couldn't write, assume fatal error for now */
    15.6      if ( written == 0 ) {
    15.7 -        this->enabled = 0;
    15.8 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    15.9      }
   15.10  #ifdef DEBUG_AUDIO
   15.11      fprintf(stderr, "Wrote %d bytes of audio data\n", written);
    16.1 --- a/src/audio/sun/SDL_sunaudio.c	Wed Mar 18 10:09:39 2015 -0400
    16.2 +++ b/src/audio/sun/SDL_sunaudio.c	Mon Mar 16 02:11:39 2015 -0400
    16.3 @@ -158,7 +158,7 @@
    16.4          if (write(this->hidden->audio_fd, this->hidden->ulaw_buf,
    16.5              this->hidden->fragsize) < 0) {
    16.6              /* Assume fatal error, for now */
    16.7 -            this->enabled = 0;
    16.8 +            SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    16.9          }
   16.10          this->hidden->written += this->hidden->fragsize;
   16.11      } else {
   16.12 @@ -168,7 +168,7 @@
   16.13          if (write(this->hidden->audio_fd, this->hidden->mixbuf,
   16.14              this->spec.size) < 0) {
   16.15              /* Assume fatal error, for now */
   16.16 -            this->enabled = 0;
   16.17 +            SDL_AudioDeviceDisconnected(SDL_FALSE, this);
   16.18          }
   16.19          this->hidden->written += this->hidden->fragsize;
   16.20      }
    17.1 --- a/src/audio/xaudio2/SDL_xaudio2.c	Wed Mar 18 10:09:39 2015 -0400
    17.2 +++ b/src/audio/xaudio2/SDL_xaudio2.c	Mon Mar 16 02:11:39 2015 -0400
    17.3 @@ -221,7 +221,7 @@
    17.4  
    17.5      if (result != S_OK) {  /* uhoh, panic! */
    17.6          IXAudio2SourceVoice_FlushSourceBuffers(source);
    17.7 -        this->enabled = 0;
    17.8 +        SDL_AudioDeviceDisconnected(SDL_FALSE, this);
    17.9      }
   17.10  }
   17.11  
    18.1 --- a/test/Makefile.in	Wed Mar 18 10:09:39 2015 -0400
    18.2 +++ b/test/Makefile.in	Mon Mar 16 02:11:39 2015 -0400
    18.3 @@ -38,6 +38,7 @@
    18.4  	testloadso$(EXE) \
    18.5  	testlock$(EXE) \
    18.6  	testmultiaudio$(EXE) \
    18.7 +	testaudiohotplug$(EXE) \
    18.8  	testnative$(EXE) \
    18.9  	testoverlay2$(EXE) \
   18.10  	testplatform$(EXE) \
   18.11 @@ -105,6 +106,9 @@
   18.12  testmultiaudio$(EXE): $(srcdir)/testmultiaudio.c
   18.13  	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
   18.14  
   18.15 +testaudiohotplug$(EXE): $(srcdir)/testaudiohotplug.c
   18.16 +	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
   18.17 +
   18.18  testatomic$(EXE): $(srcdir)/testatomic.c
   18.19  	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
   18.20  
    19.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.2 +++ b/test/testaudiohotplug.c	Mon Mar 16 02:11:39 2015 -0400
    19.3 @@ -0,0 +1,182 @@
    19.4 +/*
    19.5 +  Copyright (C) 1997-2014 Sam Lantinga <slouken@libsdl.org>
    19.6 +
    19.7 +  This software is provided 'as-is', without any express or implied
    19.8 +  warranty.  In no event will the authors be held liable for any damages
    19.9 +  arising from the use of this software.
   19.10 +
   19.11 +  Permission is granted to anyone to use this software for any purpose,
   19.12 +  including commercial applications, and to alter it and redistribute it
   19.13 +  freely.
   19.14 +*/
   19.15 +
   19.16 +/* Program to test hotplugging of audio devices */
   19.17 +
   19.18 +#include "SDL_config.h"
   19.19 +
   19.20 +#include <stdio.h>
   19.21 +#include <stdlib.h>
   19.22 +
   19.23 +#if HAVE_SIGNAL_H
   19.24 +#include <signal.h>
   19.25 +#endif
   19.26 +
   19.27 +#ifdef __EMSCRIPTEN__
   19.28 +#include <emscripten/emscripten.h>
   19.29 +#endif
   19.30 +
   19.31 +#include "SDL.h"
   19.32 +
   19.33 +static SDL_AudioSpec spec;
   19.34 +static Uint8 *sound = NULL;     /* Pointer to wave data */
   19.35 +static Uint32 soundlen = 0;     /* Length of wave data */
   19.36 +
   19.37 +static int posindex = 0;
   19.38 +static Uint32 positions[64];
   19.39 +
   19.40 +/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
   19.41 +static void
   19.42 +quit(int rc)
   19.43 +{
   19.44 +    SDL_Quit();
   19.45 +    exit(rc);
   19.46 +}
   19.47 +
   19.48 +void SDLCALL
   19.49 +fillerup(void *_pos, Uint8 * stream, int len)
   19.50 +{
   19.51 +    Uint32 pos = *((Uint32 *) _pos);
   19.52 +    Uint8 *waveptr;
   19.53 +    int waveleft;
   19.54 +
   19.55 +    /* Set up the pointers */
   19.56 +    waveptr = sound + pos;
   19.57 +    waveleft = soundlen - pos;
   19.58 +
   19.59 +    /* Go! */
   19.60 +    while (waveleft <= len) {
   19.61 +        SDL_memcpy(stream, waveptr, waveleft);
   19.62 +        stream += waveleft;
   19.63 +        len -= waveleft;
   19.64 +        waveptr = sound;
   19.65 +        waveleft = soundlen;
   19.66 +        pos = 0;
   19.67 +    }
   19.68 +    SDL_memcpy(stream, waveptr, len);
   19.69 +    pos += len;
   19.70 +    *((Uint32 *) _pos) = pos;
   19.71 +}
   19.72 +
   19.73 +static int done = 0;
   19.74 +void
   19.75 +poked(int sig)
   19.76 +{
   19.77 +    done = 1;
   19.78 +}
   19.79 +
   19.80 +static void
   19.81 +iteration()
   19.82 +{
   19.83 +    SDL_Event e;
   19.84 +    SDL_AudioDeviceID dev;
   19.85 +    while (SDL_PollEvent(&e)) {
   19.86 +        if (e.type == SDL_QUIT) {
   19.87 +            done = 1;
   19.88 +        } else if (e.type == SDL_AUDIODEVICEADDED) {
   19.89 +            const char *name = SDL_GetAudioDeviceName(e.adevice.which, 0);
   19.90 +            SDL_Log("New %s audio device: %s\n", e.adevice.iscapture ? "capture" : "output", name);
   19.91 +            if (!e.adevice.iscapture) {
   19.92 +                positions[posindex] = 0;
   19.93 +                spec.userdata = &positions[posindex++];
   19.94 +                spec.callback = fillerup;
   19.95 +                dev = SDL_OpenAudioDevice(name, 0, &spec, NULL, 0);
   19.96 +                if (!dev) {
   19.97 +                    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open '%s': %s\n", name, SDL_GetError());
   19.98 +                } else {
   19.99 +                    SDL_Log("Opened '%s' as %u\n", name, (unsigned int) dev);
  19.100 +                    SDL_PauseAudioDevice(dev, 0);
  19.101 +                }
  19.102 +            }
  19.103 +        } else if (e.type == SDL_AUDIODEVICEREMOVED) {
  19.104 +            dev = (SDL_AudioDeviceID) e.adevice.which;
  19.105 +            SDL_Log("%s device %u removed.\n", e.adevice.iscapture ? "capture" : "output", (unsigned int) dev);
  19.106 +            SDL_CloseAudioDevice(dev);
  19.107 +        }
  19.108 +    }
  19.109 +}
  19.110 +
  19.111 +#ifdef __EMSCRIPTEN__
  19.112 +void
  19.113 +loop()
  19.114 +{
  19.115 +    if(done)
  19.116 +        emscripten_cancel_main_loop();
  19.117 +    else
  19.118 +        iteration();
  19.119 +}
  19.120 +#endif
  19.121 +
  19.122 +int
  19.123 +main(int argc, char *argv[])
  19.124 +{
  19.125 +    int i;
  19.126 +    char filename[4096];
  19.127 +
  19.128 +	/* Enable standard application logging */
  19.129 +	SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
  19.130 +
  19.131 +    /* Load the SDL library */
  19.132 +    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
  19.133 +        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s\n", SDL_GetError());
  19.134 +        return (1);
  19.135 +    }
  19.136 +
  19.137 +    SDL_CreateWindow("testaudiohotplug", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
  19.138 +
  19.139 +    if (argc > 1) {
  19.140 +        SDL_strlcpy(filename, argv[1], sizeof(filename));
  19.141 +    } else {
  19.142 +        SDL_strlcpy(filename, "sample.wav", sizeof(filename));
  19.143 +    }
  19.144 +    /* Load the wave file into memory */
  19.145 +    if (SDL_LoadWAV(filename, &spec, &sound, &soundlen) == NULL) {
  19.146 +        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't load %s: %s\n", filename, SDL_GetError());
  19.147 +        quit(1);
  19.148 +    }
  19.149 +
  19.150 +#if HAVE_SIGNAL_H
  19.151 +    /* Set the signals */
  19.152 +#ifdef SIGHUP
  19.153 +    signal(SIGHUP, poked);
  19.154 +#endif
  19.155 +    signal(SIGINT, poked);
  19.156 +#ifdef SIGQUIT
  19.157 +    signal(SIGQUIT, poked);
  19.158 +#endif
  19.159 +    signal(SIGTERM, poked);
  19.160 +#endif /* HAVE_SIGNAL_H */
  19.161 +
  19.162 +    /* Show the list of available drivers */
  19.163 +    SDL_Log("Available audio drivers:");
  19.164 +    for (i = 0; i < SDL_GetNumAudioDrivers(); ++i) {
  19.165 +		SDL_Log("%i: %s", i, SDL_GetAudioDriver(i));
  19.166 +    }
  19.167 +
  19.168 +    SDL_Log("Using audio driver: %s\n", SDL_GetCurrentAudioDriver());
  19.169 +
  19.170 +#ifdef __EMSCRIPTEN__
  19.171 +    emscripten_set_main_loop(loop, 0, 1);
  19.172 +#else
  19.173 +    while (!done) {
  19.174 +        SDL_Delay(100);
  19.175 +        iteration();
  19.176 +    }
  19.177 +#endif
  19.178 +
  19.179 +    /* Clean up on signal */
  19.180 +    SDL_Quit();
  19.181 +    SDL_FreeWAV(sound);
  19.182 +    return (0);
  19.183 +}
  19.184 +
  19.185 +/* vi: set ts=4 sw=4 expandtab: */