Skip to main content

rtc_sdp/extmap/
mod.rs

1//! `a=extmap` RTP header-extension declarations.
2//!
3//! An [`ExtMap`](crate::extmap::ExtMap) binds a header-extension URI to the small integer id that will appear in RTP
4//! packets. Both sides must agree, which is the whole point of negotiating it in SDP: the id is
5//! per-session, while the URI is the stable name.
6//!
7//! The `*_URI` constants are the extensions this stack uses — audio level, video orientation,
8//! absolute send time, transport-wide CC, and the SDES ids that make simulcast demultiplexing
9//! possible.
10#[cfg(test)]
11mod extmap_test;
12
13use super::direction::*;
14use crate::description::common::*;
15use shared::error::{Error, Result};
16
17use std::fmt;
18use std::io;
19use url::Url;
20
21/// Default ext values
22pub const DEF_EXT_MAP_VALUE_ABS_SEND_TIME: usize = 1;
23/// The default id this crate assigns to the transport-wide CC extension.
24pub const DEF_EXT_MAP_VALUE_TRANSPORT_CC: usize = 2;
25/// The default id assigned to the SDES `mid` extension.
26pub const DEF_EXT_MAP_VALUE_SDES_MID: usize = 3;
27/// The default id assigned to the SDES RTP stream id extension.
28pub const DEF_EXT_MAP_VALUE_SDES_RTP_STREAM_ID: usize = 4;
29
30/// The absolute-send-time extension URI, used for bandwidth estimation.
31pub const ABS_SEND_TIME_URI: &str = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time";
32/// The transport-wide congestion control extension URI.
33pub const TRANSPORT_CC_URI: &str =
34    "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
35/// The SDES `mid` extension URI, which tags each packet with its m-line.
36pub const SDES_MID_URI: &str = "urn:ietf:params:rtp-hdrext:sdes:mid";
37/// The SDES RTP stream id (RID) extension URI, which identifies a simulcast layer.
38pub const SDES_RTP_STREAM_ID_URI: &str = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
39/// The SDES repaired RTP stream id extension URI, which identifies an RTX layer's target.
40pub const SDES_REPAIR_RTP_STREAM_ID_URI: &str =
41    "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id";
42
43/// The audio-level extension URI, carrying per-packet loudness.
44pub const AUDIO_LEVEL_URI: &str = "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
45/// The video-orientation (CVO) extension URI, carrying rotation flags.
46pub const VIDEO_ORIENTATION_URI: &str = "urn:3gpp:video-orientation";
47
48/// ExtMap represents the activation of a single RTP header extension
49#[derive(Debug, Clone, Default)]
50pub struct ExtMap {
51    /// The id this extension is negotiated under, as it appears in RTP packets.
52    pub value: u16,
53    /// The direction the extension applies in, if the attribute restricted it.
54    pub direction: Direction,
55    /// The extension's canonical URI.
56    pub uri: Option<Url>,
57    /// Extension-specific attributes trailing the URI.
58    pub ext_attr: Option<String>,
59}
60
61impl fmt::Display for ExtMap {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "{}", self.value)?;
64
65        if self.direction != Direction::Unspecified {
66            write!(f, "/{}", self.direction)?;
67        }
68
69        if let Some(uri) = &self.uri {
70            write!(f, " {uri}")?;
71        }
72
73        if let Some(ext_attr) = &self.ext_attr {
74            write!(f, " {ext_attr}")?;
75        }
76
77        Ok(())
78    }
79}
80
81impl ExtMap {
82    /// converts this object to an Attribute
83    pub fn convert(&self) -> Attribute {
84        Attribute {
85            key: "extmap".to_string(),
86            value: Some(self.to_string()),
87        }
88    }
89
90    /// unmarshal creates an Extmap from a string
91    pub fn unmarshal<R: io::BufRead>(reader: &mut R) -> Result<Self> {
92        let mut line = String::new();
93        reader.read_line(&mut line)?;
94        let parts: Vec<&str> = line.trim().splitn(2, ':').collect();
95        if parts.len() != 2 {
96            return Err(Error::ParseExtMap(line));
97        }
98
99        let fields: Vec<&str> = parts[1].split_whitespace().collect();
100        if fields.len() < 2 {
101            return Err(Error::ParseExtMap(line));
102        }
103
104        let valdir: Vec<&str> = fields[0].split('/').collect();
105        let value = valdir[0].parse::<u16>()?;
106        // RFC 8285 section 4.3: the two-byte-header extension ID is "in the
107        // range 1-255 inclusive" (0 is reserved for padding). One-byte-header
108        // IDs (1-14) are a subset of the same range.
109        if !(1..=255).contains(&value) {
110            return Err(Error::ParseExtMap(format!(
111                "{} -- extmap key must be in the range 1-255",
112                valdir[0]
113            )));
114        }
115
116        let mut direction = Direction::Unspecified;
117        if valdir.len() == 2 {
118            direction = Direction::new(valdir[1]);
119            if direction == Direction::Unspecified {
120                return Err(Error::ParseExtMap(format!(
121                    "unknown direction from {}",
122                    valdir[1]
123                )));
124            }
125        }
126
127        let uri = Some(Url::parse(fields[1])?);
128
129        let ext_attr = if fields.len() == 3 {
130            Some(fields[2].to_owned())
131        } else {
132            None
133        };
134
135        Ok(ExtMap {
136            value,
137            direction,
138            uri,
139            ext_attr,
140        })
141    }
142
143    /// marshal creates a string from an ExtMap
144    pub fn marshal(&self) -> String {
145        "extmap:".to_string() + self.to_string().as_str()
146    }
147}