Failed to open audio recording device [name]. SDL Error: CoreAudio error (AudioQueueStart): -66681 [MacOS]
When using our app, if an audio input or output device is unplugged, I can switch to another available device on all platforms (Windows, MacOS, Linux). However, after plugging the device back in, the app can only switch back to it on Windows.
On MacOS, I encounter the following error: "Failed to open audio recording device [name]. SDL Error: CoreAudio error (AudioQueueStart): -66681".
This indicates that the unplugged device is still in use or blocked by SDL2 or MacOS. I have tried the following code in the "InitRecordingDevice" method after calling "CloseAudio()", but it still does not work:
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_InitSubSystem(SDL_INIT_AUDIO);
I have also added "SDL_ClearQueuedAudio(dev)" in the "CloseAudio" method before calling "CloseAudioRecordingDevice", but this has not resolved the issue either.
Are there any solutions for this problem?
Are there any solutions for this problem?
This sounds like a bug in SDL; I'll try to reproduce it here tomorrow morning.
This reproduces for me with testaudiohotplug on both SDL2 and SDL3 (just unplug USB headphones and plug them back in while the test program is running). Digging deeper.
Looks like (in SDL3, at least) device->currently_opened is zero when DeviceAliveNotification is fired...so when we tell SDL that the device is disconnected, it doesn't try to close it, so all sorts of stuff is still dangling.
Not sure why yet, looking into it still.
Wait, no, that was the recording half of the USB headset. But I'm also noticing the testaudiohotplug doesn't close the device at all, so I bet that's what's happening.
Nope, that didn't fix it.
I'm starting to believe this is a macOS bug; the internet (since Mac OS X 10.5, apparently) has been asking "why does this error pop up some times?" and no one seems to know.
Notably, it seems to hang all the other playing devices too, for several seconds, when replugging the USB headset. If I start the device as unplugged and plug it in after everything else, it picks it up fine and works (but then, an unplug/replug after will still cause failure).
I added a 5 second delay to see if it just needed a little time to get itself together on hotplug; didn't fix it.
Feels like the CoreAudio framework is holding some state from the previous device connection that confuses it on replugging. I could be wrong. I'll ask our Apple contact.
Okay, I've moved the SDL coreaudio code out to a standalone program that still reproduces the issue, which is to say this could still be our bug, since it's still our code, but now I can hand this to Apple as a ~500 line Objective-C program without any external dependencies, including SDL itself:
https://gist.github.com/icculus/b021a110eb1bcdfa72f169a620e2d46e
Stay tuned!
Any Update
I never got a reply from Apple. :(
I filed this with Apple, outside of the usual contact.
rdar://15576779
Moving to 3.x, but if Apple moves on the bug report, we can revisit.
Hi, I am also seeing a crash that I believe is related to this. If I try to re-open the default device after seeing the first -66681 error message, I get this crash with the following trace:
frame #0: 0x0000000101385430 tlplay`SDL_AtomicSet_REAL(a=0x000000000000006c, v=1) at SDL_atomic.c:194:12
frame #1: 0x000000010148d330 tlplay`default_device_changed(inObjectID=1, inNumberAddresses=1, inAddresses=0x000060000000ded0, inUserData=0x000060000332d9a0) at SDL_coreaudio.m:675:5
frame #2: 0x000000018fd17a04 CoreAudio`HALObject::PropertiesChanged(unsigned int, AudioObjectPropertyAddress const*) + 1564
frame #3: 0x000000018fb9c228 CoreAudio`HALSystem::PropertiesChanged(unsigned int, AudioObjectPropertyAddress const*) + 412
frame #4: 0x000000018fc6bce8 CoreAudio`HALC_ShellPlugIn::ProxyObject_PropertiesChanged(unsigned int, unsigned int, AudioObjectPropertyAddress const*) + 1060
frame #5: 0x000000018fcc31cc CoreAudio`HALC_ProxyNotifications::CallListener_f(void*) + 92
frame #6: 0x000000018d81c400 libdispatch.dylib`_dispatch_client_callout + 20
frame #7: 0x000000018d823a88 libdispatch.dylib`_dispatch_lane_serial_drain + 668
frame #8: 0x000000018d82462c libdispatch.dylib`_dispatch_lane_invoke + 436
frame #9: 0x000000018d8258e8 libdispatch.dylib`_dispatch_workloop_invoke + 1764
frame #10: 0x000000018d82f244 libdispatch.dylib`_dispatch_workloop_worker_thread + 648
frame #11: 0x000000018d9c8074 libsystem_pthread.dylib`_pthread_wqthread + 288
In default_device_changed() it looks like this->hidden is null. If I change the code to add a check for null the crash seems to be fixed, does this seem like a reasonable change?
if (this->hidden)
{
SDL_AtomicSet(&this->hidden->device_change_flag, 1); /* let the audioqueue thread pick up on this when safe to do so. */
}
This is on macOS 13.7 with SDL2 2.30.9. I am triggering the error by turning on/off a bluetooth headset.
I am also seeing these errors in lldb when turning on/off the bluetooth headset:
2024-11-04 14:15:58.569647-0800 tlplay[29868:35257908] HALC_ProxyObjectMap.cpp:153 HALC_ProxyObjectMap::_CopyObjectByObjectID: failed to create the local object
2024-11-04 14:15:58.569670-0800 tlplay[29868:35257908] HALC_ShellDevice.cpp:2606 HALC_ShellDevice::RebuildControlList: couldn't find the control object
2024-11-04 14:15:58.632811-0800 tlplay[29868:35258610] AudioHardware-mac-imp.cpp:660 AudioObjectGetPropertyData: no object with given ID 0
2024-11-04 14:15:58.772863-0800 tlplay[29868:35258812] [aqsrv] AQServer.cpp:80 Exception caught in AudioQueueInternalNotifyRunning - error -66671
2024-11-04 14:15:58.796104-0800 tlplay[29868:35258812] [aqsrv] AQServer.cpp:80 Exception caught in AudioQueueInternalNotifyRunning - error -66671
2024-11-04 14:15:59.016756-0800 tlplay[29868:35258812] [aqsrv] AQServer.cpp:80 Exception caught in AudioQueueInternalStop_Sync - error -66671
2024-11-04 14:15:59.039934-0800 tlplay[29868:35257955] [aqsrv] AQServer.cpp:80 Exception caught in AudioQueueInternalStop_Sync - error -66671
In default_device_changed() it looks like this->hidden is null. If I change the code to add a check for null the crash seems to be fixed, does this seem like a reasonable change?
Sorry for the delay in response, I just saw this comment.
Looking at the latest SDL2 code, it seems like it shouldn't be able to hit this with this->hidden == NULL, unless we've already closed the device (and either macOS's free() implementation zero'd out this's memory block, or the memory block was reallocated to something else already that happens to be NULL), since this->hidden is allocated at the start of COREAUDIO_OpenDevice and doesn't change or get free'd until COREAUDIO_CloseDevice.
I think it's more likely we have a weird condition that the default_device_changed callback fires unexpectedly after we've closed the SDL audio device.
If I try to re-open the default device after seeing the first -66681 error message,
Actually, my guess is that once you see this, everything has gone haywire at the CoreAudio layer, because something hangs up in there for a long long time, so we're probably getting this callback long after we thought we cleaned everything up.
I could be wrong, but we really need a fix from Apple for the original problem, because it's probably also causing this crash secondhand.
Thanks for the response; I created a new issue with a small test program to reproduce the crash: https://github.com/libsdl-org/SDL/issues/11836
For now I've reverted to version 2.0.12 which seem to be working OK. Is there any more information/testing I can provide that would help with the Apple ticket?
I added some code to SDL2 to make sure that the device pointer is still valid before accessing it, which hopefully will resolve the crash you're seeing. It won't help with CoreAudio getting confused by the missing bluetooth device, though.
As for the apple bug report, it's still sitting there open, so I don't know what to say about it; I wrote them a small standalone reproduction case, so I can't think of anything more to be added to the report. Hopefully they'll do something with it eventually.
Do we know why 2.0.12 is working okay?
We didn't track default device changes at all in 2.0.12, so the crashing code didn't exist yet. I assume the bluetooth disconnect hang still happens, though.
The bluetooth connect/disconnect seems to be working fine in 2.0.12, I have been testing it quite a bit. I'm tracking the default device by polling for device changes and closing and re-opening the device.