use anyhow::{anyhow, Context, Result};
pub const SAMPLE_RATE: u32 = 48_000;
pub const CHANNELS: usize = 2;
pub const SAMPLES_PER_CHANNEL: usize = 960;
pub const SAMPLES_PER_FRAME: usize = SAMPLES_PER_CHANNEL * CHANNELS;
const MAX_PACKET_BYTES: usize = 1_275;
fn require_frame_len(samples: usize) -> Result<()> {
if samples != SAMPLES_PER_FRAME {
return Err(anyhow!(
"Opus frame must be {SAMPLES_PER_FRAME} interleaved samples ({SAMPLES_PER_CHANNEL} per channel), got {samples}"
));
}
Ok(())
}
pub struct OpusEncoder {
inner: opus::Encoder,
}
impl OpusEncoder {
pub fn new() -> Result<Self> {
let inner = opus::Encoder::new(
SAMPLE_RATE,
opus::Channels::Stereo,
opus::Application::Audio,
)
.context("creating 48 kHz stereo Opus encoder")?;
Ok(Self { inner })
}
pub fn encode(&mut self, pcm: &[f32]) -> Result<Vec<u8>> {
require_frame_len(pcm.len())?;
let mut packet = vec![0; MAX_PACKET_BYTES];
let bytes = self
.inner
.encode_float(pcm, &mut packet)
.context("encoding Opus frame")?;
packet.truncate(bytes);
Ok(packet)
}
}
pub struct OpusDecoder {
inner: opus::Decoder,
}
impl OpusDecoder {
pub fn new() -> Result<Self> {
let inner = opus::Decoder::new(SAMPLE_RATE, opus::Channels::Stereo)
.context("creating 48 kHz stereo Opus decoder")?;
Ok(Self { inner })
}
pub fn decode(&mut self, packet: &[u8], pcm: &mut [f32]) -> Result<usize> {
require_frame_len(pcm.len())?;
self.inner
.decode_float(packet, pcm, false)
.context("decoding Opus packet")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encoder_rejects_a_non_20_ms_stereo_frame() {
let mut encoder = OpusEncoder::new().expect("creates encoder");
let error = encoder
.encode(&vec![0.0; SAMPLES_PER_FRAME / 2])
.expect_err("rejects short frame");
let message = error.to_string();
assert!(message.contains(&SAMPLES_PER_FRAME.to_string()));
assert!(message.contains(&(SAMPLES_PER_FRAME / 2).to_string()));
}
}