Skip to main content

OpusDecoder

Struct OpusDecoder 

Source
pub struct OpusDecoder {
    pub gain_q8: i32,
    /* private fields */
}
Expand description

An Opus decoder: Opus packets in, PCM out.

One decoder handles one stream, and almost everything it needs is carried between packets rather than contained in them: filter histories, the overlap-add buffer, the resampler, and which layer coded the previous frame. So the same instance has to be fed the whole stream in order, and handing a packet to a fresh decoder does not produce the same audio as decoding it in sequence.

A packet says for itself how long it is and which bandwidth and layer it used, so a decoder needs no configuration beyond the rate and channel count the caller wants back. It follows the stream wherever the encoder went, including mode and bandwidth changes mid-stream.

Missing packets are expected rather than exceptional. Call decode with an empty slice to conceal a loss, or decode_fec on the following packet to recover the gap from a redundant copy if the encoder coded one.

use opus_pure::{Application, OpusDecoder, OpusEncoder};

let mut encoder = OpusEncoder::new(48_000, 2, Application::Audio)?;
let mut decoder = OpusDecoder::new(48_000, 2)?;

let mut packet = vec![0u8; 4000];
let n = encoder.encode(&vec![0.0f32; 960 * 2], 960, &mut packet)?;

let mut pcm = vec![0.0f32; 960 * 2];
assert_eq!(decoder.decode(&packet[..n], 960, &mut pcm)?, 960);
assert_eq!(decoder.decode(&[], 960, &mut pcm)?, 960);   // conceal a loss

Fields§

§gain_q8: i32

Output gain, in Q8 dB. Applied to every decoded sample; 0 is unity.

This is libopus’s OPUS_SET_GAIN, and the reason it exists is OpusHead::output_gain_q8: RFC 7845 §5.1 puts a gain in the Ogg header and says players SHOULD apply it, but nothing in a container can reach inside a decoder to do so. Copy it across after reading the header and the stream plays at the loudness its author asked for.

Applied before the soft clip on the 16-bit path, as libopus does, so a gain that pushes the signal past full scale is clipped rather than wrapped.

Implementations§

Source§

impl OpusDecoder

Source

pub fn new(sampling_rate: i32, channels: usize) -> Result<Self>

Create a decoder producing sampling_rate Hz and channels channels.

The rate must be one of 8000, 12000, 16000, 24000 or 48000, and the channel count 1 or 2; anything else is Error::InvalidArgument.

Neither has to match how the stream was encoded. These describe the PCM the caller wants back, and the decoder resamples and mixes to reach it, so a mono stream decodes to stereo and a 48 kHz one decodes to 16 kHz. Requesting 48000 avoids a resampling step on the way out.

For more than two channels, see OpusMSDecoder.

Source

pub fn sample_rate(&self) -> i32

The sample rate this decoder was created with, in Hz.

Source

pub fn channels(&self) -> usize

The channel count this decoder was created with.

Source

pub fn final_range(&self) -> u32

Range-coder state left by the last decoded frame (libopus OPUS_GET_FINAL_RANGE).

A decoder that has read a packet correctly ends in exactly the state the encoder ended in, so comparing this against OpusEncoder::final_range is a cheap check that the two agree bit for bit. That is what the RFC 6716 test vectors compare, and what tells a desync apart from a merely disappointing decode. It is not needed to decode audio.

Source

pub fn last_packet_duration(&self) -> usize

Samples per channel in the last packet decoded from real data (libopus OPUS_GET_LAST_PACKET_DURATION), or 0 before the first one.

Concealed and FEC-recovered frames do not change it, so after a loss it still reports the last packet that actually arrived. To ask the same question of a packet you are holding but have not decoded — which is what a muxer or a jitter buffer wants — use packet::samples instead; it reads the TOC and needs no decoder at all.

Source

pub fn reset_state(&mut self) -> Result<()>

Discard everything the decoder has learned, keeping its settings.

This is libopus’s OPUS_RESET_STATE, and the moment to call it is between two unrelated streams sharing one decoder. Almost everything interesting in an Opus decoder is carried between packets — the LTP and LPC histories, the overlap-add buffer, the resampler, which layer coded the previous frame — so a second stream started on a used decoder begins by blending into the end of the first.

Equivalent to building a new decoder with the same sample rate and channel count, and carrying gain_q8 across. As on the encoder, it re-initialises rather than rewinds, so it is not free.

Source

pub fn decode( &mut self, input: &[u8], frame_size: usize, output: &mut [f32], ) -> Result<usize>

Decode one packet into frame_size samples per channel of float PCM, returning how many it produced.

output is interleaved and must hold frame_size * channels samples. An empty input means a lost packet and runs packet-loss concealment.

