1use std::collections::HashSet;
48
49use o_sfu_rfc::rtp as rfc_rtp;
50
51#[cfg(any(test, feature = "test-support"))]
52use super::diagnostic::{ParseDiagnostic, ParseDiagnosticKind, ParseDiagnosticSpec, RfcReference};
53use super::{
54 CodecSetting, HeaderExtension, HeaderExtensionUri, MediaCapabilities, MediaCodec,
55 MediaCodecCapability, MediaFormat, MediaKind, MediaStream, PayloadType, RtcpFeedback,
56 RtcpFeedbackKind,
57};
58
59#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
61pub enum RtpNegotiationError {
62 #[error("unsupported producer codec {codec_name} payload {payload_type}")]
63 UnsupportedProducerCodec {
64 codec_name: String,
65 payload_type: u8,
66 },
67 #[error("invalid apt parameter for codec {codec_name} payload {payload_type}")]
68 InvalidAptParameter {
69 codec_name: String,
70 payload_type: u8,
71 },
72 #[error("missing media codec for rtx payload {payload_type} apt {associated_payload_type}")]
73 MissingAssociatedMediaCodecForRtx {
74 payload_type: u8,
75 associated_payload_type: u8,
76 },
77 #[error("no compatible consumer codec")]
78 NoCompatibleConsumerCodec,
79}
80
81#[cfg(any(test, feature = "test-support"))]
82const RFC_3264_SECTION_6: RfcReference = RfcReference::new(
83 "RFC 3264",
84 "section 6",
85 "https://www.rfc-editor.org/rfc/rfc3264.html#section-6",
86);
87#[cfg(any(test, feature = "test-support"))]
88const RFC_4588_SECTION_8_1: RfcReference = RfcReference::new(
89 "RFC 4588",
90 "section 8.1",
91 "https://www.rfc-editor.org/rfc/rfc4588.html#section-8.1",
92);
93
94#[cfg(any(test, feature = "test-support"))]
95impl ParseDiagnostic for RtpNegotiationError {
96 fn diagnostic(&self) -> ParseDiagnosticSpec {
97 match self {
98 Self::UnsupportedProducerCodec { .. } => ParseDiagnosticSpec::new(
99 ParseDiagnosticKind::UnsupportedFeature,
100 "producer codec is valid but not supported by router capabilities",
101 RFC_3264_SECTION_6,
102 "capture producer media stream and router media capabilities and replay derive_consumable_rtp_parameters",
103 ),
104 Self::InvalidAptParameter { .. } => ParseDiagnosticSpec::new(
105 ParseDiagnosticKind::InvalidInput,
106 "RTX codec has an invalid or missing apt parameter",
107 RFC_4588_SECTION_8_1,
108 "capture producer media stream and replay derive_consumable_rtp_parameters to inspect RTX linkage",
109 ),
110 Self::MissingAssociatedMediaCodecForRtx { .. } => ParseDiagnosticSpec::new(
111 ParseDiagnosticKind::InvalidInput,
112 "RTX codec references an associated payload type that is not negotiated",
113 RFC_4588_SECTION_8_1,
114 "capture producer media stream and replay derive_consumable_rtp_parameters to inspect RTX linkage",
115 ),
116 Self::NoCompatibleConsumerCodec => ParseDiagnosticSpec::new(
117 ParseDiagnosticKind::UnsupportedFeature,
118 "consumer capabilities have no compatible media codec with the consumable set",
119 RFC_3264_SECTION_6,
120 "capture consumable media stream and consumer capabilities and replay negotiate_consumer_rtp_parameters",
121 ),
122 }
123 }
124}
125
126pub fn derive_consumable_rtp_parameters(
167 producer_parameters: &MediaStream,
168 capabilities: &MediaCapabilities,
169) -> Result<MediaStream, RtpNegotiationError> {
170 let mut producer_to_router_payload_types = Vec::<(PayloadType, PayloadType)>::new();
178 let mut consumable_formats = Vec::new();
179
180 for format in producer_parameters.formats() {
185 if format.codec().is_rtx() {
189 continue;
190 }
191 let Some(capability_format) = find_matching_media_capability(format, capabilities) else {
192 return Err(RtpNegotiationError::UnsupportedProducerCodec {
196 codec_name: format.codec().as_str().to_owned(),
197 payload_type: format.payload_type(),
198 });
199 };
200 let negotiated_payload_type = negotiated_payload_type(capability_format, format);
201 producer_to_router_payload_types.push((format.payload_type_id(), negotiated_payload_type));
202 let feedback =
203 intersect_feedback(format.rtcp_feedback(), capability_format.rtcp_feedback());
204 consumable_formats.push(format_with_overrides(
205 format,
206 negotiated_payload_type,
207 None,
208 &feedback,
209 ));
210 }
211
212 for format in producer_parameters.formats() {
213 if !format.codec().is_rtx() {
214 continue;
215 }
216 let associated_payload_type = parse_rtx_associated_payload(format)?;
217 let Some(negotiated_associated_payload_type) = producer_to_router_payload_types
218 .iter()
219 .find_map(|(original, mapped)| {
220 (*original == associated_payload_type).then_some(*mapped)
221 })
222 else {
223 return Err(RtpNegotiationError::MissingAssociatedMediaCodecForRtx {
227 payload_type: format.payload_type(),
228 associated_payload_type: associated_payload_type.value(),
229 });
230 };
231 let Some(capability_format) =
232 find_matching_rtx_capability(format, negotiated_associated_payload_type, capabilities)
233 else {
234 continue;
239 };
240 let negotiated_payload_type = negotiated_payload_type(capability_format, format);
241 let feedback =
242 intersect_feedback(format.rtcp_feedback(), capability_format.rtcp_feedback());
243 consumable_formats.push(format_with_overrides(
244 format,
245 negotiated_payload_type,
246 Some(negotiated_associated_payload_type),
247 &feedback,
248 ));
249 }
250 normalize_nack_rtx(&mut consumable_formats);
251
252 let header_extensions = capabilities
253 .header_extensions()
254 .filter(|extension| {
255 producer_parameters
259 .header_extensions()
260 .any(|producer_extension| producer_extension.uri_kind() == extension.uri_kind())
261 })
262 .cloned()
263 .collect::<Vec<_>>();
264 let bindings = producer_parameters
265 .bindings()
266 .cloned()
267 .map(|binding| binding.with_payload_type_mapping(&producer_to_router_payload_types))
268 .collect::<Vec<_>>();
269
270 let mut consumable = MediaStream::new(consumable_formats, header_extensions, bindings);
271 if let Some(mid) = producer_parameters.mid() {
272 consumable = consumable.with_mid(mid);
273 }
274 Ok(consumable)
275}
276
277pub fn negotiate_consumer_rtp_parameters(
295 consumable_parameters: &MediaStream,
296 consumer_capabilities: &MediaCapabilities,
297) -> Result<MediaStream, RtpNegotiationError> {
298 let negotiated_header_extensions =
299 negotiate_header_extensions(consumable_parameters, consumer_capabilities);
300 let feedback_policy = bwe_feedback_policy(&negotiated_header_extensions);
301
302 let mut negotiated_formats = consumable_parameters
303 .formats()
304 .filter_map(|format| {
305 if format.codec().is_rtx() {
309 return None;
310 }
311 let capability_format = find_matching_media_capability(format, consumer_capabilities)?;
312 let feedback =
313 intersect_feedback(format.rtcp_feedback(), capability_format.rtcp_feedback());
314 let feedback = apply_bwe_feedback_policy(feedback, feedback_policy);
315 Some(format_with_overrides(
316 format,
317 format.payload_type_id(),
318 None,
319 &feedback,
320 ))
321 })
322 .collect::<Vec<_>>();
323
324 if negotiated_formats.is_empty() {
325 return Err(RtpNegotiationError::NoCompatibleConsumerCodec);
329 }
330
331 for format in consumable_parameters.formats() {
332 if !format.codec().is_rtx() {
333 continue;
334 }
335 let Some(capability_format) = find_matching_consumer_rtx_capability(
336 format,
337 &negotiated_formats,
338 consumer_capabilities,
339 ) else {
340 continue;
341 };
342 let feedback =
343 intersect_feedback(format.rtcp_feedback(), capability_format.rtcp_feedback());
344 let feedback = apply_bwe_feedback_policy(feedback, feedback_policy);
345 negotiated_formats.push(format_with_overrides(
346 format,
347 format.payload_type_id(),
348 None,
349 &feedback,
350 ));
351 }
352 normalize_nack_rtx(&mut negotiated_formats);
353
354 let bindings = consumable_parameters
355 .bindings()
356 .filter(|binding| {
357 binding.payload_type_id().is_none_or(|payload_type| {
358 formats_contain_payload_type(&negotiated_formats, payload_type)
359 })
360 })
361 .cloned()
362 .collect::<Vec<_>>();
363
364 let mut negotiated =
365 MediaStream::new(negotiated_formats, negotiated_header_extensions, bindings);
366 if let Some(mid) = consumable_parameters.mid() {
367 negotiated = negotiated.with_mid(mid);
368 }
369 Ok(negotiated)
370}
371
372#[must_use]
377pub fn can_consume(
378 consumable_parameters: &MediaStream,
379 consumer_capabilities: &MediaCapabilities,
380) -> bool {
381 negotiate_consumer_rtp_parameters(consumable_parameters, consumer_capabilities).is_ok()
382}
383
384fn negotiated_payload_type(
393 capability_format: &MediaCodecCapability,
394 format: &MediaFormat,
395) -> PayloadType {
396 capability_format
397 .payload_type_id()
398 .unwrap_or(format.payload_type_id())
399}
400
401fn find_matching_media_capability<'a>(
408 format: &MediaFormat,
409 capabilities: &'a MediaCapabilities,
410) -> Option<&'a MediaCodecCapability> {
411 capabilities.codecs().find(|capability_format| {
412 !capability_format.codec().is_rtx()
413 && codec_match_ignoring_payload_type(format, capability_format)
414 })
415}
416
417fn find_matching_rtx_capability<'a>(
424 format: &MediaFormat,
425 negotiated_associated_payload_type: PayloadType,
426 capabilities: &'a MediaCapabilities,
427) -> Option<&'a MediaCodecCapability> {
428 capabilities.codecs().find(|capability_format| {
429 capability_format.codec().is_rtx()
430 && codec_match_ignoring_payload_type(format, capability_format)
431 && capability_format.rtx_associated_payload_type_id()
432 == Some(negotiated_associated_payload_type)
433 })
434}
435
436fn find_matching_consumer_rtx_capability<'a>(
441 format: &MediaFormat,
442 negotiated_formats: &[MediaFormat],
443 capabilities: &'a MediaCapabilities,
444) -> Option<&'a MediaCodecCapability> {
445 let associated_payload_type = parse_rtx_associated_payload(format).ok()?;
446 if !formats_contain_primary_payload_type(negotiated_formats, associated_payload_type) {
449 return None;
450 }
451 capabilities.codecs().find(|capability_format| {
452 capability_format.codec().is_rtx()
453 && codec_match_ignoring_payload_type(format, capability_format)
454 && capability_format.rtx_associated_payload_type_id() == Some(associated_payload_type)
455 })
456}
457
458fn codec_match_ignoring_payload_type(
464 format: &MediaFormat,
465 capability_format: &MediaCodecCapability,
466) -> bool {
467 if format.media_kind() != capability_format.media_kind()
468 || format.codec() != capability_format.codec()
469 || format.clock_rate() != capability_format.clock_rate()
470 {
471 return false;
472 }
473 if normalized_channels(format.media_kind(), format.channels())
474 != normalized_channels(capability_format.media_kind(), capability_format.channels())
475 {
476 return false;
477 }
478 critical_codec_settings_match(format, capability_format)
479}
480
481fn critical_codec_settings_match(
488 format: &MediaFormat,
489 capability_format: &MediaCodecCapability,
490) -> bool {
491 match format.codec() {
492 MediaCodec::H264 => h264_critical_settings_match(format, capability_format),
493 MediaCodec::Vp9 => vp9_critical_settings_match(format, capability_format),
494 _ => true,
495 }
496}
497
498fn h264_critical_settings_match(
502 format: &MediaFormat,
503 capability_format: &MediaCodecCapability,
504) -> bool {
505 let Some(format_packetization_mode) = h264_packetization_mode(format.settings()) else {
506 return false;
507 };
508 let Some(capability_packetization_mode) = h264_packetization_mode(capability_format.settings())
509 else {
510 return false;
511 };
512 if format_packetization_mode != capability_packetization_mode {
516 return false;
517 }
518 let format_profile_level_id = format
519 .settings()
520 .find_map(|setting| match setting {
521 CodecSetting::H264ProfileLevelId(profile_level_id) => Some(profile_level_id.as_str()),
522 _ => None,
523 })
524 .unwrap_or(rfc_rtp::fmtp::H264_DEFAULT_PROFILE_LEVEL_ID);
525 let capability_profile_level_id = capability_format
526 .settings()
527 .find_map(|setting| match setting {
528 CodecSetting::H264ProfileLevelId(profile_level_id) => Some(profile_level_id.as_str()),
529 _ => None,
530 })
531 .unwrap_or(rfc_rtp::fmtp::H264_DEFAULT_PROFILE_LEVEL_ID);
532 let Some(parsed_format_profile_level_id) =
533 rfc_rtp::h264::ProfileLevelId::parse(format_profile_level_id)
534 else {
535 return false;
536 };
537 let Some(parsed_capability_profile_level_id) =
538 rfc_rtp::h264::ProfileLevelId::parse(capability_profile_level_id)
539 else {
540 return false;
541 };
542 parsed_format_profile_level_id.profile() == parsed_capability_profile_level_id.profile()
543 && parsed_format_profile_level_id.level() <= parsed_capability_profile_level_id.level()
544}
545
546fn h264_packetization_mode<'a>(
547 settings: impl Iterator<Item = &'a CodecSetting>,
548) -> Option<rfc_rtp::h264::PacketizationMode> {
549 for setting in settings {
550 match setting {
551 CodecSetting::H264PacketizationMode(mode) => return Some(*mode),
552 CodecSetting::Other { key, .. } if key == rfc_rtp::fmtp::H264_PACKETIZATION_MODE => {
553 return None;
554 }
555 _ => {}
556 }
557 }
558 rfc_rtp::h264::PacketizationMode::from_fmtp_value(
559 rfc_rtp::fmtp::H264_DEFAULT_PACKETIZATION_MODE,
560 )
561}
562
563fn vp9_critical_settings_match(
564 format: &MediaFormat,
565 capability_format: &MediaCodecCapability,
566) -> bool {
567 effective_vp9_profile_id(format.settings())
568 .zip(effective_vp9_profile_id(capability_format.settings()))
569 .is_some_and(|(format_profile, capability_profile)| format_profile == capability_profile)
570}
571
572fn effective_vp9_profile_id<'a>(
573 settings: impl Iterator<Item = &'a CodecSetting>,
574) -> Option<rfc_rtp::Vp9ProfileId> {
575 let mut profile_id = None;
576 for setting in settings {
577 match setting {
578 CodecSetting::Vp9ProfileId(value) => profile_id = Some(*value),
579 CodecSetting::Other { key, .. } if key == rfc_rtp::fmtp::VP9_PROFILE_ID => return None,
580 _ => {}
581 }
582 }
583 Some(profile_id.unwrap_or(rfc_rtp::fmtp::VP9_DEFAULT_PROFILE_ID))
584}
585
586fn parse_rtx_associated_payload(format: &MediaFormat) -> Result<PayloadType, RtpNegotiationError> {
590 format.rtx_associated_payload_type_id().ok_or_else(|| {
591 RtpNegotiationError::InvalidAptParameter {
592 codec_name: format.codec().as_str().to_owned(),
593 payload_type: format.payload_type(),
594 }
595 })
596}
597
598fn format_with_overrides(
605 source: &MediaFormat,
606 payload_type: PayloadType,
607 apt_override: Option<PayloadType>,
608 feedback: &[RtcpFeedback],
609) -> MediaFormat {
610 let mut format = MediaFormat::new(
611 source.media_kind(),
612 source.codec().clone(),
613 payload_type,
614 source.clock_rate(),
615 );
616 if let Some(channels) = source.channels() {
617 format = format.with_channels(channels);
618 }
619 for setting in source
620 .settings()
621 .filter(|setting| !matches!(setting, CodecSetting::RtxAssociation(_)))
622 .cloned()
623 {
624 format = format.with_setting(setting);
625 }
626 if let Some(apt) = apt_override {
627 format = format.with_setting(CodecSetting::RtxAssociation(apt));
628 } else if let Some(apt) = source.rtx_associated_payload_type_id() {
629 format = format.with_setting(CodecSetting::RtxAssociation(apt));
630 }
631 for entry in feedback {
632 format = format.with_rtcp_feedback(entry.clone());
633 }
634 format
635}
636
637fn intersect_feedback<'a>(
642 format_feedback: impl Iterator<Item = &'a RtcpFeedback>,
643 capability_feedback: impl Iterator<Item = &'a RtcpFeedback>,
644) -> Vec<RtcpFeedback> {
645 let capability_feedback = capability_feedback.cloned().collect::<Vec<_>>();
646 format_feedback
647 .filter(|feedback| capability_feedback.contains(feedback))
648 .cloned()
649 .collect()
650}
651
652fn normalize_nack_rtx(formats: &mut Vec<MediaFormat>) {
653 let nack_payload_types = formats
660 .iter()
661 .filter(|format| !format.codec().is_rtx() && has_generic_nack(format))
662 .map(MediaFormat::payload_type_id)
663 .collect::<HashSet<_>>();
664 let mut paired_payload_types = HashSet::new();
665 formats.retain(|format| {
666 !format.codec().is_rtx()
667 || format.rtx_associated_payload_type_id().is_some_and(|apt| {
668 nack_payload_types.contains(&apt) && paired_payload_types.insert(apt)
669 })
670 });
671
672 for format in formats.iter_mut() {
673 if format.codec().is_rtx()
674 || !has_generic_nack(format)
675 || paired_payload_types.contains(&format.payload_type_id())
676 {
677 continue;
678 }
679 let feedback = format
680 .rtcp_feedback()
681 .filter(|feedback| !matches!(feedback.kind(), RtcpFeedbackKind::Nack))
682 .cloned()
683 .collect::<Vec<_>>();
684 *format = format_with_overrides(format, format.payload_type_id(), None, &feedback);
685 }
686}
687
688fn has_generic_nack(format: &MediaFormat) -> bool {
689 format
690 .rtcp_feedback()
691 .any(|feedback| matches!(feedback.kind(), RtcpFeedbackKind::Nack))
692}
693
694fn normalized_channels(media_kind: MediaKind, channels: Option<u16>) -> Option<u16> {
698 if media_kind == MediaKind::Audio {
699 Some(channels.unwrap_or(1))
700 } else {
701 None
702 }
703}
704
705fn negotiate_header_extensions(
715 consumable_parameters: &MediaStream,
716 consumer_capabilities: &MediaCapabilities,
717) -> Vec<HeaderExtension> {
718 consumable_parameters
719 .header_extensions()
720 .filter(|extension| {
724 consumer_capabilities
725 .header_extensions()
726 .any(|supported| supported.uri_kind() == extension.uri_kind())
727 })
728 .cloned()
729 .collect()
730}
731
732fn formats_contain_payload_type(formats: &[MediaFormat], payload_type: PayloadType) -> bool {
733 formats
734 .iter()
735 .any(|format| format.payload_type_id() == payload_type)
736}
737
738fn formats_contain_primary_payload_type(
739 formats: &[MediaFormat],
740 payload_type: PayloadType,
741) -> bool {
742 formats
743 .iter()
744 .any(|format| !format.codec().is_rtx() && format.payload_type_id() == payload_type)
745}
746
747#[derive(Debug, Clone, Copy, PartialEq, Eq)]
748enum BweFeedbackPolicy {
749 PreferTransportCc,
750 PreferGoogRemb,
751 DisableBoth,
752}
753
754fn bwe_feedback_policy(header_extensions: &[HeaderExtension]) -> BweFeedbackPolicy {
779 if header_extensions.iter().any(|extension| {
780 matches!(
781 extension.uri_kind(),
782 HeaderExtensionUri::TransportWideCcDraft01
783 )
784 }) {
785 return BweFeedbackPolicy::PreferTransportCc;
786 }
787 if header_extensions
788 .iter()
789 .any(|extension| matches!(extension.uri_kind(), HeaderExtensionUri::AbsSendTime))
790 {
791 return BweFeedbackPolicy::PreferGoogRemb;
792 }
793 BweFeedbackPolicy::DisableBoth
794}
795
796fn apply_bwe_feedback_policy(
800 feedback: Vec<RtcpFeedback>,
801 policy: BweFeedbackPolicy,
802) -> Vec<RtcpFeedback> {
803 feedback
804 .into_iter()
805 .filter(|entry| match policy {
806 BweFeedbackPolicy::PreferTransportCc => {
807 !matches!(entry.kind(), RtcpFeedbackKind::GoogRemb)
808 }
809 BweFeedbackPolicy::PreferGoogRemb => {
810 !matches!(entry.kind(), RtcpFeedbackKind::TransportCc)
811 }
812 BweFeedbackPolicy::DisableBoth => !matches!(
813 entry.kind(),
814 RtcpFeedbackKind::TransportCc | RtcpFeedbackKind::GoogRemb
815 ),
816 })
817 .collect()
818}