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
impl OpusPlaybackDecoder
Sourcepub(crate) fn new(sample_rate: u32, channels: i32) -> Option<Self>
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.
Sourcepub(crate) fn decode_to_pcm(&mut self, packet: &[u8]) -> Option<&[u8]>
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.
Sourcepub(crate) fn decode_red_into_queue(
&mut self,
payload: &[u8],
primary_ts: i64,
queue: &PlayQueue,
)
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.
- 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. - Resolve block boundaries: turn the headers into
(ts, start, len)triples, oldest-first, where each redundanttsisprimary_ts - offsetand the primary is whatever bytes remain. - Anchor the first packet: with no prior
last_ts, decode only the primary and setlast_tsto it — its trailing redundancy describes frames never played, so it is not replayed. - Recover and advance: otherwise decode every block whose
tsis strictly newer thanlast_ts(in oldest-first order, so a gap left by a dropped packet is filled before the primary), pushing PCM toqueueand advancinglast_ts. Blocks at or belowlast_tsare already-played duplicates and are skipped — the timestamp dedup that makes redundancy free of double-decoding under no loss.