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:
@@ -186,13 +186,15 @@ class ALSAStream:
|
|||||||
self._process = subprocess.Popen(
|
self._process = subprocess.Popen(
|
||||||
cmd,
|
cmd,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise RuntimeError("arecord not found - install alsa-utils: sudo apt install alsa-utils")
|
raise RuntimeError("arecord not found - install alsa-utils: sudo apt install alsa-utils")
|
||||||
|
|
||||||
self._thread = threading.Thread(target=self._read_loop, daemon=True)
|
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._thread.start()
|
||||||
|
self._stderr_thread.start()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
@@ -206,6 +208,12 @@ class ALSAStream:
|
|||||||
if self._thread:
|
if self._thread:
|
||||||
self._thread.join(timeout=1)
|
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):
|
def _read_loop(self):
|
||||||
chunk_size = self.sample_rate * self.channels * 2 // 10 # 100 ms chunks
|
chunk_size = self.sample_rate * self.channels * 2 // 10 # 100 ms chunks
|
||||||
while self._running and self._process.poll() is None:
|
while self._running and self._process.poll() is None:
|
||||||
@@ -306,6 +314,14 @@ class AudioSystem:
|
|||||||
if spec_lower in dev.name.lower():
|
if spec_lower in dev.name.lower():
|
||||||
return dev
|
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
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user