1use std::{fmt, time::Duration};
2
3use crate::Bitrate;
4
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14pub enum RtcUdpIoBackend {
15 #[default]
16 Tokio,
17 IoUring,
18}
19
20impl RtcUdpIoBackend {
21 #[must_use]
22 pub const fn wire_name(self) -> &'static str {
23 match self {
24 Self::Tokio => "tokio",
25 Self::IoUring => "io_uring",
26 }
27 }
28}
29
30impl fmt::Display for RtcUdpIoBackend {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 f.write_str(self.wire_name())
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct RoomMediaLimits {
42 max_active_audio_speakers: usize,
43 max_video_downloads_per_receiver: usize,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
48pub enum RoomMediaLimitsError {
49 #[error("maximum active audio speakers must be greater than zero")]
50 MaxActiveAudioSpeakersZero,
51 #[error("maximum video downloads per receiver must be greater than zero")]
52 MaxVideoDownloadsPerReceiverZero,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
57pub enum RtcPortRangeError {
58 #[error("RTC port range minimum must be greater than zero")]
59 MinZero,
60 #[error("RTC port range maximum must be greater than or equal to the minimum")]
61 MinAboveMax,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct RtcPortRange {
67 min: u16,
68 max: u16,
69}
70
71impl RtcPortRange {
72 pub const fn try_new(min: u16, max: u16) -> Result<Self, RtcPortRangeError> {
79 if min == 0 {
80 return Err(RtcPortRangeError::MinZero);
81 }
82 if max < min {
83 return Err(RtcPortRangeError::MinAboveMax);
84 }
85 Ok(Self { min, max })
86 }
87
88 #[must_use]
89 pub const fn min(self) -> u16 {
90 self.min
91 }
92
93 #[must_use]
94 pub const fn max(self) -> u16 {
95 self.max
96 }
97
98 #[must_use]
100 pub const fn port_count(self) -> u16 {
101 self.max - self.min + 1
102 }
103
104 pub fn ports(self) -> impl Iterator<Item = u16> {
105 self.min..=self.max
106 }
107
108 #[must_use]
113 pub fn split_for_workers(self, worker_count: usize) -> Option<Vec<Self>> {
114 if worker_count == 0 || worker_count > usize::from(self.port_count()) {
115 return None;
116 }
117 let total_ports = usize::from(self.port_count());
118 let base_ports_per_worker = total_ports / worker_count;
119 let extra_ports = total_ports % worker_count;
120 let mut next_min = u32::from(self.min);
121 let mut ranges = Vec::with_capacity(worker_count);
122 for worker_idx in 0..worker_count {
123 let worker_port_count = base_ports_per_worker + usize::from(worker_idx < extra_ports);
124 let worker_port_count = u32::try_from(worker_port_count).ok()?;
125 let max_inclusive = next_min + worker_port_count - 1;
126 ranges.push(
127 Self::try_new(
128 u16::try_from(next_min).ok()?,
129 u16::try_from(max_inclusive).ok()?,
130 )
131 .ok()?,
132 );
133 next_min = max_inclusive + 1;
134 }
135 Some(ranges)
136 }
137}
138
139impl RoomMediaLimits {
140 pub const DEFAULT_MAX_ACTIVE_AUDIO_SPEAKERS: usize = 4;
141 pub const DEFAULT_MAX_VIDEO_DOWNLOADS_PER_RECEIVER: usize = 10;
142
143 pub const fn try_new(
149 max_active_audio_speakers: usize,
150 max_video_downloads_per_receiver: usize,
151 ) -> Result<Self, RoomMediaLimitsError> {
152 if max_active_audio_speakers == 0 {
153 return Err(RoomMediaLimitsError::MaxActiveAudioSpeakersZero);
154 }
155 if max_video_downloads_per_receiver == 0 {
156 return Err(RoomMediaLimitsError::MaxVideoDownloadsPerReceiverZero);
157 }
158 Ok(Self {
159 max_active_audio_speakers,
160 max_video_downloads_per_receiver,
161 })
162 }
163
164 #[must_use]
165 pub const fn conservative() -> Self {
166 Self {
167 max_active_audio_speakers: Self::DEFAULT_MAX_ACTIVE_AUDIO_SPEAKERS,
168 max_video_downloads_per_receiver: Self::DEFAULT_MAX_VIDEO_DOWNLOADS_PER_RECEIVER,
169 }
170 }
171
172 #[must_use]
173 pub const fn max_active_audio_speakers(self) -> usize {
174 self.max_active_audio_speakers
175 }
176
177 #[must_use]
178 pub const fn max_video_downloads_per_receiver(self) -> usize {
179 self.max_video_downloads_per_receiver
180 }
181}
182
183impl Default for RoomMediaLimits {
184 fn default() -> Self {
185 Self::conservative()
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub struct VideoAdaptationTuning {
202 pub(crate) multiparty_scalable_video_threshold: usize,
203 pub(crate) thumbnail_budget_divisor: u64,
204 pub(crate) soft_pause_dwell: Duration,
205 pub(crate) upgrade_dwell: Duration,
206 pub(crate) receiver_budget_headroom_percent: u8,
207 pub(crate) audio_reserve_per_speaker: Bitrate,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
211pub enum VideoAdaptationTuningError {
212 #[error("multiparty scalable video threshold must be greater than zero")]
213 MultipartyScalableVideoThresholdZero,
214 #[error("thumbnail budget divisor must be greater than zero")]
215 ThumbnailBudgetDivisorZero,
216 #[error("soft pause dwell must be greater than zero")]
217 SoftPauseDwellZero,
218 #[error("upgrade dwell must be greater than zero")]
219 UpgradeDwellZero,
220 #[error("soft pause dwell exceeds the portable deadline range")]
221 SoftPauseDwellTooLong,
222 #[error("upgrade dwell exceeds the portable deadline range")]
223 UpgradeDwellTooLong,
224 #[error("receiver budget headroom percent must not exceed 100")]
225 ReceiverBudgetHeadroomPercentTooHigh,
226}
227
228impl VideoAdaptationTuning {
229 pub const DEFAULT_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD: usize = 3;
230 pub const DEFAULT_THUMBNAIL_BUDGET_DIVISOR: u64 = 2;
231 pub const DEFAULT_SOFT_PAUSE_DWELL: Duration = Duration::from_millis(750);
232 pub const DEFAULT_UPGRADE_DWELL: Duration = Duration::from_millis(750);
233 pub const MAX_DWELL: Duration = Duration::from_hours(100 * 365 * 24);
237 pub const DEFAULT_RECEIVER_BUDGET_HEADROOM_PERCENT: u8 = 0;
238 pub const DEFAULT_AUDIO_RESERVE_PER_SPEAKER: Bitrate = Bitrate::zero();
239
240 pub const fn try_new(
248 multiparty_scalable_video_threshold: usize,
249 thumbnail_budget_divisor: u64,
250 soft_pause_dwell: Duration,
251 upgrade_dwell: Duration,
252 receiver_budget_headroom_percent: u8,
253 audio_reserve_per_speaker: Bitrate,
254 ) -> Result<Self, VideoAdaptationTuningError> {
255 if multiparty_scalable_video_threshold == 0 {
256 return Err(VideoAdaptationTuningError::MultipartyScalableVideoThresholdZero);
257 }
258 if thumbnail_budget_divisor == 0 {
259 return Err(VideoAdaptationTuningError::ThumbnailBudgetDivisorZero);
260 }
261 if soft_pause_dwell.is_zero() {
262 return Err(VideoAdaptationTuningError::SoftPauseDwellZero);
263 }
264 if upgrade_dwell.is_zero() {
265 return Err(VideoAdaptationTuningError::UpgradeDwellZero);
266 }
267 if soft_pause_dwell.as_nanos() > Self::MAX_DWELL.as_nanos() {
268 return Err(VideoAdaptationTuningError::SoftPauseDwellTooLong);
269 }
270 if upgrade_dwell.as_nanos() > Self::MAX_DWELL.as_nanos() {
271 return Err(VideoAdaptationTuningError::UpgradeDwellTooLong);
272 }
273 if receiver_budget_headroom_percent > 100 {
274 return Err(VideoAdaptationTuningError::ReceiverBudgetHeadroomPercentTooHigh);
275 }
276 Ok(Self {
277 multiparty_scalable_video_threshold,
278 thumbnail_budget_divisor,
279 soft_pause_dwell,
280 upgrade_dwell,
281 receiver_budget_headroom_percent,
282 audio_reserve_per_speaker,
283 })
284 }
285}
286
287impl Default for VideoAdaptationTuning {
288 fn default() -> Self {
289 Self {
290 multiparty_scalable_video_threshold: Self::DEFAULT_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD,
291 thumbnail_budget_divisor: Self::DEFAULT_THUMBNAIL_BUDGET_DIVISOR,
292 soft_pause_dwell: Self::DEFAULT_SOFT_PAUSE_DWELL,
293 upgrade_dwell: Self::DEFAULT_UPGRADE_DWELL,
294 receiver_budget_headroom_percent: Self::DEFAULT_RECEIVER_BUDGET_HEADROOM_PERCENT,
295 audio_reserve_per_speaker: Self::DEFAULT_AUDIO_RESERVE_PER_SPEAKER,
296 }
297 }
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub struct SessionBitrateLimits {
307 max_bitrate_in: Bitrate,
308 max_bitrate_out: Bitrate,
309}
310
311impl SessionBitrateLimits {
312 #[must_use]
313 pub const fn new(max_bitrate_in: Bitrate, max_bitrate_out: Bitrate) -> Self {
314 Self {
315 max_bitrate_in,
316 max_bitrate_out,
317 }
318 }
319
320 #[must_use]
321 pub const fn max_bitrate_in(self) -> Bitrate {
322 self.max_bitrate_in
323 }
324
325 #[must_use]
326 pub const fn max_bitrate_out(self) -> Bitrate {
327 self.max_bitrate_out
328 }
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337pub struct VideoBitrateLimits {
338 max_video_bitrate: Bitrate,
339}
340
341impl VideoBitrateLimits {
342 pub const DEFAULT_MAX_VIDEO_BITRATE: Bitrate = Bitrate::from_mbps(4);
343
344 #[must_use]
345 pub const fn new(max_video_bitrate: Bitrate) -> Self {
346 Self { max_video_bitrate }
347 }
348
349 #[must_use]
350 pub const fn max_video_bitrate(self) -> Bitrate {
351 self.max_video_bitrate
352 }
353}
354
355impl Default for VideoBitrateLimits {
356 fn default() -> Self {
357 Self::new(Self::DEFAULT_MAX_VIDEO_BITRATE)
358 }
359}
360
361#[cfg(test)]
362#[path = "TESTS/media.rs"]
363mod tests;