Skip to content

pcm_meter: let the s16 scope degrade instead of killing the caller#516

Open
Gjuju wants to merge 1 commit into
alsa-project:masterfrom
Gjuju:scope-silence-instead-of-abort
Open

pcm_meter: let the s16 scope degrade instead of killing the caller#516
Gjuju wants to merge 1 commit into
alsa-project:masterfrom
Gjuju:scope-silence-instead-of-abort

Conversation

@Gjuju

@Gjuju Gjuju commented Jul 21, 2026

Copy link
Copy Markdown

The bug

s16_enable() returns -EINVAL for any format it cannot convert to S16 — the DSD
formats, the 3-byte packed ones (S24_3LE/S24_3BE/S20_3LE), float.
snd_pcm_scope_enable() records that as "not enabled" and carries on:

err = scope->ops->enable(scope);
scope->enabled = (err >= 0);

But every other scope stays enabled, and scopes reach the s16 buffer through
snd_pcm_scope_s16_get_channel_buffer(), which asserts on exactly the pointer
s16_enable() never allocated:

pcm_meter.c:1224: snd_pcm_scope_s16_get_channel_buffer: Assertion 's16->buf_areas' failed.

The application dies. This is not hypothetical and not third-party only: alsa-lib's own
src/pcm/scopes/level.c calls the accessor the same way, so the library aborts its own
caller. Downstream, libpeppyalsa (the VU-meter scope used by moOde audio player) makes
a music player abort mid-playback the moment a DSD track starts.

Note the failure is not DSD-specific — any format outside the 17 the switch lists
triggers it, including S24_3LE, which plenty of USB DACs expose natively. A chain that
happens to have a plug in front of the meter is merely lucky.

The fix

Allocate the buffer anyway and leave it zero, so a scope reads silence on a format
the s16 conversion cannot see. Callers need no change, and none can be made to abort —
which matters, since returning NULL instead would only move an unfixed caller from
SIGABRT to SIGSEGV. The condition is now reported once through SNDERR() instead of
being swallowed.

Testing

x86-64, Debian 13, USB DAC with a native DSD path, moOde audio player with its VU meter
scope in the chain. Playing a DSD64 .dsf with dop=no:

  • before: MPD aborts on the assertion, the UI reports "Socket open failed"
  • after: the DAC receives DSD_U32_BE @ 88200 bit-perfect, MPD stays up
    (NRestarts: 0), needles read zero, and the log carries
    s16 scope: no S16 conversion for format DSD_U32_BE, scopes will read silence

Left alone

The neighbouring S16 + MMAP_NONINTERLEAVED branch assigns s16->buf and then also
returns -EINVAL, reaching the same dead end. It looks unfinished rather than
deliberate, so this patch does not touch it — happy to follow up if you can confirm the
intent.

s16_enable() returns -EINVAL for any format it cannot convert to S16 -
DSD, the 3-byte packed formats, float. snd_pcm_scope_enable() records
that as "not enabled" and carries on, so the buffer is never allocated.

Any other scope stays enabled, though, and scopes reach the s16 buffer
through snd_pcm_scope_s16_get_channel_buffer(), which asserts on exactly
that never-allocated pointer. The application dies. Our own level scope
does this, so alsa-lib kills its own caller; the same happens to
libpeppyalsa, where a DSD track aborts the music player mid-playback.

Allocate the buffer anyway and leave it zero, so a scope reads silence on
a format the s16 conversion cannot see. Callers need no change and none
can be made to abort. The condition is reported once through SNDERR
rather than being swallowed.

The neighbouring S16/MMAP_NONINTERLEAVED branch returns -EINVAL after
assigning s16->buf and reaches the same dead end; it is left alone here
because its intent is unclear.
Signed-off-by: Julien Gainza <gainza.julien@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Gjuju
Gjuju force-pushed the scope-silence-instead-of-abort branch from fee0a47 to cccbb24 Compare July 21, 2026 23:14
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.

1 participant