Repository navigation
Audio: fix the Linux ALSA backends and their settings - #2363
Merged
Merged
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Selecting a driver initializes it, which changes the model while the previous driver's panel is still alive: the miniaudio panel, not finding the new device name in its list, reset its combo box and queued "No device" as the output, committed with OK. Switching from miniaudio to ALSA (raw) gave an engine without output, silently. The previous panel is removed before the driver changes, and the device, channel, rate and buffer size values it queued are dropped (SettingsCommandDispatcher::discardDeferred). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The device list came from snd_device_name_hint, which has no hw: entry at all: sysdefault, surround*, iec958, usbstream and the plugin PCMs. Rates and buffer sizes were the standard values within min..max of an unconstrained configuration; with zita, surround21/41/50/51 opened with none of the 77 combinations offered, sysdefault with 21 of 49. The fallback for an unknown device was sysdefault with at least one input: dmix and dsnoop in duplex, the configuration that ran at a third of the speed. - cards are enumerated (snd_card_next, ctl) as hw:CARD=<id>,DEV=<n>, stable when USB devices enumerate in another order, with the card's name; plughw: and "default" follow as "with conversion" - each direction is probed as the engine opens it (mmap access, a format zita handles) and lists the rates and buffer sizes it accepts at each rate - separate output and input devices, capture-only devices included; channel counts within the device's range; different cards are pointed out - a busy device stays listed, "(in use)", with what it offered when free - names from another backend are replaced by the first hardware card, without inputs; an ALSA name that is not plugged in is kept - the scan happens when the driver is used, not at startup Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the ALSA factory asks for exclusive access: "hw:" with a period of the buffer size instead of dmix's 1024 x 3, which also dropped out 11 to 16 times a second in duplex; the engine falls back to shared when busy - ids ":<card index>,<dev>" are stored as ":CARD=<card id>,DEV=<dev>", which miniaudio still prefixes with hw/dmix/dsnoop; saved ":3,0" ids are converted when read - a saved device that is not listed is asked for by name: a zero id means the system default device, which played instead of the unplugged one - devices are scanned on first use instead of in the constructor (about 1 s, and it probes the jack and pulse plugins) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PortAudio's ALSA names end with the card index, "MM-1: USB Audio (hw:3,0)". When it changes, the saved device was not found and initialize() replaced it with the default device. The exact name is tried first, then the same name without the index. Pa_Initialize (about 370 ms) now runs when the driver is used, not at every startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the exception text reaches the warning: "ALSA: cannot open hw:CARD=MM1,DEV=0 for playback: Device or resource busy" instead of "The desired audio settings could not be applied" - restart_engine() destroys the stopped engine before opening the new one: a device can only be opened once - an engine whose stream died (audio_engine::failed(): unplugged device, continuous under-runs) is stopped and reported Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A label in the audio toolbar shows the xruns the engine counted since it started, hidden while there are none; its tooltip has the period and the latencies. Nothing showed them: a glitching configuration was only audible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cept The panel had one device for both directions, with every channel it has (8 in and 8 out on an Axe-Fx), and the generic rate list, which PortAudio's ALSA host refuses when the hardware lacks the rate. Output and input are chosen separately with their channel counts; the rates are the ones Pa_IsFormatSupported accepts for that choice. A device saved for a direction it does not have (from the single-device panel) reads as none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
failed() is also reported for a device that under-runs on every period, e.g. a full-speed USB device asked for a period shorter than its packets when four periods were not enough. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A "Lock memory" setting, on by default, keeps score's memory resident (ossia::lock_process_memory) from startup, and unlocks it when turned off. It needs an unlimited memlock limit; with a finite one score runs as before and logs why memory is not locked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…leaks) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With "Lock memory" on, mlockall(MCL_FUTURE) locks every later mapping, files included: each page of a sound file, waveform cache or mapped file port read so far stayed in RAM, and a project with gigabytes of audio could not have its page cache reclaimed. The long-lived file mappings are unlocked right after mapping (ossia::unlock_memory). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- an engine whose stream died gets no more callbacks, so it never set stop_received and stayed in previous_audio until exit: it is destroyed right after stop(), which closed its stream and joined its thread - the panel's rate or buffer size going back to the current value now drops the value queued before: it was applied on accept while the panel showed the current one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the panel keeps PortAudio initialized for its lifetime (released around Rescan, which has to enumerate again) and caches the rates per devices and channel counts: each spin box step re-enumerated every ALSA device and opened the device eight times - a device found under another card index is stored under its current name, so its rates are listed and the setting follows it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- what a PCM offered when free is remembered per direction: a card whose capture is always held never cached its playback - no rate or buffer size of 0 is queued when the lists are empty (SCORE_DISABLE_ALSA) - the pcm/card info buffers are allocated once per scan, not per card Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pa_IsFormatSupported opens the device, retrying a busy one for a second (pa_linux_alsa.c, busyRetries_ = 100 x 10 ms): with the device in use, by score's own engine for instance, each panel update froze for eight seconds. Busy retries are off while probing, and the first busy answer ends it. Rescan enumerates once instead of twice; PortAudio keeps its device list while a PortAudio engine runs, which the comment now says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
snd_pcm_hw_params_test_* each check one parameter with all the others free: a buffer of 4 x 128 frames passed with a period of 512. The period is now set on a copy before testing the buffer. The channel count is set to the nearest of 2 first, as period sizes can depend on the frame size (HDA periods are whole multiples of 128 bytes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…priority Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous commit unlocked the mapping of WAV files, but the audio thread reads the samples straight from it: an evicted page meant a disk read in the audio callback, and WAV became the only format that could be evicted (the decoded ones are heap memory, locked like the rest). The mapping stays locked, and a background thread reads the whole file in when it is loaded, as a sampler loads its samples: playback never waits for the disk, and with "Lock memory" on the samples then stay in RAM. Mapped avnd file ports stay locked too, as their non-mapped path is heap memory. Only the waveform cache, which only the GUI reads, is unlocked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes for the Linux audio backends: ALSA (raw), ALSA (PortAudio), ALSA (MiniAudio). Depends on ossia/libossia#966, which this branch's submodule points to.
Reported symptoms, all reproduced on an Axe-Fx III, B&W MM-1, Logitech BRIO and an HDA codec:
Root causes
ALSAFactory::initialize()fell back tosysdefaultwith at least one input. That is dmix + dsnoop in duplex, which ran at 33 % speed (fixed in libossia, and no longer the fallback).surround*PCMs opened with none of the 77 combinations offered.snd_device_name_hintlists nohw:device at all.initialize()and queued "No device" as the output, which was committed on OK.Changes
hw:CARD=<id>,DEV=<n>(stable across USB re-enumeration), plusplughw:anddefault"with conversion".hw:, period = buffer size). Ids stored as:CARD=<id>,DEV=<n>, with old:N,Mids converted. A missing device is asked for by name instead of silently using the default device.Pa_IsFormatSupported, no busy retries while probing, and devices matched across card renumbering.SettingsCommandDispatcher::discardDeferred).mlockallwithMCL_ONFAULT, only with an unlimited memlock limit). Sound files the audio thread reads from a mapping (WAV) stay locked and are read in by a background thread when loaded, as a sampler loads its samples, so playback never waits for the disk. Only the GUI's waveform cache is unlocked.Verification
Known open
Input and output on two different cards (e.g. BRIO in, Axe-Fx out) drift with no compensation. Small buffers can fail there (the engine now reports it), and long sessions will slip every few minutes. A playback-driven loop with a resampled capture FIFO would fix it; that is not in this PR.
🤖 Generated with Claude Code