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