From cccbb24267924ebdf428a53d59a83fd4f4c81f8b Mon Sep 17 00:00:00 2001 From: Julien Gainza Date: Tue, 21 Jul 2026 23:50:05 +0200 Subject: [PATCH] pcm_meter: let the s16 scope degrade instead of killing the caller 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 Co-Authored-By: Claude Opus 4.8 --- src/pcm/pcm_meter.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/pcm/pcm_meter.c b/src/pcm/pcm_meter.c index 8fc5c15d..df8c612a 100644 --- a/src/pcm/pcm_meter.c +++ b/src/pcm/pcm_meter.c @@ -1005,6 +1005,7 @@ typedef struct _snd_pcm_scope_s16 { snd_pcm_uframes_t old; int16_t *buf; snd_pcm_channel_area_t *buf_areas; + int silent; } snd_pcm_scope_s16_t; static int s16_enable(snd_pcm_scope_t *scope) @@ -1043,7 +1044,20 @@ static int s16_enable(snd_pcm_scope_t *scope) idx = snd_pcm_linear_convert_index(spcm->format, SND_PCM_FORMAT_S16); break; default: - return -EINVAL; + /* No S16 conversion exists for this format - DSD, the 3-byte + * packed formats, float. Leaving the scope disabled is worse + * than useless: scopes that depend on it, including our own + * level scope, call snd_pcm_scope_s16_get_channel_buffer() + * from their update callback and that assert()s on the buffer + * this function never allocated, killing the application. + * Hand out a silent buffer instead, so a scope reads zero on a + * format it cannot see rather than aborting the process. + */ + snd_error(PCM, "s16 scope: no S16 conversion for format %s, scopes will read silence", + snd_pcm_format_name(spcm->format)); + s16->silent = 1; + idx = 0; + break; } s16->index = idx; if (spcm->format == SND_PCM_FORMAT_IMA_ADPCM) { @@ -1051,7 +1065,10 @@ static int s16_enable(snd_pcm_scope_t *scope) if (!s16->adpcm_states) return -ENOMEM; } - s16->buf = malloc(meter->buf_size * 2 * spcm->channels); + /* calloc, not malloc: when s16->silent is set nothing ever writes to + * this buffer and scopes must read silence, not uninitialised memory. + */ + s16->buf = calloc(meter->buf_size * 2 * spcm->channels, 1); if (!s16->buf) { free(s16->adpcm_states); return -ENOMEM; @@ -1103,6 +1120,11 @@ static void s16_update(snd_pcm_scope_t *scope) snd_pcm_t *spcm = meter->gen.slave; snd_pcm_sframes_t size; snd_pcm_uframes_t offset; + if (s16->silent) { + /* Nothing to convert; the buffer stays zero. */ + s16->old = meter->now; + return; + } size = meter->now - s16->old; if (size < 0) size += spcm->boundary;