The output is not bounded by ±1: the codec rings, and a signal mastered near full scale comes back slightly over it. libopus behaves the same way. Convert to integer PCM with decode_s16, which handles that, or apply SoftClip yourself if you need the float and are converting later.

Examples found in repository?
examples/decode.rs (line 72)
20fn main() -> Result<(), Box<dyn std::error::Error>> {
21    let args: Vec<String> = std::env::args().collect();
22    if args.len() < 3 {
23        eprintln!("usage: {} <input.opus> <output.wav> [output_rate]", args[0]);
24        std::process::exit(2);
25    }
26
27    let file = std::fs::File::open(&args[1])?;
28    let mut reader = OggOpusReader::new(std::io::BufReader::new(file))?;
29    let head = reader.head().clone();
30    let channels = head.channel_count as usize;
31
32    // Opus decodes to 8/12/16/24/48 kHz only; fall back to 48 kHz when the file
33    // records something else (or nothing).
34    let requested: i32 = args
35        .get(3)
36        .map(|s| s.parse())
37        .transpose()?
38        .unwrap_or(head.input_sample_rate as i32);
39    let rate = match requested {
40        8_000 | 12_000 | 16_000 | 24_000 | 48_000 => requested,
41        other => {
42            eprintln!("note: {other} Hz is not an Opus decode rate; using 48000 Hz");
43            48_000
44        }
45    };
46
47    println!(
48        "{}: {channels} ch, pre-skip {} samples, gain {:+.2} dB, vendor {:?}",
49        args[1],
50        head.pre_skip,
51        head.output_gain_db(),
52        reader.tags().vendor,
53    );
54    for comment in &reader.tags().comments {
55        println!("  {comment}");
56    }
57
58    // `decoder` carries the channel count and the header's output gain, which
59    // RFC 7845 §5.1 says a player should apply and which is silent when it is
60    // missed: the file simply plays at the wrong level.
61    let mut decoder = head.decoder(rate)?;
62    let mut trim = Trim::new(&head, rate, channels)?;
63
64    // Sized for the longest packet Opus allows, so the loop does not care what
65    // frame size the file was made with. `decode` returns what it produced.
66    let mut block = vec![0.0f32; MAX_PACKET_SAMPLES * channels];
67    let mut samples = Vec::new();
68    let mut packets = 0usize;
69
70    for packet in reader.packets() {
71        let packet = packet?;
72        let n = decoder.decode(&packet.data, MAX_PACKET_SAMPLES, &mut block)?;
73        samples.extend_from_slice(trim.keep(&packet, &block[..n * channels]));
74        packets += 1;
75    }
76
77    let per_channel = trim.samples_emitted();
78    wav::write(
79        &args[2],
80        &wav::Wav {
81            sample_rate: rate as u32,
82            channels: head.channel_count as u16,
83            samples,
84        },
85    )?;
86    println!(
87        "  {packets} packets -> {} ({rate} Hz, {per_channel} samples/ch = {:.2} s of audio)",
88        args[2],
89        per_channel as f64 / rate as f64,
90    );
91    Ok(())
92}
Source

pub fn decode_s16( &mut self, input: &[u8], frame_size: usize, output: &mut [i16], ) -> Result<usize>

Decode one packet into frame_size samples per channel of 16-bit PCM, returning how many it produced.

Soft-clips before converting, so the result is inside the 16-bit range without the broadband distortion that saturating there would cause, and without a step at the packet boundary when a peak straddles one. This is what libopus’s opus_decode does and opus_decode_float does not, and it is the reason to prefer this entry point over converting the float output by hand. See SoftClip for what the curve is.

let mut decoder = OpusDecoder::new(48_000, 2)?;
let mut pcm = vec![0i16; 960 * 2];
let samples = decoder.decode_s16(&packet[..n], 960, &mut pcm)?;
Source

pub fn decode_fec( &mut self, packet: &[u8], frame_size: usize, output: &mut [f32], ) -> Result<usize>

Reconstruct the previous packet from this one’s in-band FEC, as float.

Call this when a packet is lost and the packet after it has arrived: SILK and hybrid streams can carry a low-rate copy of the frame before, which reconstructs it far better than concealment can. Falls back to concealment when the packet carries no such copy.

Source

pub fn decode_fec_s16( &mut self, packet: &[u8], frame_size: usize, output: &mut [i16], ) -> Result<usize>

decode_fec into 16-bit PCM, soft-clipped the same way decode_s16 is.

Trait Implementations§

Source§

impl Debug for OpusDecoder

Shows the decoder’s configuration and omits its coding state, for the same reason OpusEncoder’s does.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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 = !

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.