use crate::sdp::codec::SdpMediaType;
use crate::sdp::codec_string::{CodecString, CodecStringEntry};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CodecImplementation {
name: String,
modname: Option<String>,
media_type: Option<SdpMediaType>,
rate: Option<u32>,
ptime: Option<u32>,
bitrate: Option<u32>,
channels: Option<u32>,
}
impl CodecImplementation {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
modname: None,
media_type: None,
rate: None,
ptime: None,
bitrate: None,
channels: None,
}
}
pub fn with_media_type(mut self, media_type: SdpMediaType) -> Self {
self.media_type = Some(media_type);
self
}
pub fn with_modname(mut self, modname: impl Into<String>) -> Self {
self.modname = Some(modname.into());
self
}
pub fn with_rate(mut self, rate: u32) -> Self {
self.rate = Some(rate);
self
}
pub fn with_ptime(mut self, ptime: u32) -> Self {
self.ptime = Some(ptime);
self
}
pub fn with_bitrate(mut self, bitrate: u32) -> Self {
self.bitrate = Some(bitrate);
self
}
pub fn with_channels(mut self, channels: u32) -> Self {
self.channels = Some(channels);
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn modname(&self) -> Option<&str> {
self.modname
.as_deref()
}
pub fn media_type(&self) -> Option<&SdpMediaType> {
self.media_type
.as_ref()
}
pub fn rate(&self) -> Option<u32> {
self.rate
}
pub fn ptime(&self) -> Option<u32> {
self.ptime
}
pub fn bitrate(&self) -> Option<u32> {
self.bitrate
}
pub fn channels(&self) -> Option<u32> {
self.channels
}
}
fn matches_implementation(entry: &CodecStringEntry, imp: &CodecImplementation) -> bool {
if !entry
.name()
.eq_ignore_ascii_case(imp.name())
{
return false;
}
if let Some(entry_mod) = entry.modname() {
match imp.modname() {
Some(imp_mod) if entry_mod.eq_ignore_ascii_case(imp_mod) => {}
Some(_) => return false,
None => {}
}
}
if matches!(imp.media_type(), Some(SdpMediaType::Video)) {
return true;
}
let is_g722 = entry
.name()
.eq_ignore_ascii_case("g722");
if !is_g722 {
if let (Some(er), Some(ir)) = (nonzero(entry.rate()), imp.rate()) {
if er != ir {
return false;
}
}
}
if let (Some(ep), Some(ip)) = (nonzero(entry.ptime()), imp.ptime()) {
if ep != ip {
return false;
}
}
if let (Some(eb), Some(ib)) = (nonzero(entry.bitrate()), imp.bitrate()) {
if eb != ib {
return false;
}
}
if let Some(ic) = imp.channels() {
let required = entry
.channels()
.unwrap_or(1);
if required != 0 && required != ic {
return false;
}
}
true
}
fn nonzero(v: Option<u32>) -> Option<u32> {
v.filter(|&n| n != 0)
}
impl CodecString {
pub fn retain_available<'a, I>(&mut self, implementations: I) -> Vec<CodecStringEntry>
where
I: IntoIterator<Item = &'a CodecImplementation>,
{
let impls: Vec<&CodecImplementation> = implementations
.into_iter()
.collect();
let mut removed = Vec::new();
for entry in std::mem::take(self) {
if impls
.iter()
.any(|imp| matches_implementation(&entry, imp))
{
self.push(entry);
} else {
removed.push(entry);
}
}
removed
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(s: &str) -> CodecString {
s.parse()
.unwrap()
}
#[test]
fn unavailable_codec_removed_case_insensitive_both_directions() {
let mut cs = parse("pcmu@20i,EVS~mode=1,AMR");
let impls = vec![
CodecImplementation::new("PCMU"),
CodecImplementation::new("amr"),
];
let removed = cs.retain_available(&impls);
assert_eq!(removed.len(), 1);
assert_eq!(removed[0].name(), "EVS");
assert_eq!(removed[0].fmtp(), Some("mode=1"));
assert_eq!(cs.len(), 2);
assert!(cs.contains_name("pcmu"));
assert!(cs.contains_name("AMR"));
}
#[test]
fn amr_ptime_mismatch_is_removed() {
let mut cs = parse("AMR@8000h@40i");
let impls = vec![CodecImplementation::new("AMR")
.with_rate(8000)
.with_ptime(20)];
let removed = cs.retain_available(&impls);
assert_eq!(removed.len(), 1);
assert_eq!(removed[0].name(), "AMR");
assert!(cs.is_empty());
}
#[test]
fn amr_ptime_mismatch_survives_name_only_implementation_list() {
let mut cs = parse("AMR@8000h@40i");
let impls = vec![CodecImplementation::new("AMR")];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 1);
}
#[test]
fn modname_mismatch_is_removed() {
let mut cs = CodecString::new();
cs.push(
CodecStringEntry::new("EVS")
.unwrap()
.with_module("mod_evs")
.unwrap(),
);
let impls = vec![CodecImplementation::new("EVS").with_modname("mod_other")];
let removed = cs.retain_available(&impls);
assert_eq!(removed.len(), 1);
assert!(cs.is_empty());
}
#[test]
fn modname_unknown_on_implementation_does_not_constrain() {
let mut cs = CodecString::new();
cs.push(
CodecStringEntry::new("EVS")
.unwrap()
.with_module("mod_evs")
.unwrap(),
);
let impls = vec![CodecImplementation::new("EVS")];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 1);
}
#[test]
fn removed_entries_preserve_original_order() {
let mut cs = parse("EVS,H264,AMR@8000h@40i");
let impls = vec![CodecImplementation::new("PCMU")];
let removed = cs.retain_available(&impls);
assert_eq!(removed.len(), 3);
assert_eq!(removed[0].name(), "EVS");
assert_eq!(removed[1].name(), "H264");
assert_eq!(removed[2].name(), "AMR");
assert!(cs.is_empty());
}
#[test]
fn video_implementation_matches_regardless_of_rate() {
let mut cs = parse("VP8@8000h");
let impls = vec![CodecImplementation::new("VP8")
.with_media_type(SdpMediaType::Video)
.with_rate(90000)];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 1);
}
#[test]
fn g722_matches_at_either_advertised_rate() {
let mut cs = parse("G722@16000h@20i,G722@8000h@20i");
let impls = vec![CodecImplementation::new("G722")
.with_rate(8000)
.with_ptime(20)];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 2);
}
#[test]
fn pcmu_zero_rate_does_not_constrain() {
let mut cs = parse("PCMU@0h");
let impls = vec![CodecImplementation::new("PCMU").with_rate(8000)];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 1);
}
#[test]
fn pcmu_zero_ptime_does_not_constrain() {
let mut cs = parse("PCMU@0i");
let impls = vec![CodecImplementation::new("PCMU").with_ptime(20)];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 1);
}
#[test]
fn pcmu_zero_bitrate_does_not_constrain() {
let mut cs = parse("PCMU@0b");
let impls = vec![CodecImplementation::new("PCMU").with_bitrate(64000)];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 1);
}
#[test]
fn pcmu_zero_channels_does_not_constrain() {
let mut cs = parse("PCMU@0c");
let impls = vec![CodecImplementation::new("PCMU").with_channels(2)];
let removed = cs.retain_available(&impls);
assert!(removed.is_empty());
assert_eq!(cs.len(), 1);
}
}