SDL: coreaudio: Workaround for crash when disconnecting a bluetooth audio device. (f7197)

From f71979dcb3a2988b8f40158bf89512ce1d660b76 Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Sat, 4 Jan 2025 00:27:33 -0500
Subject: [PATCH] coreaudio: Workaround for crash when disconnecting a
 bluetooth audio device.

Our understanding of what's going on here might be incorrect, but it seems
like we're getting this callback at a point where we shouldn't be able to,
with a device we've already closed.

If we're on the wrong track, this code should still be harmless; it just
verifies a device is still in the open list before dereferencing it.

Reference Issue #10432.

(cherry picked from commit 20574c016a82aa2710e3d2d79eebc293b3a36112)
---
 src/audio/coreaudio/SDL_coreaudio.m | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/audio/coreaudio/SDL_coreaudio.m b/src/audio/coreaudio/SDL_coreaudio.m
index 0cdc3ec2a6000..ff982b66b929c 100644
--- a/src/audio/coreaudio/SDL_coreaudio.m
+++ b/src/audio/coreaudio/SDL_coreaudio.m
@@ -673,7 +673,21 @@ static OSStatus default_device_changed(AudioObjectID inObjectID, UInt32 inNumber
 #if DEBUG_COREAUDIO
     printf("COREAUDIO: default device changed for SDL audio device %p!\n", this);
 #endif
-    SDL_AtomicSet(&this->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */
+
+    /* due to a bug (?) in CoreAudio, this seems able to fire for a device pointer that's already closed, so check our list to make sure
+       the pointer is still valid before touching it. https://github.com/libsdl-org/SDL/issues/10432 */
+    if (open_devices) {
+        int i;
+        for (i = 0; i < num_open_devices; i++) {
+            SDL_AudioDevice *device = open_devices[i];
+            if (device == this) {
+                if (this->hidden) {
+                    SDL_AtomicSet(&this->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */
+                }
+                return noErr;
+            }
+        }
+    }
     return noErr;
 }
 #endif