Skip to content
File

Blob: firmware/vendor/str0m/src/format/format_params.rs

rust317 lines
1use serde::{Deserialize, Serialize};
2use std::fmt;
3 
4use crate::sdp::FormatParam;
5 
6/// Codec specific format parameters.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
8pub struct FormatParams {
9 /// Opus specific parameter.
10 ///
11 /// The minimum duration of media represented by a packet.
12 pub min_p_time: Option<u8>,
13 
14 /// Opus specific parameter.
15 ///
16 /// Decoder-side preference for stereo audio. Default is mono per
17 /// RFC 7587 Section 7.1: <https://datatracker.ietf.org/doc/html/rfc7587#section-7.1>
18 pub stereo: Option<bool>,
19 
20 /// Opus specific parameter.
21 ///
22 /// Sender-side stereo hint for Opus. When set, it signals the intent to
23 /// transmit stereo from the sender, matching the symmetric m-line model.
24 pub sprop_stereo: Option<bool>,
25 
26 /// Opus specific parameter.
27 ///
28 /// Specifies that the decoder can do Opus in-band FEC
29 pub use_inband_fec: Option<bool>,
30 
31 /// Opus specific parameter.
32 ///
33 /// Specifies that the decoder prefers DTX (Discontinuous Transmission) such that
34 /// the packet rate is greatly lowered during periods of silence.
35 pub use_dtx: Option<bool>,
36 
37 /// Whether h264 sending media encoded at a different level in the offerer-to-answerer
38 /// direction than the level in the answerer-to-offerer direction, is allowed.
39 pub level_asymmetry_allowed: Option<bool>,
40 
41 /// What h264 packetization mode is used.
42 ///
43 /// * 0 - single nal.
44 /// * 1 - STAP-A, FU-A is allowed. Non-interleaved.
45 pub packetization_mode: Option<u8>,
46 
47 /// H264 profile level.
48 ///
49 /// * 42 00 1f - 4200=baseline (B) 1f=level 3.1
50 /// * 42 e0 1f - 42e0=constrained baseline (CB) 1f=level 3.1
51 /// * 4d 00 1f - 4d00=main (M) 1f=level 3.1
52 /// * 64 00 1f - 6400=high (H) 1f=level 3.1
53 pub profile_level_id: Option<u32>,
54 
55 /// VP9 profile id.
56 pub profile_id: Option<u32>,
57 
58 /// AV1 profile.
59 ///
60 /// Indicates the highest AV1 profile that may have been used to generate
61 /// the bitstream or that the receiver supports. The range of possible values
62 /// is identical to the seq_profile syntax element specified in AV1. If the
63 /// parameter is not present, it MUST be inferred to be 0 ("Main" profile).
64 ///
65 /// 0 8-bit or 10-bit 4:2:0
66 /// 1 8-bit or 10-bit 4:4:4
67 /// 2 8-bit or 10-bit 4:2:2
68 /// 2 12-bit 4:2:0, 4:2:2, 4:4:4
69 pub profile: Option<u8>,
70 
71 /// AV1 level-idx.
72 ///
73 /// Indicates the highest AV1 level that may have been used to generate the
74 /// bitstream or that the receiver supports. The range of possible values
75 /// is identical to the seq_level_idx syntax element specified in AV1. If
76 /// the parameter is not present, it MUST be inferred to be 5 (level 3.1).
77 pub level_idx: Option<u8>,
78 
79 /// AV1 tier.
80 ///
81 /// Indicates the highest tier that may have been used to generate the bitstream
82 /// or that the receiver supports. The range of possible values is identical
83 /// to the seq_tier syntax element specified in AV1. If the parameter is not
84 /// present, the tier MUST be inferred to be 0.
85 pub tier: Option<u8>,
86 
87 /// H.265/HEVC profile, tier, and level.
88 ///
89 /// Contains:
90 /// * profile: Main, Main10, Main Still Picture, etc.
91 /// * tier: Main or High.
92 /// * level: 3.1, 4.0, 5.0, etc.
93 ///
94 /// See ITU-T H.265 Annex A for complete definitions.
95 pub h265_profile_tier_level: Option<crate::packet::H265ProfileTierLevel>,
96 
97 /// H.266/VVC profile, tier, and level.
98 ///
99 /// Contains:
100 /// * profile: Main 10, Main 10 4:4:4, etc.
101 /// * tier: Main or High.
102 /// * level: 3.1, 4.0, 5.0, etc.
103 ///
104 /// See ITU-T H.266 Annex A and RFC 9328 §7.2 for complete definitions.
105 pub h266_profile_tier_level: Option<crate::packet::H266ProfileTierLevel>,
106 
107 /// H.265 sprop-max-don-diff parameter (RFC 7798 §7.1).
108 /// When > 0, DONL fields are included in H.265 RTP packets to support
109 /// out-of-order NAL unit decoding.
110 pub sprop_max_don_diff: Option<u16>,
111}
112 
113#[cfg(feature = "drv")]
114crate::drv_identity_copy!(FormatParams);
115 
116impl FormatParams {
117 /// Parse an fmtp line to create a FormatParams.
118 ///
119 /// Example `minptime=10;useinbandfec=1`.
120 pub fn parse_line(line: &str) -> Self {
121 let key_vals: Vec<_> = line
122 .split(';')
123 .filter_map(|pair| {
124 let mut kv = pair.split('=');
125 match (kv.next(), kv.next()) {
126 (Some(k), Some(v)) => Some((k.trim().to_string(), v.trim().to_string())),
127 _ => None,
128 }
129 })
130 .collect();
131 
132 let mut p = FormatParams::default();
133 
134 for param in FormatParam::parse_pairs(key_vals) {
135 p.set_param(&param);
136 }
137 
138 p
139 }
140 
141 pub(crate) fn set_param(&mut self, param: &FormatParam) {
142 use FormatParam::*;
143 match param {
144 MinPTime(v) => self.min_p_time = Some(*v),
145 Stereo(v) => self.stereo = Some(*v),
146 SpropStereo(v) => self.sprop_stereo = Some(*v),
147 UseInbandFec(v) => self.use_inband_fec = Some(*v),
148 UseDtx(v) => self.use_dtx = Some(*v),
149 LevelAsymmetryAllowed(v) => self.level_asymmetry_allowed = Some(*v),
150 PacketizationMode(v) => self.packetization_mode = Some(*v),
151 ProfileLevelId(v) => self.profile_level_id = Some(*v),
152 ProfileId(v) => self.profile_id = Some(*v),
153 Profile(v) => self.profile = Some(*v),
154 LevelIdx(v) => self.level_idx = Some(*v),
155 Tier(v) => self.tier = Some(*v),
156 H265ProfileTierLevel(v) => self.h265_profile_tier_level = Some(*v),
157 H266ProfileTierLevel(v) => self.h266_profile_tier_level = Some(*v),
158 SpropMaxDonDiff(v) => self.sprop_max_don_diff = Some(*v),
159 Apt(_) => {}
160 Unknown => {}
161 }
162 }
163 
164 pub(crate) fn to_format_param(self) -> Vec<FormatParam> {
165 use FormatParam::*;
166 let mut r = Vec::with_capacity(5);
167 
168 if let Some(v) = self.min_p_time {
169 r.push(MinPTime(v));
170 }
171 if let Some(v) = self.stereo {
172 r.push(Stereo(v));
173 }
174 if let Some(v) = self.sprop_stereo {
175 r.push(SpropStereo(v));
176 }
177 if let Some(v) = self.use_inband_fec {
178 r.push(UseInbandFec(v));
179 }
180 if let Some(v) = self.use_dtx {
181 r.push(UseDtx(v));
182 }
183 if let Some(v) = self.level_asymmetry_allowed {
184 r.push(LevelAsymmetryAllowed(v));
185 }
186 if let Some(v) = self.packetization_mode {
187 r.push(PacketizationMode(v));
188 }
189 if let Some(v) = self.profile_level_id {
190 r.push(ProfileLevelId(v));
191 }
192 if let Some(v) = self.profile_id {
193 r.push(ProfileId(v));
194 }
195 if let Some(v) = self.profile {
196 r.push(Profile(v));
197 }
198 if let Some(v) = self.level_idx {
199 r.push(LevelIdx(v));
200 }
201 if let Some(v) = self.tier {
202 r.push(Tier(v));
203 }
204 // H.265 Profile/Tier/Level: Keep as composite, it serializes all three params
205 if let Some(ptl) = self.h265_profile_tier_level {
206 r.push(H265ProfileTierLevel(ptl));
207 }
208 // H.266 Profile/Tier/Level: serialized as one composite (all three fmtp params).
209 if let Some(ptl) = self.h266_profile_tier_level {
210 r.push(H266ProfileTierLevel(ptl));
211 }
212 if let Some(v) = self.sprop_max_don_diff {
213 r.push(SpropMaxDonDiff(v));
214 }
215 
216 r
217 }
218}
219 
220impl fmt::Display for FormatParams {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 let s = self
223 .to_format_param()
224 .into_iter()
225 .map(|f| f.to_string())
226 .collect::<Vec<_>>()
227 .join(";");
228 write!(f, "{s}")
229 }
230}
231 
232#[cfg(test)]
233mod test {
234 use super::*;
235 
236 #[test]
237 fn sprop_max_don_diff_roundtrip() {
238 // Test that sprop-max-don-diff parameter round-trips correctly
239 let f = FormatParams {
240 sprop_max_don_diff: Some(32),
241 ..Default::default()
242 };
243 
244 // Serialize to string
245 let fmtp_str = f.to_string();
246 assert_eq!(fmtp_str, "sprop-max-don-diff=32");
247 
248 // Parse back
249 let parsed = FormatParams::parse_line(&fmtp_str);
250 assert_eq!(parsed.sprop_max_don_diff, Some(32));
251 }
252 
253 #[test]
254 fn sprop_stereo_roundtrip() {
255 let f = FormatParams {
256 sprop_stereo: Some(true),
257 ..Default::default()
258 };
259 
260 let fmtp_str = f.to_string();
261 assert_eq!(fmtp_str, "sprop-stereo=1");
262 
263 let parsed = FormatParams::parse_line(&fmtp_str);
264 assert_eq!(parsed.sprop_stereo, Some(true));
265 }
266 
267 #[test]
268 fn h265_combined_params_roundtrip() {
269 use crate::packet::H265ProfileTierLevel;
270 
271 // Test H.265 with both profile/tier/level and sprop-max-don-diff
272 let ptl = H265ProfileTierLevel::new(1, 0, 93).unwrap();
273 let f = FormatParams {
274 h265_profile_tier_level: Some(ptl),
275 sprop_max_don_diff: Some(32),
276 ..Default::default()
277 };
278 
279 // Serialize to string
280 let fmtp_str = f.to_string();
281 assert!(fmtp_str.contains("profile-id=1"));
282 assert!(fmtp_str.contains("tier-flag=0"));
283 assert!(fmtp_str.contains("level-id=93"));
284 assert!(fmtp_str.contains("sprop-max-don-diff=32"));
285 
286 // Parse back
287 let parsed = FormatParams::parse_line(&fmtp_str);
288 assert_eq!(parsed.h265_profile_tier_level, Some(ptl));
289 assert_eq!(parsed.sprop_max_don_diff, Some(32));
290 }
291 
292 #[test]
293 fn h266_combined_params_roundtrip() {
294 use crate::packet::H266ProfileTierLevel;
295 
296 // Test H.266 with both profile/tier/level and sprop-max-don-diff
297 let ptl = H266ProfileTierLevel::new(1, 0, 51).unwrap();
298 let f = FormatParams {
299 h266_profile_tier_level: Some(ptl),
300 sprop_max_don_diff: Some(32),
301 ..Default::default()
302 };
303 
304 // Serialize to string
305 let fmtp_str = f.to_string();
306 assert!(fmtp_str.contains("profile-id=1"));
307 assert!(fmtp_str.contains("tier-flag=0"));
308 assert!(fmtp_str.contains("level-id=51"));
309 assert!(fmtp_str.contains("sprop-max-don-diff=32"));
310 
311 // Parse back
312 let parsed = FormatParams::parse_line(&fmtp_str);
313 assert_eq!(parsed.h266_profile_tier_level, Some(ptl));
314 assert_eq!(parsed.sprop_max_don_diff, Some(32));
315 }
316}