Feature request: keep the ALSA output running briefly between streams (--keep-output-alive <s>)

Aperta
#109 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
42/100
Tipo di issue
Funzionalità
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
cpp

Direzione di ricerca

Start with alsa_sink.h and alsa_sink.cpp, then trace the existing fixed-volume option through CLI parsing, configuration, and sendspin-cli.conf.example. Use the attached patch and the modeled parse/config tests as references, and run the build-armv6 container with SENDSPIN_CLI_WERROR=ON. Done means the ALSA keep-alive behavior, timeout, CLI/config bounds, and no-op behavior for other backends are implemented and all 122 tests pass.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Problem

With an HDMI (or S/PDIF) receiver behind the player, the start of every stream is cut off.
At each stream end and start, AlsaAudioSink stops the PCM:

  • clear() calls snd_pcm_drop() + snd_pcm_prepare()
  • configure() does the same when the next stream has the same format

The PCM then sits in PREPARED (or in XRUN, if the ring runs dry before clear() arrives)
until the next stream writes. While it does, no audio packets go out over HDMI, so the receiver
drops the input and has to re-lock when audio comes back. That takes about 2-3 s on an Onkyo
TX-NR626. For Music Assistant announcements (TTS) this is a real problem: MA ends the music
stream, plays the announcement as its own stream and then resumes the music, so the receiver
re-locks twice, and most of a short announcement is lost. A pre-announce chime is too short
to cover the re-lock.

Setup: Raspberry Pi Zero W, sendspin-cli 0.4.0 (linux-armv6), Music Assistant 2.10.8.
Output is onkyo_hdmi, a copy of the hdmi:CARD=vc4hdmi,DEV=0 chain in asound.conf with the
hw rate pinned to 48 kHz, plus audio-format = flac:48000:24:2,pcm:48000:24:2, so the format
never changes between streams and only the same-format path below is involved.

Measured by polling /proc/asound/card0/pcm0p/sub0/status every 250 ms during
radio -> TTS -> radio:

RUNNING   (radio)
PREPARED  ~1 s     <- stream end, clear()
RUNNING   (TTS, ~6 s)
XRUN      ~0.3 s   <- ring ran dry before the stream-end message
PREPARED  ~2 s
RUNNING   (radio)

Workarounds that do not work

Both try to keep the device busy from outside the player. Each puts a layer between the player
and the device that reports snd_pcm_delay() too coarsely or too jittery for the 5 ms
HARD_SYNC_THRESHOLD_US. The result is a constant Lost sync / hard-sync loop, which you hear
as choppy audio:

  • snd-aloop + alsaloop feeding HDMI. The loopback pointer moves in jiffies, and the Pi
    kernel runs HZ=100. Result: Lost sync (~90 ms off) about every second, with any
    alsaloop latency or sync mode.
  • System-mode PulseAudio with module-suspend-on-idle unloaded (-o pulse). The same
    Lost sync storm, see #32, plus 100 % CPU on a Pi Zero.

So this has to happen inside the sink, where the delay reported to the sync task comes straight
from the device.

Proposal

An opt-in option --keep-output-alive <seconds> (config key keep-output-alive), 0-3600,
default 0 (off), ALSA backend only. Other backends get a no-op AudioSink::set_keep_alive().
The goal is only to bridge the switch from one stream to the next, not to keep the receiver
awake forever: a few seconds (e.g. keep-output-alive = 10) are enough.

When enabled:

  1. clear() and the same-format path in configure() no longer drop the PCM. If it is
    running, they rewind the queued audio with snd_pcm_rewind(), keeping one period in front
    of the hardware pointer as a DMA safety margin. Then they write a short run of silence right
    away. If the rewind fails, the queued tail simply plays out.
  2. poll() (main loop, ~10 ms) tops the ring up with silence to about 2 periods once write()
    has not delivered real audio for 60 ms. It uses try_lock on device_mutex_: a write()
    blocked in snd_pcm_wait() holds the mutex, so a stream that is running is never touched
    and the main loop never stalls. The same path re-prepares an XRUN (e.g. the ring ran dry
    at the end of a stream before clear()), so the signal does not drop there either.
  3. Time limit. Every real write() and every same-format configure() (re)starts a window
    of <seconds>; a clear() restarts it while it is still open. When it runs out, the PCM is
    dropped and prepared exactly as without the option, so the receiver sees the signal end and
    its auto standby still works. After that, clear() behaves as without the option, and the
    next stream starts the PCM again and opens a new window.
  4. Sync is not touched: the keep-alive silence is just queued frames, and the next real
    write() measures it through snd_pcm_delay() as usual.

Results

A patch against v0.4.0 (about 170 added lines in alsa_sink.{h,cpp}, plus option plumbing, help
text, sendspin-cli.conf.example and parse/config tests modeled on --fixed-volume) is
attached. It builds with SENDSPIN_CLI_WERROR=ON in the build-armv6 container, and all
122 tests pass. clear() and configure() are unchanged on current main, so it should apply
there with little or no change.

On the Pi Zero W above, with keep-output-alive = 10:

  • HDMI PCM RUNNING without a break through radio -> TTS -> radio (polled every 250 ms)
  • the receiver no longer re-locks, and TTS announcements play from the first word
  • no Lost sync during the switches; CPU the same as without the option (~75 % idle)
  • after a pause the PCM went from RUNNING to PREPARED exactly 10 s after the last audio
    (so the receiver can go to standby), and the next stream started normally

Open questions

  • Name and unit: is keep-output-alive <seconds> fine, or would you prefer milliseconds or an
    output-level setting?
  • Is a hard cap (3600 s) sensible, or should there be a "forever" value for setups that want
    it? I left that out on purpose so that receivers can always go to standby.
  • PortAudio, Pulse and PipeWire are left out on purpose. Pulse and PipeWire already have their
    own idle/suspend handling.

keep-output-alive-v0.4.0.patch.txt

Lingua principale
C++
Stelle
18
Fork
7
Merge medio
1g 12h
PR unite (30g)
26

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Sendspin/sendspin-cpp-cli

Tutte le issue di Sendspin/sendspin-cpp-cli

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.