Skip to main content

o_sfu/config/
transport.rs

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