webrtc/api/media_engine/
mod.rs

1#[cfg(test)]
2mod media_engine_test;
3
4use std::collections::HashMap;
5use std::ops::Range;
6use std::sync::atomic::Ordering;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use portable_atomic::AtomicBool;
10use sdp::description::session::SessionDescription;
11use util::sync::Mutex as SyncMutex;
12
13use crate::error::{Error, Result};
14use crate::peer_connection::sdp::{
15    codecs_from_media_description, rtp_extensions_from_media_description,
16};
17use crate::rtp_transceiver::rtp_codec::{
18    codec_parameters_fuzzy_search, CodecMatch, RTCRtpCodecCapability, RTCRtpCodecParameters,
19    RTCRtpHeaderExtensionCapability, RTCRtpHeaderExtensionParameters, RTCRtpParameters,
20    RTPCodecType,
21};
22use crate::rtp_transceiver::rtp_transceiver_direction::RTCRtpTransceiverDirection;
23use crate::rtp_transceiver::{fmtp, PayloadType, RTCPFeedback};
24use crate::stats::stats_collector::StatsCollector;
25use crate::stats::CodecStats;
26use crate::stats::StatsReportType::Codec;
27
28/// MIME_TYPE_H264 H264 MIME type.
29/// Note: Matching should be case insensitive.
30pub const MIME_TYPE_H264: &str = "video/H264";
31/// MIME_TYPE_HEVC HEVC/H265 MIME type.
32/// Note: Matching should be case insensitive.
33pub const MIME_TYPE_HEVC: &str = "video/H265";
34/// MIME_TYPE_OPUS Opus MIME type
35/// Note: Matching should be case insensitive.
36pub const MIME_TYPE_OPUS: &str = "audio/opus";
37/// MIME_TYPE_VP8 VP8 MIME type
38/// Note: Matching should be case insensitive.
39pub const MIME_TYPE_VP8: &str = "video/VP8";
40/// MIME_TYPE_VP9 VP9 MIME type
41/// Note: Matching should be case insensitive.
42pub const MIME_TYPE_VP9: &str = "video/VP9";
43/// MIME_TYPE_AV1 AV1 MIME type
44/// Note: Matching should be case insensitive.
45pub const MIME_TYPE_AV1: &str = "video/AV1";
46/// MIME_TYPE_G722 G722 MIME type
47/// Note: Matching should be case insensitive.
48pub const MIME_TYPE_G722: &str = "audio/G722";
49/// MIME_TYPE_PCMU PCMU MIME type
50/// Note: Matching should be case insensitive.
51pub const MIME_TYPE_PCMU: &str = "audio/PCMU";
52/// MIME_TYPE_PCMA PCMA MIME type
53/// Note: Matching should be case insensitive.
54pub const MIME_TYPE_PCMA: &str = "audio/PCMA";
55/// MIME_TYPE_TELEPHONE_EVENT telephone-event MIME type
56/// Note: Matching should be case insensitive.
57pub const MIME_TYPE_TELEPHONE_EVENT: &str = "audio/telephone-event";
58
59const VALID_EXT_IDS: Range<isize> = 1..15;
60
61#[derive(Default, Clone)]
62pub(crate) struct MediaEngineHeaderExtension {
63    pub(crate) uri: String,
64    pub(crate) is_audio: bool,
65    pub(crate) is_video: bool,
66    pub(crate) allowed_direction: Option<RTCRtpTransceiverDirection>,
67}
68
69impl MediaEngineHeaderExtension {
70    pub fn is_matching_direction(&self, dir: RTCRtpTransceiverDirection) -> bool {
71        if let Some(allowed_direction) = self.allowed_direction {
72            use RTCRtpTransceiverDirection::*;
73            allowed_direction == Inactive && dir == Inactive
74                || allowed_direction.has_send() && dir.has_send()
75                || allowed_direction.has_recv() && dir.has_recv()
76        } else {
77            // None means all directions matches.
78            true
79        }
80    }
81}
82
83/// A MediaEngine defines the codecs supported by a PeerConnection, and the
84/// configuration of those codecs. A MediaEngine must not be shared between
85/// PeerConnections.
86#[derive(Default)]
87pub struct MediaEngine {
88    // If we have attempted to negotiate a codec type yet.
89    pub(crate) negotiated_video: AtomicBool,
90    pub(crate) negotiated_audio: AtomicBool,
91
92    pub(crate) video_codecs: Vec<RTCRtpCodecParameters>,
93    pub(crate) audio_codecs: Vec<RTCRtpCodecParameters>,
94    pub(crate) negotiated_video_codecs: SyncMutex<Vec<RTCRtpCodecParameters>>,
95    pub(crate) negotiated_audio_codecs: SyncMutex<Vec<RTCRtpCodecParameters>>,
96
97    header_extensions: Vec<MediaEngineHeaderExtension>,
98    proposed_header_extensions: SyncMutex<HashMap<isize, MediaEngineHeaderExtension>>,
99    pub(crate) negotiated_header_extensions: SyncMutex<HashMap<isize, MediaEngineHeaderExtension>>,
100}
101
102impl MediaEngine {
103    /// register_default_codecs registers the default codecs supported by Pion WebRTC.
104    /// register_default_codecs is not safe for concurrent use.
105    pub fn register_default_codecs(&mut self) -> Result<()> {
106        // Default Audio Codecs
107        for codec in vec![
108            RTCRtpCodecParameters {
109                capability: RTCRtpCodecCapability {
110                    mime_type: MIME_TYPE_OPUS.to_owned(),
111                    clock_rate: 48000,
112                    channels: 2,
113                    sdp_fmtp_line: "minptime=10;useinbandfec=1".to_owned(),
114                    rtcp_feedback: vec![],
115                },
116                payload_type: 111,
117                ..Default::default()
118            },
119            RTCRtpCodecParameters {
120                capability: RTCRtpCodecCapability {
121                    mime_type: MIME_TYPE_G722.to_owned(),
122                    clock_rate: 8000,
123                    channels: 0,
124                    sdp_fmtp_line: "".to_owned(),
125                    rtcp_feedback: vec![],
126                },
127                payload_type: 9,
128                ..Default::default()
129            },
130            RTCRtpCodecParameters {
131                capability: RTCRtpCodecCapability {
132                    mime_type: MIME_TYPE_PCMU.to_owned(),
133                    clock_rate: 8000,
134                    channels: 0,
135                    sdp_fmtp_line: "".to_owned(),
136                    rtcp_feedback: vec![],
137                },
138                payload_type: 0,
139                ..Default::default()
140            },
141            RTCRtpCodecParameters {
142                capability: RTCRtpCodecCapability {
143                    mime_type: MIME_TYPE_PCMA.to_owned(),
144                    clock_rate: 8000,
145                    channels: 0,
146                    sdp_fmtp_line: "".to_owned(),
147                    rtcp_feedback: vec![],
148                },
149                payload_type: 8,
150                ..Default::default()
151            },
152        ] {
153            self.register_codec(codec, RTPCodecType::Audio)?;
154        }
155
156        let video_rtcp_feedback = vec![
157            RTCPFeedback {
158                typ: "goog-remb".to_owned(),
159                parameter: "".to_owned(),
160            },
161            RTCPFeedback {
162                typ: "ccm".to_owned(),
163                parameter: "fir".to_owned(),
164            },
165            RTCPFeedback {
166                typ: "nack".to_owned(),
167                parameter: "".to_owned(),
168            },
169            RTCPFeedback {
170                typ: "nack".to_owned(),
171                parameter: "pli".to_owned(),
172            },
173        ];
174        for codec in vec![
175            RTCRtpCodecParameters {
176                capability: RTCRtpCodecCapability {
177                    mime_type: MIME_TYPE_VP8.to_owned(),
178                    clock_rate: 90000,
179                    channels: 0,
180                    sdp_fmtp_line: "".to_owned(),
181                    rtcp_feedback: video_rtcp_feedback.clone(),
182                },
183                payload_type: 96,
184                ..Default::default()
185            },
186            RTCRtpCodecParameters {
187                capability: RTCRtpCodecCapability {
188                    mime_type: MIME_TYPE_VP9.to_owned(),
189                    clock_rate: 90000,
190                    channels: 0,
191                    sdp_fmtp_line: "profile-id=0".to_owned(),
192                    rtcp_feedback: video_rtcp_feedback.clone(),
193                },
194                payload_type: 98,
195                ..Default::default()
196            },
197            RTCRtpCodecParameters {
198                capability: RTCRtpCodecCapability {
199                    mime_type: MIME_TYPE_VP9.to_owned(),
200                    clock_rate: 90000,
201                    channels: 0,
202                    sdp_fmtp_line: "profile-id=1".to_owned(),
203                    rtcp_feedback: video_rtcp_feedback.clone(),
204                },
205                payload_type: 100,
206                ..Default::default()
207            },
208            RTCRtpCodecParameters {
209                capability: RTCRtpCodecCapability {
210                    mime_type: MIME_TYPE_H264.to_owned(),
211                    clock_rate: 90000,
212                    channels: 0,
213                    sdp_fmtp_line:
214                        "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"
215                            .to_owned(),
216                    rtcp_feedback: video_rtcp_feedback.clone(),
217                },
218                payload_type: 102,
219                ..Default::default()
220            },
221            RTCRtpCodecParameters {
222                capability: RTCRtpCodecCapability {
223                    mime_type: MIME_TYPE_H264.to_owned(),
224                    clock_rate: 90000,
225                    channels: 0,
226                    sdp_fmtp_line:
227                        "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f"
228                            .to_owned(),
229                    rtcp_feedback: video_rtcp_feedback.clone(),
230                },
231                payload_type: 127,
232                ..Default::default()
233            },
234            RTCRtpCodecParameters {
235                capability: RTCRtpCodecCapability {
236                    mime_type: MIME_TYPE_H264.to_owned(),
237                    clock_rate: 90000,
238                    channels: 0,
239                    sdp_fmtp_line:
240                        "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f"
241                            .to_owned(),
242                    rtcp_feedback: video_rtcp_feedback.clone(),
243                },
244                payload_type: 125,
245                ..Default::default()
246            },
247            RTCRtpCodecParameters {
248                capability: RTCRtpCodecCapability {
249                    mime_type: MIME_TYPE_H264.to_owned(),
250                    clock_rate: 90000,
251                    channels: 0,
252                    sdp_fmtp_line:
253                        "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f"
254                            .to_owned(),
255                    rtcp_feedback: video_rtcp_feedback.clone(),
256                },
257                payload_type: 108,
258                ..Default::default()
259            },
260            RTCRtpCodecParameters {
261                capability: RTCRtpCodecCapability {
262                    mime_type: MIME_TYPE_H264.to_owned(),
263                    clock_rate: 90000,
264                    channels: 0,
265                    sdp_fmtp_line:
266                        "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f"
267                            .to_owned(),
268                    rtcp_feedback: video_rtcp_feedback.clone(),
269                },
270                payload_type: 127,
271                ..Default::default()
272            },
273            RTCRtpCodecParameters {
274                capability: RTCRtpCodecCapability {
275                    mime_type: MIME_TYPE_H264.to_owned(),
276                    clock_rate: 90000,
277                    channels: 0,
278                    sdp_fmtp_line:
279                        "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640032"
280                            .to_owned(),
281                    rtcp_feedback: video_rtcp_feedback.clone(),
282                },
283                payload_type: 123,
284                ..Default::default()
285            },
286            RTCRtpCodecParameters {
287                capability: RTCRtpCodecCapability {
288                    mime_type: MIME_TYPE_AV1.to_owned(),
289                    clock_rate: 90000,
290                    channels: 0,
291                    sdp_fmtp_line: "profile-id=0".to_owned(),
292                    rtcp_feedback: video_rtcp_feedback.clone(),
293                },
294                payload_type: 41,
295                ..Default::default()
296            },
297            RTCRtpCodecParameters {
298                capability: RTCRtpCodecCapability {
299                    mime_type: MIME_TYPE_HEVC.to_owned(),
300                    clock_rate: 90000,
301                    channels: 0,
302                    sdp_fmtp_line: "".to_owned(),
303                    rtcp_feedback: video_rtcp_feedback,
304                },
305                payload_type: 126,
306                ..Default::default()
307            },
308            RTCRtpCodecParameters {
309                capability: RTCRtpCodecCapability {
310                    mime_type: "video/ulpfec".to_owned(),
311                    clock_rate: 90000,
312                    channels: 0,
313                    sdp_fmtp_line: "".to_owned(),
314                    rtcp_feedback: vec![],
315                },
316                payload_type: 116,
317                ..Default::default()
318            },
319        ] {
320            self.register_codec(codec, RTPCodecType::Video)?;
321        }
322
323        Ok(())
324    }
325
326    /// add_codec will append codec if it not exists
327    fn add_codec(codecs: &mut Vec<RTCRtpCodecParameters>, codec: RTCRtpCodecParameters) {
328        for c in codecs.iter() {
329            if c.capability.mime_type == codec.capability.mime_type
330                && c.payload_type == codec.payload_type
331            {
332                return;
333            }
334        }
335        codecs.push(codec);
336    }
337
338    /// register_codec adds codec to the MediaEngine
339    /// These are the list of codecs supported by this PeerConnection.
340    /// register_codec is not safe for concurrent use.
341    pub fn register_codec(
342        &mut self,
343        mut codec: RTCRtpCodecParameters,
344        typ: RTPCodecType,
345    ) -> Result<()> {
346        codec.stats_id = format!(
347            "RTPCodec-{}",
348            SystemTime::now()
349                .duration_since(UNIX_EPOCH)
350                .unwrap()
351                .as_nanos()
352        );
353        match typ {
354            RTPCodecType::Audio => {
355                MediaEngine::add_codec(&mut self.audio_codecs, codec);
356                Ok(())
357            }
358            RTPCodecType::Video => {
359                MediaEngine::add_codec(&mut self.video_codecs, codec);
360                Ok(())
361            }
362            _ => Err(Error::ErrUnknownType),
363        }
364    }
365
366    /// Adds a header extension to the MediaEngine
367    /// To determine the negotiated value use [`MediaEngine::get_header_extension_id`] after signaling is complete.
368    ///
369    /// The `allowed_direction` controls for which transceiver directions the extension matches. If
370    /// set to `None` it matches all directions. The `SendRecv` direction would match all transceiver
371    /// directions apart from `Inactive`. Inactive only matches inactive.
372    pub fn register_header_extension(
373        &mut self,
374        extension: RTCRtpHeaderExtensionCapability,
375        typ: RTPCodecType,
376        allowed_direction: Option<RTCRtpTransceiverDirection>,
377    ) -> Result<()> {
378        let ext = {
379            match self
380                .header_extensions
381                .iter_mut()
382                .find(|ext| ext.uri == extension.uri)
383            {
384                Some(ext) => ext,
385                None => {
386                    // We have registered too many extensions
387                    if self.header_extensions.len() > VALID_EXT_IDS.end as usize {
388                        return Err(Error::ErrRegisterHeaderExtensionNoFreeID);
389                    }
390                    self.header_extensions.push(MediaEngineHeaderExtension {
391                        allowed_direction,
392                        ..Default::default()
393                    });
394
395                    // Unwrap is fine because we just pushed
396                    self.header_extensions.last_mut().unwrap()
397                }
398            }
399        };
400
401        if typ == RTPCodecType::Audio {
402            ext.is_audio = true;
403        } else if typ == RTPCodecType::Video {
404            ext.is_video = true;
405        }
406
407        ext.uri = extension.uri;
408
409        if ext.allowed_direction != allowed_direction {
410            return Err(Error::ErrRegisterHeaderExtensionInvalidDirection);
411        }
412
413        Ok(())
414    }
415
416    /// register_feedback adds feedback mechanism to already registered codecs.
417    pub fn register_feedback(&mut self, feedback: RTCPFeedback, typ: RTPCodecType) {
418        match typ {
419            RTPCodecType::Video => {
420                for v in &mut self.video_codecs {
421                    v.capability.rtcp_feedback.push(feedback.clone());
422                }
423            }
424            RTPCodecType::Audio => {
425                for a in &mut self.audio_codecs {
426                    a.capability.rtcp_feedback.push(feedback.clone());
427                }
428            }
429            _ => {}
430        }
431    }
432
433    /// get_header_extension_id returns the negotiated ID for a header extension.
434    /// If the Header Extension isn't enabled ok will be false
435    pub async fn get_header_extension_id(
436        &self,
437        extension: RTCRtpHeaderExtensionCapability,
438    ) -> (isize, bool, bool) {
439        let negotiated_header_extensions = self.negotiated_header_extensions.lock();
440        if negotiated_header_extensions.is_empty() {
441            return (0, false, false);
442        }
443
444        for (id, h) in &*negotiated_header_extensions {
445            if extension.uri == h.uri {
446                return (*id, h.is_audio, h.is_video);
447            }
448        }
449
450        (0, false, false)
451    }
452
453    /// clone_to copies any user modifiable state of the MediaEngine
454    /// all internal state is reset
455    pub(crate) fn clone_to(&self) -> Self {
456        MediaEngine {
457            video_codecs: self.video_codecs.clone(),
458            audio_codecs: self.audio_codecs.clone(),
459            header_extensions: self.header_extensions.clone(),
460            ..Default::default()
461        }
462    }
463
464    pub(crate) async fn get_codec_by_payload(
465        &self,
466        payload_type: PayloadType,
467    ) -> Result<(RTCRtpCodecParameters, RTPCodecType)> {
468        if self.negotiated_video.load(Ordering::SeqCst) {
469            let negotiated_video_codecs = self.negotiated_video_codecs.lock();
470            for codec in &*negotiated_video_codecs {
471                if codec.payload_type == payload_type {
472                    return Ok((codec.clone(), RTPCodecType::Video));
473                }
474            }
475        }
476        if self.negotiated_audio.load(Ordering::SeqCst) {
477            let negotiated_audio_codecs = self.negotiated_audio_codecs.lock();
478            for codec in &*negotiated_audio_codecs {
479                if codec.payload_type == payload_type {
480                    return Ok((codec.clone(), RTPCodecType::Audio));
481                }
482            }
483        }
484        if !self.negotiated_video.load(Ordering::SeqCst) {
485            for codec in &self.video_codecs {
486                if codec.payload_type == payload_type {
487                    return Ok((codec.clone(), RTPCodecType::Video));
488                }
489            }
490        }
491        if !self.negotiated_audio.load(Ordering::SeqCst) {
492            for codec in &self.audio_codecs {
493                if codec.payload_type == payload_type {
494                    return Ok((codec.clone(), RTPCodecType::Audio));
495                }
496            }
497        }
498
499        Err(Error::ErrCodecNotFound)
500    }
501
502    pub(crate) async fn collect_stats(&self, collector: &StatsCollector) {
503        let mut reports = HashMap::new();
504
505        for codec in &self.video_codecs {
506            reports.insert(codec.stats_id.clone(), Codec(CodecStats::from(codec)));
507        }
508
509        for codec in &self.audio_codecs {
510            reports.insert(codec.stats_id.clone(), Codec(CodecStats::from(codec)));
511        }
512
513        collector.merge(reports);
514    }
515
516    /// Look up a codec and enable if it exists
517    pub(crate) fn match_remote_codec(
518        &self,
519        remote_codec: &RTCRtpCodecParameters,
520        typ: RTPCodecType,
521        exact_matches: &[RTCRtpCodecParameters],
522        partial_matches: &[RTCRtpCodecParameters],
523    ) -> Result<CodecMatch> {
524        let codecs = if typ == RTPCodecType::Audio {
525            &self.audio_codecs
526        } else {
527            &self.video_codecs
528        };
529
530        let remote_fmtp = fmtp::parse(
531            &remote_codec.capability.mime_type,
532            remote_codec.capability.sdp_fmtp_line.as_str(),
533        );
534        if let Some(apt) = remote_fmtp.parameter("apt") {
535            let payload_type = apt.parse::<u8>()?;
536
537            let mut apt_match = CodecMatch::None;
538            let mut apt_codec = None;
539            for codec in exact_matches {
540                if codec.payload_type == payload_type {
541                    apt_match = CodecMatch::Exact;
542                    apt_codec = Some(codec);
543                    break;
544                }
545            }
546
547            if apt_match == CodecMatch::None {
548                for codec in partial_matches {
549                    if codec.payload_type == payload_type {
550                        apt_match = CodecMatch::Partial;
551                        apt_codec = Some(codec);
552                        break;
553                    }
554                }
555            }
556
557            if apt_match == CodecMatch::None {
558                return Ok(CodecMatch::None); // not an error, we just ignore this codec we don't support
559            }
560
561            // replace the apt value with the original codec's payload type
562            let mut to_match_codec = remote_codec.clone();
563            if let Some(apt_codec) = apt_codec {
564                let (apt_matched, mt) = codec_parameters_fuzzy_search(apt_codec, codecs);
565                if mt == apt_match {
566                    to_match_codec.capability.sdp_fmtp_line =
567                        to_match_codec.capability.sdp_fmtp_line.replacen(
568                            &format!("apt={payload_type}"),
569                            &format!("apt={}", apt_matched.payload_type),
570                            1,
571                        );
572                }
573            }
574
575            // if apt's media codec is partial match, then apt codec must be partial match too
576            let (_, mut match_type) = codec_parameters_fuzzy_search(&to_match_codec, codecs);
577            if match_type == CodecMatch::Exact && apt_match == CodecMatch::Partial {
578                match_type = CodecMatch::Partial;
579            }
580            return Ok(match_type);
581        }
582
583        let (_, match_type) = codec_parameters_fuzzy_search(remote_codec, codecs);
584        Ok(match_type)
585    }
586
587    /// Look up a header extension and enable if it exists
588    pub(crate) async fn update_header_extension(
589        &self,
590        id: isize,
591        extension: &str,
592        typ: RTPCodecType,
593    ) -> Result<()> {
594        let mut negotiated_header_extensions = self.negotiated_header_extensions.lock();
595        let mut proposed_header_extensions = self.proposed_header_extensions.lock();
596
597        for local_extension in &self.header_extensions {
598            if local_extension.uri != extension {
599                continue;
600            }
601
602            let negotiated_ext = negotiated_header_extensions
603                .iter_mut()
604                .find(|(_, ext)| ext.uri == extension);
605
606            if let Some(n_ext) = negotiated_ext {
607                if *n_ext.0 == id {
608                    n_ext.1.is_video |= typ == RTPCodecType::Video;
609                    n_ext.1.is_audio |= typ == RTPCodecType::Audio;
610                } else {
611                    let nid = n_ext.0;
612                    log::warn!("Invalid ext id mapping in update_header_extension. {} was negotiated as {}, but was {} in call", extension, nid, id);
613                }
614            } else {
615                // We either only have a proposal or we have neither proposal nor a negotiated id
616                // Accept whatevers the peer suggests
617
618                if let Some(prev_ext) = negotiated_header_extensions.get(&id) {
619                    let prev_uri = &prev_ext.uri;
620                    log::warn!("Assigning {} to {} would override previous assignment to {}, no action taken", id, extension, prev_uri);
621                } else {
622                    let h = MediaEngineHeaderExtension {
623                        uri: extension.to_owned(),
624                        is_audio: local_extension.is_audio && typ == RTPCodecType::Audio,
625                        is_video: local_extension.is_video && typ == RTPCodecType::Video,
626                        allowed_direction: local_extension.allowed_direction,
627                    };
628                    negotiated_header_extensions.insert(id, h);
629                }
630            }
631
632            // Clear any proposals we had for this id
633            proposed_header_extensions.remove(&id);
634        }
635        Ok(())
636    }
637
638    pub(crate) async fn push_codecs(&self, codecs: Vec<RTCRtpCodecParameters>, typ: RTPCodecType) {
639        for codec in codecs {
640            if typ == RTPCodecType::Audio {
641                let mut negotiated_audio_codecs = self.negotiated_audio_codecs.lock();
642                MediaEngine::add_codec(&mut negotiated_audio_codecs, codec);
643            } else if typ == RTPCodecType::Video {
644                let mut negotiated_video_codecs = self.negotiated_video_codecs.lock();
645                MediaEngine::add_codec(&mut negotiated_video_codecs, codec);
646            }
647        }
648    }
649
650    /// Update the MediaEngine from a remote description
651    pub(crate) async fn update_from_remote_description(
652        &self,
653        desc: &SessionDescription,
654    ) -> Result<()> {
655        for media in &desc.media_descriptions {
656            let typ = if !self.negotiated_audio.load(Ordering::SeqCst)
657                && media.media_name.media.to_lowercase() == "audio"
658            {
659                self.negotiated_audio.store(true, Ordering::SeqCst);
660                RTPCodecType::Audio
661            } else if !self.negotiated_video.load(Ordering::SeqCst)
662                && media.media_name.media.to_lowercase() == "video"
663            {
664                self.negotiated_video.store(true, Ordering::SeqCst);
665                RTPCodecType::Video
666            } else {
667                continue;
668            };
669
670            let codecs = codecs_from_media_description(media)?;
671
672            let mut exact_matches = vec![]; //make([]RTPCodecParameters, 0, len(codecs))
673            let mut partial_matches = vec![]; //make([]RTPCodecParameters, 0, len(codecs))
674
675            for codec in codecs {
676                let match_type =
677                    self.match_remote_codec(&codec, typ, &exact_matches, &partial_matches)?;
678
679                if match_type == CodecMatch::Exact {
680                    exact_matches.push(codec);
681                } else if match_type == CodecMatch::Partial {
682                    partial_matches.push(codec);
683                }
684            }
685
686            // use exact matches when they exist, otherwise fall back to partial
687            if !exact_matches.is_empty() {
688                self.push_codecs(exact_matches, typ).await;
689            } else if !partial_matches.is_empty() {
690                self.push_codecs(partial_matches, typ).await;
691            } else {
692                // no match, not negotiated
693                continue;
694            }
695
696            let extensions = rtp_extensions_from_media_description(media)?;
697
698            for (extension, id) in extensions {
699                self.update_header_extension(id, &extension, typ).await?;
700            }
701        }
702
703        Ok(())
704    }
705
706    pub(crate) fn get_codecs_by_kind(&self, typ: RTPCodecType) -> Vec<RTCRtpCodecParameters> {
707        if typ == RTPCodecType::Video {
708            if self.negotiated_video.load(Ordering::SeqCst) {
709                let negotiated_video_codecs = self.negotiated_video_codecs.lock();
710                negotiated_video_codecs.clone()
711            } else {
712                self.video_codecs.clone()
713            }
714        } else if typ == RTPCodecType::Audio {
715            if self.negotiated_audio.load(Ordering::SeqCst) {
716                let negotiated_audio_codecs = self.negotiated_audio_codecs.lock();
717                negotiated_audio_codecs.clone()
718            } else {
719                self.audio_codecs.clone()
720            }
721        } else {
722            vec![]
723        }
724    }
725
726    pub(crate) fn get_rtp_parameters_by_kind(
727        &self,
728        typ: RTPCodecType,
729        direction: RTCRtpTransceiverDirection,
730    ) -> RTCRtpParameters {
731        let mut header_extensions = vec![];
732
733        if self.negotiated_video.load(Ordering::SeqCst) && typ == RTPCodecType::Video
734            || self.negotiated_audio.load(Ordering::SeqCst) && typ == RTPCodecType::Audio
735        {
736            let negotiated_header_extensions = self.negotiated_header_extensions.lock();
737            for (id, e) in &*negotiated_header_extensions {
738                if e.is_matching_direction(direction)
739                    && (e.is_audio && typ == RTPCodecType::Audio
740                        || e.is_video && typ == RTPCodecType::Video)
741                {
742                    header_extensions.push(RTCRtpHeaderExtensionParameters {
743                        id: *id,
744                        uri: e.uri.clone(),
745                    });
746                }
747            }
748        } else {
749            let mut proposed_header_extensions = self.proposed_header_extensions.lock();
750            let mut negotiated_header_extensions = self.negotiated_header_extensions.lock();
751
752            for local_extension in &self.header_extensions {
753                let relevant = local_extension.is_matching_direction(direction)
754                    && (local_extension.is_audio && typ == RTPCodecType::Audio
755                        || local_extension.is_video && typ == RTPCodecType::Video);
756
757                if !relevant {
758                    continue;
759                }
760
761                if let Some((id, negotiated_extension)) = negotiated_header_extensions
762                    .iter_mut()
763                    .find(|(_, e)| e.uri == local_extension.uri)
764                {
765                    // We have previously negotiated this extension, make sure to record it as
766                    // active for the current type
767                    negotiated_extension.is_audio |= typ == RTPCodecType::Audio;
768                    negotiated_extension.is_video |= typ == RTPCodecType::Video;
769
770                    header_extensions.push(RTCRtpHeaderExtensionParameters {
771                        id: *id,
772                        uri: negotiated_extension.uri.clone(),
773                    });
774
775                    continue;
776                }
777
778                if let Some((id, negotiated_extension)) = proposed_header_extensions
779                    .iter_mut()
780                    .find(|(_, e)| e.uri == local_extension.uri)
781                {
782                    // We have previously proposed this extension, re-use it
783                    header_extensions.push(RTCRtpHeaderExtensionParameters {
784                        id: *id,
785                        uri: negotiated_extension.uri.clone(),
786                    });
787
788                    continue;
789                }
790
791                // Figure out which (unused id) to propose.
792                let id = VALID_EXT_IDS.clone().find(|id| {
793                    !negotiated_header_extensions.keys().any(|nid| nid == id)
794                        && !proposed_header_extensions.keys().any(|pid| pid == id)
795                });
796
797                if let Some(id) = id {
798                    proposed_header_extensions.insert(
799                        id,
800                        MediaEngineHeaderExtension {
801                            uri: local_extension.uri.clone(),
802                            is_audio: local_extension.is_audio,
803                            is_video: local_extension.is_video,
804                            allowed_direction: local_extension.allowed_direction,
805                        },
806                    );
807
808                    header_extensions.push(RTCRtpHeaderExtensionParameters {
809                        id,
810                        uri: local_extension.uri.clone(),
811                    });
812                } else {
813                    log::warn!("No available RTP extension ID for {}", local_extension.uri);
814                }
815            }
816        }
817
818        RTCRtpParameters {
819            header_extensions,
820            codecs: self.get_codecs_by_kind(typ),
821        }
822    }
823
824    pub(crate) async fn get_rtp_parameters_by_payload_type(
825        &self,
826        payload_type: PayloadType,
827    ) -> Result<RTCRtpParameters> {
828        let (codec, typ) = self.get_codec_by_payload(payload_type).await?;
829
830        let mut header_extensions = vec![];
831        {
832            let negotiated_header_extensions = self.negotiated_header_extensions.lock();
833            for (id, e) in &*negotiated_header_extensions {
834                if e.is_audio && typ == RTPCodecType::Audio
835                    || e.is_video && typ == RTPCodecType::Video
836                {
837                    header_extensions.push(RTCRtpHeaderExtensionParameters {
838                        uri: e.uri.clone(),
839                        id: *id,
840                    });
841                }
842            }
843        }
844
845        Ok(RTCRtpParameters {
846            header_extensions,
847            codecs: vec![codec],
848        })
849    }
850}