Skip to content

Add AlsaSettings for opening an ALSA PCM by address - #644

Open
whatisyourname0 wants to merge 2 commits into
spatialaudio:masterfrom
whatisyourname0:feat/alsa-direct-device
Open

whatisyourname0 wants to merge 2 commits into
spatialaudio:masterfrom
whatisyourname0:feat/alsa-direct-device

Conversation

@whatisyourname0

@whatisyourname0 whatisyourname0 commented Oct 17, 2026 •

Copy link
Copy Markdown

Applications that already have an ALSA PCM address currently have to match it to a PortAudio device index or display name before opening a stream. This adds AlsaSettings(device_string=...) through the existing extra_settings interface, using PortAudio's PaAlsaStreamInfo.deviceString and paUseHostApiSpecificDeviceSpecification.

This implements the ALSA device-string item discussed in #4. Related PR #85 proposes a broader host-API interface; this change uses the existing platform-specific settings pattern and exposes only the PCM address. It does not resolve all of #4.

import sounddevice as sd

# ALSA's null PCM allows this example to run without an audio device.
with sd.RawInputStream(
    samplerate=48000,
    channels=2,
    dtype='int16',
    blocksize=1024,
    latency=4096 / 48000,
    extra_settings=sd.AlsaSettings(device_string='null'),
) as stream:
    data, overflowed = stream.read(1024)
    print(len(data), overflowed)
# Synthetic test result: 4096 False

For a hardware PCM or a named ALSA configuration, replace 'null' with the desired address. The settings object retains the C string, and the stream retains its effective settings. Existing device-index and name selection remain available.

API behavior

  • With AlsaSettings, device=None selects the direct address and overrides default.device. An explicit conflicting device index or name raises ValueError; -2 is accepted.
  • Channels and sample rate must be provided explicitly or through defaults. Stream latency must be a finite, nonnegative number; 'low' and 'high' depend on enumerated device information and are unavailable on this path.
  • Format checks do not require latency, since PortAudio ignores suggested latency for these checks.
  • Input, output, mixed and direct duplex streams are supported. stream.device reports -2 for a directly specified direction.
  • Convenience output with explicit mapping=[1] raises an explanatory error because its automatic stereo detection requires device information. OutputStream supports explicit channel counts.

Validation

Tested locally on Linux ARM64 with Python 3.14.7, PortAudio 19.6.0 and ALSA 1.2.14.

A small C program first exercised PortAudio's existing API: two null-PCM capture/reopen cycles, a deterministic file PCM capture with all 16,384 bytes matching, and failure for a nonexistent PCM.

This PR is limited to ALSA PCM-address support and its API documentation. The test suite, pytest dependency, test-related CI steps and lockfile changes have been removed from the diff, following the scope discussion below.

The original 33 synthetic ALSA regression cases, isolated configuration/data fixture and standalone reproduction instructions are preserved on test/alsa-direct-device-regressions, at commit 07f99356cebd5d737a65fdaee517785445d949c8. This preserves validation material for review; testing infrastructure will be discussed separately in the context of #14 and #131.

To run the preserved tests against this feature-only branch on Linux, with PortAudio installed:

# From a checkout of this PR's fork on feat/alsa-direct-device:
git fetch origin test/alsa-direct-device-regressions
validation_dir=$(mktemp -d /tmp/sounddevice-alsa.XXXXXX)
git archive 07f99356cebd5d737a65fdaee517785445d949c8 tests | tar -x -C "$validation_dir"
python -m pip install . pytest numpy
python -m pytest -q -o cache_dir="$validation_dir/.pytest_cache" "$validation_dir/tests"

The fixture is configured before PortAudio initialization, includes no system/user ALSA configuration, and removes its PCM configuration/data files at session cleanup. No application code is required. The extracted review suite remains in $validation_dir until manually removed.

After separating the tests, local Linux ARM64 validation with CPython 3.14.7 and PortAudio 19.6.0 passed all 33 preserved cases against a wheel built from the feature-only working tree. Import/device/version smoke checks also passed. Pyright source analysis, a warning-as-error Sphinx HTML build, git diff --check, and uv lock --check passed locally. The feature, CFFI binding and API documentation files are byte-for-byte unchanged from the original candidate.

The following compatibility results are from validation of that original candidate, rather than a newly repeated multi-version matrix:

