use core::ffi::{CStr, c_int};
use opusic_sys as sys;
const OPUS_SAMPLE_RATE_HZ: sys::opus_int32 = 48_000;
fn opus_strerror(code: c_int) -> String {
let ptr = unsafe { sys::opus_strerror(code) };
if ptr.is_null() {
return format!("opus error {code}");
}
unsafe { CStr::from_ptr(ptr) }
.to_string_lossy()
.into_owned()
}
fn frame_size_arg(samples: usize, what: &str) -> Result<c_int, String> {
c_int::try_from(samples).map_err(|_| format!("opus {what} frame too large: {samples} samples"))
}
fn max_bytes_arg(bytes: usize) -> Result<i32, String> {
i32::try_from(bytes).map_err(|_| format!("opus output buffer too large: {bytes} bytes"))
}
pub struct Encoder {
raw: *mut sys::OpusEncoder,
}
unsafe impl Send for Encoder {}
impl Encoder {
pub fn new_voip_mono() -> Result<Self, String> {
let mut error: c_int = sys::OPUS_OK;
let raw = unsafe {
sys::opus_encoder_create(
OPUS_SAMPLE_RATE_HZ,
1,
sys::OPUS_APPLICATION_VOIP,
&raw mut error,
)
};
if error != sys::OPUS_OK || raw.is_null() {
return Err(format!("opus encoder init: {}", opus_strerror(error)));
}
Ok(Self { raw })
}
pub fn encode(&mut self, pcm: &[i16], output: &mut [u8]) -> Result<usize, String> {
let frame_size = frame_size_arg(pcm.len(), "encode")?;
let max_bytes = max_bytes_arg(output.len())?;
let written = unsafe {
sys::opus_encode(
self.raw,
pcm.as_ptr(),
frame_size,
output.as_mut_ptr(),
max_bytes,
)
};
if written < 0 {
return Err(format!("opus encode: {}", opus_strerror(written)));
}
Ok(written as usize)
}
}
impl Drop for Encoder {
fn drop(&mut self) {
unsafe { sys::opus_encoder_destroy(self.raw) };
}
}
pub struct Decoder {
raw: *mut sys::OpusDecoder,
}
unsafe impl Send for Decoder {}
impl Decoder {
pub fn new_mono() -> Result<Self, String> {
let mut error: c_int = sys::OPUS_OK;
let raw = unsafe { sys::opus_decoder_create(OPUS_SAMPLE_RATE_HZ, 1, &raw mut error) };
if error != sys::OPUS_OK || raw.is_null() {
return Err(format!("opus decoder init: {}", opus_strerror(error)));
}
Ok(Self { raw })
}
pub fn decode(
&mut self,
packet: &[u8],
output: &mut [i16],
fec: bool,
) -> Result<usize, String> {
let frame_size = frame_size_arg(output.len(), "decode")?;
let len = max_bytes_arg(packet.len())?;
let samples = unsafe {
sys::opus_decode(
self.raw,
packet.as_ptr(),
len,
output.as_mut_ptr(),
frame_size,
c_int::from(fec),
)
};
if samples < 0 {
return Err(format!("opus decode: {}", opus_strerror(samples)));
}
Ok(samples as usize)
}
}
impl Drop for Decoder {
fn drop(&mut self) {
unsafe { sys::opus_decoder_destroy(self.raw) };
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
fn tone_20ms() -> Vec<i16> {
let frame = OPUS_SAMPLE_RATE_HZ as usize / 50;
(0..frame)
.map(|i| {
let t = i as f64 / f64::from(OPUS_SAMPLE_RATE_HZ);
(12_000.0 * (2.0 * PI * 440.0 * t).sin()) as i16
})
.collect()
}
#[test]
fn encode_then_decode_round_trips_a_tone() {
let mut encoder = Encoder::new_voip_mono().expect("encoder");
let mut decoder = Decoder::new_mono().expect("decoder");
let pcm = tone_20ms();
let mut packet = vec![0u8; 4_000];
let bytes = encoder.encode(&pcm, &mut packet).expect("encode");
assert!(bytes > 0 && bytes <= packet.len(), "packet length {bytes}");
let mut out = vec![0i16; 5_760];
let samples = decoder
.decode(&packet[..bytes], &mut out, false)
.expect("decode");
assert_eq!(samples, pcm.len(), "decoded sample count matches the frame");
}
#[test]
fn encode_rejects_a_bogus_frame_size() {
let mut encoder = Encoder::new_voip_mono().expect("encoder");
let mut packet = vec![0u8; 4_000];
let err = encoder
.encode(&vec![0i16; 137], &mut packet)
.expect_err("bogus frame size must be rejected");
assert!(err.contains("opus encode"), "error was: {err}");
}
}