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 lossFields§
§gain_q8: i32Output 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
impl OpusDecoder
Sourcepub fn new(sampling_rate: i32, channels: usize) -> Result<Self>
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.
Sourcepub fn sample_rate(&self) -> i32
pub fn sample_rate(&self) -> i32
The sample rate this decoder was created with, in Hz.
Sourcepub fn final_range(&self) -> u32
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.
Sourcepub fn last_packet_duration(&self) -> usize
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.
Sourcepub fn reset_state(&mut self) -> Result<()>
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.
Sourcepub fn decode(
&mut self,
input: &[u8],
frame_size: usize,
output: &mut [f32],
) -> Result<usize>
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?
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}Sourcepub fn decode_s16(
&mut self,
input: &[u8],
frame_size: usize,
output: &mut [i16],
) -> Result<usize>
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)?;Sourcepub fn decode_fec(
&mut self,
packet: &[u8],
frame_size: usize,
output: &mut [f32],
) -> Result<usize>
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.
Sourcepub fn decode_fec_s16(
&mut self,
packet: &[u8],
frame_size: usize,
output: &mut [i16],
) -> Result<usize>
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.