Commit Graph
1 Commits
Author SHA1 Message Date
047b379bd2 feat(audio): add PipeWire capture path with system-audio sources (#889)
* feat(audio): add PipeWire capture path with system-audio sources

Enumerate PipeWire sinks via pw-dump and expose their monitors as
"System audio: <name>" capture devices at negative device indices
(-2..), alongside the existing PortAudio microphones. Recording,
device test, and stream-reconnection paths route to a
pw-record-backed stream shaped like a PyAudio stream, so the VAD and
silence-detection loop is unchanged.

Playback ducking is skipped when the selected source is a monitor,
since ducking the default sink would also duck the captured audio.

Closes #751

* test(audio): restore stashed numpy instead of re-importing it

test_audio_input imports numpy lazily; deleting the MagicMock that
earlier test modules leave in sys.modules re-imported a second numpy
module object whose lazy submodule attributes (linalg, testing) were
unpopulated while sys.modules kept the first object's entries. The
next test file to reach for numpy.testing hit a __getattr__ import
loop (RecursionError in TestDownmixToMono on CI).

Restore the conftest-stashed _vocalinux_real_numpy instead, matching
the pattern tests/test_recognition_manager_device_config.py already
uses.

* fix(pipewire): drop PyAudio requirement for PipeWire sources, rebind source after reconnect

* docs(pipewire): restore PipeWireCaptureSource class docstring

* fix(audio): initialize PyAudio for mic reconnection after PipeWire session

A session started on a PipeWire sink never creates a PyAudio instance;
when capture later reconnects onto a PortAudio device the source needs
one to reopen, and a failed open dereferenced None calling terminate.

* fix(audio): harden PipeWire source resolution, reads, and cleanup

- resolve stored names by node.name/description; a stored name that no
  longer resolves never falls back to a positional index
- require pw-record before listing system-audio sources in the picker
- bound pw-record chunk reads with a stall watchdog so stopping cannot
  block on a wedged stream
- close the reconnection probe stream when its verification read fails
- log cleanup failures instead of swallowing them; narrow exception
  handlers in enumeration and PyAudio reconnection init
- annotate test helpers and signatures per repo style

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-09-30 02:07:41 -07:00