Skip to main content

o_sfu/config/
transport.rs

1use 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;