const LOG_GUARD_EPSILON: f64 = 1e-10;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusMode {
Silk,
Celt,
Hybrid,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OpusBandwidth {
NarrowBand,
MediumBand,
WideBand,
SuperWideBand,
FullBand,
}
impl OpusBandwidth {
#[inline]
#[must_use]
pub const fn max_frequency_hz(self) -> f32 {
match self {
Self::NarrowBand => 4_000.0,
Self::MediumBand => 6_000.0,
Self::WideBand => 8_000.0,
Self::SuperWideBand => 12_000.0,
Self::FullBand => 20_000.0,
}
}
#[inline]
#[must_use]
pub const fn supports_silk(self) -> bool {
matches!(self, Self::NarrowBand | Self::MediumBand | Self::WideBand)
}
#[inline]
#[must_use]
pub const fn supports_celt(self) -> bool {
!matches!(self, Self::NarrowBand | Self::MediumBand)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct OpusConfig {
pub mode: Option<OpusMode>,
pub bandwidth: OpusBandwidth,
pub bit_budget: u32,
pub frame_size_ms: f32,
pub min_bits_per_band: u8,
}
impl OpusConfig {
#[inline]
#[must_use]
pub fn new(bit_budget: u32) -> Self {
Self {
mode: None,
bandwidth: OpusBandwidth::FullBand,
bit_budget,
frame_size_ms: 20.0,
min_bits_per_band: 1,
}
}
#[inline]
#[must_use]
pub fn with_mode(mode: OpusMode, bit_budget: u32) -> Self {
Self {
mode: Some(mode),
bandwidth: OpusBandwidth::FullBand,
bit_budget,
frame_size_ms: 20.0,
min_bits_per_band: 1,
}
}
}
#[must_use]
pub fn detect_mode(samples: &[f32], _sample_rate: u32, bandwidth: OpusBandwidth) -> OpusMode {
if !bandwidth.supports_celt() {
return OpusMode::Silk;
}
if !bandwidth.supports_silk() {
return OpusMode::Celt;
}
let n = samples.len();
if n < 4 {
return OpusMode::Celt;
}
const N_SUB_BANDS: usize = 8;
let band_size = n / N_SUB_BANDS;
if band_size == 0 {
return OpusMode::Celt;
}
let mut band_energies = [0.0f64; N_SUB_BANDS];
for b in 0..N_SUB_BANDS {
let start = b * band_size;
let end = ((b + 1) * band_size).min(n);
band_energies[b] = samples[start..end]
.iter()
.map(|&x| (x as f64).powi(2))
.sum::<f64>()
+ LOG_GUARD_EPSILON; }
let arith_mean = band_energies.iter().sum::<f64>() / N_SUB_BANDS as f64;
let log_sum: f64 = band_energies.iter().map(|&e| e.ln()).sum::<f64>();
let geom_mean = (log_sum / N_SUB_BANDS as f64).exp();
let sfm = (geom_mean / arith_mean) as f32;
if sfm > 0.8 {
OpusMode::Celt
} else if sfm < 0.4 {
OpusMode::Silk
} else {
OpusMode::Hybrid
}
}