Skip to main content

o_sfu_router/model/
rtp.rs

1//! Router-native RTP model for the transport edge.
2//!
3//! This module defines the typed domain model used to describe media streams,
4//! codecs, and their negotiated properties. It sits between the signaling/SDP
5//! layer and the raw packet loop, allowing the router to reason about media
6//! without parsing raw bytes or string-heavy protocol bags.
7//!
8//! ### RTP Packet Context
9//!
10//! Most of the types defined here map directly to fields in the "RFC 3550"
11//! RTP header or its extensions:
12//!
13//! ```text
14//!  0                   1                   2                   3
15//!  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
16//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
17//! |V=2|P|X|  CC   |M|     PT      |       Sequence Number         |
18//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
19//! |                           Timestamp                           |
20//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
21//! |           Synchronization Source (SSRC) identifier            |
22//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
23//! ```
24//!
25//! - `V`: version (always 2)
26//! - `P`: padding bit
27//! - `X`: extension bit (if set, an extension header follows the SSRC)
28//! - `CC`: "CSRC count", the number of contributing source identifiers (0-15) that follow the SSRC
29//! - `M`: "Marker" bit, used by profiles to mark significant events like the end of a video frame
30//! - `PT`: "Payload Type" (identifies the codec)
31//! - `SSRC`: unique stream identifier
32//!
33//! RFC references for this module:
34//! - RTP base protocol: <https://www.rfc-editor.org/rfc/rfc3550>
35//! - RTP A/V profile payload assignments: <https://www.rfc-editor.org/rfc/rfc3551>
36//! - RTP header extension framework: <https://www.rfc-editor.org/rfc/rfc8285>
37
38use std::borrow::Cow;
39
40use o_sfu_rfc::{rtp as rfc_rtp, webrtc as rfc_webrtc};
41pub use rfc_rtp::{HeaderExtensionId, Mid, PayloadType, Rid, Ssrc};
42
43use super::MediaKind;
44
45/// Canonical name for a media codec (e.g. "opus", "vp8", "h264")
46pub type MediaCodec = rfc_rtp::CodecName;
47
48/// Uniform resource identifier for a header extension (e.g. "urn:ietf:params:rtp-hdrext:ssrc-audio-level")
49pub type HeaderExtensionUri = rfc_webrtc::RtpHeaderExtensionUri;
50
51/// Categories of feedback messages sent over RTCP to control stream behavior.
52///
53/// These define how the receiver reports issues (like packet loss) or requests
54/// changes (like a new keyframe) to the sender.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum RtcpFeedbackKind {
57    /// Generic NACK from
58    /// [RFC 4585 section 6.2.1](https://www.rfc-editor.org/rfc/rfc4585.html#section-6.2.1).
59    Nack,
60    /// Picture Loss Indication from
61    /// [RFC 4585 section 6.3.1](https://www.rfc-editor.org/rfc/rfc4585.html#section-6.3.1).
62    NackPli,
63    /// Full Intra Request from
64    /// [RFC 5104 section 4.3.1](https://www.rfc-editor.org/rfc/rfc5104.html#section-4.3.1).
65    CcmFir,
66    /// Google-specific receiver estimated maximum bitrate
67    GoogRemb,
68    /// Transport-wide congestion control (draft-holmer-rmcat-transport-wide-cc-extensions)
69    TransportCc,
70    /// Any other vendor-specific or experimental feedback type
71    Other(String),
72}
73
74/// One negotiated RTCP feedback mechanism for a codec.
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct RtcpFeedback {
77    kind: RtcpFeedbackKind,
78    parameter: Option<String>,
79}
80
81impl RtcpFeedback {
82    #[must_use]
83    pub fn new(kind: RtcpFeedbackKind, parameter: Option<String>) -> Self {
84        Self { kind, parameter }
85    }
86
87    #[must_use]
88    pub fn kind(&self) -> &RtcpFeedbackKind {
89        &self.kind
90    }
91
92    #[must_use]
93    pub fn parameter(&self) -> Option<&str> {
94        self.parameter.as_deref()
95    }
96}
97
98/// Typed codec parameter that affects interoperability.
99///
100/// These correspond to "a=fmtp" parameters in SDP. Mismatched settings
101/// here usually mean the receiver cannot decode the sender's bitstream.
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub enum CodecSetting {
104    /// RTX associated payload type from
105    /// [RFC 4588 section 8.1](https://www.rfc-editor.org/rfc/rfc4588.html#section-8.1).
106    RtxAssociation(PayloadType),
107    /// h264-specific packetization mode
108    H264PacketizationMode(rfc_rtp::h264::PacketizationMode),
109    /// H264 profile and level (e.g. "42e01f" for Constrained Baseline Level 3.1)
110    H264ProfileLevelId(String),
111    /// VP9-specific profile identifier
112    Vp9ProfileId(rfc_rtp::Vp9ProfileId),
113    /// OPUS-specific flag for in-band forward error correction
114    UseInBandFec(bool),
115    /// Generic catch-all for unknown or vendor parameters
116    Other { key: String, value: String },
117}
118
119impl CodecSetting {
120    #[must_use]
121    pub fn key(&self) -> &str {
122        match self {
123            Self::RtxAssociation(_) => rfc_rtp::fmtp::RTX_ASSOCIATION,
124            Self::H264PacketizationMode(_) => rfc_rtp::fmtp::H264_PACKETIZATION_MODE,
125            Self::H264ProfileLevelId(_) => rfc_rtp::fmtp::H264_PROFILE_LEVEL_ID,
126            Self::Vp9ProfileId(_) => rfc_rtp::fmtp::VP9_PROFILE_ID,
127            Self::UseInBandFec(_) => rfc_rtp::fmtp::OPUS_USE_IN_BAND_FEC,
128            Self::Other { key, .. } => key.as_str(),
129        }
130    }
131
132    #[must_use]
133    pub fn wire_value(&self) -> Cow<'_, str> {
134        match self {
135            Self::RtxAssociation(payload_type) => Cow::Owned(payload_type.value().to_string()),
136            Self::H264PacketizationMode(mode) => Cow::Owned(mode.fmtp_value().to_string()),
137            Self::H264ProfileLevelId(profile_level_id) => Cow::Borrowed(profile_level_id.as_str()),
138            Self::Vp9ProfileId(profile_id) => Cow::Owned(profile_id.value().to_string()),
139            Self::UseInBandFec(enabled) => Cow::Borrowed(if *enabled {
140                rfc_rtp::fmtp::VALUE_ENABLED
141            } else {
142                rfc_rtp::fmtp::VALUE_DISABLED
143            }),
144            Self::Other { value, .. } => Cow::Borrowed(value.as_str()),
145        }
146    }
147}
148
149/// RTP header-extension configuration (RFC 8285).
150///
151/// Allows carrying extra metadata (like bandwidth estimation hints or
152/// audio levels) in a standard way within the RTP packet header.
153///
154/// ```text
155///  0                   1                   2                   3
156///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
157/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
158/// |      defined by profile       |           length              |
159/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
160/// |  ID   |  len  |     data...                                   |
161/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
162/// ```
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct HeaderExtension {
165    uri: HeaderExtensionUri,
166    id: HeaderExtensionId,
167    encrypt: bool,
168}
169
170impl HeaderExtension {
171    #[must_use]
172    pub fn new(uri: impl Into<HeaderExtensionUri>, id: impl Into<HeaderExtensionId>) -> Self {
173        Self {
174            uri: uri.into(),
175            id: id.into(),
176            encrypt: false,
177        }
178    }
179
180    #[must_use]
181    pub fn with_encryption(mut self, encrypt: bool) -> Self {
182        self.encrypt = encrypt;
183        self
184    }
185
186    #[must_use]
187    pub fn uri_kind(&self) -> &HeaderExtensionUri {
188        &self.uri
189    }
190
191    #[must_use]
192    pub fn id(&self) -> HeaderExtensionId {
193        self.id
194    }
195
196    #[must_use]
197    pub fn uri(&self) -> &str {
198        self.uri.as_str()
199    }
200
201    #[must_use]
202    pub fn encrypt(&self) -> bool {
203        self.encrypt
204    }
205}
206
207/// Codec capability advertised by a router or endpoint.
208///
209/// Represents one possible way an endpoint can encode or decode media.
210/// The `payload_type` is optional here because capabilities are often
211/// just templates (e.g. "i support VP8") before a concrete session pins
212/// a specific PT number.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct MediaCodecCapability {
215    media_kind: MediaKind,
216    codec: MediaCodec,
217    clock_rate: u32,
218    payload_type: Option<PayloadType>,
219    channels: Option<u16>,
220    settings: Vec<CodecSetting>,
221    rtcp_feedback: Vec<RtcpFeedback>,
222}
223
224impl MediaCodecCapability {
225    #[must_use]
226    pub fn new(media_kind: MediaKind, codec: impl Into<MediaCodec>, clock_rate: u32) -> Self {
227        Self {
228            media_kind,
229            codec: codec.into(),
230            clock_rate,
231            payload_type: None,
232            channels: None,
233            settings: Vec::new(),
234            rtcp_feedback: Vec::new(),
235        }
236    }
237
238    #[must_use]
239    pub fn with_payload_type(mut self, payload_type: PayloadType) -> Self {
240        self.payload_type = Some(payload_type);
241        self
242    }
243
244    #[must_use]
245    pub fn with_channels(mut self, channels: u16) -> Self {
246        self.channels = Some(channels);
247        self
248    }
249
250    #[must_use]
251    pub fn with_setting(mut self, setting: CodecSetting) -> Self {
252        self.settings.push(setting);
253        self
254    }
255
256    /// An invalid RTX `apt` remains in [`Self::parameters`] while
257    /// [`Self::rtx_associated_payload_type`] returns `None`.
258    #[must_use]
259    pub fn with_parameter(self, name: impl Into<String>, value: impl Into<String>) -> Self {
260        let setting = codec_setting_from_wire(&self.codec, name.into(), value.into());
261        self.with_setting(setting)
262    }
263
264    #[must_use]
265    pub fn with_rtcp_feedback(mut self, feedback: RtcpFeedback) -> Self {
266        self.rtcp_feedback.push(feedback);
267        self
268    }
269
270    #[must_use]
271    pub fn media_kind(&self) -> MediaKind {
272        self.media_kind
273    }
274
275    #[must_use]
276    pub fn codec(&self) -> &MediaCodec {
277        &self.codec
278    }
279
280    #[must_use]
281    pub fn codec_name(&self) -> &str {
282        self.codec.as_str()
283    }
284
285    #[must_use]
286    pub fn clock_rate(&self) -> u32 {
287        self.clock_rate
288    }
289
290    #[must_use]
291    pub fn payload_type_id(&self) -> Option<PayloadType> {
292        self.payload_type
293    }
294
295    #[must_use]
296    pub fn payload_type(&self) -> Option<u8> {
297        self.payload_type.map(PayloadType::value)
298    }
299
300    #[must_use]
301    pub fn channels(&self) -> Option<u16> {
302        self.channels
303    }
304
305    pub fn settings(&self) -> impl Iterator<Item = &CodecSetting> {
306        self.settings.iter()
307    }
308
309    pub fn parameters(&self) -> impl Iterator<Item = (String, String)> + '_ {
310        self.settings
311            .iter()
312            .map(|setting| (setting.key().to_owned(), setting.wire_value().into_owned()))
313    }
314
315    pub fn rtcp_feedback(&self) -> impl Iterator<Item = &RtcpFeedback> {
316        self.rtcp_feedback.iter()
317    }
318
319    #[must_use]
320    pub fn rtx_associated_payload_type_id(&self) -> Option<PayloadType> {
321        self.settings.iter().find_map(|setting| match setting {
322            CodecSetting::RtxAssociation(payload_type) => Some(*payload_type),
323            _ => None,
324        })
325    }
326
327    #[must_use]
328    pub fn rtx_associated_payload_type(&self) -> Option<u8> {
329        self.rtx_associated_payload_type_id()
330            .map(PayloadType::value)
331    }
332}
333
334/// Full set of codec and extension capabilities for an RTP endpoint.
335#[derive(Debug, Clone, Default, PartialEq, Eq)]
336pub struct MediaCapabilities {
337    codecs: Vec<MediaCodecCapability>,
338    header_extensions: Vec<HeaderExtension>,
339}
340
341impl MediaCapabilities {
342    #[must_use]
343    pub fn new(codecs: Vec<MediaCodecCapability>, header_extensions: Vec<HeaderExtension>) -> Self {
344        Self {
345            codecs,
346            header_extensions,
347        }
348    }
349
350    pub fn codecs(&self) -> impl Iterator<Item = &MediaCodecCapability> {
351        self.codecs.iter()
352    }
353
354    pub fn header_extensions(&self) -> impl Iterator<Item = &HeaderExtension> {
355        self.header_extensions.iter()
356    }
357}
358
359/// Negotiated codec format for a concrete media stream.
360///
361/// Unlike a capability, a format has a fixed `payload_type` that matches the
362/// actual value expected in the RTP packets on the wire.
363#[derive(Debug, Clone, PartialEq, Eq)]
364pub struct MediaFormat {
365    media_kind: MediaKind,
366    codec: MediaCodec,
367    payload_type: PayloadType,
368    clock_rate: u32,
369    channels: Option<u16>,
370    settings: Vec<CodecSetting>,
371    rtcp_feedback: Vec<RtcpFeedback>,
372}
373
374impl MediaFormat {
375    #[must_use]
376    pub fn new(
377        media_kind: MediaKind,
378        codec: impl Into<MediaCodec>,
379        payload_type: PayloadType,
380        clock_rate: u32,
381    ) -> Self {
382        Self {
383            media_kind,
384            codec: codec.into(),
385            payload_type,
386            clock_rate,
387            channels: None,
388            settings: Vec::new(),
389            rtcp_feedback: Vec::new(),
390        }
391    }
392
393    #[must_use]
394    pub fn with_channels(mut self, channels: u16) -> Self {
395        self.channels = Some(channels);
396        self
397    }
398
399    #[must_use]
400    pub fn with_setting(mut self, setting: CodecSetting) -> Self {
401        self.settings.push(setting);
402        self
403    }
404
405    #[must_use]
406    pub fn with_parameter(self, name: impl Into<String>, value: impl Into<String>) -> Self {
407        let setting = codec_setting_from_wire(&self.codec, name.into(), value.into());
408        self.with_setting(setting)
409    }
410
411    #[must_use]
412    pub fn with_rtcp_feedback(mut self, feedback: RtcpFeedback) -> Self {
413        self.rtcp_feedback.push(feedback);
414        self
415    }
416
417    #[must_use]
418    pub fn media_kind(&self) -> MediaKind {
419        self.media_kind
420    }
421
422    #[must_use]
423    pub fn codec(&self) -> &MediaCodec {
424        &self.codec
425    }
426
427    #[must_use]
428    pub fn codec_name(&self) -> &str {
429        self.codec.as_str()
430    }
431
432    #[must_use]
433    pub fn payload_type_id(&self) -> PayloadType {
434        self.payload_type
435    }
436
437    #[must_use]
438    pub fn payload_type(&self) -> u8 {
439        self.payload_type.value()
440    }
441
442    #[must_use]
443    pub fn clock_rate(&self) -> u32 {
444        self.clock_rate
445    }
446
447    #[must_use]
448    pub fn channels(&self) -> Option<u16> {
449        self.channels
450    }
451
452    pub fn settings(&self) -> impl Iterator<Item = &CodecSetting> {
453        self.settings.iter()
454    }
455
456    pub fn parameters(&self) -> impl Iterator<Item = (String, String)> + '_ {
457        self.settings
458            .iter()
459            .map(|setting| (setting.key().to_owned(), setting.wire_value().into_owned()))
460    }
461
462    pub fn rtcp_feedback(&self) -> impl Iterator<Item = &RtcpFeedback> {
463        self.rtcp_feedback.iter()
464    }
465
466    #[must_use]
467    pub fn rtx_associated_payload_type_id(&self) -> Option<PayloadType> {
468        self.settings.iter().find_map(|setting| match setting {
469            CodecSetting::RtxAssociation(payload_type) => Some(*payload_type),
470            _ => None,
471        })
472    }
473
474    #[must_use]
475    pub fn rtx_associated_payload_type(&self) -> Option<u8> {
476        self.rtx_associated_payload_type_id()
477            .map(PayloadType::value)
478    }
479}
480
481/// Routing bridge between a negotiated media format and a physical stream.
482///
483/// Ties logical codec formats to the physical multiplexing identifiers (primary SSRC,
484/// repair SSRC, or simulcast RID) resolved from RTP packet headers and SDP.
485///
486/// ```text
487/// Wire RTP Packet Multiplexing:
488///   Primary Stream:  [ RTP Header: SSRC 1000 ] ------------+
489///                                                          |
490///   RTX Repair:      [ RTP Header: SSRC 1001 ] (FID/RRID)  +--> StreamBinding
491///                                                          |    - SSRC: 1000
492///   Simulcast Layer: [ Header Ext: RID "h"   ] ------------+    - Repair SSRC: 1001
493///                                                               - RID: "h"
494///                                                               - PayloadType: 96
495/// ```
496///
497/// FID supplies `repair_ssrc`. RID and repaired RID associate packets through `rid`.
498///
499/// References:
500/// - RFC 4588 section 5 (RTX source association): <https://www.rfc-editor.org/rfc/rfc4588.html#section-5>
501/// - RFC 5576 section 4.2 (SDP FID grouping): <https://www.rfc-editor.org/rfc/rfc5576.html#section-4.2>
502/// - RFC 8852 section 3.3 (RID-based repair): <https://www.rfc-editor.org/rfc/rfc8852.html#section-3.3>
503#[derive(Debug, Clone, Default, PartialEq, Eq)]
504pub struct StreamBinding {
505    ssrc: Option<Ssrc>,
506    repair_ssrc: Option<Ssrc>,
507    rid: Option<Rid>,
508    payload_type: Option<PayloadType>,
509    max_bitrate: Option<u64>,
510}
511
512impl StreamBinding {
513    #[must_use]
514    pub fn new() -> Self {
515        Self::default()
516    }
517
518    #[must_use]
519    pub fn with_ssrc(mut self, ssrc: impl Into<Ssrc>) -> Self {
520        self.ssrc = Some(ssrc.into());
521        self
522    }
523
524    /// Associates an RTX source with its primary per
525    /// <https://www.rfc-editor.org/rfc/rfc4588.html#section-5> and the FID form
526    /// in <https://www.rfc-editor.org/rfc/rfc5576.html#section-4.2>.
527    #[must_use]
528    pub fn with_repair_ssrc(mut self, repair_ssrc: impl Into<Ssrc>) -> Self {
529        self.repair_ssrc = Some(repair_ssrc.into());
530        self
531    }
532
533    #[must_use]
534    pub fn with_rid(mut self, rid: impl Into<Rid>) -> Self {
535        self.rid = Some(rid.into());
536        self
537    }
538
539    #[must_use]
540    pub fn with_payload_type(mut self, payload_type: PayloadType) -> Self {
541        self.payload_type = Some(payload_type);
542        self
543    }
544
545    #[must_use]
546    pub fn with_max_bitrate(mut self, max_bitrate: u64) -> Self {
547        self.max_bitrate = Some(max_bitrate);
548        self
549    }
550
551    #[must_use]
552    pub fn ssrc(&self) -> Option<u32> {
553        self.ssrc.map(Ssrc::value)
554    }
555
556    #[must_use]
557    pub fn repair_ssrc(&self) -> Option<u32> {
558        self.repair_ssrc.map(Ssrc::value)
559    }
560
561    #[must_use]
562    pub fn rid(&self) -> Option<&str> {
563        self.rid.as_ref().map(Rid::as_str)
564    }
565
566    #[must_use]
567    pub(super) fn with_payload_type_mapping(
568        mut self,
569        payload_types: &[(PayloadType, PayloadType)],
570    ) -> Self {
571        if let Some(payload_type) = self.payload_type {
572            let mapped_payload_type = payload_types
573                .iter()
574                .find_map(|(original, mapped)| (*original == payload_type).then_some(*mapped))
575                .unwrap_or(payload_type);
576            self.payload_type = Some(mapped_payload_type);
577        }
578        self
579    }
580
581    #[must_use]
582    pub fn payload_type_id(&self) -> Option<PayloadType> {
583        self.payload_type
584    }
585
586    #[must_use]
587    pub fn payload_type(&self) -> Option<u8> {
588        self.payload_type.map(PayloadType::value)
589    }
590
591    #[must_use]
592    pub fn max_bitrate(&self) -> Option<u64> {
593        self.max_bitrate
594    }
595}
596
597/// Description of one logical media stream (e.g. "camera") at the router boundary.
598///
599/// Combines negotiated formats (codecs), extensions, and the stream bindings
600/// that tell the router how to identify the packets on the wire.
601#[derive(Debug, Clone, Default, PartialEq, Eq)]
602pub struct MediaStream {
603    formats: Vec<MediaFormat>,
604    header_extensions: Vec<HeaderExtension>,
605    bindings: Vec<StreamBinding>,
606    mid: Option<Mid>,
607}
608
609impl MediaStream {
610    #[must_use]
611    pub fn new(
612        formats: Vec<MediaFormat>,
613        header_extensions: Vec<HeaderExtension>,
614        bindings: Vec<StreamBinding>,
615    ) -> Self {
616        Self {
617            formats,
618            header_extensions,
619            bindings,
620            mid: None,
621        }
622    }
623
624    #[must_use]
625    pub fn with_mid(mut self, mid: impl Into<Mid>) -> Self {
626        self.mid = Some(mid.into());
627        self
628    }
629
630    pub fn formats(&self) -> impl Iterator<Item = &MediaFormat> {
631        self.formats.iter()
632    }
633
634    pub fn header_extensions(&self) -> impl Iterator<Item = &HeaderExtension> {
635        self.header_extensions.iter()
636    }
637
638    pub fn bindings(&self) -> impl Iterator<Item = &StreamBinding> {
639        self.bindings.iter()
640    }
641
642    #[must_use]
643    pub fn mid(&self) -> Option<&str> {
644        self.mid.as_ref().map(Mid::as_str)
645    }
646}
647
648fn codec_setting_from_wire(codec: &MediaCodec, key: String, value: String) -> CodecSetting {
649    match key.as_str() {
650        rfc_rtp::fmtp::RTX_ASSOCIATION => value
651            .parse::<u8>()
652            .ok()
653            .and_then(PayloadType::try_new)
654            .map_or(
655                CodecSetting::Other { key, value },
656                CodecSetting::RtxAssociation,
657            ),
658        rfc_rtp::fmtp::H264_PACKETIZATION_MODE => value
659            .parse::<u8>()
660            .ok()
661            .and_then(rfc_rtp::h264::PacketizationMode::from_fmtp_value)
662            .map_or_else(
663                || CodecSetting::Other { key, value },
664                CodecSetting::H264PacketizationMode,
665            ),
666        rfc_rtp::fmtp::H264_PROFILE_LEVEL_ID => CodecSetting::H264ProfileLevelId(value),
667        rfc_rtp::fmtp::VP9_PROFILE_ID if codec == &MediaCodec::Vp9 => value
668            .parse::<u8>()
669            .ok()
670            .and_then(rfc_rtp::Vp9ProfileId::try_new)
671            .map_or(
672                CodecSetting::Other { key, value },
673                CodecSetting::Vp9ProfileId,
674            ),
675        rfc_rtp::fmtp::OPUS_USE_IN_BAND_FEC => match value.as_str() {
676            rfc_rtp::fmtp::VALUE_ENABLED | rfc_rtp::fmtp::VALUE_TRUE => {
677                CodecSetting::UseInBandFec(true)
678            }
679            rfc_rtp::fmtp::VALUE_DISABLED | rfc_rtp::fmtp::VALUE_FALSE => {
680                CodecSetting::UseInBandFec(false)
681            }
682            _ => CodecSetting::Other { key, value },
683        },
684        _ => CodecSetting::Other { key, value },
685    }
686}