fn codec_params(codec: Option<&str>, pt: Option<u8>) -> (f64, f64) {
let s = codec.map(|c| c.to_ascii_uppercase());
match (s.as_deref(), pt) {
(Some(c), _) if c.contains("PCMU") || c.contains("PCMA") || c.contains("G.711") => {
(0.0, 25.1)
}
(Some(c), _) if c.contains("G.729") => (10.0, 19.0),
(Some(c), _) if c.contains("G.723") => (15.0, 13.0),
(Some(c), _) if c.contains("G.722") => (4.0, 15.7),
(Some(c), _) if c.contains("OPUS") => (0.0, 25.0),
(Some(c), _) if c.contains("TELEPHONE") => (0.0, 25.0),
(_, Some(0)) | (_, Some(8)) => (0.0, 25.1),
_ => (10.0, 10.0),
}
}
fn delay_impairment(oneway_ms: f64) -> f64 {
let d = oneway_ms.max(0.0);
0.024 * d + 0.11 * (d - 177.3).max(0.0)
}
fn equipment_impairment(codec: Option<&str>, pt: Option<u8>, loss_pct: f64) -> f64 {
let (ie, bpl) = codec_params(codec, pt);
let p = loss_pct.max(0.0);
ie + (95.0 - ie) * p / (p / 1.0 + bpl)
}
pub fn estimate_mos(
codec: Option<&str>,
pt: Option<u8>,
loss_pct: f64,
oneway_ms: Option<f64>,
jitter_ms: Option<f64>,
) -> Option<f64> {
let oneway = oneway_ms.unwrap_or(0.0);
let jitter = jitter_ms.unwrap_or(0.0);
let dejitter = (40.0_f64).max(jitter * 3.0);
let d = oneway + dejitter;
let id = delay_impairment(d);
let ie = equipment_impairment(codec, pt, loss_pct);
let r = (93.2 - id - ie).clamp(0.0, 100.0);
let mos = if r >= 100.0 {
4.5
} else {
1.0 + 0.035 * r + 7e-6 * r * (r - 60.0) * (100.0 - r)
};
Some(mos.clamp(1.0, 4.5))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn g711_lossless_low_delay_is_high_mos() {
let m = estimate_mos(Some("PCMU"), Some(0), 0.0, Some(10.0), Some(2.0)).unwrap();
assert!(m > 4.0, "expected high MOS, got {m}");
}
#[test]
fn high_loss_lowers_mos() {
let good = estimate_mos(Some("PCMU"), Some(0), 0.0, Some(20.0), None).unwrap();
let bad = estimate_mos(Some("PCMU"), Some(0), 20.0, Some(20.0), None).unwrap();
assert!(bad < good, "{bad} should be < {good}");
}
#[test]
fn high_delay_lowers_mos() {
let low = estimate_mos(Some("PCMU"), Some(0), 0.0, Some(50.0), None).unwrap();
let high = estimate_mos(Some("PCMU"), Some(0), 0.0, Some(300.0), None).unwrap();
assert!(high < low, "{high} should be < {low}");
}
}