1use std::fmt;
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)]
57pub struct RtcPortRange {
58 min: u16,
59 max: u16,
60}
61
62impl RtcPortRange {
63 #[must_use]
69 pub const fn new(min: u16, max: u16) -> Self {
70 Self { min, max }
71 }
72
73 #[must_use]
74 pub const fn min(self) -> u16 {
75 self.min
76 }
77
78 #[must_use]
79 pub const fn max(self) -> u16 {
80 self.max
81 }
82
83 #[must_use]
89 pub const fn port_count(self) -> u16 {
90 self.max - self.min + 1
91 }
92
93 pub fn ports(self) -> impl Iterator<Item = u16> {
94 self.min..=self.max
95 }
96
97 #[must_use]
106 pub fn split_for_workers(self, worker_count: usize) -> Option<Vec<Self>> {
107 if worker_count == 0 || worker_count > usize::from(self.port_count()) {
108 return None;
109 }
110 let total_ports = usize::from(self.port_count());
111 let base_ports_per_worker = total_ports / worker_count;
112 let extra_ports = total_ports % worker_count;
113 let mut next_min = u32::from(self.min);
114 let mut ranges = Vec::with_capacity(worker_count);
115 for worker_idx in 0..worker_count {
116 let worker_port_count = base_ports_per_worker + usize::from(worker_idx < extra_ports);
117 let worker_port_count = u32::try_from(worker_port_count).ok()?;
118 let max_inclusive = next_min + worker_port_count - 1;
119 ranges.push(Self::new(
120 u16::try_from(next_min).ok()?,
121 u16::try_from(max_inclusive).ok()?,
122 ));
123 next_min = max_inclusive + 1;
124 }
125 Some(ranges)
126 }
127}
128
129impl RoomMediaLimits {
130 pub const DEFAULT_MAX_ACTIVE_AUDIO_SPEAKERS: usize = 4;
131 pub const DEFAULT_MAX_VIDEO_DOWNLOADS_PER_RECEIVER: usize = 10;
132
133 pub const fn try_new(
139 max_active_audio_speakers: usize,
140 max_video_downloads_per_receiver: usize,
141 ) -> Result<Self, RoomMediaLimitsError> {
142 if max_active_audio_speakers == 0 {
143 return Err(RoomMediaLimitsError::MaxActiveAudioSpeakersZero);
144 }
145 if max_video_downloads_per_receiver == 0 {
146 return Err(RoomMediaLimitsError::MaxVideoDownloadsPerReceiverZero);
147 }
148 Ok(Self {
149 max_active_audio_speakers,
150 max_video_downloads_per_receiver,
151 })
152 }
153
154 #[must_use]
155 pub const fn conservative() -> Self {
156 Self {
157 max_active_audio_speakers: Self::DEFAULT_MAX_ACTIVE_AUDIO_SPEAKERS,
158 max_video_downloads_per_receiver: Self::DEFAULT_MAX_VIDEO_DOWNLOADS_PER_RECEIVER,
159 }
160 }
161
162 #[must_use]
163 pub const fn max_active_audio_speakers(self) -> usize {
164 self.max_active_audio_speakers
165 }
166
167 #[must_use]
168 pub const fn max_video_downloads_per_receiver(self) -> usize {
169 self.max_video_downloads_per_receiver
170 }
171}
172
173impl Default for RoomMediaLimits {
174 fn default() -> Self {
175 Self::conservative()
176 }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub struct VideoAdaptationTuning {
191 pub(crate) multiparty_scalable_video_threshold: usize,
192 pub(crate) thumbnail_budget_divisor: u64,
193 pub(crate) downswitch_pressure_observations: u8,
194 pub(crate) upswitch_stable_observations: u8,
195 pub(crate) receiver_budget_headroom_percent: u8,
196 pub(crate) audio_reserve_per_speaker: Bitrate,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
200pub enum VideoAdaptationTuningError {
201 #[error("multiparty scalable video threshold must be greater than zero")]
202 MultipartyScalableVideoThresholdZero,
203 #[error("thumbnail budget divisor must be greater than zero")]
204 ThumbnailBudgetDivisorZero,
205 #[error("downswitch pressure observations must be greater than zero")]
206 DownswitchPressureObservationsZero,
207 #[error("upswitch stable observations must be greater than zero")]
208 UpswitchStableObservationsZero,
209 #[error("receiver budget headroom percent must not exceed 100")]
210 ReceiverBudgetHeadroomPercentTooHigh,
211}
212
213impl VideoAdaptationTuning {
214 pub const DEFAULT_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD: usize = 3;
215 pub const DEFAULT_THUMBNAIL_BUDGET_DIVISOR: u64 = 2;
216 pub const DEFAULT_DOWNSWITCH_PRESSURE_OBSERVATIONS: u8 = 2;
217 pub const DEFAULT_UPSWITCH_STABLE_OBSERVATIONS: u8 = 3;
218 pub const DEFAULT_RECEIVER_BUDGET_HEADROOM_PERCENT: u8 = 0;
219 pub const DEFAULT_AUDIO_RESERVE_PER_SPEAKER: Bitrate = Bitrate::zero();
220
221 pub const fn try_new(
229 multiparty_scalable_video_threshold: usize,
230 thumbnail_budget_divisor: u64,
231 downswitch_pressure_observations: u8,
232 upswitch_stable_observations: u8,
233 receiver_budget_headroom_percent: u8,
234 audio_reserve_per_speaker: Bitrate,
235 ) -> Result<Self, VideoAdaptationTuningError> {
236 if multiparty_scalable_video_threshold == 0 {
237 return Err(VideoAdaptationTuningError::MultipartyScalableVideoThresholdZero);
238 }
239 if thumbnail_budget_divisor == 0 {
240 return Err(VideoAdaptationTuningError::ThumbnailBudgetDivisorZero);
241 }
242 if downswitch_pressure_observations == 0 {
243 return Err(VideoAdaptationTuningError::DownswitchPressureObservationsZero);
244 }
245 if upswitch_stable_observations == 0 {
246 return Err(VideoAdaptationTuningError::UpswitchStableObservationsZero);
247 }
248 if receiver_budget_headroom_percent > 100 {
249 return Err(VideoAdaptationTuningError::ReceiverBudgetHeadroomPercentTooHigh);
250 }
251 Ok(Self {
252 multiparty_scalable_video_threshold,
253 thumbnail_budget_divisor,
254 downswitch_pressure_observations,
255 upswitch_stable_observations,
256 receiver_budget_headroom_percent,
257 audio_reserve_per_speaker,
258 })
259 }
260}
261
262impl Default for VideoAdaptationTuning {
263 fn default() -> Self {
264 Self {
265 multiparty_scalable_video_threshold: Self::DEFAULT_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD,
266 thumbnail_budget_divisor: Self::DEFAULT_THUMBNAIL_BUDGET_DIVISOR,
267 downswitch_pressure_observations: Self::DEFAULT_DOWNSWITCH_PRESSURE_OBSERVATIONS,
268 upswitch_stable_observations: Self::DEFAULT_UPSWITCH_STABLE_OBSERVATIONS,
269 receiver_budget_headroom_percent: Self::DEFAULT_RECEIVER_BUDGET_HEADROOM_PERCENT,
270 audio_reserve_per_speaker: Self::DEFAULT_AUDIO_RESERVE_PER_SPEAKER,
271 }
272 }
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub struct SessionBitrateLimits {
282 max_bitrate_in: Bitrate,
283 max_bitrate_out: Bitrate,
284}
285
286impl SessionBitrateLimits {
287 #[must_use]
288 pub const fn new(max_bitrate_in: Bitrate, max_bitrate_out: Bitrate) -> Self {
289 Self {
290 max_bitrate_in,
291 max_bitrate_out,
292 }
293 }
294
295 #[must_use]
296 pub const fn max_bitrate_in(self) -> Bitrate {
297 self.max_bitrate_in
298 }
299
300 #[must_use]
301 pub const fn max_bitrate_out(self) -> Bitrate {
302 self.max_bitrate_out
303 }
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub struct VideoBitrateLimits {
312 max_video_bitrate: Bitrate,
313}
314
315impl VideoBitrateLimits {
316 pub const DEFAULT_MAX_VIDEO_BITRATE: Bitrate = Bitrate::from_mbps(4);
317
318 #[must_use]
319 pub const fn new(max_video_bitrate: Bitrate) -> Self {
320 Self { max_video_bitrate }
321 }
322
323 #[must_use]
324 pub const fn max_video_bitrate(self) -> Bitrate {
325 self.max_video_bitrate
326 }
327}
328
329impl Default for VideoBitrateLimits {
330 fn default() -> Self {
331 Self::new(Self::DEFAULT_MAX_VIDEO_BITRATE)
332 }
333}
334
335#[cfg(test)]
336#[path = "TESTS/media.rs"]
337mod tests;