use std::time::Duration;
use crate::{Activity, Error};
const RATES: [u32; 5] = [8_000, 12_000, 16_000, 24_000, 48_000];
const FRAME_DURATIONS: [u128; 6] = [2_500, 5_000, 10_000, 20_000, 40_000, 60_000];
pub(crate) fn pick_rate(input_rate: u32) -> u32 {
RATES.iter().copied().find(|&r| r >= input_rate).unwrap_or(48_000)
}
pub(crate) fn validate_rate(rate: u32) -> Result<(), Error> {
if RATES.contains(&rate) {
return Ok(());
}
Err(Error::Unsupported(format!(
"opus only supports 8/12/16/24/48 kHz (got {rate})"
)))
}
pub(crate) fn validate_channels(count: u32) -> Result<i32, Error> {
match count {
1 | 2 => Ok(count as i32),
other => Err(Error::Unsupported(format!(
"opus only supports 1 or 2 channels (got {other})"
))),
}
}
pub(crate) fn frame_size(sample_rate: u32, duration: Duration) -> Result<usize, Error> {
let micros = duration.as_micros();
if !FRAME_DURATIONS.contains(µs) {
return Err(Error::Unsupported(format!(
"opus frame duration must be 2.5/5/10/20/40/60 ms (got {micros} us)"
)));
}
Ok((sample_rate as u128 * micros / 1_000_000) as usize)
}
pub(crate) fn error(code: i32, context: &str) -> Error {
Error::Unsupported(format!("libopus {context} failed (code {code})"))
}
pub(crate) fn decode_error(code: i32) -> Error {
if code == unsafe_libopus::OPUS_INVALID_PACKET {
return Error::Decode(format!("libopus rejected the packet (code {code})"));
}
error(code, "opus_decode_float")
}
pub(crate) fn activity(packet: &[u8], in_dtx: bool) -> Activity {
if packet.is_empty() {
return if in_dtx { Activity::Dtx } else { Activity::Active };
}
if carries_nothing(packet) {
Activity::Dtx
} else {
Activity::Active
}
}
pub(crate) fn carries_nothing(packet: &[u8]) -> bool {
let Some((sizes, count)) = frame_sizes(packet) else {
return false;
};
match sizes.get(..count) {
Some(sizes) => !sizes.is_empty() && sizes.iter().all(|&size| size == 0),
None => false,
}
}
fn frame_sizes(packet: &[u8]) -> Option<([i16; 48], usize)> {
let len = i32::try_from(packet.len()).ok()?;
let mut sizes = [0i16; 48];
let count = unsafe {
unsafe_libopus::opus_packet_parse(
packet.as_ptr(),
len,
std::ptr::null_mut(),
std::ptr::null_mut(),
sizes.as_mut_ptr(),
std::ptr::null_mut(),
)
};
Some((sizes, usize::try_from(count).ok()?))
}
pub(crate) fn bitrate_floor(sample_rate: u32, frame_size: usize) -> u64 {
let frame_rate = u64::from(sample_rate) / frame_size.max(1) as u64;
let floor = 3 * frame_rate * 8;
if frame_rate < 50 { floor.max(2_400) } else { floor }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rate_picker_snaps_up() {
assert_eq!(pick_rate(44_100), 48_000);
assert_eq!(pick_rate(22_050), 24_000);
for &r in &RATES {
assert_eq!(pick_rate(r), r);
}
}
#[test]
fn activity_reads_the_opus_framing() {
assert_eq!(activity(&[0xf8], false), Activity::Dtx);
assert_eq!(activity(&[0x08], false), Activity::Dtx);
assert_eq!(activity(&[0xfb, 0x03], false), Activity::Dtx);
assert_eq!(activity(&[0xf8, 0xff, 0xfe], false), Activity::Active);
assert_eq!(activity(&[0xf8, 0xff], false), Activity::Active);
assert_eq!(activity(&[], true), Activity::Dtx);
assert_eq!(activity(&[], false), Activity::Active);
}
#[test]
fn activity_never_calls_a_coded_frame_silence() {
let mut refresh = vec![0xfb, 0x82, 57];
refresh.extend(std::iter::repeat_n(0xaa, 57));
let mut onset = vec![0xfb, 0x82, 0];
onset.extend(std::iter::repeat_n(0xaa, 57));
assert_eq!(activity(&refresh, true), Activity::Active);
assert_eq!(activity(&onset, true), Activity::Active);
assert_eq!(activity(&[0xfb, 0x82, 0], true), Activity::Dtx);
}
#[test]
fn bitrate_floor_matches_libopus() {
assert_eq!(bitrate_floor(48_000, 960), 1_200); assert_eq!(bitrate_floor(48_000, 120), 9_600); assert_eq!(bitrate_floor(48_000, 480), 2_400); assert_eq!(bitrate_floor(48_000, 1_920), 2_400); assert_eq!(bitrate_floor(48_000, 2_880), 2_400); }
}