Skip to main content

OpusPlaybackDecoder

Struct OpusPlaybackDecoder 

Source
pub(crate) struct OpusPlaybackDecoder {
    pub(crate) dec: Decoder,
    pub(crate) channels: usize,
    pub(crate) pcm: Vec<i16>,
    pub(crate) packet: Vec<u8>,
    pub(crate) last_ts: Option<i64>,
}
Expand description

Turns the mic uplink back into PCM: the client always sends the mic as Opus, so every inbound packet must be decoded before it can be queued for the virtual sink. It decodes one packet to interleaved S16LE PCM, reusing a scratch buffer across calls to stay off the per-decode allocation path. Lives behind a Mutex on PbShared and is driven from write / write_red.

Fields§

§dec: Decoder§channels: usize§pcm: Vec<i16>§packet: Vec<u8>

Inbound scratch for a buffer-protocol payload, copied here under the GIL so the off-GIL decode never reads a buffer another Python thread could be mutating.

§last_ts: Option<i64>

RFC 2198 RED recovery cursor: the timestamp of the last frame decoded, so a redundant copy of a dropped frame is decoded exactly once, in order. None until the first RED frame arrives.

Implementations§

Source§

impl OpusPlaybackDecoder

Source

pub(crate) fn new(sample_rate: u32, channels: i32) -> Option<Self>

Create a mono/stereo Opus decoder for the mic uplink; None if creation fails. channels <= 1 decodes as mono, otherwise stereo.

Source

pub(crate) fn decode_to_pcm(&mut self, packet: &[u8]) -> Option<&[u8]>

Decode one Opus packet and return the interleaved S16LE PCM as bytes, or None for an empty or undecodable packet.

The scratch pcm buffer is grown once to 5760 * channels — an Opus packet decodes to at most 120 ms, which is 5760 samples per channel at 48 kHz — then reused across calls, and the result is a view straight over it, so a decode never allocates. The view borrows the decoder, so callers must queue it before decoding the next packet. Reinterpreting the samples as S16LE bytes assumes a little-endian host, exactly as the capture side does when it fills accum from PulseAudio fragments.

Source

pub(crate) fn decode_red_into_queue( &mut self, payload: &[u8], primary_ts: i64, queue: &PlayQueue, )

Reconstruct the mic uplink across packet loss: recover any frames the sender dropped from the redundant copies RED carries, and decode each new frame exactly once into queue. This is what lets a lossy UDP/WebRTC uplink play through gaps without ever waiting for a retransmit. It all runs off the GIL — the work is pure byte-slicing plus Opus decode with no Python state, so releasing the GIL keeps the mic path from serializing behind the rest of the interpreter.

  1. Parse the block headers: walk the redundant headers (F bit set) collecting each block’s 14-bit timestamp offset and 10-bit length, then consume the 1-byte primary header (F bit clear). A truncated payload, or more redundancy than RED_MAX_DISTANCE, bails out.
  2. Resolve block boundaries: turn the headers into (ts, start, len) triples, oldest-first, where each redundant ts is primary_ts - offset and the primary is whatever bytes remain.
  3. Anchor the first packet: with no prior last_ts, decode only the primary and set last_ts to it — its trailing redundancy describes frames never played, so it is not replayed.
  4. Recover and advance: otherwise decode every block whose ts is strictly newer than last_ts (in oldest-first order, so a gap left by a dropped packet is filled before the primary), pushing PCM to queue and advancing last_ts. Blocks at or below last_ts are already-played duplicates and are skipped — the timestamp dedup that makes redundancy free of double-decoding under no loss.

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.
§

impl<T> Ungil for T
where T: Send,