#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CodecCapability {
pub name: String,
pub profiles: Vec<String>,
pub max_level: u32,
pub hardware_accelerated: bool,
}
impl CodecCapability {
#[must_use]
pub fn new(
name: impl Into<String>,
profiles: Vec<String>,
max_level: u32,
hardware_accelerated: bool,
) -> Self {
Self {
name: name.into(),
profiles,
max_level,
hardware_accelerated,
}
}
#[must_use]
pub fn supports_profile(&self, profile: &str) -> bool {
self.profiles.iter().any(|p| p == profile)
}
#[must_use]
pub fn is_hw_accelerated(&self) -> bool {
self.hardware_accelerated
}
}
#[derive(Debug, Default)]
pub struct CodecNegotiator {
pub local_caps: Vec<CodecCapability>,
pub remote_caps: Vec<CodecCapability>,
}
impl CodecNegotiator {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_local(&mut self, cap: CodecCapability) {
self.local_caps.push(cap);
}
pub fn add_remote(&mut self, cap: CodecCapability) {
self.remote_caps.push(cap);
}
#[must_use]
pub fn common_codecs(&self) -> Vec<&str> {
self.local_caps
.iter()
.filter(|l| self.remote_caps.iter().any(|r| r.name == l.name))
.map(|l| l.name.as_str())
.collect()
}
#[must_use]
pub fn preferred_codec(&self) -> Option<&str> {
let common = self.common_codecs();
if common.is_empty() {
return None;
}
for name in &common {
if let Some(cap) = self.local_caps.iter().find(|c| &c.name.as_str() == name) {
if cap.hardware_accelerated {
return Some(name);
}
}
}
common.into_iter().next()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NegotiationResult {
pub selected_codec: String,
pub profile: String,
pub level: u32,
pub hardware_accelerated: bool,
}
impl NegotiationResult {
#[must_use]
pub fn is_hardware(&self) -> bool {
self.hardware_accelerated
}
}
#[must_use]
pub fn negotiate(
local: &[CodecCapability],
remote: &[CodecCapability],
) -> Option<NegotiationResult> {
let mut ordered: Vec<&CodecCapability> = local.iter().collect();
ordered.sort_by_key(|c| u8::from(!c.hardware_accelerated));
for local_cap in ordered {
if let Some(remote_cap) = remote.iter().find(|r| r.name == local_cap.name) {
let common_profile = local_cap
.profiles
.iter()
.find(|p| remote_cap.profiles.contains(p));
if let Some(profile) = common_profile {
let level = local_cap.max_level.min(remote_cap.max_level);
return Some(NegotiationResult {
selected_codec: local_cap.name.clone(),
profile: profile.clone(),
level,
hardware_accelerated: local_cap.hardware_accelerated,
});
}
}
}
None
}
use crate::types::{PixelFormat, SampleFormat};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PixelFormatCaps {
pub formats: Vec<PixelFormat>,
}
impl PixelFormatCaps {
#[must_use]
pub fn new(formats: Vec<PixelFormat>) -> Self {
Self { formats }
}
#[must_use]
pub fn negotiate(&self, other: &Self) -> Option<PixelFormat> {
self.formats
.iter()
.find(|f| other.formats.contains(f))
.copied()
}
#[must_use]
pub fn common_formats(&self, other: &Self) -> Vec<PixelFormat> {
self.formats
.iter()
.filter(|f| other.formats.contains(f))
.copied()
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SampleFormatCaps {
pub formats: Vec<SampleFormat>,
}
impl SampleFormatCaps {
#[must_use]
pub fn new(formats: Vec<SampleFormat>) -> Self {
Self { formats }
}
#[must_use]
pub fn negotiate(&self, other: &Self) -> Option<SampleFormat> {
self.formats
.iter()
.find(|f| other.formats.contains(f))
.copied()
}
#[must_use]
pub fn common_formats(&self, other: &Self) -> Vec<SampleFormat> {
self.formats
.iter()
.filter(|f| other.formats.contains(f))
.copied()
.collect()
}
}
#[derive(Debug, Clone)]
pub struct FormatCapabilities {
pub codec_name: String,
pub pixel_formats: PixelFormatCaps,
pub sample_formats: SampleFormatCaps,
pub sample_rates: Vec<u32>,
pub channel_counts: Vec<u32>,
}
impl FormatCapabilities {
#[must_use]
pub fn video(codec_name: impl Into<String>, pixel_formats: Vec<PixelFormat>) -> Self {
Self {
codec_name: codec_name.into(),
pixel_formats: PixelFormatCaps::new(pixel_formats),
sample_formats: SampleFormatCaps::new(vec![]),
sample_rates: vec![],
channel_counts: vec![],
}
}
#[must_use]
pub fn audio(
codec_name: impl Into<String>,
sample_formats: Vec<SampleFormat>,
sample_rates: Vec<u32>,
channel_counts: Vec<u32>,
) -> Self {
Self {
codec_name: codec_name.into(),
pixel_formats: PixelFormatCaps::new(vec![]),
sample_formats: SampleFormatCaps::new(sample_formats),
sample_rates,
channel_counts,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatNegotiationResult {
pub pixel_format: Option<PixelFormat>,
pub sample_format: Option<SampleFormat>,
pub sample_rate: Option<u32>,
pub channel_count: Option<u32>,
}
#[must_use]
pub fn negotiate_formats(
decoder: &FormatCapabilities,
encoder: &FormatCapabilities,
) -> Option<FormatNegotiationResult> {
let is_video =
!decoder.pixel_formats.formats.is_empty() && !encoder.pixel_formats.formats.is_empty();
let is_audio =
!decoder.sample_formats.formats.is_empty() && !encoder.sample_formats.formats.is_empty();
if !is_video && !is_audio {
return None;
}
let pixel_format = if is_video {
let pf = decoder.pixel_formats.negotiate(&encoder.pixel_formats);
pf?;
pf
} else {
None
};
let sample_format = if is_audio {
let sf = decoder.sample_formats.negotiate(&encoder.sample_formats);
sf?;
sf
} else {
None
};
let sample_rate = if is_audio {
decoder
.sample_rates
.iter()
.find(|r| encoder.sample_rates.contains(r))
.copied()
} else {
None
};
let channel_count = if is_audio {
decoder
.channel_counts
.iter()
.find(|c| encoder.channel_counts.contains(c))
.copied()
} else {
None
};
Some(FormatNegotiationResult {
pixel_format,
sample_format,
sample_rate,
channel_count,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolutionRange {
pub min_width: u32,
pub max_width: u32,
pub min_height: u32,
pub max_height: u32,
}
impl ResolutionRange {
#[must_use]
pub fn new(min_width: u32, max_width: u32, min_height: u32, max_height: u32) -> Self {
Self {
min_width,
max_width,
min_height,
max_height,
}
}
#[must_use]
pub fn contains(&self, width: u32, height: u32) -> bool {
width >= self.min_width
&& width <= self.max_width
&& height >= self.min_height
&& height <= self.max_height
}
#[must_use]
pub fn intersect(&self, other: &Self) -> Option<Self> {
let min_w = self.min_width.max(other.min_width);
let max_w = self.max_width.min(other.max_width);
let min_h = self.min_height.max(other.min_height);
let max_h = self.max_height.min(other.max_height);
if min_w <= max_w && min_h <= max_h {
Some(Self::new(min_w, max_w, min_h, max_h))
} else {
None
}
}
}
impl Default for ResolutionRange {
fn default() -> Self {
Self {
min_width: 1,
max_width: 8192,
min_height: 1,
max_height: 4320,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BitrateRange {
pub min_bps: u64,
pub max_bps: u64,
}
impl BitrateRange {
#[must_use]
pub fn new(min_bps: u64, max_bps: u64) -> Self {
Self { min_bps, max_bps }
}
#[must_use]
pub fn contains(&self, bps: u64) -> bool {
bps >= self.min_bps && bps <= self.max_bps
}
#[must_use]
pub fn intersect(&self, other: &Self) -> Option<Self> {
let min = self.min_bps.max(other.min_bps);
let max = self.max_bps.min(other.max_bps);
if min <= max {
Some(Self::new(min, max))
} else {
None
}
}
}
impl Default for BitrateRange {
fn default() -> Self {
Self {
min_bps: 0,
max_bps: u64::MAX,
}
}
}
#[derive(Debug, Clone)]
pub struct EndpointCapabilities {
pub codec: CodecCapability,
pub formats: FormatCapabilities,
pub resolution: ResolutionRange,
pub bitrate: BitrateRange,
}
impl EndpointCapabilities {
#[must_use]
pub fn video(
codec: CodecCapability,
pixel_formats: Vec<PixelFormat>,
resolution: ResolutionRange,
bitrate: BitrateRange,
) -> Self {
Self {
formats: FormatCapabilities::video(&codec.name, pixel_formats),
codec,
resolution,
bitrate,
}
}
#[must_use]
pub fn audio(
codec: CodecCapability,
sample_formats: Vec<SampleFormat>,
sample_rates: Vec<u32>,
channel_counts: Vec<u32>,
bitrate: BitrateRange,
) -> Self {
Self {
formats: FormatCapabilities::audio(
&codec.name,
sample_formats,
sample_rates,
channel_counts,
),
codec,
resolution: ResolutionRange::default(),
bitrate,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct AutoNegotiationResult {
pub codec: String,
pub profile: String,
pub level: u32,
pub hardware_accelerated: bool,
pub format: FormatNegotiationResult,
pub resolution: Option<ResolutionRange>,
pub bitrate: Option<BitrateRange>,
pub score: f64,
}
#[must_use]
pub fn auto_negotiate(
decoder: &EndpointCapabilities,
encoder: &EndpointCapabilities,
) -> Option<AutoNegotiationResult> {
let codec_result = negotiate(
std::slice::from_ref(&decoder.codec),
std::slice::from_ref(&encoder.codec),
)?;
let format_result = negotiate_formats(&decoder.formats, &encoder.formats)?;
let resolution = decoder.resolution.intersect(&encoder.resolution);
let bitrate = decoder.bitrate.intersect(&encoder.bitrate);
let score = compute_score(&codec_result, &format_result, &resolution, &bitrate);
Some(AutoNegotiationResult {
codec: codec_result.selected_codec,
profile: codec_result.profile,
level: codec_result.level,
hardware_accelerated: codec_result.hardware_accelerated,
format: format_result,
resolution,
bitrate,
score,
})
}
fn compute_score(
codec: &NegotiationResult,
format: &FormatNegotiationResult,
resolution: &Option<ResolutionRange>,
bitrate: &Option<BitrateRange>,
) -> f64 {
let mut score = 0.0;
if codec.hardware_accelerated {
score += 0.2;
}
let level_norm = f64::from(codec.level.min(63).saturating_sub(10)) / 53.0;
score += level_norm * 0.3;
if let Some(pf) = format.pixel_format {
let depth_score = f64::from(pf.bits_per_component().min(16)) / 16.0;
score += depth_score * 0.2;
}
if resolution.is_some() {
score += 0.15;
}
if bitrate.is_some() {
score += 0.15;
}
score.min(1.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn av1_cap(hw: bool) -> CodecCapability {
CodecCapability::new("av1", vec!["main".to_string(), "high".to_string()], 40, hw)
}
fn vp9_cap() -> CodecCapability {
CodecCapability::new("vp9", vec!["profile0".to_string()], 50, false)
}
#[test]
fn test_supports_profile_positive() {
let cap = av1_cap(false);
assert!(cap.supports_profile("main"));
assert!(cap.supports_profile("high"));
}
#[test]
fn test_supports_profile_negative() {
let cap = av1_cap(false);
assert!(!cap.supports_profile("baseline"));
}
#[test]
fn test_is_hw_accelerated() {
assert!(av1_cap(true).is_hw_accelerated());
assert!(!av1_cap(false).is_hw_accelerated());
}
#[test]
fn test_common_codecs_overlap() {
let mut neg = CodecNegotiator::new();
neg.add_local(av1_cap(false));
neg.add_local(vp9_cap());
neg.add_remote(av1_cap(false));
let common = neg.common_codecs();
assert_eq!(common, vec!["av1"]);
}
#[test]
fn test_common_codecs_no_overlap() {
let mut neg = CodecNegotiator::new();
neg.add_local(vp9_cap());
neg.add_remote(av1_cap(false));
assert!(neg.common_codecs().is_empty());
}
#[test]
fn test_preferred_codec_hw_first() {
let mut neg = CodecNegotiator::new();
neg.add_local(vp9_cap()); neg.add_local(av1_cap(true)); neg.add_remote(vp9_cap());
neg.add_remote(av1_cap(true));
assert_eq!(neg.preferred_codec(), Some("av1"));
}
#[test]
fn test_preferred_codec_none() {
let mut neg = CodecNegotiator::new();
neg.add_local(vp9_cap());
neg.add_remote(av1_cap(false));
assert!(neg.preferred_codec().is_none());
}
#[test]
fn test_preferred_codec_fallback() {
let mut neg = CodecNegotiator::new();
neg.add_local(av1_cap(false));
neg.add_local(vp9_cap());
neg.add_remote(av1_cap(false));
neg.add_remote(vp9_cap());
let pref = neg.preferred_codec();
assert!(pref.is_some());
}
#[test]
fn test_negotiate_success() {
let local = vec![av1_cap(false)];
let remote = vec![av1_cap(false)];
let result = negotiate(&local, &remote).expect("negotiation should succeed");
assert_eq!(result.selected_codec, "av1");
assert!(result.profile == "main" || result.profile == "high");
assert_eq!(result.level, 40);
assert!(!result.is_hardware());
}
#[test]
fn test_negotiate_prefers_hw() {
let local = vec![vp9_cap(), av1_cap(true)];
let remote = vec![vp9_cap(), av1_cap(true)];
let result = negotiate(&local, &remote).expect("negotiation should succeed");
assert_eq!(result.selected_codec, "av1");
assert!(result.is_hardware());
}
#[test]
fn test_negotiate_level_min() {
let local = vec![CodecCapability::new(
"av1",
vec!["main".to_string()],
50,
false,
)];
let remote = vec![CodecCapability::new(
"av1",
vec!["main".to_string()],
30,
false,
)];
let result = negotiate(&local, &remote).expect("negotiation should succeed");
assert_eq!(result.level, 30);
}
#[test]
fn test_negotiate_no_common() {
let local = vec![av1_cap(false)];
let remote = vec![vp9_cap()];
assert!(negotiate(&local, &remote).is_none());
}
#[test]
fn test_negotiate_profile_mismatch() {
let local = vec![CodecCapability::new(
"av1",
vec!["high".to_string()],
40,
false,
)];
let remote = vec![CodecCapability::new(
"av1",
vec!["baseline".to_string()],
40,
false,
)];
assert!(negotiate(&local, &remote).is_none());
}
#[test]
fn test_negotiation_result_is_hardware() {
let r = NegotiationResult {
selected_codec: "av1".to_string(),
profile: "main".to_string(),
level: 40,
hardware_accelerated: true,
};
assert!(r.is_hardware());
let r2 = NegotiationResult {
hardware_accelerated: false,
..r
};
assert!(!r2.is_hardware());
}
#[test]
fn test_pixel_format_negotiate_common() {
let dec = PixelFormatCaps::new(vec![PixelFormat::Yuv420p, PixelFormat::Nv12]);
let enc = PixelFormatCaps::new(vec![PixelFormat::Nv12, PixelFormat::Yuv420p]);
assert_eq!(dec.negotiate(&enc), Some(PixelFormat::Yuv420p));
}
#[test]
fn test_pixel_format_negotiate_no_common() {
let dec = PixelFormatCaps::new(vec![PixelFormat::Yuv420p]);
let enc = PixelFormatCaps::new(vec![PixelFormat::Rgb24]);
assert_eq!(dec.negotiate(&enc), None);
}
#[test]
fn test_pixel_format_common_formats() {
let dec = PixelFormatCaps::new(vec![
PixelFormat::Yuv420p,
PixelFormat::Nv12,
PixelFormat::Rgb24,
]);
let enc = PixelFormatCaps::new(vec![PixelFormat::Nv12, PixelFormat::Rgb24]);
let common = dec.common_formats(&enc);
assert_eq!(common, vec![PixelFormat::Nv12, PixelFormat::Rgb24]);
}
#[test]
fn test_sample_format_negotiate_common() {
let dec = SampleFormatCaps::new(vec![SampleFormat::F32, SampleFormat::S16]);
let enc = SampleFormatCaps::new(vec![SampleFormat::S16, SampleFormat::S24]);
assert_eq!(dec.negotiate(&enc), Some(SampleFormat::S16));
}
#[test]
fn test_sample_format_negotiate_no_common() {
let dec = SampleFormatCaps::new(vec![SampleFormat::F32]);
let enc = SampleFormatCaps::new(vec![SampleFormat::S24]);
assert_eq!(dec.negotiate(&enc), None);
}
#[test]
fn test_sample_format_common_formats() {
let dec = SampleFormatCaps::new(vec![
SampleFormat::F32,
SampleFormat::S16,
SampleFormat::S24,
]);
let enc = SampleFormatCaps::new(vec![SampleFormat::S24, SampleFormat::F32]);
let common = dec.common_formats(&enc);
assert_eq!(common, vec![SampleFormat::F32, SampleFormat::S24]);
}
#[test]
fn test_negotiate_formats_video() {
let decoder =
FormatCapabilities::video("av1", vec![PixelFormat::Yuv420p, PixelFormat::Yuv420p10le]);
let encoder =
FormatCapabilities::video("av1", vec![PixelFormat::Yuv420p10le, PixelFormat::Yuv420p]);
let result = negotiate_formats(&decoder, &encoder).expect("should negotiate");
assert_eq!(result.pixel_format, Some(PixelFormat::Yuv420p));
assert_eq!(result.sample_format, None);
}
#[test]
fn test_negotiate_formats_audio() {
let decoder = FormatCapabilities::audio(
"opus",
vec![SampleFormat::F32, SampleFormat::S16],
vec![48000, 44100],
vec![2, 1],
);
let encoder = FormatCapabilities::audio(
"opus",
vec![SampleFormat::S16, SampleFormat::F32],
vec![48000],
vec![2],
);
let result = negotiate_formats(&decoder, &encoder).expect("should negotiate");
assert_eq!(result.sample_format, Some(SampleFormat::F32));
assert_eq!(result.sample_rate, Some(48000));
assert_eq!(result.channel_count, Some(2));
assert_eq!(result.pixel_format, None);
}
#[test]
fn test_negotiate_formats_no_common_pixel_format() {
let decoder = FormatCapabilities::video("av1", vec![PixelFormat::Yuv420p]);
let encoder = FormatCapabilities::video("av1", vec![PixelFormat::Rgb24]);
assert!(negotiate_formats(&decoder, &encoder).is_none());
}
#[test]
fn test_negotiate_formats_no_common_sample_format() {
let decoder =
FormatCapabilities::audio("opus", vec![SampleFormat::F32], vec![48000], vec![2]);
let encoder =
FormatCapabilities::audio("opus", vec![SampleFormat::S24], vec![48000], vec![2]);
assert!(negotiate_formats(&decoder, &encoder).is_none());
}
#[test]
fn test_negotiate_formats_empty_caps() {
let decoder = FormatCapabilities::video("av1", vec![]);
let encoder = FormatCapabilities::video("av1", vec![]);
assert!(negotiate_formats(&decoder, &encoder).is_none());
}
#[test]
fn test_negotiate_formats_video_with_semi_planar() {
let decoder = FormatCapabilities::video("av1", vec![PixelFormat::Nv12, PixelFormat::P010]);
let encoder =
FormatCapabilities::video("av1", vec![PixelFormat::P010, PixelFormat::Yuv420p]);
let result = negotiate_formats(&decoder, &encoder).expect("should negotiate");
assert_eq!(result.pixel_format, Some(PixelFormat::P010));
}
#[test]
fn test_format_capabilities_video_constructor() {
let caps = FormatCapabilities::video("vp9", vec![PixelFormat::Yuv420p]);
assert_eq!(caps.codec_name, "vp9");
assert_eq!(caps.pixel_formats.formats.len(), 1);
assert!(caps.sample_formats.formats.is_empty());
assert!(caps.sample_rates.is_empty());
assert!(caps.channel_counts.is_empty());
}
#[test]
fn test_format_capabilities_audio_constructor() {
let caps = FormatCapabilities::audio(
"flac",
vec![SampleFormat::S16, SampleFormat::S24],
vec![44100, 48000, 96000],
vec![1, 2],
);
assert_eq!(caps.codec_name, "flac");
assert!(caps.pixel_formats.formats.is_empty());
assert_eq!(caps.sample_formats.formats.len(), 2);
assert_eq!(caps.sample_rates.len(), 3);
assert_eq!(caps.channel_counts.len(), 2);
}
#[test]
fn test_resolution_range_contains() {
let r = ResolutionRange::new(1, 1920, 1, 1080);
assert!(r.contains(1920, 1080));
assert!(r.contains(1, 1));
assert!(r.contains(1280, 720));
assert!(!r.contains(3840, 2160));
assert!(!r.contains(0, 0));
}
#[test]
fn test_resolution_range_intersect() {
let a = ResolutionRange::new(1, 3840, 1, 2160);
let b = ResolutionRange::new(640, 1920, 480, 1080);
let intersect = a.intersect(&b).expect("should intersect");
assert_eq!(intersect.min_width, 640);
assert_eq!(intersect.max_width, 1920);
assert_eq!(intersect.min_height, 480);
assert_eq!(intersect.max_height, 1080);
}
#[test]
fn test_resolution_range_no_intersect() {
let a = ResolutionRange::new(1, 640, 1, 480);
let b = ResolutionRange::new(1920, 3840, 1080, 2160);
assert!(a.intersect(&b).is_none());
}
#[test]
fn test_resolution_range_default() {
let r = ResolutionRange::default();
assert!(r.contains(1920, 1080));
assert!(r.contains(7680, 4320));
}
#[test]
fn test_bitrate_range_contains() {
let b = BitrateRange::new(1_000_000, 10_000_000);
assert!(b.contains(5_000_000));
assert!(b.contains(1_000_000));
assert!(b.contains(10_000_000));
assert!(!b.contains(500_000));
assert!(!b.contains(20_000_000));
}
#[test]
fn test_bitrate_range_intersect() {
let a = BitrateRange::new(500_000, 20_000_000);
let b = BitrateRange::new(1_000_000, 15_000_000);
let intersect = a.intersect(&b).expect("should intersect");
assert_eq!(intersect.min_bps, 1_000_000);
assert_eq!(intersect.max_bps, 15_000_000);
}
#[test]
fn test_bitrate_range_no_intersect() {
let a = BitrateRange::new(1_000_000, 2_000_000);
let b = BitrateRange::new(5_000_000, 10_000_000);
assert!(a.intersect(&b).is_none());
}
#[test]
fn test_bitrate_range_default() {
let b = BitrateRange::default();
assert!(b.contains(0));
assert!(b.contains(1_000_000_000));
}
#[test]
fn test_auto_negotiate_video_success() {
let decoder = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 50, true),
vec![PixelFormat::Yuv420p, PixelFormat::Yuv420p10le],
ResolutionRange::new(1, 3840, 1, 2160),
BitrateRange::new(500_000, 20_000_000),
);
let encoder = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 40, false),
vec![PixelFormat::Yuv420p10le, PixelFormat::Yuv420p],
ResolutionRange::new(1, 1920, 1, 1080),
BitrateRange::new(1_000_000, 15_000_000),
);
let result = auto_negotiate(&decoder, &encoder).expect("should negotiate");
assert_eq!(result.codec, "av1");
assert_eq!(result.profile, "main");
assert_eq!(result.level, 40); assert!(result.hardware_accelerated); assert_eq!(result.format.pixel_format, Some(PixelFormat::Yuv420p));
let res = result.resolution.expect("should have resolution");
assert_eq!(res.max_width, 1920);
assert_eq!(res.max_height, 1080);
let br = result.bitrate.expect("should have bitrate");
assert_eq!(br.min_bps, 1_000_000);
assert_eq!(br.max_bps, 15_000_000);
assert!(result.score > 0.0);
assert!(result.score <= 1.0);
}
#[test]
fn test_auto_negotiate_audio_success() {
let decoder = EndpointCapabilities::audio(
CodecCapability::new("opus", vec!["default".into()], 0, false),
vec![SampleFormat::F32, SampleFormat::S16],
vec![48000, 44100],
vec![2, 1],
BitrateRange::new(64_000, 510_000),
);
let encoder = EndpointCapabilities::audio(
CodecCapability::new("opus", vec!["default".into()], 0, false),
vec![SampleFormat::S16, SampleFormat::F32],
vec![48000],
vec![2],
BitrateRange::new(96_000, 256_000),
);
let result = auto_negotiate(&decoder, &encoder).expect("should negotiate");
assert_eq!(result.codec, "opus");
assert_eq!(result.format.sample_format, Some(SampleFormat::F32));
assert_eq!(result.format.sample_rate, Some(48000));
assert_eq!(result.format.channel_count, Some(2));
let br = result.bitrate.expect("should have bitrate");
assert_eq!(br.min_bps, 96_000);
assert_eq!(br.max_bps, 256_000);
}
#[test]
fn test_auto_negotiate_codec_mismatch() {
let decoder = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 40, false),
vec![PixelFormat::Yuv420p],
ResolutionRange::default(),
BitrateRange::default(),
);
let encoder = EndpointCapabilities::video(
CodecCapability::new("vp9", vec!["profile0".into()], 40, false),
vec![PixelFormat::Yuv420p],
ResolutionRange::default(),
BitrateRange::default(),
);
assert!(auto_negotiate(&decoder, &encoder).is_none());
}
#[test]
fn test_auto_negotiate_format_mismatch() {
let decoder = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 40, false),
vec![PixelFormat::Yuv420p],
ResolutionRange::default(),
BitrateRange::default(),
);
let encoder = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 40, false),
vec![PixelFormat::Rgb24],
ResolutionRange::default(),
BitrateRange::default(),
);
assert!(auto_negotiate(&decoder, &encoder).is_none());
}
#[test]
fn test_auto_negotiate_hw_boosts_score() {
let make_endpoint = |hw: bool| {
EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 40, hw),
vec![PixelFormat::Yuv420p],
ResolutionRange::default(),
BitrateRange::default(),
)
};
let hw_result =
auto_negotiate(&make_endpoint(true), &make_endpoint(true)).expect("should negotiate");
let sw_result =
auto_negotiate(&make_endpoint(false), &make_endpoint(false)).expect("should negotiate");
assert!(hw_result.score > sw_result.score);
}
#[test]
fn test_auto_negotiate_resolution_no_overlap_still_returns() {
let decoder = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 40, false),
vec![PixelFormat::Yuv420p],
ResolutionRange::new(1, 640, 1, 480),
BitrateRange::default(),
);
let encoder = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 40, false),
vec![PixelFormat::Yuv420p],
ResolutionRange::new(1920, 3840, 1080, 2160),
BitrateRange::default(),
);
let result = auto_negotiate(&decoder, &encoder).expect("should still negotiate");
assert!(result.resolution.is_none());
}
#[test]
fn test_score_range() {
let endpoint = EndpointCapabilities::video(
CodecCapability::new("av1", vec!["main".into()], 63, true),
vec![PixelFormat::Yuv420p10le],
ResolutionRange::default(),
BitrateRange::default(),
);
let result = auto_negotiate(&endpoint, &endpoint).expect("should negotiate");
assert!(result.score >= 0.0);
assert!(result.score <= 1.0);
}
#[test]
fn test_endpoint_capabilities_video_constructor() {
let ep = EndpointCapabilities::video(
CodecCapability::new("vp9", vec!["profile0".into()], 50, false),
vec![PixelFormat::Yuv420p, PixelFormat::Nv12],
ResolutionRange::new(1, 1920, 1, 1080),
BitrateRange::new(1_000_000, 10_000_000),
);
assert_eq!(ep.codec.name, "vp9");
assert_eq!(ep.formats.pixel_formats.formats.len(), 2);
assert_eq!(ep.resolution.max_width, 1920);
assert_eq!(ep.bitrate.max_bps, 10_000_000);
}
#[test]
fn test_endpoint_capabilities_audio_constructor() {
let ep = EndpointCapabilities::audio(
CodecCapability::new("flac", vec!["default".into()], 0, false),
vec![SampleFormat::S16, SampleFormat::S24],
vec![44100, 48000, 96000],
vec![1, 2],
BitrateRange::new(0, 5_000_000),
);
assert_eq!(ep.codec.name, "flac");
assert_eq!(ep.formats.sample_formats.formats.len(), 2);
assert_eq!(ep.formats.sample_rates.len(), 3);
}
}