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