use std::io::{Read, Write};
use rusty_opus::{Application, OpusEncoder};
fn read_wav(path: &str) -> (u32, u16, Vec<f32>) {
let mut buf = Vec::new();
std::fs::File::open(path).unwrap().read_to_end(&mut buf).unwrap();
let rate = u32::from_le_bytes([buf[24], buf[25], buf[26], buf[27]]);
let channels = u16::from_le_bytes([buf[22], buf[23]]);
let mut i = 12;
let (mut off, mut len) = (0usize, 0usize);
while i + 8 <= buf.len() {
let sz = u32::from_le_bytes([buf[i + 4], buf[i + 5], buf[i + 6], buf[i + 7]]) as usize;
if &buf[i..i + 4] == b"data" {
off = i + 8;
len = sz.min(buf.len() - (i + 8));
break;
}
i += 8 + sz + (sz & 1);
}
let s = buf[off..off + len]
.chunks_exact(2)
.map(|b| i16::from_le_bytes([b[0], b[1]]) as f32 / 32768.0)
.collect();
(rate, channels, s)
}
const CRC_POLY: u32 = 0x04c1_1db7;
fn crc32(data: &[u8]) -> u32 {
let mut crc: u32 = 0;
for &b in data {
crc ^= (b as u32) << 24;
for _ in 0..8 {
crc = if crc & 0x8000_0000 != 0 {
(crc << 1) ^ CRC_POLY
} else {
crc << 1
};
}
}
crc
}
#[allow(clippy::too_many_arguments)]
fn write_page(
out: &mut Vec<u8>,
body: &[u8],
granule: u64,
serial: u32,
seq: u32,
header_type: u8,
) {
let mut segs = Vec::new();
let mut rem = body.len();
loop {
if rem >= 255 {
segs.push(255u8);
rem -= 255;
} else {
segs.push(rem as u8);
break;
}
}
assert!(segs.len() <= 255, "packet too large for one page");
let mut page = Vec::with_capacity(27 + segs.len() + body.len());
page.extend_from_slice(b"OggS");
page.push(0); page.push(header_type);
page.extend_from_slice(&granule.to_le_bytes());
page.extend_from_slice(&serial.to_le_bytes());
page.extend_from_slice(&seq.to_le_bytes());
page.extend_from_slice(&0u32.to_le_bytes()); page.push(segs.len() as u8);
page.extend_from_slice(&segs);
page.extend_from_slice(body);
let crc = crc32(&page);
page[22..26].copy_from_slice(&crc.to_le_bytes());
out.extend_from_slice(&page);
}
fn main() {
let a: Vec<String> = std::env::args().collect();
let (rate, channels, pcm) = read_wav(&a[1]);
let bitrate: i32 = a.get(3).map(|s| s.parse().unwrap()).unwrap_or(32_000);
let app = match a.get(4).map(|s| s.as_str()) {
Some("voip") => Application::Voip,
_ => Application::Audio,
};
let ch = channels as usize;
let frame = rate as usize / 50;
let step = frame * ch;
let serial: u32 = 0x5255_5354;
let mut enc = OpusEncoder::new(rate as i32, ch, app).unwrap();
enc.bitrate_bps = bitrate;
if a.get(5).map(|s| s == "cbr").unwrap_or(false) {
enc.use_cbr = true;
}
let mut out = Vec::new();
let mut seq = 0u32;
let mut head = Vec::new();
head.extend_from_slice(b"OpusHead");
head.push(1);
head.push(channels as u8);
head.extend_from_slice(&312u16.to_le_bytes());
head.extend_from_slice(&rate.to_le_bytes());
head.extend_from_slice(&0i16.to_le_bytes());
head.push(0); write_page(&mut out, &head, 0, serial, seq, 0x02);
seq += 1;
let mut tags = Vec::new();
tags.extend_from_slice(b"OpusTags");
let vendor = b"rusty-opus";
tags.extend_from_slice(&(vendor.len() as u32).to_le_bytes());
tags.extend_from_slice(vendor);
tags.extend_from_slice(&0u32.to_le_bytes());
write_page(&mut out, &tags, 0, serial, seq, 0x00);
seq += 1;
let mut buf = vec![0u8; 4000];
let mut granule: u64 = 312;
let nframes = pcm.len() / step;
let mut total = 0usize;
for (i, chunk) in pcm.chunks_exact(step).enumerate() {
let n = enc.encode(chunk, frame, &mut buf).expect("encode");
total += n;
granule += (frame as u64) * 48000 / rate as u64;
let last = i + 1 == nframes;
write_page(&mut out, &buf[..n], granule, serial, seq, if last { 0x04 } else { 0x00 });
seq += 1;
}
std::fs::File::create(&a[2]).unwrap().write_all(&out).unwrap();
eprintln!(
"wrote {} ({} packets, {} payload bytes, {:.1} kbps)",
a[2],
nframes,
total,
total as f64 * 8.0 / (nframes as f64 * frame as f64 / rate as f64) / 1000.0
);
}
#[global_allocator]
static RUSTY_ALLOC: rusty_alloc_api::RustyAlloc = rusty_alloc_api::RustyAlloc;