1#![allow(dead_code)]
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct CodecCapability {
38 pub name: String,
40 pub profiles: Vec<String>,
42 pub max_level: u32,
44 pub hardware_accelerated: bool,
46}
47
48impl CodecCapability {
49 #[must_use]
51 pub fn new(
52 name: impl Into<String>,
53 profiles: Vec<String>,
54 max_level: u32,
55 hardware_accelerated: bool,
56 ) -> Self {
57 Self {
58 name: name.into(),
59 profiles,
60 max_level,
61 hardware_accelerated,
62 }
63 }
64
65 #[must_use]
67 pub fn supports_profile(&self, profile: &str) -> bool {
68 self.profiles.iter().any(|p| p == profile)
69 }
70
71 #[must_use]
73 pub fn is_hw_accelerated(&self) -> bool {
74 self.hardware_accelerated
75 }
76}
77
78#[derive(Debug, Default)]
80pub struct CodecNegotiator {
81 pub local_caps: Vec<CodecCapability>,
83 pub remote_caps: Vec<CodecCapability>,
85}
86
87impl CodecNegotiator {
88 #[must_use]
90 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn add_local(&mut self, cap: CodecCapability) {
96 self.local_caps.push(cap);
97 }
98
99 pub fn add_remote(&mut self, cap: CodecCapability) {
101 self.remote_caps.push(cap);
102 }
103
104 #[must_use]
106 pub fn common_codecs(&self) -> Vec<&str> {
107 self.local_caps
108 .iter()
109 .filter(|l| self.remote_caps.iter().any(|r| r.name == l.name))
110 .map(|l| l.name.as_str())
111 .collect()
112 }
113
114 #[must_use]
118 pub fn preferred_codec(&self) -> Option<&str> {
119 let common = self.common_codecs();
121 if common.is_empty() {
122 return None;
123 }
124 for name in &common {
126 if let Some(cap) = self.local_caps.iter().find(|c| &c.name.as_str() == name) {
127 if cap.hardware_accelerated {
128 return Some(name);
129 }
130 }
131 }
132 common.into_iter().next()
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct NegotiationResult {
139 pub selected_codec: String,
141 pub profile: String,
143 pub level: u32,
145 pub hardware_accelerated: bool,
147}
148
149impl NegotiationResult {
150 #[must_use]
152 pub fn is_hardware(&self) -> bool {
153 self.hardware_accelerated
154 }
155}
156
157#[must_use]
164pub fn negotiate(
165 local: &[CodecCapability],
166 remote: &[CodecCapability],
167) -> Option<NegotiationResult> {
168 let mut ordered: Vec<&CodecCapability> = local.iter().collect();
170 ordered.sort_by_key(|c| u8::from(!c.hardware_accelerated));
171
172 for local_cap in ordered {
173 if let Some(remote_cap) = remote.iter().find(|r| r.name == local_cap.name) {
174 let common_profile = local_cap
176 .profiles
177 .iter()
178 .find(|p| remote_cap.profiles.contains(p));
179 if let Some(profile) = common_profile {
180 let level = local_cap.max_level.min(remote_cap.max_level);
181 return Some(NegotiationResult {
182 selected_codec: local_cap.name.clone(),
183 profile: profile.clone(),
184 level,
185 hardware_accelerated: local_cap.hardware_accelerated,
186 });
187 }
188 }
189 }
190 None
191}
192
193use crate::types::{PixelFormat, SampleFormat};
198
199#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct PixelFormatCaps {
202 pub formats: Vec<PixelFormat>,
204}
205
206impl PixelFormatCaps {
207 #[must_use]
209 pub fn new(formats: Vec<PixelFormat>) -> Self {
210 Self { formats }
211 }
212
213 #[must_use]
215 pub fn negotiate(&self, other: &Self) -> Option<PixelFormat> {
216 self.formats
217 .iter()
218 .find(|f| other.formats.contains(f))
219 .copied()
220 }
221
222 #[must_use]
224 pub fn common_formats(&self, other: &Self) -> Vec<PixelFormat> {
225 self.formats
226 .iter()
227 .filter(|f| other.formats.contains(f))
228 .copied()
229 .collect()
230 }
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
235pub struct SampleFormatCaps {
236 pub formats: Vec<SampleFormat>,
238}
239
240impl SampleFormatCaps {
241 #[must_use]
243 pub fn new(formats: Vec<SampleFormat>) -> Self {
244 Self { formats }
245 }
246
247 #[must_use]
249 pub fn negotiate(&self, other: &Self) -> Option<SampleFormat> {
250 self.formats
251 .iter()
252 .find(|f| other.formats.contains(f))
253 .copied()
254 }
255
256 #[must_use]
258 pub fn common_formats(&self, other: &Self) -> Vec<SampleFormat> {
259 self.formats
260 .iter()
261 .filter(|f| other.formats.contains(f))
262 .copied()
263 .collect()
264 }
265}
266
267#[derive(Debug, Clone)]
269pub struct FormatCapabilities {
270 pub codec_name: String,
272 pub pixel_formats: PixelFormatCaps,
274 pub sample_formats: SampleFormatCaps,
276 pub sample_rates: Vec<u32>,
278 pub channel_counts: Vec<u32>,
280}
281
282impl FormatCapabilities {
283 #[must_use]
285 pub fn video(codec_name: impl Into<String>, pixel_formats: Vec<PixelFormat>) -> Self {
286 Self {
287 codec_name: codec_name.into(),
288 pixel_formats: PixelFormatCaps::new(pixel_formats),
289 sample_formats: SampleFormatCaps::new(vec![]),
290 sample_rates: vec![],
291 channel_counts: vec![],
292 }
293 }
294
295 #[must_use]
297 pub fn audio(
298 codec_name: impl Into<String>,
299 sample_formats: Vec<SampleFormat>,
300 sample_rates: Vec<u32>,
301 channel_counts: Vec<u32>,
302 ) -> Self {
303 Self {
304 codec_name: codec_name.into(),
305 pixel_formats: PixelFormatCaps::new(vec![]),
306 sample_formats: SampleFormatCaps::new(sample_formats),
307 sample_rates,
308 channel_counts,
309 }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct FormatNegotiationResult {
316 pub pixel_format: Option<PixelFormat>,
318 pub sample_format: Option<SampleFormat>,
320 pub sample_rate: Option<u32>,
322 pub channel_count: Option<u32>,
324}
325
326#[must_use]
333pub fn negotiate_formats(
334 decoder: &FormatCapabilities,
335 encoder: &FormatCapabilities,
336) -> Option<FormatNegotiationResult> {
337 let is_video =
338 !decoder.pixel_formats.formats.is_empty() && !encoder.pixel_formats.formats.is_empty();
339 let is_audio =
340 !decoder.sample_formats.formats.is_empty() && !encoder.sample_formats.formats.is_empty();
341
342 if !is_video && !is_audio {
343 return None;
344 }
345
346 let pixel_format = if is_video {
347 let pf = decoder.pixel_formats.negotiate(&encoder.pixel_formats);
348 pf?;
349 pf
350 } else {
351 None
352 };
353
354 let sample_format = if is_audio {
355 let sf = decoder.sample_formats.negotiate(&encoder.sample_formats);
356 sf?;
357 sf
358 } else {
359 None
360 };
361
362 let sample_rate = if is_audio {
363 decoder
364 .sample_rates
365 .iter()
366 .find(|r| encoder.sample_rates.contains(r))
367 .copied()
368 } else {
369 None
370 };
371
372 let channel_count = if is_audio {
373 decoder
374 .channel_counts
375 .iter()
376 .find(|c| encoder.channel_counts.contains(c))
377 .copied()
378 } else {
379 None
380 };
381
382 Some(FormatNegotiationResult {
383 pixel_format,
384 sample_format,
385 sample_rate,
386 channel_count,
387 })
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
396pub struct ResolutionRange {
397 pub min_width: u32,
399 pub max_width: u32,
401 pub min_height: u32,
403 pub max_height: u32,
405}
406
407impl ResolutionRange {
408 #[must_use]
410 pub fn new(min_width: u32, max_width: u32, min_height: u32, max_height: u32) -> Self {
411 Self {
412 min_width,
413 max_width,
414 min_height,
415 max_height,
416 }
417 }
418
419 #[must_use]
421 pub fn contains(&self, width: u32, height: u32) -> bool {
422 width >= self.min_width
423 && width <= self.max_width
424 && height >= self.min_height
425 && height <= self.max_height
426 }
427
428 #[must_use]
430 pub fn intersect(&self, other: &Self) -> Option<Self> {
431 let min_w = self.min_width.max(other.min_width);
432 let max_w = self.max_width.min(other.max_width);
433 let min_h = self.min_height.max(other.min_height);
434 let max_h = self.max_height.min(other.max_height);
435 if min_w <= max_w && min_h <= max_h {
436 Some(Self::new(min_w, max_w, min_h, max_h))
437 } else {
438 None
439 }
440 }
441}
442
443impl Default for ResolutionRange {
444 fn default() -> Self {
445 Self {
446 min_width: 1,
447 max_width: 8192,
448 min_height: 1,
449 max_height: 4320,
450 }
451 }
452}
453
454#[derive(Debug, Clone, PartialEq, Eq)]
456pub struct BitrateRange {
457 pub min_bps: u64,
459 pub max_bps: u64,
461}
462
463impl BitrateRange {
464 #[must_use]
466 pub fn new(min_bps: u64, max_bps: u64) -> Self {
467 Self { min_bps, max_bps }
468 }
469
470 #[must_use]
472 pub fn contains(&self, bps: u64) -> bool {
473 bps >= self.min_bps && bps <= self.max_bps
474 }
475
476 #[must_use]
478 pub fn intersect(&self, other: &Self) -> Option<Self> {
479 let min = self.min_bps.max(other.min_bps);
480 let max = self.max_bps.min(other.max_bps);
481 if min <= max {
482 Some(Self::new(min, max))
483 } else {
484 None
485 }
486 }
487}
488
489impl Default for BitrateRange {
490 fn default() -> Self {
491 Self {
492 min_bps: 0,
493 max_bps: u64::MAX,
494 }
495 }
496}
497
498#[derive(Debug, Clone)]
503pub struct EndpointCapabilities {
504 pub codec: CodecCapability,
506 pub formats: FormatCapabilities,
508 pub resolution: ResolutionRange,
510 pub bitrate: BitrateRange,
512}
513
514impl EndpointCapabilities {
515 #[must_use]
517 pub fn video(
518 codec: CodecCapability,
519 pixel_formats: Vec<PixelFormat>,
520 resolution: ResolutionRange,
521 bitrate: BitrateRange,
522 ) -> Self {
523 Self {
524 formats: FormatCapabilities::video(&codec.name, pixel_formats),
525 codec,
526 resolution,
527 bitrate,
528 }
529 }
530
531 #[must_use]
533 pub fn audio(
534 codec: CodecCapability,
535 sample_formats: Vec<SampleFormat>,
536 sample_rates: Vec<u32>,
537 channel_counts: Vec<u32>,
538 bitrate: BitrateRange,
539 ) -> Self {
540 Self {
541 formats: FormatCapabilities::audio(
542 &codec.name,
543 sample_formats,
544 sample_rates,
545 channel_counts,
546 ),
547 codec,
548 resolution: ResolutionRange::default(),
549 bitrate,
550 }
551 }
552}
553
554#[derive(Debug, Clone, PartialEq)]
556pub struct AutoNegotiationResult {
557 pub codec: String,
559 pub profile: String,
561 pub level: u32,
563 pub hardware_accelerated: bool,
565 pub format: FormatNegotiationResult,
567 pub resolution: Option<ResolutionRange>,
569 pub bitrate: Option<BitrateRange>,
571 pub score: f64,
573}
574
575#[must_use]
604pub fn auto_negotiate(
605 decoder: &EndpointCapabilities,
606 encoder: &EndpointCapabilities,
607) -> Option<AutoNegotiationResult> {
608 let codec_result = negotiate(
610 std::slice::from_ref(&decoder.codec),
611 std::slice::from_ref(&encoder.codec),
612 )?;
613
614 let format_result = negotiate_formats(&decoder.formats, &encoder.formats)?;
616
617 let resolution = decoder.resolution.intersect(&encoder.resolution);
619
620 let bitrate = decoder.bitrate.intersect(&encoder.bitrate);
622
623 let score = compute_score(&codec_result, &format_result, &resolution, &bitrate);
625
626 Some(AutoNegotiationResult {
627 codec: codec_result.selected_codec,
628 profile: codec_result.profile,
629 level: codec_result.level,
630 hardware_accelerated: codec_result.hardware_accelerated,
631 format: format_result,
632 resolution,
633 bitrate,
634 score,
635 })
636}
637
638fn compute_score(
647 codec: &NegotiationResult,
648 format: &FormatNegotiationResult,
649 resolution: &Option<ResolutionRange>,
650 bitrate: &Option<BitrateRange>,
651) -> f64 {
652 let mut score = 0.0;
653
654 if codec.hardware_accelerated {
656 score += 0.2;
657 }
658
659 let level_norm = f64::from(codec.level.min(63).saturating_sub(10)) / 53.0;
661 score += level_norm * 0.3;
662
663 if let Some(pf) = format.pixel_format {
665 let depth_score = f64::from(pf.bits_per_component().min(16)) / 16.0;
666 score += depth_score * 0.2;
667 }
668
669 if resolution.is_some() {
671 score += 0.15;
672 }
673
674 if bitrate.is_some() {
676 score += 0.15;
677 }
678
679 score.min(1.0)
681}
682
683pub trait FormatCost: Clone + PartialEq {
700 fn conversion_cost(&self, target: &Self) -> Option<u32>;
703}
704
705fn pf_is_yuv420(f: &PixelFormat) -> bool {
709 matches!(
710 f,
711 PixelFormat::Yuv420p | PixelFormat::Nv12 | PixelFormat::Nv21
712 )
713}
714
715fn pf_is_yuv422(f: &PixelFormat) -> bool {
717 matches!(f, PixelFormat::Yuv422p)
718}
719
720fn pf_is_yuv444(f: &PixelFormat) -> bool {
722 matches!(f, PixelFormat::Yuv444p)
723}
724
725fn pf_is_rgb(f: &PixelFormat) -> bool {
727 matches!(f, PixelFormat::Rgb24 | PixelFormat::Rgba32)
728}
729
730fn pf_is_gray(f: &PixelFormat) -> bool {
732 matches!(f, PixelFormat::Gray8 | PixelFormat::Gray16)
733}
734
735fn pf_is_hbd_yuv(f: &PixelFormat) -> bool {
737 matches!(
738 f,
739 PixelFormat::Yuv420p10le | PixelFormat::Yuv420p12le | PixelFormat::P010 | PixelFormat::P016
740 )
741}
742
743impl FormatCost for PixelFormat {
744 fn conversion_cost(&self, target: &Self) -> Option<u32> {
745 if self == target {
746 return Some(0);
747 }
748
749 if (pf_is_yuv420(self) && pf_is_yuv420(target))
751 || (pf_is_yuv422(self) && pf_is_yuv422(target))
752 || (pf_is_yuv444(self) && pf_is_yuv444(target))
753 || (pf_is_rgb(self) && pf_is_rgb(target))
754 || (pf_is_gray(self) && pf_is_gray(target))
755 || (pf_is_hbd_yuv(self) && pf_is_hbd_yuv(target))
756 {
757 return Some(1);
758 }
759
760 let self_yuv =
761 pf_is_yuv420(self) || pf_is_yuv422(self) || pf_is_yuv444(self) || pf_is_hbd_yuv(self);
762 let target_yuv = pf_is_yuv420(target)
763 || pf_is_yuv422(target)
764 || pf_is_yuv444(target)
765 || pf_is_hbd_yuv(target);
766
767 if self_yuv && target_yuv {
769 return Some(2);
770 }
771
772 if (pf_is_rgb(self) && target_yuv) || (self_yuv && pf_is_rgb(target)) {
774 return Some(3);
775 }
776
777 Some(4)
779 }
780}
781
782impl FormatCost for SampleFormat {
785 fn conversion_cost(&self, target: &Self) -> Option<u32> {
786 if self == target {
787 return Some(0);
788 }
789
790 if self.to_packed() == target.to_packed() {
793 return Some(1);
794 }
795
796 let self_float = self.is_float();
798 let target_float = target.is_float();
799 if self_float == target_float {
800 return Some(2);
801 }
802
803 Some(3)
805 }
806}
807
808#[derive(Debug, Clone, PartialEq)]
828pub enum FormatConversionResult<F> {
829 Direct(F),
831 Convert {
834 from: F,
836 to: F,
838 cost: u32,
840 },
841 Incompatible,
843}
844
845pub struct FormatNegotiator<'a, F> {
875 pub decoder_produces: &'a [F],
877 pub encoder_accepts: &'a [F],
879}
880
881impl<F> FormatNegotiator<'_, F>
882where
883 F: FormatCost + std::fmt::Debug,
884{
885 #[must_use]
887 pub fn negotiate(&self) -> FormatConversionResult<F> {
888 for prod in self.decoder_produces {
890 for acc in self.encoder_accepts {
891 if prod == acc {
892 return FormatConversionResult::Direct(prod.clone());
893 }
894 }
895 }
896
897 let mut best: Option<(F, F, u32)> = None;
899 for prod in self.decoder_produces {
900 for acc in self.encoder_accepts {
901 if let Some(cost) = prod.conversion_cost(acc) {
902 let is_better = best.as_ref().map_or(true, |(_, _, bc)| cost < *bc);
903 if is_better {
904 best = Some((prod.clone(), acc.clone(), cost));
905 }
906 }
907 }
908 }
909
910 if let Some((from, to, cost)) = best {
911 FormatConversionResult::Convert { from, to, cost }
912 } else {
913 FormatConversionResult::Incompatible
914 }
915 }
916}
917
918#[cfg(test)]
923mod tests {
924 use super::*;
925
926 fn av1_cap(hw: bool) -> CodecCapability {
927 CodecCapability::new("av1", vec!["main".to_string(), "high".to_string()], 40, hw)
928 }
929
930 fn vp9_cap() -> CodecCapability {
931 CodecCapability::new("vp9", vec!["profile0".to_string()], 50, false)
932 }
933
934 #[test]
936 fn test_supports_profile_positive() {
937 let cap = av1_cap(false);
938 assert!(cap.supports_profile("main"));
939 assert!(cap.supports_profile("high"));
940 }
941
942 #[test]
944 fn test_supports_profile_negative() {
945 let cap = av1_cap(false);
946 assert!(!cap.supports_profile("baseline"));
947 }
948
949 #[test]
951 fn test_is_hw_accelerated() {
952 assert!(av1_cap(true).is_hw_accelerated());
953 assert!(!av1_cap(false).is_hw_accelerated());
954 }
955
956 #[test]
958 fn test_common_codecs_overlap() {
959 let mut neg = CodecNegotiator::new();
960 neg.add_local(av1_cap(false));
961 neg.add_local(vp9_cap());
962 neg.add_remote(av1_cap(false));
963 let common = neg.common_codecs();
964 assert_eq!(common, vec!["av1"]);
965 }
966
967 #[test]
969 fn test_common_codecs_no_overlap() {
970 let mut neg = CodecNegotiator::new();
971 neg.add_local(vp9_cap());
972 neg.add_remote(av1_cap(false));
973 assert!(neg.common_codecs().is_empty());
974 }
975
976 #[test]
978 fn test_preferred_codec_hw_first() {
979 let mut neg = CodecNegotiator::new();
980 neg.add_local(vp9_cap()); neg.add_local(av1_cap(true)); neg.add_remote(vp9_cap());
983 neg.add_remote(av1_cap(true));
984 assert_eq!(neg.preferred_codec(), Some("av1"));
986 }
987
988 #[test]
990 fn test_preferred_codec_none() {
991 let mut neg = CodecNegotiator::new();
992 neg.add_local(vp9_cap());
993 neg.add_remote(av1_cap(false));
994 assert!(neg.preferred_codec().is_none());
995 }
996
997 #[test]
999 fn test_preferred_codec_fallback() {
1000 let mut neg = CodecNegotiator::new();
1001 neg.add_local(av1_cap(false));
1002 neg.add_local(vp9_cap());
1003 neg.add_remote(av1_cap(false));
1004 neg.add_remote(vp9_cap());
1005 let pref = neg.preferred_codec();
1007 assert!(pref.is_some());
1008 }
1009
1010 #[test]
1012 fn test_negotiate_success() {
1013 let local = vec![av1_cap(false)];
1014 let remote = vec![av1_cap(false)];
1015 let result = negotiate(&local, &remote).expect("negotiation should succeed");
1016 assert_eq!(result.selected_codec, "av1");
1017 assert!(result.profile == "main" || result.profile == "high");
1018 assert_eq!(result.level, 40);
1019 assert!(!result.is_hardware());
1020 }
1021
1022 #[test]
1024 fn test_negotiate_prefers_hw() {
1025 let local = vec![vp9_cap(), av1_cap(true)];
1026 let remote = vec![vp9_cap(), av1_cap(true)];
1027 let result = negotiate(&local, &remote).expect("negotiation should succeed");
1028 assert_eq!(result.selected_codec, "av1");
1029 assert!(result.is_hardware());
1030 }
1031
1032 #[test]
1034 fn test_negotiate_level_min() {
1035 let local = vec![CodecCapability::new(
1036 "av1",
1037 vec!["main".to_string()],
1038 50,
1039 false,
1040 )];
1041 let remote = vec![CodecCapability::new(
1042 "av1",
1043 vec!["main".to_string()],
1044 30,
1045 false,
1046 )];
1047 let result = negotiate(&local, &remote).expect("negotiation should succeed");
1048 assert_eq!(result.level, 30);
1049 }
1050
1051 #[test]
1053 fn test_negotiate_no_common() {
1054 let local = vec![av1_cap(false)];
1055 let remote = vec![vp9_cap()];
1056 assert!(negotiate(&local, &remote).is_none());
1057 }
1058
1059 #[test]
1061 fn test_negotiate_profile_mismatch() {
1062 let local = vec![CodecCapability::new(
1063 "av1",
1064 vec!["high".to_string()],
1065 40,
1066 false,
1067 )];
1068 let remote = vec![CodecCapability::new(
1069 "av1",
1070 vec!["baseline".to_string()],
1071 40,
1072 false,
1073 )];
1074 assert!(negotiate(&local, &remote).is_none());
1075 }
1076
1077 #[test]
1079 fn test_negotiation_result_is_hardware() {
1080 let r = NegotiationResult {
1081 selected_codec: "av1".to_string(),
1082 profile: "main".to_string(),
1083 level: 40,
1084 hardware_accelerated: true,
1085 };
1086 assert!(r.is_hardware());
1087 let r2 = NegotiationResult {
1088 hardware_accelerated: false,
1089 ..r
1090 };
1091 assert!(!r2.is_hardware());
1092 }
1093
1094 #[test]
1097 fn test_pixel_format_negotiate_common() {
1098 let dec = PixelFormatCaps::new(vec![PixelFormat::Yuv420p, PixelFormat::Nv12]);
1099 let enc = PixelFormatCaps::new(vec![PixelFormat::Nv12, PixelFormat::Yuv420p]);
1100 assert_eq!(dec.negotiate(&enc), Some(PixelFormat::Yuv420p));
1102 }
1103
1104 #[test]
1105 fn test_pixel_format_negotiate_no_common() {
1106 let dec = PixelFormatCaps::new(vec![PixelFormat::Yuv420p]);
1107 let enc = PixelFormatCaps::new(vec![PixelFormat::Rgb24]);
1108 assert_eq!(dec.negotiate(&enc), None);
1109 }
1110
1111 #[test]
1112 fn test_pixel_format_common_formats() {
1113 let dec = PixelFormatCaps::new(vec![
1114 PixelFormat::Yuv420p,
1115 PixelFormat::Nv12,
1116 PixelFormat::Rgb24,
1117 ]);
1118 let enc = PixelFormatCaps::new(vec![PixelFormat::Nv12, PixelFormat::Rgb24]);
1119 let common = dec.common_formats(&enc);
1120 assert_eq!(common, vec![PixelFormat::Nv12, PixelFormat::Rgb24]);
1121 }
1122
1123 #[test]
1124 fn test_sample_format_negotiate_common() {
1125 let dec = SampleFormatCaps::new(vec![SampleFormat::F32, SampleFormat::S16]);
1126 let enc = SampleFormatCaps::new(vec![SampleFormat::S16, SampleFormat::S24]);
1127 assert_eq!(dec.negotiate(&enc), Some(SampleFormat::S16));
1128 }
1129
1130 #[test]
1131 fn test_sample_format_negotiate_no_common() {
1132 let dec = SampleFormatCaps::new(vec![SampleFormat::F32]);
1133 let enc = SampleFormatCaps::new(vec![SampleFormat::S24]);
1134 assert_eq!(dec.negotiate(&enc), None);
1135 }
1136
1137 #[test]
1138 fn test_sample_format_common_formats() {
1139 let dec = SampleFormatCaps::new(vec![
1140 SampleFormat::F32,
1141 SampleFormat::S16,
1142 SampleFormat::S24,
1143 ]);
1144 let enc = SampleFormatCaps::new(vec![SampleFormat::S24, SampleFormat::F32]);
1145 let common = dec.common_formats(&enc);
1146 assert_eq!(common, vec![SampleFormat::F32, SampleFormat::S24]);
1147 }
1148
1149 #[test]
1150 fn test_negotiate_formats_video() {
1151 let decoder =
1152 FormatCapabilities::video("av1", vec![PixelFormat::Yuv420p, PixelFormat::Yuv420p10le]);
1153 let encoder =
1154 FormatCapabilities::video("av1", vec![PixelFormat::Yuv420p10le, PixelFormat::Yuv420p]);
1155 let result = negotiate_formats(&decoder, &encoder).expect("should negotiate");
1156 assert_eq!(result.pixel_format, Some(PixelFormat::Yuv420p));
1157 assert_eq!(result.sample_format, None);
1158 }
1159
1160 #[test]
1161 fn test_negotiate_formats_audio() {
1162 let decoder = FormatCapabilities::audio(
1163 "opus",
1164 vec![SampleFormat::F32, SampleFormat::S16],
1165 vec![48000, 44100],
1166 vec![2, 1],
1167 );
1168 let encoder = FormatCapabilities::audio(
1169 "opus",
1170 vec![SampleFormat::S16, SampleFormat::F32],
1171 vec![48000],
1172 vec![2],
1173 );
1174 let result = negotiate_formats(&decoder, &encoder).expect("should negotiate");
1175 assert_eq!(result.sample_format, Some(SampleFormat::F32));
1176 assert_eq!(result.sample_rate, Some(48000));
1177 assert_eq!(result.channel_count, Some(2));
1178 assert_eq!(result.pixel_format, None);
1179 }
1180
1181 #[test]
1182 fn test_negotiate_formats_no_common_pixel_format() {
1183 let decoder = FormatCapabilities::video("av1", vec![PixelFormat::Yuv420p]);
1184 let encoder = FormatCapabilities::video("av1", vec![PixelFormat::Rgb24]);
1185 assert!(negotiate_formats(&decoder, &encoder).is_none());
1186 }
1187
1188 #[test]
1189 fn test_negotiate_formats_no_common_sample_format() {
1190 let decoder =
1191 FormatCapabilities::audio("opus", vec![SampleFormat::F32], vec![48000], vec![2]);
1192 let encoder =
1193 FormatCapabilities::audio("opus", vec![SampleFormat::S24], vec![48000], vec![2]);
1194 assert!(negotiate_formats(&decoder, &encoder).is_none());
1195 }
1196
1197 #[test]
1198 fn test_negotiate_formats_empty_caps() {
1199 let decoder = FormatCapabilities::video("av1", vec![]);
1200 let encoder = FormatCapabilities::video("av1", vec![]);
1201 assert!(negotiate_formats(&decoder, &encoder).is_none());
1202 }
1203
1204 #[test]
1205 fn test_negotiate_formats_video_with_semi_planar() {
1206 let decoder = FormatCapabilities::video("av1", vec![PixelFormat::Nv12, PixelFormat::P010]);
1207 let encoder =
1208 FormatCapabilities::video("av1", vec![PixelFormat::P010, PixelFormat::Yuv420p]);
1209 let result = negotiate_formats(&decoder, &encoder).expect("should negotiate");
1210 assert_eq!(result.pixel_format, Some(PixelFormat::P010));
1211 }
1212
1213 #[test]
1214 fn test_format_capabilities_video_constructor() {
1215 let caps = FormatCapabilities::video("vp9", vec![PixelFormat::Yuv420p]);
1216 assert_eq!(caps.codec_name, "vp9");
1217 assert_eq!(caps.pixel_formats.formats.len(), 1);
1218 assert!(caps.sample_formats.formats.is_empty());
1219 assert!(caps.sample_rates.is_empty());
1220 assert!(caps.channel_counts.is_empty());
1221 }
1222
1223 #[test]
1224 fn test_format_capabilities_audio_constructor() {
1225 let caps = FormatCapabilities::audio(
1226 "flac",
1227 vec![SampleFormat::S16, SampleFormat::S24],
1228 vec![44100, 48000, 96000],
1229 vec![1, 2],
1230 );
1231 assert_eq!(caps.codec_name, "flac");
1232 assert!(caps.pixel_formats.formats.is_empty());
1233 assert_eq!(caps.sample_formats.formats.len(), 2);
1234 assert_eq!(caps.sample_rates.len(), 3);
1235 assert_eq!(caps.channel_counts.len(), 2);
1236 }
1237
1238 #[test]
1241 fn test_resolution_range_contains() {
1242 let r = ResolutionRange::new(1, 1920, 1, 1080);
1243 assert!(r.contains(1920, 1080));
1244 assert!(r.contains(1, 1));
1245 assert!(r.contains(1280, 720));
1246 assert!(!r.contains(3840, 2160));
1247 assert!(!r.contains(0, 0));
1248 }
1249
1250 #[test]
1251 fn test_resolution_range_intersect() {
1252 let a = ResolutionRange::new(1, 3840, 1, 2160);
1253 let b = ResolutionRange::new(640, 1920, 480, 1080);
1254 let intersect = a.intersect(&b).expect("should intersect");
1255 assert_eq!(intersect.min_width, 640);
1256 assert_eq!(intersect.max_width, 1920);
1257 assert_eq!(intersect.min_height, 480);
1258 assert_eq!(intersect.max_height, 1080);
1259 }
1260
1261 #[test]
1262 fn test_resolution_range_no_intersect() {
1263 let a = ResolutionRange::new(1, 640, 1, 480);
1264 let b = ResolutionRange::new(1920, 3840, 1080, 2160);
1265 assert!(a.intersect(&b).is_none());
1266 }
1267
1268 #[test]
1269 fn test_resolution_range_default() {
1270 let r = ResolutionRange::default();
1271 assert!(r.contains(1920, 1080));
1272 assert!(r.contains(7680, 4320));
1273 }
1274
1275 #[test]
1278 fn test_bitrate_range_contains() {
1279 let b = BitrateRange::new(1_000_000, 10_000_000);
1280 assert!(b.contains(5_000_000));
1281 assert!(b.contains(1_000_000));
1282 assert!(b.contains(10_000_000));
1283 assert!(!b.contains(500_000));
1284 assert!(!b.contains(20_000_000));
1285 }
1286
1287 #[test]
1288 fn test_bitrate_range_intersect() {
1289 let a = BitrateRange::new(500_000, 20_000_000);
1290 let b = BitrateRange::new(1_000_000, 15_000_000);
1291 let intersect = a.intersect(&b).expect("should intersect");
1292 assert_eq!(intersect.min_bps, 1_000_000);
1293 assert_eq!(intersect.max_bps, 15_000_000);
1294 }
1295
1296 #[test]
1297 fn test_bitrate_range_no_intersect() {
1298 let a = BitrateRange::new(1_000_000, 2_000_000);
1299 let b = BitrateRange::new(5_000_000, 10_000_000);
1300 assert!(a.intersect(&b).is_none());
1301 }
1302
1303 #[test]
1304 fn test_bitrate_range_default() {
1305 let b = BitrateRange::default();
1306 assert!(b.contains(0));
1307 assert!(b.contains(1_000_000_000));
1308 }
1309
1310 #[test]
1313 fn test_auto_negotiate_video_success() {
1314 let decoder = EndpointCapabilities::video(
1315 CodecCapability::new("av1", vec!["main".into()], 50, true),
1316 vec![PixelFormat::Yuv420p, PixelFormat::Yuv420p10le],
1317 ResolutionRange::new(1, 3840, 1, 2160),
1318 BitrateRange::new(500_000, 20_000_000),
1319 );
1320 let encoder = EndpointCapabilities::video(
1321 CodecCapability::new("av1", vec!["main".into()], 40, false),
1322 vec![PixelFormat::Yuv420p10le, PixelFormat::Yuv420p],
1323 ResolutionRange::new(1, 1920, 1, 1080),
1324 BitrateRange::new(1_000_000, 15_000_000),
1325 );
1326 let result = auto_negotiate(&decoder, &encoder).expect("should negotiate");
1327 assert_eq!(result.codec, "av1");
1328 assert_eq!(result.profile, "main");
1329 assert_eq!(result.level, 40); assert!(result.hardware_accelerated); assert_eq!(result.format.pixel_format, Some(PixelFormat::Yuv420p));
1332
1333 let res = result.resolution.expect("should have resolution");
1334 assert_eq!(res.max_width, 1920);
1335 assert_eq!(res.max_height, 1080);
1336
1337 let br = result.bitrate.expect("should have bitrate");
1338 assert_eq!(br.min_bps, 1_000_000);
1339 assert_eq!(br.max_bps, 15_000_000);
1340
1341 assert!(result.score > 0.0);
1342 assert!(result.score <= 1.0);
1343 }
1344
1345 #[test]
1346 fn test_auto_negotiate_audio_success() {
1347 let decoder = EndpointCapabilities::audio(
1348 CodecCapability::new("opus", vec!["default".into()], 0, false),
1349 vec![SampleFormat::F32, SampleFormat::S16],
1350 vec![48000, 44100],
1351 vec![2, 1],
1352 BitrateRange::new(64_000, 510_000),
1353 );
1354 let encoder = EndpointCapabilities::audio(
1355 CodecCapability::new("opus", vec!["default".into()], 0, false),
1356 vec![SampleFormat::S16, SampleFormat::F32],
1357 vec![48000],
1358 vec![2],
1359 BitrateRange::new(96_000, 256_000),
1360 );
1361 let result = auto_negotiate(&decoder, &encoder).expect("should negotiate");
1362 assert_eq!(result.codec, "opus");
1363 assert_eq!(result.format.sample_format, Some(SampleFormat::F32));
1364 assert_eq!(result.format.sample_rate, Some(48000));
1365 assert_eq!(result.format.channel_count, Some(2));
1366
1367 let br = result.bitrate.expect("should have bitrate");
1368 assert_eq!(br.min_bps, 96_000);
1369 assert_eq!(br.max_bps, 256_000);
1370 }
1371
1372 #[test]
1373 fn test_auto_negotiate_codec_mismatch() {
1374 let decoder = EndpointCapabilities::video(
1375 CodecCapability::new("av1", vec!["main".into()], 40, false),
1376 vec![PixelFormat::Yuv420p],
1377 ResolutionRange::default(),
1378 BitrateRange::default(),
1379 );
1380 let encoder = EndpointCapabilities::video(
1381 CodecCapability::new("vp9", vec!["profile0".into()], 40, false),
1382 vec![PixelFormat::Yuv420p],
1383 ResolutionRange::default(),
1384 BitrateRange::default(),
1385 );
1386 assert!(auto_negotiate(&decoder, &encoder).is_none());
1387 }
1388
1389 #[test]
1390 fn test_auto_negotiate_format_mismatch() {
1391 let decoder = EndpointCapabilities::video(
1392 CodecCapability::new("av1", vec!["main".into()], 40, false),
1393 vec![PixelFormat::Yuv420p],
1394 ResolutionRange::default(),
1395 BitrateRange::default(),
1396 );
1397 let encoder = EndpointCapabilities::video(
1398 CodecCapability::new("av1", vec!["main".into()], 40, false),
1399 vec![PixelFormat::Rgb24],
1400 ResolutionRange::default(),
1401 BitrateRange::default(),
1402 );
1403 assert!(auto_negotiate(&decoder, &encoder).is_none());
1404 }
1405
1406 #[test]
1407 fn test_auto_negotiate_hw_boosts_score() {
1408 let make_endpoint = |hw: bool| {
1409 EndpointCapabilities::video(
1410 CodecCapability::new("av1", vec!["main".into()], 40, hw),
1411 vec![PixelFormat::Yuv420p],
1412 ResolutionRange::default(),
1413 BitrateRange::default(),
1414 )
1415 };
1416 let hw_result =
1417 auto_negotiate(&make_endpoint(true), &make_endpoint(true)).expect("should negotiate");
1418 let sw_result =
1419 auto_negotiate(&make_endpoint(false), &make_endpoint(false)).expect("should negotiate");
1420 assert!(hw_result.score > sw_result.score);
1421 }
1422
1423 #[test]
1424 fn test_auto_negotiate_resolution_no_overlap_still_returns() {
1425 let decoder = EndpointCapabilities::video(
1427 CodecCapability::new("av1", vec!["main".into()], 40, false),
1428 vec![PixelFormat::Yuv420p],
1429 ResolutionRange::new(1, 640, 1, 480),
1430 BitrateRange::default(),
1431 );
1432 let encoder = EndpointCapabilities::video(
1433 CodecCapability::new("av1", vec!["main".into()], 40, false),
1434 vec![PixelFormat::Yuv420p],
1435 ResolutionRange::new(1920, 3840, 1080, 2160),
1436 BitrateRange::default(),
1437 );
1438 let result = auto_negotiate(&decoder, &encoder).expect("should still negotiate");
1439 assert!(result.resolution.is_none());
1440 }
1441
1442 #[test]
1443 fn test_score_range() {
1444 let endpoint = EndpointCapabilities::video(
1446 CodecCapability::new("av1", vec!["main".into()], 63, true),
1447 vec![PixelFormat::Yuv420p10le],
1448 ResolutionRange::default(),
1449 BitrateRange::default(),
1450 );
1451 let result = auto_negotiate(&endpoint, &endpoint).expect("should negotiate");
1452 assert!(result.score >= 0.0);
1453 assert!(result.score <= 1.0);
1454 }
1455
1456 #[test]
1457 fn test_endpoint_capabilities_video_constructor() {
1458 let ep = EndpointCapabilities::video(
1459 CodecCapability::new("vp9", vec!["profile0".into()], 50, false),
1460 vec![PixelFormat::Yuv420p, PixelFormat::Nv12],
1461 ResolutionRange::new(1, 1920, 1, 1080),
1462 BitrateRange::new(1_000_000, 10_000_000),
1463 );
1464 assert_eq!(ep.codec.name, "vp9");
1465 assert_eq!(ep.formats.pixel_formats.formats.len(), 2);
1466 assert_eq!(ep.resolution.max_width, 1920);
1467 assert_eq!(ep.bitrate.max_bps, 10_000_000);
1468 }
1469
1470 #[test]
1471 fn test_endpoint_capabilities_audio_constructor() {
1472 let ep = EndpointCapabilities::audio(
1473 CodecCapability::new("flac", vec!["default".into()], 0, false),
1474 vec![SampleFormat::S16, SampleFormat::S24],
1475 vec![44100, 48000, 96000],
1476 vec![1, 2],
1477 BitrateRange::new(0, 5_000_000),
1478 );
1479 assert_eq!(ep.codec.name, "flac");
1480 assert_eq!(ep.formats.sample_formats.formats.len(), 2);
1481 assert_eq!(ep.formats.sample_rates.len(), 3);
1482 }
1483}