fix: allow virtual ALSA PCM names (dsnoop etc.); log arecord stderr

AudioSystem.find_device() now falls back to treating an unmatched spec as
a direct ALSA PCM name when the ALSA backend is available.  Virtual devices
defined in asound.conf (dsnoop, plug, etc.) never appear in 'arecord -l'
so they were always rejected as 'not found', even when valid.

ALSAStream now captures arecord stderr via a reader thread instead of
discarding it, so errors like 'Device or resource busy' are logged as
warnings and visible in docker compose logs.
This commit is contained in:
2026-04-26 14:39:15 +02:00
parent e67e27f047
commit 9ac23e9f1d
+17 -1
View File
@@ -186,13 +186,15 @@ class ALSAStream:
self._process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
stderr=subprocess.PIPE,
)
except FileNotFoundError:
raise RuntimeError("arecord not found - install alsa-utils: sudo apt install alsa-utils")
self._thread = threading.Thread(target=self._read_loop, daemon=True)
self._stderr_thread = threading.Thread(target=self._log_stderr, daemon=True)
self._thread.start()
self._stderr_thread.start()
return self
def __exit__(self, *args):
@@ -206,6 +208,12 @@ class ALSAStream:
if self._thread:
self._thread.join(timeout=1)
def _log_stderr(self):
for line in self._process.stderr:
line = line.decode('utf-8', errors='replace').rstrip()
if line:
self.logger.warning(f"arecord: {line}")
def _read_loop(self):
chunk_size = self.sample_rate * self.channels * 2 // 10 # 100 ms chunks
while self._running and self._process.poll() is None:
@@ -306,6 +314,14 @@ class AudioSystem:
if spec_lower in dev.name.lower():
return dev
# No hardware device matched. Treat spec as a direct ALSA PCM name so
# virtual devices defined in asound.conf (dsnoop, plug, etc.) work without
# appearing in `arecord -l`.
if 'alsa' in self._backends:
return AudioDevice(
id=spec, name=spec, channels=2, sample_rate=44100, backend='alsa'
)
return None