o_sfu/config/
transport.rs1use std::{
2 net::IpAddr,
3 num::{NonZeroU64, NonZeroUsize},
4 thread,
5};
6
7use anyhow::{Result, anyhow, ensure};
8use o_sfu_core::prelude::{
9 Bitrate, RoomMediaLimits, RoomWorkerPolicy, RtcPortRange, RtcUdpIoBackend,
10 VideoAdaptationTuning, VideoAdaptationTuningError, VideoBitrateLimits,
11};
12
13use super::{
14 TransportConfig,
15 env::{Env, EnvParse, EnvValue, positive},
16};
17
18impl EnvParse for RtcUdpIoBackend {
19 fn parse(value: EnvValue) -> Result<Self> {
20 let key = value.key();
21 match value.as_str() {
22 "tokio" => Ok(Self::Tokio),
23 "io_uring" => {
24 ensure!(
25 cfg!(target_os = "linux"),
26 "{key}=io_uring is only supported on Linux"
27 );
28 Ok(Self::IoUring)
29 }
30 other => Err(anyhow!(
31 "{key} must be one of tokio or io_uring, got {other}"
32 )),
33 }
34 }
35}
36
37impl TransportConfig {
38 pub(super) fn from_env(env: &Env<'_>) -> Result<Self> {
39 if env.var::<String>("TRANSPORT_BACKEND").optional()?.is_some() {
40 return Err(anyhow!(
41 "TRANSPORT_BACKEND is no longer supported; o-sfu always boots the RTC transport"
42 ));
43 }
44 let announced_ip = env
45 .var::<IpAddr>("ANNOUNCED_IP")
46 .alias("PUBLIC_IP")
47 .required()?;
48 let rtc_min_port = env.var("RTC_MIN_PORT").default(40_000)?;
49 let max_bitrate_in_bps = env
50 .var("MAX_BITRATE_IN")
51 .check(positive)
52 .default(8_000_000)?;
53 let max_bitrate_out_bps = env
54 .var("MAX_BITRATE_OUT")
55 .check(positive)
56 .default(10_000_000)?;
57 let max_video_bitrate_bps = env
58 .var("MAX_VIDEO_BITRATE")
59 .check(positive)
60 .default(VideoBitrateLimits::DEFAULT_MAX_VIDEO_BITRATE.as_bps())?;
61 let rtc_max_port = env.var("RTC_MAX_PORT").default(49_999)?;
62 let rtc_udp_io_backend = env
63 .var("RTC_UDP_IO_BACKEND")
64 .default(RtcUdpIoBackend::Tokio)?;
65 let rtc_media_worker_count = env
66 .var("RTC_MEDIA_WORKER_COUNT")
67 .check(positive)
68 .default(default_rtc_media_worker_count())?;
69 let room_max_local_routers =
70 NonZeroUsize::new(env.var("ROOM_MAX_LOCAL_ROUTERS").default(1)?)
71 .ok_or_else(|| anyhow!("ROOM_MAX_LOCAL_ROUTERS must be greater than zero"))?;
72 let packet_loop_delay_threshold = NonZeroU64::new(
73 env.var("ROOM_SPILLOVER_PACKET_LOOP_DELAY_MS")
74 .default(RoomWorkerPolicy::DEFAULT_PACKET_LOOP_DELAY_THRESHOLD_MS)?,
75 )
76 .ok_or_else(|| anyhow!("ROOM_SPILLOVER_PACKET_LOOP_DELAY_MS must be greater than zero"))?;
77 let room_media_limits = room_media_limits_from_env(env)?;
78 let video_adaptation_tuning = video_adaptation_tuning_from_env(env)?;
79 let rtc_port_range = RtcPortRange::new(rtc_min_port, rtc_max_port);
80 ensure!(
81 rtc_port_range.min() <= rtc_port_range.max(),
82 "RTC_MAX_PORT must be greater than or equal to RTC_MIN_PORT"
83 );
84 ensure!(
85 rtc_media_worker_count <= usize::from(rtc_port_range.port_count()),
86 "RTC_MEDIA_WORKER_COUNT must be less than or equal to the available RTC port count"
87 );
88 ensure!(
89 room_max_local_routers.get() <= rtc_media_worker_count,
90 "ROOM_MAX_LOCAL_ROUTERS must be less than or equal to RTC_MEDIA_WORKER_COUNT"
91 );
92 ensure!(
93 !announced_ip.is_unspecified(),
94 "ANNOUNCED_IP must be a concrete advertised address"
95 );
96 ensure!(
97 !announced_ip.is_multicast(),
98 "ANNOUNCED_IP cannot be a multicast address"
99 );
100 let room_worker_policy =
101 RoomWorkerPolicy::new(room_max_local_routers, packet_loop_delay_threshold);
102 Ok(Self {
103 announced_ip,
104 max_bitrate_in: Bitrate::from_bps(max_bitrate_in_bps),
105 max_bitrate_out: Bitrate::from_bps(max_bitrate_out_bps),
106 video_bitrate_limits: VideoBitrateLimits::new(Bitrate::from_bps(max_video_bitrate_bps)),
107 rtc_port_range,
108 rtc_udp_io_backend,
109 rtc_media_worker_count,
110 room_worker_policy,
111 room_media_limits,
112 video_adaptation_tuning,
113 })
114 }
115}
116
117fn room_media_limits_from_env(env: &Env<'_>) -> Result<RoomMediaLimits> {
118 let active_audio_speakers = env
119 .var("ROOM_MAX_ACTIVE_AUDIO_SPEAKERS")
120 .check(positive)
121 .default(RoomMediaLimits::DEFAULT_MAX_ACTIVE_AUDIO_SPEAKERS)?;
122 let video_downloads_per_receiver = env
123 .var("ROOM_MAX_VIDEO_DOWNLOADS_PER_RECEIVER")
124 .check(positive)
125 .default(RoomMediaLimits::DEFAULT_MAX_VIDEO_DOWNLOADS_PER_RECEIVER)?;
126 Ok(RoomMediaLimits::try_new(
127 active_audio_speakers,
128 video_downloads_per_receiver,
129 )?)
130}
131
132fn video_adaptation_tuning_from_env(env: &Env<'_>) -> Result<VideoAdaptationTuning> {
133 let multiparty_scalable_video_threshold = env
134 .var("ROOM_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD")
135 .check(positive)
136 .default(VideoAdaptationTuning::DEFAULT_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD)?;
137 let thumbnail_budget_divisor = env
138 .var("ROOM_THUMBNAIL_BUDGET_DIVISOR")
139 .check(positive)
140 .default(VideoAdaptationTuning::DEFAULT_THUMBNAIL_BUDGET_DIVISOR)?;
141 let downswitch_pressure_observations = env
142 .var("ROOM_DOWNSWITCH_PRESSURE_OBSERVATIONS")
143 .check(positive)
144 .default(VideoAdaptationTuning::DEFAULT_DOWNSWITCH_PRESSURE_OBSERVATIONS)?;
145 let upswitch_stable_observations = env
146 .var("ROOM_UPSWITCH_STABLE_OBSERVATIONS")
147 .check(positive)
148 .default(VideoAdaptationTuning::DEFAULT_UPSWITCH_STABLE_OBSERVATIONS)?;
149 let receiver_budget_headroom_percent = env
150 .var("ROOM_RECEIVER_BUDGET_HEADROOM_PERCENT")
151 .default(VideoAdaptationTuning::DEFAULT_RECEIVER_BUDGET_HEADROOM_PERCENT)?;
152 let audio_reserve_per_speaker_bps = env
153 .var("ROOM_AUDIO_RESERVE_PER_SPEAKER_BPS")
154 .default(VideoAdaptationTuning::DEFAULT_AUDIO_RESERVE_PER_SPEAKER.as_bps())?;
155 VideoAdaptationTuning::try_new(
156 multiparty_scalable_video_threshold,
157 thumbnail_budget_divisor,
158 downswitch_pressure_observations,
159 upswitch_stable_observations,
160 receiver_budget_headroom_percent,
161 Bitrate::from_bps(audio_reserve_per_speaker_bps),
162 )
163 .map_err(video_adaptation_tuning_error)
164}
165
166pub fn default_rtc_media_worker_count() -> usize {
167 thread::available_parallelism().map_or(1, NonZeroUsize::get)
168}
169
170fn video_adaptation_tuning_error(error: VideoAdaptationTuningError) -> anyhow::Error {
171 match error {
172 VideoAdaptationTuningError::MultipartyScalableVideoThresholdZero => {
173 anyhow!("ROOM_MULTIPARTY_SCALABLE_VIDEO_THRESHOLD must be greater than zero")
174 }
175 VideoAdaptationTuningError::ThumbnailBudgetDivisorZero => {
176 anyhow!("ROOM_THUMBNAIL_BUDGET_DIVISOR must be greater than zero")
177 }
178 VideoAdaptationTuningError::DownswitchPressureObservationsZero => {
179 anyhow!("ROOM_DOWNSWITCH_PRESSURE_OBSERVATIONS must be greater than zero")
180 }
181 VideoAdaptationTuningError::UpswitchStableObservationsZero => {
182 anyhow!("ROOM_UPSWITCH_STABLE_OBSERVATIONS must be greater than zero")
183 }
184 VideoAdaptationTuningError::ReceiverBudgetHeadroomPercentTooHigh => {
185 anyhow!("ROOM_RECEIVER_BUDGET_HEADROOM_PERCENT must not exceed 100")
186 }
187 }
188}
189
190#[cfg(test)]
191#[path = "TESTS/transport.rs"]
192mod tests;