Browse Source

audio: Destroy the logical audio device before sending DEVICE_REMOVED event.

This prevents catastrophe if someone tries to close the device in an event
filter in response to the event.

Note that this means SDL_GetAudioStreamDevice() for any stream on this
device will return 0 during the event filter!

Fixes #8331.
Ryan C. Gordon 1 year ago
parent
commit
f1fc198278
2 changed files with 23 additions and 14 deletions
  1. 19 12
      src/audio/SDL_audio.c
  2. 4 2
      src/audio/SDL_audiocvt.c

+ 19 - 12
src/audio/SDL_audio.c

@@ -398,8 +398,8 @@ SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name,
         // Post the event, if desired
         if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_ADDED)) {
             SDL_Event event;
+            SDL_zero(event);
             event.type = SDL_EVENT_AUDIO_DEVICE_ADDED;
-            event.common.timestamp = 0;
             event.adevice.which = device->instance_id;
             event.adevice.iscapture = iscapture;
             SDL_PushEvent(&event);
@@ -412,17 +412,20 @@ SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name,
 // this _also_ destroys the logical device!
 static void DisconnectLogicalAudioDevice(SDL_LogicalAudioDevice *logdev)
 {
+    SDL_assert(logdev != NULL);  // currently, this is always true.
+
+    const SDL_AudioDeviceID instance_id = logdev->instance_id;
+    const SDL_bool iscapture = logdev->physical_device->iscapture;
+    DestroyLogicalAudioDevice(logdev);
+
     if (SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) {
         SDL_Event event;
         SDL_zero(event);
         event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED;
-        event.common.timestamp = 0;
-        event.adevice.which = logdev->instance_id;
-        event.adevice.iscapture = logdev->physical_device->iscapture ? 1 : 0;
+        event.adevice.which = instance_id;
+        event.adevice.iscapture = iscapture ? 1 : 0;
         SDL_PushEvent(&event);
     }
-
-    DestroyLogicalAudioDevice(logdev);
 }
 
 // Called when a device is removed from the system, or it fails unexpectedly, from any thread, possibly even the audio device's thread.
@@ -495,22 +498,26 @@ void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device)
 
     // if there's an audio thread, don't free until thread is terminating, otherwise free stuff now.
     const SDL_bool should_destroy = SDL_AtomicGet(&device->thread_alive) ? SDL_FALSE : SDL_TRUE;
+
+    const SDL_AudioDeviceID instance_id = device->instance_id;
+    const SDL_bool iscapture = device->iscapture;
+
     SDL_UnlockMutex(device->lock);
 
+    if (should_destroy) {
+        DestroyPhysicalAudioDevice(device);
+    }
+
     // Post the event, if we haven't tried to before and if it's desired
     if (was_live && SDL_EventEnabled(SDL_EVENT_AUDIO_DEVICE_REMOVED)) {
         SDL_Event event;
         SDL_zero(event);
         event.type = SDL_EVENT_AUDIO_DEVICE_REMOVED;
         event.common.timestamp = 0;
-        event.adevice.which = device->instance_id;
-        event.adevice.iscapture = device->iscapture ? 1 : 0;
+        event.adevice.which = instance_id;
+        event.adevice.iscapture = iscapture ? 1 : 0;
         SDL_PushEvent(&event);
     }
-
-    if (should_destroy) {
-        DestroyPhysicalAudioDevice(device);
-    }
 }
 
 

+ 4 - 2
src/audio/SDL_audiocvt.c

@@ -1167,8 +1167,10 @@ void SDL_DestroyAudioStream(SDL_AudioStream *stream)
 
     const SDL_bool simplified = stream->simplified;
     if (simplified) {
-        SDL_assert(stream->bound_device->simplified);
-        SDL_CloseAudioDevice(stream->bound_device->instance_id);  // this will unbind the stream.
+        if (stream->bound_device) {
+            SDL_assert(stream->bound_device->simplified);
+            SDL_CloseAudioDevice(stream->bound_device->instance_id);  // this will unbind the stream.
+        }
     } else {
         SDL_UnbindAudioStream(stream);
     }