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.
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.
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 patch reverts the previous reversion, and then adds code to queue up
events to be sent the next time SDL pumps the event queue. This guarantees
that the event watcher/filter _never_ runs from an SDL audio device thread
or some other backend-specific internal thread.
This reverts commit 76f81797b7.
This worked in the normal cases, but:
A device thread that calls SDL_DisconnectAudioDevice due to failure will fire
the disconnect event from the device thread...and if there's an event watcher
that uses that moment to close the device, we still end up in the same
situation, where the device thread tries to join on itself.
Better solutions are still pending.
Otherwise, they risk the device thread joining on itself.
Now we make sure the reference is held at the logical device level until
the physical device is closed, so it can't destroy the device in normal
usage until the thread is joined, etc.
First stage happens before we destroy objects, and is generally used to
shut down hotplug. The second stage is the usual deinit, which cleans up
the lowlevel API, unloads shared libraries, etc.
- No more tapdance to either join the audio device thread or have it detach
itself. Significant simplication of and fixes to the locking code to prevent
deadlocks.
- Physical devices now keep a refcount. Each logical device increments it,
as does the existence of a device thread, etc. Last unref destroys the
device and takes it out of the device_hash. Since there's a lot of moving
parts that might be holding a reference to a physical device, this seemed
like a safer way to protect the object.
- Disconnected devices now continue to function as zombie devices. Playback
devices will still consume data (and just throw it away), and capture devices
will continue to produce data (which always be silence). This helps apps
that don't handle disconnect events; the device still stops playing/capturing,
but bound audio streams will still consume data so they don't allocate more
data infinitely, and apps that depend on an audio callback firing regularly
to make progress won't hang.
Please note that disconnected audio devices must now be explicitly closed!
They always _should_ have been, but before this commit, SDL3 would destroy the
disconnected device for you (and manually closing afterwards was a safe no-op).
Reference Issue #8331.
Fixes#8386.
(and probably others).
All devices are in a single hash, whether playback or capture, or physical
or logical. Lookups are keyed on device ID and map to either
`SDL_AudioDevice *` for physical devices or `SDL_LogicalAudioDevice *` for
logical devices (as an implementation detail, you can determine which object
type you have by checking a specific bit in the device ID).
This simplifies a bunch of code, makes some cases significantly more
efficient, and solves the problem of having to lock each physical
device while the device list rwlock is held to find logical devices by ID.
Device IDs hash perfectly evenly, too, being incrementing integers.
This is an attempt to centralize all the error handling, instead of
implicitly counting on WaitDevice implementations to disconnect the device
to report an error.