Original Linux ARM64 results:

Interpreter Result Installation
CPython 3.7.17 33 passed Wheel built with Python 3.14
CPython 3.8 33 passed Same wheel
CPython 3.9–3.14 33 passed per version Source install
PyPy 3.9 / 3.11 32 passed, 1 skipped per version Source install, no NumPy

The skipped case uses the NumPy convenience API; raw input/output/duplex and settings validation run on both PyPy versions. The preserved test setup installs NumPy for CPython and records the optional skip on PyPy; this PR no longer changes CI. Python 3.7/3.8 source builds are limited by the existing project's modern license metadata/build-tool compatibility; the wheel tests establish runtime compatibility only. Python 3.7 used ALSA 1.2.4, and the other local environments used ALSA 1.2.14, with PortAudio 19.6.0 throughout.

The tests cover direct capture/output/duplex, byte-for-byte input, reopening after failure, settings lifetime (including caller-mutated lists), explicit empty settings pairs, invalid arguments, default overrides, mixed direct/indexed streams, and existing index/name selection. Earlier separate application tests additionally checked callback delivery and reopening with device-name queries forbidden; those application tests are excluded from this PR.

GitHub-hosted CI results for the revised head are tracked in the PR checks. External link checking has not been repeated locally.

Physical input check

A separate local run used a MacBook Pro built-in microphone through Apple Virtualization.framework and an experimental virtio-snd guest kernel. The Linux runtime was a chroot containing Python 3.14.7, PortAudio 19.6.0 and ALSA 1.2.14. The installed sounddevice source matched this candidate byte-for-byte.

arecord completed two 10-second captures. A C program using PaAlsaStreamInfo.deviceString completed 60 seconds and a 5-second reopen. With query_devices() forbidden, AlsaSettings('hw:0,0') completed 60 seconds (2,880,000 mono frames), two 5-second reopens and recovery after a nonexistent PCM failed. All captures had nonzero signal, with no reported read errors or overflows. The host input route was monitored as the built-in microphone. Audio samples were processed in memory; only aggregate measurements were saved.

This establishes physical input through that particular VZ guest, including experimental capture compatibility changes. Native Linux USB devices, hotplug and stock virtio-snd remain unverified. The capture trial did not run the recorder or Docker Compose. Guest shutdown did not complete within the harness timeout and required a second launcher signal to force-stop the test VM; orderly VM shutdown was not established.

Limits and review questions

The separately preserved regression suite uses synthetic ALSA null/file tests. Physical input has additionally been tested through the VZ route above. Native Linux USB devices, hotplug, and native macOS/Windows execution of sounddevice have not been tested. The test latency request does not establish a negotiated 4,096-frame hardware buffer. No performance improvement is claimed.

An ALSA PCM address does not provide persistent hardware identity. Serial lookup, hotplug resolution and application recovery remain outside this patch. PortAudio initialization may still enumerate devices; this change bypasses sounddevice's name/index resolution when constructing direct stream parameters.

Would overriding default.device when AlsaSettings is supplied fit the existing API, or should direct selection require an explicit sentinel? Is requiring numeric latency acceptable for this path?

@mgeier

mgeier commented Oct 18, 2026

Copy link
Copy Markdown
Member

Thanks for this PR!

It seems to add two (or more?) different things, though: a new ALSA setting and a whole new testing framework.

I think both are valuable, but they should be discussed separately.

Regarding tests, there are already some discussions in #14 and #131, and there has been a very recent PR that also comes with its own test framework: #645.

@whatisyourname0

Copy link
Copy Markdown
Author

Thanks for the review! I agree that adding AlsaSettings and introducing test infrastructure should be discussed separately.

I'll update this PR to focus on the ALSA PCM-address support and its documentation, removing the new test suite, test dependencies, and test-related CI changes from the diff. I'll preserve the regression tests on a separate branch and keep the validation results and reproduction instructions available for reviewing the feature.

Thanks also for pointing me to #14, #131, and #645. I'll follow up on testing separately, building on those discussions before proposing a shared approach.

@whatisyourname0

Copy link
Copy Markdown
Author

Done in 8e0aeba: this PR now contains only the ALSA PCM-address support and its documentation. The tests and reproduction instructions are preserved on test/alsa-direct-device-regressions, with links and validation details in the updated PR description.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants