pub(crate) fn capture_run(
inner: &Arc<Inner>,
settings: &Settings,
callback: &Py<PyAny>,
)Expand description
Own one whole capture run end to end on a dedicated thread — connect PulseAudio,
encode, and deliver — until stopped. It runs on its own thread because the PulseAudio
mainloop must be pumped continuously and independently of Python: sharing the caller’s
thread would tie capture cadence to the GIL and let any Python stall starve the audio.
The body handed to spawn_worker.
Publishes start_state for the handshake (FAILED on any startup error, RUNNING on
entering the hot loop) and returns when stop_state leaves STOP_NONE or on a fatal
error. The startup sequence, in order:
- Seed the mirrors + validate: copies the settings snapshot into the
Innerper-frame atomics, and rejects an invalid Opus frame duration, channel count, or sample spec before touching PulseAudio. - Buffer attr / latency: a configured
latency_msusesADJUST_LATENCYwithfragsizeset to that latency; otherwisefragsizeis floored at ~20 ms, which yields a prompt first frame and avoids PipeWire’s ~2 s default fragment. - Connect + probe: drives the context to
Readyon the bounded pump (re-checkingstop_pendingeach turn), and up-front validates a NAMED device via an introspect probe — an asyncconnect_recordwould not fail synchronously on a bad name. The probe closure is called from C inside mainloop dispatch, so its body iscatch_unwind-guarded to keep a panic from unwinding across the FFI boundary. - Encoder + record stream: creates the
PcmEncoder(mono/stereo or surround) and drives the record stream toReady. - Delivery thread: spawns the delivery thread that pops from the
DeliveryRingand runs the Python callback there, so GIL stalls cannot back up the PA pump; a callback error is reported as an unraisable exception and never propagates into the loop. The buffer pool is sized to the worst-case body — RED prefix plus a max Opus packet, scaled by stream count for surround (one self-delimited packet per stream). - Hot loop:
pumps on the ~20 ms bound, then drains every buffered fragment via peek/discard (aHoleis an xrun — the read index is just advanced), feeding each intoRunState. A stop is observed within the pump bound even when the source is wedged. On exit it disconnects the stream, drops the encoder, closes and joins the delivery ring, and reports any dropped stale frames.