1#[path = "common/wav.rs"]
14mod wav;
15
16use opus_pure::{Application, MAX_PACKET_BYTES, OggOpusWriter, OpusEncoder, OpusHead, OpusTags};
17
18fn main() -> Result<(), Box<dyn std::error::Error>> {
19 let args: Vec<String> = std::env::args().collect();
20 if args.len() < 3 {
21 eprintln!("usage: {} <input.wav> <output.opus> [bitrate_bps]", args[0]);
22 std::process::exit(2);
23 }
24 let bitrate: i32 = args
25 .get(3)
26 .map(|s| s.parse())
27 .transpose()?
28 .unwrap_or(64_000);
29
30 let input = wav::read(&args[1])?;
31 if input.samples.is_empty() {
32 return Err(format!("{}: no audio samples", args[1]).into());
33 }
34 let channels = input.channels as usize;
35 let rate = input.sample_rate as i32;
36
37 let frame = (rate / 50) as usize;
41
42 let mut encoder = OpusEncoder::new(rate, channels, Application::Audio)?;
43 encoder.bitrate_bps = bitrate;
44
45 let mut tags = OpusTags::new();
46 tags.push("ENCODER", concat!("opus-pure ", env!("CARGO_PKG_VERSION")))?;
47 let head = OpusHead::for_encoder(&encoder, input.sample_rate);
50
51 let ticks = 48_000 / rate as usize;
56 let total = input.samples.len() / channels; let lookahead = (head.pre_skip as usize).div_ceil(ticks);
64 let frames = (total + lookahead).div_ceil(frame);
65 let final_granule = u64::from(head.pre_skip) + (total * ticks) as u64;
72
73 let file = std::fs::File::create(&args[2])?;
74 let mut writer = OggOpusWriter::with_tags(std::io::BufWriter::new(file), head, tags)?;
75
76 let mut packet = vec![0u8; MAX_PACKET_BYTES];
77 let mut payload_bytes = 0usize;
78 let per_frame = frame * channels;
79 let mut block = vec![0.0f32; per_frame];
80
81 for i in 0..frames {
82 let start = (i * per_frame).min(input.samples.len());
85 let end = (start + per_frame).min(input.samples.len());
86 block[..end - start].copy_from_slice(&input.samples[start..end]);
87 block[end - start..].fill(0.0);
88
89 let n = encoder.encode(&block, frame, &mut packet)?;
90 if i + 1 == frames {
91 let duration = final_granule - writer.granule() as u64;
92 writer.write_packet_with_duration(&packet[..n], duration as u32)?;
93 } else {
94 writer.write_packet(&packet[..n])?;
95 }
96 payload_bytes += n;
97 }
98 writer.finish()?;
99
100 let secs = total as f64 / rate as f64;
101 println!(
102 "{} -> {}\n {channels} ch @ {rate} Hz, {frames} frames ({secs:.2} s of audio)\n \
103 {payload_bytes} payload bytes = {:.1} kb/s (target {:.1} kb/s)",
104 args[1],
105 args[2],
106 payload_bytes as f64 * 8.0 / secs / 1000.0,
107 bitrate as f64 / 1000.0,
108 );
109 Ok(())
110}