Skip to main content

PaCaptureSession

Struct PaCaptureSession 

Source
pub(crate) struct PaCaptureSession {
    pub(crate) stream: Stream,
    pub(crate) context: Context,
    pub(crate) mainloop: Mainloop,
}
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 (RUNNING on entering the hot loop, FAILED with a last_error on any terminal error, IDLE on a clean stop) and returns when stop_state leaves STOP_NONE or on a fatal error. The startup sequence, in order:

  1. Seed the mirrors: copies the settings snapshot (already validated by extract_settings) into the Inner per-frame atomics.
  2. Buffer attr / latency: a configured latency_ms uses ADJUST_LATENCY with fragsize set to that latency; otherwise fragsize is floored at ~20 ms, which yields a prompt first frame and avoids PipeWire’s ~2 s default fragment.
  3. Connect + probe: drives the context to Ready on the bounded pump (re-checking stop_pending each turn), and up-front validates a NAMED device via an introspect probe — an async connect_record would not fail synchronously on a bad name. The probe closure is called from C inside mainloop dispatch, so its body is catch_unwind-guarded to keep a panic from unwinding across the FFI boundary.
  4. Encoder + record stream: creates the PcmEncoder (mono/stereo or surround) and drives the record stream to Ready.
  5. Delivery thread: spawns the delivery thread that pops from the DeliveryRing and 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).
  6. Hot loop: pumps on the ~20 ms bound, then drains every buffered fragment via peek/discard (a Hole is an xrun — the read index is just advanced), feeding each into RunState. 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.

One PulseAudio session for capture: mainloop, context, and the record stream, all recreated together on reconnect. Drop order matters (declaration order): the stream must die first, then its owning context, then the mainloop both pulse threads pump — the reverse of the build. A wrong order is a use-after-free on the libpulse side.

Fields§

§stream: Stream§context: Context

Must outlive the stream (the connection owns it); never read after open.

§mainloop: Mainloop

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.