During playback, don't queue another buffer unless there are none in the queue.
During capture, there may be multiple buffers of audio available.
WASAPI_CaptureFromDevice only processes one buffer, and if we always wait on the next event, we will never catch up if it falls behind.
When running in a container, the underlying Pipewire version may not match the library version, so retrieve and check the core version info to see if it meets the preferred version requirements.
Checking for the pipewire-pulse service is unreliable when used in containers such as Flatpak, so simply use a minimum version check instead and prefer it over the Pulseaudio backend if at least version 1.0.0.
This allows using a much smaller (1.5 KB) lookup table, in exchange for a small amount of extra work per frame.
The extra work (a few extra loads/mul/adds) is negligible, and can execute in parallel.
The reduction in cache misses almost certainly outweighs any added cost.
The table is generated at runtime, and takes less than 0.02ms on my computer.
Compiler support for loading/storing multiple registers at once (i.e vld1q_f32_x4) seems very poor, so avoiding them for now.
Also switched to aligned stores with SSE. Although both SSE and NEON support unaligned stores, there is more likely to be a penalty to them, i.e when crossing a cache line. So might as align them.
Use DBus to query Systemd to check if the pipewire-pulse service is in the "running" state. If it is, then it is certain that Pipewire is being used instead of Pulseaudio as the preferred system mixer.
If DBus support is not enabled or Systemd is not being used on the underlying system, this check will simply fail and the standard driver order will be tested.
- SDL_RWops is now an opaque struct.
- SDL_AllocRW is gone. If an app is creating a custom RWops, they pass the
function pointers to SDL_CreateRW(), which are stored internally.
- SDL_RWclose is gone, there is only SDL_DestroyRW(), which calls the
implementation's `->close` method before freeing other things.
- There is only one path to create and use RWops now, so we don't have to
worry about whether `->close` will call SDL_DestroyRW, or if this will
risk any Properties not being released, etc.
- SDL_RWFrom* still works as expected, for getting a RWops without having
to supply your own implementation. Objects from these functions are also
destroyed with SDL_DestroyRW.
- Lots of other cleanup and SDL3ization of the library code.
Separately checking the state of a file before operating on it may allow
an attacker to modify the file between the two operations. (CWE-367)
Fix by using fstat() instead of stat().
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.
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()?
This fixes problems where Pulse callbacks don't fire in the order we expect,
or fail to fire at all, and avoids extra round trips to the Pulse server to
lookup information we could have trivially obtained already.
The end result is we would occasionally miss default device changes, etc, and
this resolves that better.
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.
Fully committing to it...!
This left SDL_wave.* alone for now, since there's a ton of comments in there
and this code hasn't changed much from SDL2 so far. But as SDL2 ages out a
little more, I'll likely switch this over, too.
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.
The following objects now have properties that can be user modified:
* SDL_AudioStream
* SDL_Gamepad
* SDL_Joystick
* SDL_RWops
* SDL_Renderer
* SDL_Sensor
* SDL_Surface
* SDL_Texture
* SDL_Window
We need to do this early in the file, so that it will be taken into
account when deciding whether to define NEED_SCALAR_CONVERTER_FALLBACKS
and therefore provide a non-SIMD fallback.
Mitigates: https://github.com/libsdl-org/SDL/issues/8352
Signed-off-by: Simon McVittie <smcv@collabora.com>
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.
This should retry until GetCurrentPosition succeeds. Otherwise, we would be
going on to the next iteration too soon.
Also generally streamlined the code while I was in here.
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.
Android claims to work with multiple devices, but doesn't actually appear to
(at least, afaict), and it will report tons of devices that all just seem
to play to the current default output, so for now, turn this off and only
expose a default device.
And then, with that default output, attempt to recover on errors by throwing
away the current AAudioStream and building a new one.
This let me plug/unplug a set of headphones from the headphone jack and audio
would switch correctly to the new output.
Now we sleep the thread in WaitDevice until ALSA reawakens it because it
needs more data, and we feed it exactly as much as it can take at that
point.
Like the recent PulseAudio changes, this is both more efficient, reliable,
and consistent.
In practice, this seems to buffer a little upfront and then gives a pretty
consistent request flow after that of 1/4 of the requested buffer size without
variation, which is significantly better than the previous code that would
vary a little each frame.
Plus, as long as the device asks for _anything_, we won't block forever, and
if it asks for more than our expected buffer size, we'll run multiple times
to satisfy it, so this is likely more robust against dropouts in general, too.
This reverts commit 6fd0613ac8.
Turns out that the Steam Runtime is still on PulseAudio 1.1, and the only
thing we (currently) need a newer Pulse for is pa_threaded_mainloop_set_name,
so let's just go back to treating that symbol as optional.
We might need to force a higher version at some point, but it's not worth it
over this.
Otherwise, we get into situations where all bound streams need to change
their output formats when a device pauses...and it makes the fast case
slow: when pausing a single input, it needs to silence and then convert a
silent buffer, instead of just zeroing out the device buffer and being done.
Since these get proxied to a different thread, if we wait for that thread
to finish while holding the lock, and the management thread _also_ requests
the lock, we're screwed.
WaitDevice never holds the lock by design, so just mark devices as failed
and clean up or recover them in there.
The audio processing thread isn't scheduled in lock-step with the audio callback so sometimes the callback would consume the same data twice and sometimes the audio processing thread would write to the same buffer twice.
Also handle variable sizes in the audio callback so the Android audio system doesn't have to do additional buffering to match our buffer size requirements.
Saves locks and copies during audio thread iteration. We've added asserts
that can evaporate out in release mode to make sure everything stays in sync.
This fires if an opened device changes formats (which it can on Windows,
if the user changes this in the system control panel, and WASAPI can
report), or if a default device migrates to new hardware and the format
doesn't match.
This will fire for all logical devices on a physical device (and if it's
a format change and not a default device change, it'll fire for the
physical device too, but that's honestly not that useful and might change).
Fixes#8267.
("preconverted bytes" makes it sounds like we already converted them before
the call instead of "bytes that haven't yet hit the stage where we convert
them. Just dump the wording completely.)
Now it offers the total requested bytes in addition to the amount
immediately needed (and immediately needed might be zero if the stream
already has enough queued to satisfy the request.
You can see it in action in testaudio by mousing over a logical device; it
will show a visualizer for the current PCM (whatever is currently being
recorded on a capture device, or whatever is being mixed for output on
playback devices).
Fixes#8122.
This is adds complexity and fragility for small optimization wins.
The biggest win is the extremely common case of a single stream providing
the only output, so we'll check for that and skip silencing/mixing/converting.
Otherwise, just use a single mixer path.