use crate::media::{AdaptiveBitrate, G711Codec, G711Variant, G722Codec, OpusCodec, OpusConfig};
use crate::sdp::Codec;
pub enum SessionCodec {
G711(G711Codec),
G722(Box<G722Codec>),
Opus(Box<OpusCodec>),
}
impl SessionCodec {
pub fn for_negotiated(negotiated: &Codec) -> Result<Self, String> {
match negotiated.encoding.to_lowercase().as_str() {
"pcmu" => Ok(SessionCodec::G711(G711Codec::new(G711Variant::MuLaw))),
"pcma" => Ok(SessionCodec::G711(G711Codec::new(G711Variant::ALaw))),
"g722" => Ok(SessionCodec::G722(Box::default())),
"opus" => OpusCodec::with_config(OpusConfig::fullband_speech())
.map(|c| SessionCodec::Opus(Box::new(c)))
.map_err(|e| format!("failed to build Opus codec: {e}")),
_ => Err(format!(
"unsupported codec encoding: {}",
negotiated.encoding
)),
}
}
pub fn encode(&mut self, samples: &[i16]) -> Result<Vec<u8>, String> {
match self {
SessionCodec::G711(c) => Ok(c.encode(samples)),
SessionCodec::G722(c) => Ok(c.encode(samples)),
SessionCodec::Opus(c) => c.encode(samples),
}
}
pub fn decode(&mut self, payload: &[u8]) -> Result<Vec<i16>, String> {
match self {
SessionCodec::G711(c) => Ok(c.decode(payload)),
SessionCodec::G722(c) => Ok(c.decode(payload)),
SessionCodec::Opus(c) => c.decode(payload),
}
}
pub fn samples_per_frame(&self) -> usize {
match self {
SessionCodec::G711(c) => c.samples_per_frame(),
SessionCodec::G722(c) => c.samples_per_frame(),
SessionCodec::Opus(c) => c.samples_per_frame(),
}
}
pub fn as_adaptive_mut(&mut self) -> Option<&mut dyn AdaptiveBitrate> {
match self {
SessionCodec::Opus(c) => Some(c.as_mut() as &mut dyn AdaptiveBitrate),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn codec(encoding: &str) -> Codec {
Codec::new(0, encoding, 8000)
}
#[test]
fn for_negotiated_routes_pcmu() {
let sc = SessionCodec::for_negotiated(&codec("PCMU")).unwrap();
match sc {
SessionCodec::G711(c) => assert_eq!(c.variant(), G711Variant::MuLaw),
_ => panic!("expected G711(MuLaw)"),
}
}
#[test]
fn for_negotiated_routes_pcma() {
let sc = SessionCodec::for_negotiated(&codec("PCMA")).unwrap();
match sc {
SessionCodec::G711(c) => assert_eq!(c.variant(), G711Variant::ALaw),
_ => panic!("expected G711(ALaw)"),
}
}
#[test]
fn for_negotiated_routes_g722() {
let sc = SessionCodec::for_negotiated(&codec("G722")).unwrap();
assert!(matches!(sc, SessionCodec::G722(_)));
}
#[test]
fn for_negotiated_routes_opus() {
let sc = SessionCodec::for_negotiated(&codec("opus")).unwrap();
assert!(matches!(sc, SessionCodec::Opus(_)));
}
#[test]
fn for_negotiated_is_case_insensitive() {
for name in ["PCMU", "pcmu", "Pcmu"] {
let sc = SessionCodec::for_negotiated(&codec(name)).unwrap();
match sc {
SessionCodec::G711(c) => assert_eq!(c.variant(), G711Variant::MuLaw),
_ => panic!("expected G711(MuLaw) for {name}"),
}
}
for name in ["OPUS", "opus", "Opus"] {
let sc = SessionCodec::for_negotiated(&codec(name)).unwrap();
assert!(
matches!(sc, SessionCodec::Opus(_)),
"expected Opus for {name}"
);
}
}
#[test]
fn for_negotiated_rejects_unknown() {
match SessionCodec::for_negotiated(&codec("AMR")) {
Ok(_) => panic!("AMR must be rejected as unsupported"),
Err(e) => assert!(e.contains("AMR"), "error should mention encoding: {e}"),
}
}
#[test]
fn as_adaptive_mut_only_for_opus() {
let mut g711 = SessionCodec::for_negotiated(&codec("PCMU")).unwrap();
assert!(g711.as_adaptive_mut().is_none());
let mut g722 = SessionCodec::for_negotiated(&codec("G722")).unwrap();
assert!(g722.as_adaptive_mut().is_none());
let mut opus = SessionCodec::for_negotiated(&codec("opus")).unwrap();
assert!(opus.as_adaptive_mut().is_some());
}
}