This catches the case where we obtain a logical device while the default is
changing in another thread, so you accidentally end up with the previous
default physical device locked and returned from ObtainLogicalAudioDevice.
When no source devices are connected, the default source string can contain a sink name. If the default source and sink match, it will be caught as a sink device first and handled correctly, but if the default sink/source don't match, which happens when the sink is an HDMI output and the source is still an onboard audio chipset output name, an assert can result since the requested source device won't be flagged as a capture device. Rather than asserting, simply don't assign default devices that don't match the correct capabilities, as it's not an uncommon scenario and can be handled gracefully.
Additionally, if asserting is a no-op in release mode, sinks can be returned as sources and vice versa, which is incorrect.
This updates GetAudioDevices() to have the same behavior as SDL_GetJoysticks() where the return value will only be NULL if there is an error. Returning no devices will return a valid array containing NULL.
This means the allocator's caller doesn't need to use SDL_OutOfMemory directly
if the allocation fails.
This applies to the usual allocators: SDL_malloc, SDL_calloc, SDL_realloc
(all of these regardless of if the app supplied a custom allocator or we're
using system malloc() or an internal copy of dlmalloc under the hood),
SDL_aligned_alloc, SDL_small_alloc, SDL_strdup, SDL_asprintf, SDL_wcsdup...
probably others. If it returns something you can pass to SDL_free, it should
work.
The caller might still need to use SDL_OutOfMemory if something that wasn't
SDL allocated the memory: operator new in C++ code, Objective-C's alloc
message, win32 GlobalAlloc, etc.
Fixes#8642.
This specifically deals with two threads closing the same device at the same
time, and a thread trying to reopen the device as it's still in process of
being closed. This can happen in normal usage if a device is disconnected:
the OS might send a disconnect event, while the device thread also attempts
to manage a disconnect as system calls start to report failure.
This effort is necessary because we have to release the device lock during
close to allow the device thread to unblock and cleanly shutdown. But the
good news is that all the places that call ClosePhysicalAudioDevice can now
safely hold the device lock on entry, and that one function will manage the
lock tapdancing.
Otherwise, a disconnect/default change on another thread may cause the
device pointer to become invalid by the time the management thread runs the
task.
It just proxies all its necessary releases and frees to these without
blocking, and sets the appropriate fields to NULL so they can be used again
immediately, regardless of when the old stuff actually gets released.
ALSA is used very rarely anymore and the pipewire ALSA emulation isn't as good as using pipewire directly. The Pulseaudio emulation is very good, and Pulseaudio is still commonly available on Linux systems, so we'll default to that first and fall back to pipewire if it's not available. We'll finally try ALSA, to handle very old systems.
Fixes https://github.com/libsdl-org/SDL/issues/7541
This cleans up a ton of race conditions, and starts moving towards something
we can use with Clang's -Wthread-safety (but that has a ways to go still).
This can happen if you close the stream's underlying device directly, which
removes the binding but doesn't destroy the object.
In this case, the stream remains valid until destroyed, but still should not
be able to be bound to a new device.
This does a ton of work that can deadlock, because several crucial WASAPI
things that we want to do in response to this will block until the
notification callback has returned, so we can't call them from the handler
directly, or we'll be waiting until the thing that called us returns.
Almost nothing checks these return values, and there's no reason a valid
lock should fail to operate. The cases where a lock isn't valid (it's a
bogus pointer, it was previously destroyed, a thread is unlocking a lock it
doesn't own, etc) are undefined behavior and always were, and should be
treated as an application bug.
Reference Issue #8096.
ALSA expects handles to be of type ALSA_Device, and passing the handle for the default device as a plain string causes a crash as it attempts to deference the string contents itself as a pointer to a string.
Create immutable static ALSA_Device structs for the default devices and pass those as the handles. They are not placed in the hotplug list, and the audio layer doesn't attempt to free ALSA handles, so there is no need to worry about them being erroneously freed.
(cherry picked from commit 62266dbd4fa79090025317a71473eb764b2e1abe)
(SDL3 audio backends don't have the LockDevice interfaces, so this just
ended up being a comment.)
Both strings are _right there_ for comparing, so we can just set a flag to
note the device definitely changed.
Also simplified string management further; hotplug thread now makes a copy
of the string before releasing the lock if there was a change event, so when
the lock releases further events don't see a NULL and assume it's a new
device, causing a lot of work to ripple out and decide nothing has changed,
until the system stabilizes again. Now, it just does the right thing once.
We don't match dev->name by string, since we might use the same string for both capture and output devices. Instead use the device pointer itself as the handle.
@icculus, are we guaranteed the device pointer is valid in ALSA_OpenDevice()?