1use std::{fmt, net::SocketAddr, time::Duration};
2
3use crate::Bitrate;
4
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
15pub enum RtcUdpIoBackend {
16 #[default]
17 Tokio,
18 TokioBatch,
20 IoUring,
21}
22
23impl RtcUdpIoBackend {
24 #[must_use]
25 pub const fn wire_name(self) -> &'static str {
26 match self {
27 Self::Tokio => "tokio",
28 Self::TokioBatch => "tokio_batch",
29 Self::IoUring => "io_uring",
30 }
31 }
32}
33
34impl fmt::Display for RtcUdpIoBackend {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 f.write_str(self.wire_name())
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct RoomMediaLimits {
46 max_active_audio_speakers: usize,
47 max_video_downloads_per_receiver: usize,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
52pub enum RoomMediaLimitsError {
53 #[error("maximum active audio speakers must be greater than zero")]
54 MaxActiveAudioSpeakersZero,
55 #[error("maximum video downloads per receiver must be greater than zero")]
56 MaxVideoDownloadsPerReceiverZero,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
61pub enum RtcPortRangeError {
62 #[error("RTC port range minimum must be greater than zero")]
63 MinZero,
64 #[error("RTC port range maximum must be greater than or equal to the minimum")]
65 MinAboveMax,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct RtcTcpConfig {
70 pub bind_addr: SocketAddr,
71 pub announced_addr: SocketAddr,
72 pub max_conn: usize,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct RtcPortRange {
78 min: u16,
79 max: u16,
80}
81
82impl RtcPortRange {
83 pub const fn try_new(min: u16, max: u16) -> Result<Self, RtcPortRangeError> {
90 if min == 0 {
91 return Err(RtcPortRangeError::MinZero);
92 }
93 if max < min {
94 return Err(RtcPortRangeError::MinAboveMax);
95 }
96 Ok(Self { min, max })
97 }
98
99 #[must_use]
100 pub const fn min(self) -> u16 {
101 self.min
102 }
103
104 #[must_use]
105 pub const fn max(self) -> u16 {
106 self.max
107 }
108
109 #[must_use]
111 pub const fn port_count(self) -> u16 {
112 self.max - self.min + 1
113 }
114
115 pub fn ports(self) -> impl Iterator<Item = u16> {
116 self.min..=self.max
117 }
118
119 #[must_use]
124 pub fn split_for_workers(self, worker_count: usize) -> Option<Vec<Self>> {
125 if worker_count == 0 || worker_count > usize::from(self.port_count()) {
126 return None;
127 }
128 let total_ports = usize::from(self.port_count());
129 let base_ports_per_worker = total_ports / worker_count;
130 let extra_ports = total_ports % worker_count;
131 let mut next_min = u32::from(self.min);
132 let mut ranges = Vec::with_capacity(worker_count);
133 for worker_idx in 0..worker_count {
134 let worker_port_count = base_ports_per_worker + usize::from(worker_idx < extra_ports);
135 let worker_port_count = u32::try_from(worker_port_count).ok()?;
136 let max_inclusive = next_min + worker_port_count - 1;
137 ranges.push(
138 Self::try_new(
139 u16::try_from(next_min).ok()?,
140 u16::try_from(max_inclusive).ok()?,
141 )
142 .ok()?,
143 );
144 next_min = max_inclusive + 1;
145 }
146 Some(ranges)
147 }
148}
149
150impl RoomMediaLimits {
151 pub const DEFAULT_MAX_ACTIVE_AUDIO_SPEAKERS: usize = 4;
152 pub const DEFAULT_MAX_VIDEO_DOWNLOADS_PER_RECEIVER: usize = 10;
153
154 pub const fn try_new(
160 max_active_audio_speakers: usize,
161 max_video_downloads_per_receiver: usize,
162 ) -> Result<Self, RoomMediaLimitsError> {
163 if max_active_audio_speakers == 0 {
164 return Err(RoomMediaLimitsError::MaxActiveAudioSpeakersZero);
165 }
166 if max_video_downloads_per_receiver == 0 {
167 return Err(RoomMediaLimitsError::MaxVideoDownloadsPerReceiverZero);
168 }
169 Ok(Self {
170 max_active_audio_speakers,
171 max_video_downloads_per_receiver,
172 })
173 }
174
175 #[must_use]
176 pub const fn conservative() -> Self {
177 Self {
178 max_active_audio_speakers: Self::DEFAULT_MAX_ACTIVE_AUDIO_SPEAKERS,
179 max_video_downloads_per_receiver: Self::DEFAULT_MAX_VIDEO_DOWNLOADS_PER_RECEIVER,
180 }
181 }
182
183 #[must_use]
184 pub const fn max_active_audio_speakers(self) -> usize {
185 self.max_active_audio_speakers
186 }
187
188 #[must_use]
189 pub const fn max_video_downloads_per_receiver(self) -> usize {
190 self.max_video_downloads_per_receiver
191 }
192}
193
194impl Default for RoomMediaLimits {
195 fn default() -> Self {
196 Self::conservative()
197 }
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub struct VideoAdaptationTuning {
213 pub(crate) multiparty_scalable_video_threshold: usize,
214 pub(crate) thumbnail_budget_divisor: u64,
215 pub(crate) soft_pause_dwell: Duration,
216 pub(crate) upgrade_dwell: Duration,
217 pub(crate) receiver_budget_headroom_percent: u8,
218 pub(crate) audio_reserve_per_speaker: Bitrate,
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
222pub enum VideoAdaptationTuningError {
223 #[error("multiparty scalable video threshold must be greater than zero")]
224 MultipartyScalableVideoThresholdZero,
225 #[error("thumbnail budget divisor must be greater than zero")]
226 ThumbnailBudgetDivisorZero,
227 #[error("soft pause dwell must be greater than zero")]
228 SoftPauseDwellZero,
229 #[error("upgrade dwell must be greater than zero")]
230 UpgradeDwellZero,
231 #[error("soft pause dwell exceeds the portable deadline range")]
232 SoftPauseDwellTooLong,
233 #[error("upgrade dwell exceeds the portable deadline range")]
234 UpgradeDwellTooLong,
235 #[error("receiver budget headroom percent must not exceed 100")]
236 ReceiverBudgetHeadroomPercentTooHigh,
237}
238
239impl VideoAdaptationTuning {
240 pub const DEFAULT_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD: usize = 3;
241 pub const DEFAULT_THUMBNAIL_BUDGET_DIVISOR: u64 = 2;
242 pub const DEFAULT_SOFT_PAUSE_DWELL: Duration = Duration::from_millis(750);
243 pub const DEFAULT_UPGRADE_DWELL: Duration = Duration::from_millis(750);
244 pub const MAX_DWELL: Duration = Duration::from_hours(100 * 365 * 24);
248 pub const DEFAULT_RECEIVER_BUDGET_HEADROOM_PERCENT: u8 = 0;
249 pub const DEFAULT_AUDIO_RESERVE_PER_SPEAKER: Bitrate = Bitrate::zero();
250
251 pub const fn try_new(
259 multiparty_scalable_video_threshold: usize,
260 thumbnail_budget_divisor: u64,
261 soft_pause_dwell: Duration,
262 upgrade_dwell: Duration,
263 receiver_budget_headroom_percent: u8,
264 audio_reserve_per_speaker: Bitrate,
265 ) -> Result<Self, VideoAdaptationTuningError> {
266 if multiparty_scalable_video_threshold == 0 {
267 return Err(VideoAdaptationTuningError::MultipartyScalableVideoThresholdZero);
268 }
269 if thumbnail_budget_divisor == 0 {
270 return Err(VideoAdaptationTuningError::ThumbnailBudgetDivisorZero);
271 }
272 if soft_pause_dwell.is_zero() {
273 return Err(VideoAdaptationTuningError::SoftPauseDwellZero);
274 }
275 if upgrade_dwell.is_zero() {
276 return Err(VideoAdaptationTuningError::UpgradeDwellZero);
277 }
278 if soft_pause_dwell.as_nanos() > Self::MAX_DWELL.as_nanos() {
279 return Err(VideoAdaptationTuningError::SoftPauseDwellTooLong);
280 }
281 if upgrade_dwell.as_nanos() > Self::MAX_DWELL.as_nanos() {
282 return Err(VideoAdaptationTuningError::UpgradeDwellTooLong);
283 }
284 if receiver_budget_headroom_percent > 100 {
285 return Err(VideoAdaptationTuningError::ReceiverBudgetHeadroomPercentTooHigh);
286 }
287 Ok(Self {
288 multiparty_scalable_video_threshold,
289 thumbnail_budget_divisor,
290 soft_pause_dwell,
291 upgrade_dwell,
292 receiver_budget_headroom_percent,
293 audio_reserve_per_speaker,
294 })
295 }
296}
297
298impl Default for VideoAdaptationTuning {
299 fn default() -> Self {
300 Self {
301 multiparty_scalable_video_threshold: Self::DEFAULT_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD,
302 thumbnail_budget_divisor: Self::DEFAULT_THUMBNAIL_BUDGET_DIVISOR,
303 soft_pause_dwell: Self::DEFAULT_SOFT_PAUSE_DWELL,
304 upgrade_dwell: Self::DEFAULT_UPGRADE_DWELL,
305 receiver_budget_headroom_percent: Self::DEFAULT_RECEIVER_BUDGET_HEADROOM_PERCENT,
306 audio_reserve_per_speaker: Self::DEFAULT_AUDIO_RESERVE_PER_SPEAKER,
307 }
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub struct SessionBitrateLimits {
318 max_bitrate_in: Bitrate,
319 max_bitrate_out: Bitrate,
320}
321
322impl SessionBitrateLimits {
323 #[must_use]
324 pub const fn new(max_bitrate_in: Bitrate, max_bitrate_out: Bitrate) -> Self {
325 Self {
326 max_bitrate_in,
327 max_bitrate_out,
328 }
329 }
330
331 #[must_use]
332 pub const fn max_bitrate_in(self) -> Bitrate {
333 self.max_bitrate_in
334 }
335
336 #[must_use]
337 pub const fn max_bitrate_out(self) -> Bitrate {
338 self.max_bitrate_out
339 }
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
348pub struct VideoBitrateLimits {
349 max_video_bitrate: Bitrate,
350}
351
352impl VideoBitrateLimits {
353 pub const DEFAULT_MAX_VIDEO_BITRATE: Bitrate = Bitrate::from_mbps(4);
354
355 #[must_use]
356 pub const fn new(max_video_bitrate: Bitrate) -> Self {
357 Self { max_video_bitrate }
358 }
359
360 #[must_use]
361 pub const fn max_video_bitrate(self) -> Bitrate {
362 self.max_video_bitrate
363 }
364}
365
366impl Default for VideoBitrateLimits {
367 fn default() -> Self {
368 Self::new(Self::DEFAULT_MAX_VIDEO_BITRATE)
369 }
370}
371
372#[cfg(test)]
373#[path = "TESTS/media.rs"]
374mod tests;