use crate::CoreError;
use ::opus::{Application, Bitrate, Channels};
pub const OPUS_DEFAULT_PAYLOAD_TYPE: u8 = 111;
pub const OPUS_RTP_CLOCK_RATE: u32 = 48_000;
pub const OPUS_RTP_SAMPLES_PER_FRAME: u32 = 960;
pub const OPUS_PCM_SAMPLE_RATE: u32 = 16_000;
pub const OPUS_FRAME_SAMPLES: usize = 320;
pub const OPUS_MAX_FRAME_SAMPLES: usize = 1920;
pub const OPUS_DEFAULT_BITRATE: i32 = 28_000;
pub const OPUS_DEFAULT_PACKET_LOSS_PERC: i32 = 10;
const MAX_PACKET_BYTES: usize = 1500;
fn opus_err(op: &str, e: ::opus::Error) -> CoreError {
CoreError::Audio(format!("opus {op}: {e}"))
}
pub struct OpusEncoder {
inner: ::opus::Encoder,
}
impl OpusEncoder {
pub fn new() -> Result<Self, CoreError> {
let mut inner =
::opus::Encoder::new(OPUS_PCM_SAMPLE_RATE, Channels::Mono, Application::Voip)
.map_err(|e| opus_err("encoder create", e))?;
inner
.set_bitrate(Bitrate::Bits(OPUS_DEFAULT_BITRATE))
.map_err(|e| opus_err("set_bitrate", e))?;
inner
.set_inband_fec(true)
.map_err(|e| opus_err("set_inband_fec", e))?;
inner
.set_packet_loss_perc(OPUS_DEFAULT_PACKET_LOSS_PERC)
.map_err(|e| opus_err("set_packet_loss_perc", e))?;
inner.set_dtx(false).map_err(|e| opus_err("set_dtx", e))?;
Ok(Self { inner })
}
pub fn encode(&mut self, pcm: &[i16], out: &mut Vec<u8>) -> Result<usize, CoreError> {
if pcm.len() != OPUS_FRAME_SAMPLES {
return Err(CoreError::Audio(format!(
"opus encode: expected one 20 ms frame ({OPUS_FRAME_SAMPLES} samples), got {}",
pcm.len()
)));
}
let start = out.len();
out.resize(start + MAX_PACKET_BYTES, 0);
let n = self
.inner
.encode(pcm, &mut out[start..])
.map_err(|e| opus_err("encode", e))?;
out.truncate(start + n);
Ok(n)
}
}
pub struct OpusDecoder {
inner: ::opus::Decoder,
}
impl OpusDecoder {
pub fn new() -> Result<Self, CoreError> {
let inner = ::opus::Decoder::new(OPUS_PCM_SAMPLE_RATE, Channels::Mono)
.map_err(|e| opus_err("decoder create", e))?;
Ok(Self { inner })
}
pub fn decode(&mut self, payload: &[u8], out: &mut Vec<i16>) -> Result<usize, CoreError> {
self.decode_inner(payload, out, false)
}
pub fn decode_fec(
&mut self,
next_payload: &[u8],
out: &mut Vec<i16>,
) -> Result<usize, CoreError> {
self.decode_inner(next_payload, out, true)
}
pub fn conceal(&mut self, out: &mut Vec<i16>) -> Result<usize, CoreError> {
let start = out.len();
out.resize(start + OPUS_FRAME_SAMPLES, 0);
let n = self
.inner
.decode(&[], &mut out[start..], false)
.map_err(|e| opus_err("plc decode", e))?;
out.truncate(start + n);
Ok(n)
}
fn decode_inner(
&mut self,
payload: &[u8],
out: &mut Vec<i16>,
fec: bool,
) -> Result<usize, CoreError> {
let start = out.len();
if fec {
let lost = ::opus::packet::get_nb_samples(payload, OPUS_PCM_SAMPLE_RATE)
.map_err(|e| opus_err("fec frame sizing", e))?;
out.resize(start + lost, 0);
} else {
out.resize(start + OPUS_MAX_FRAME_SAMPLES, 0);
}
let n = self
.inner
.decode(payload, &mut out[start..], fec)
.map_err(|e| opus_err(if fec { "fec decode" } else { "decode" }, e))?;
out.truncate(start + n);
Ok(n)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sine_frame(freq: f32, amplitude: f32, offset: usize) -> Vec<i16> {
(0..OPUS_FRAME_SAMPLES)
.map(|i| {
let t = (offset + i) as f32 / OPUS_PCM_SAMPLE_RATE as f32;
((t * freq * 2.0 * std::f32::consts::PI).sin() * amplitude) as i16
})
.collect()
}
fn rms(pcm: &[i16]) -> f64 {
if pcm.is_empty() {
return 0.0;
}
let sum: f64 = pcm.iter().map(|&s| (s as f64) * (s as f64)).sum();
(sum / pcm.len() as f64).sqrt()
}
#[test]
fn constants_match_rfc7587_and_the_locked_profile() {
assert_eq!(OPUS_DEFAULT_PAYLOAD_TYPE, 111);
assert_eq!(OPUS_RTP_CLOCK_RATE, 48_000);
assert_eq!(OPUS_RTP_SAMPLES_PER_FRAME, 960);
assert_eq!(OPUS_PCM_SAMPLE_RATE, 16_000);
assert_eq!(OPUS_FRAME_SAMPLES, 320);
assert_eq!(OPUS_MAX_FRAME_SAMPLES, 1920);
assert_eq!(OPUS_RTP_SAMPLES_PER_FRAME, OPUS_RTP_CLOCK_RATE / 50);
assert_eq!(OPUS_FRAME_SAMPLES as u32, OPUS_PCM_SAMPLE_RATE / 50);
}
#[test]
fn encode_decode_round_trip_preserves_signal_energy() {
let mut enc = OpusEncoder::new().unwrap();
let mut dec = OpusDecoder::new().unwrap();
let mut input_rms = 0.0;
let mut decoded_tail = Vec::new();
for i in 0..20 {
let frame = sine_frame(440.0, 8000.0, i * OPUS_FRAME_SAMPLES);
input_rms = rms(&frame);
let mut packet = Vec::new();
let n = enc.encode(&frame, &mut packet).unwrap();
assert!(n > 0 && n <= 1275, "packet size {n} out of range");
assert_eq!(packet.len(), n);
let mut pcm = Vec::new();
let produced = dec.decode(&packet, &mut pcm).unwrap();
assert_eq!(produced, OPUS_FRAME_SAMPLES);
assert_eq!(pcm.len(), OPUS_FRAME_SAMPLES);
if i >= 5 {
decoded_tail.extend_from_slice(&pcm);
}
}
let out_rms = rms(&decoded_tail);
assert!(
(out_rms - input_rms).abs() / input_rms < 0.5,
"round-trip energy drifted too far: in {input_rms:.0}, out {out_rms:.0}"
);
}
#[test]
fn encoder_profile_is_the_locked_one() {
let mut enc = OpusEncoder::new().unwrap();
assert!(enc.inner.get_inband_fec().unwrap());
assert_eq!(
enc.inner.get_packet_loss_perc().unwrap(),
OPUS_DEFAULT_PACKET_LOSS_PERC
);
assert!(!enc.inner.get_dtx().unwrap());
assert_eq!(
enc.inner.get_bitrate().unwrap(),
Bitrate::Bits(OPUS_DEFAULT_BITRATE)
);
}
#[test]
fn encode_rejects_non_20ms_input() {
let mut enc = OpusEncoder::new().unwrap();
let mut out = Vec::new();
assert!(enc.encode(&[0i16; 160], &mut out).is_err());
assert!(enc.encode(&[0i16; 321], &mut out).is_err());
assert!(out.is_empty(), "failed encode must not write output");
}
#[test]
fn encode_appends_rather_than_replacing() {
let mut enc = OpusEncoder::new().unwrap();
let mut out = vec![0xAAu8, 0xBB];
let frame = sine_frame(440.0, 8000.0, 0);
let n = enc.encode(&frame, &mut out).unwrap();
assert_eq!(out.len(), 2 + n);
assert_eq!(&out[..2], &[0xAA, 0xBB]);
}
fn lost_loud_frame_recovery_rms(fec: bool) -> f64 {
let mut enc = if fec {
OpusEncoder::new().unwrap()
} else {
let mut inner =
::opus::Encoder::new(OPUS_PCM_SAMPLE_RATE, Channels::Mono, Application::Voip)
.unwrap();
inner
.set_bitrate(Bitrate::Bits(OPUS_DEFAULT_BITRATE))
.unwrap();
inner.set_inband_fec(false).unwrap();
OpusEncoder { inner }
};
let mut dec = OpusDecoder::new().unwrap();
const LOUD: usize = 6;
let mut packets = Vec::new();
for i in 0..=LOUD + 1 {
let frame = if i == LOUD {
sine_frame(440.0, 12000.0, i * OPUS_FRAME_SAMPLES)
} else {
sine_frame(440.0, 30.0, i * OPUS_FRAME_SAMPLES)
};
let mut packet = Vec::new();
enc.encode(&frame, &mut packet).unwrap();
packets.push(packet);
}
let mut pcm = Vec::new();
for packet in &packets[..LOUD] {
pcm.clear();
dec.decode(packet, &mut pcm).unwrap();
}
let mut recovered = Vec::new();
let n = dec.decode_fec(&packets[LOUD + 1], &mut recovered).unwrap();
assert_eq!(n, OPUS_FRAME_SAMPLES);
rms(&recovered)
}
#[test]
fn fec_actually_recovers_a_lost_frame() {
let with_fec = lost_loud_frame_recovery_rms(true);
let without_fec = lost_loud_frame_recovery_rms(false);
assert!(
with_fec > 1000.0,
"FEC recovery is near-silent (rms {with_fec:.1}) — redundancy not embedded?"
);
assert!(
with_fec > without_fec * 4.0,
"FEC recovery ({with_fec:.1}) not clearly better than PLC baseline ({without_fec:.1})"
);
}
#[test]
fn conceal_produces_a_plausible_frame_after_loss() {
let mut enc = OpusEncoder::new().unwrap();
let mut dec = OpusDecoder::new().unwrap();
let mut pcm = Vec::new();
for i in 0..10 {
let frame = sine_frame(440.0, 8000.0, i * OPUS_FRAME_SAMPLES);
let mut packet = Vec::new();
enc.encode(&frame, &mut packet).unwrap();
pcm.clear();
dec.decode(&packet, &mut pcm).unwrap();
}
let mut concealed = Vec::new();
let n = dec.conceal(&mut concealed).unwrap();
assert_eq!(n, OPUS_FRAME_SAMPLES);
assert!(
rms(&concealed) > 500.0,
"concealment is near-silent (rms {:.1})",
rms(&concealed)
);
}
#[test]
fn decode_handles_frames_larger_than_20ms() {
let mut enc =
::opus::Encoder::new(OPUS_PCM_SAMPLE_RATE, Channels::Mono, Application::Voip).unwrap();
let frame: Vec<i16> = (0..OPUS_FRAME_SAMPLES * 2)
.map(|i| {
let t = i as f32 / OPUS_PCM_SAMPLE_RATE as f32;
((t * 440.0 * 2.0 * std::f32::consts::PI).sin() * 8000.0) as i16
})
.collect();
let packet = enc.encode_vec(&frame, MAX_PACKET_BYTES).unwrap();
let mut dec = OpusDecoder::new().unwrap();
let mut pcm = Vec::new();
let n = dec.decode(&packet, &mut pcm).unwrap();
assert_eq!(n, OPUS_FRAME_SAMPLES * 2, "40 ms frame must decode fully");
assert_eq!(pcm.len(), n);
}
#[test]
fn decode_rejects_garbage() {
let mut dec = OpusDecoder::new().unwrap();
let mut pcm = Vec::new();
let garbage = vec![0xFFu8; 3];
assert!(dec.decode(&garbage, &mut pcm).is_err());
}
}