Skip to main content

decode/
decode.rs

1//! Decode a `.opus` file back to WAV.
2//!
3//!   cargo run --release --example decode -- input.opus output.wav [rate]
4//!
5//! Opus always decodes at a rate you choose, not one stored in the file, so the
6//! output rate is an argument. `OpusHead::input_sample_rate` records what the
7//! original was, and is used as the default.
8//!
9//! A decoded Opus stream is longer than the audio that went into it at both
10//! ends, and `Trim` takes both ends off: the encoder delay at the front and the
11//! final granule's end-trim at the back. Real `opusenc` files carry the second
12//! one, so leaving it out appends up to a frame of padding past the end of the
13//! audio — silent on a clip played once, an audible gap at the seam of a loop.
14
15#[path = "common/wav.rs"]
16mod wav;
17
18use opus_pure::{MAX_PACKET_SAMPLES, OggOpusReader, Trim};
19
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}