Skip to main content

o_sfu_core/engine/media_transport/
build.rs

1//! media transport construction and startup validation
2
3use std::sync::Arc;
4
5use thiserror::Error;
6
7use super::{
8    MediaTransport, SourcePolicySignal,
9    config::{MediaTransportConfig, MediaTransportDeps},
10    rtc::{RtcWorker, RtpProfile},
11};
12use crate::{MediaWorkerId, RtcUdpIoBackend};
13
14/// Per-worker [`TransportMediaId`](crate::engine::media_transport::TransportMediaId)
15/// allocation stride.
16///
17/// IDs have no worker namespace. Each `RtcWorker` may allocate at most this
18/// many IDs during its lifetime within one `MediaTransport`.
19const MEDIA_ID_STRIDE: u64 = 1_000_000_000;
20
21impl MediaTransport {
22    /// builds the runtime media transport from owner configuration and process services
23    ///
24    /// validation completes before worker startup and every worker has a bound
25    /// socket when this function returns
26    ///
27    /// # Errors
28    ///
29    /// returns [`MediaTransportBuildError`] when worker topology or the
30    /// code-controlled RTP profile is invalid, the selected UDP backend is
31    /// unavailable, the media-quality interval exceeds 24 hours or a worker
32    /// cannot start
33    pub fn build(
34        mut config: MediaTransportConfig,
35        deps: MediaTransportDeps,
36    ) -> Result<Self, MediaTransportBuildError> {
37        if config.rtc_udp_io_backend == RtcUdpIoBackend::IoUring && !cfg!(target_os = "linux") {
38            return Err(MediaTransportBuildError::UnsupportedUdpIoBackend {
39                backend: config.rtc_udp_io_backend,
40            });
41        }
42        if config.worker_count == 0 {
43            return Err(MediaTransportBuildError::InvalidWorkerCount);
44        }
45        // str0m adds the stats interval to Instant, so validate before starting workers.
46        if config
47            .media_quality_interval
48            .is_some_and(|interval| interval > MediaTransportConfig::MAX_MEDIA_QUALITY_INTERVAL)
49        {
50            return Err(MediaTransportBuildError::InvalidMediaQualityInterval);
51        }
52        config.media_quality_interval = config
53            .media_quality_interval
54            .filter(|value| !value.is_zero());
55        let worker_ranges = config
56            .rtc_port_range
57            .split_for_workers(config.worker_count)
58            .ok_or(MediaTransportBuildError::InvalidPortSplit {
59                worker_count: config.worker_count,
60                port_count: config.rtc_port_range.port_count(),
61            })?;
62        let profile = Arc::new(
63            RtpProfile::compile(config.codec_flags, config.codec_preferences)
64                .map_err(|_error| MediaTransportBuildError::InvalidRtpProfile)?,
65        );
66        let source_policy_signal = SourcePolicySignal::default();
67        let workers: Arc<[_]> = (0_u16..u16::MAX)
68            .zip(worker_ranges)
69            .map(|(worker_index, range)| {
70                // `start` returns only after socket binding, so the completed
71                // transport cannot publish a worker whose first command races I/O setup.
72                RtcWorker::start(
73                    &config,
74                    Arc::clone(&profile),
75                    range,
76                    &deps,
77                    source_policy_signal.clone(),
78                    u64::from(worker_index) * MEDIA_ID_STRIDE,
79                    MediaWorkerId::from_raw(usize::from(worker_index)),
80                )
81                .map_err(|_error| MediaTransportBuildError::WorkerStartup {
82                    worker_index: usize::from(worker_index),
83                })
84            })
85            .collect::<Result<_, _>>()?;
86        Ok(Self {
87            workers,
88            profile,
89            metrics: deps.metrics,
90            #[cfg(test)]
91            media_control_batches: Arc::default(),
92            #[cfg(any(test, feature = "testing-transport"))]
93            source_diagnostics_requests: Arc::default(),
94            source_policy_signal,
95        })
96    }
97}
98
99/// invalid construction inputs for the media transport
100#[derive(Debug, Error, Clone, Copy, PartialEq, Eq)]
101pub enum MediaTransportBuildError {
102    /// a transport cannot be built without at least one RTC worker
103    #[error("media transport worker count must be at least one")]
104    InvalidWorkerCount,
105    /// the configured UDP range cannot provide one port to every worker
106    #[error(
107        "media transport cannot split {port_count} UDP ports across {worker_count} media workers"
108    )]
109    InvalidPortSplit {
110        worker_count: usize,
111        port_count: u16,
112    },
113    /// the sampling interval exceeds the operational deadline bound
114    #[error("media-quality interval must not exceed 24 hours")]
115    InvalidMediaQualityInterval,
116    /// the selected UDP I/O backend is not available on this build target
117    #[error("rtc UDP I/O backend `{backend}` is not supported on this target")]
118    UnsupportedUdpIoBackend { backend: RtcUdpIoBackend },
119    /// the code-controlled RTC profile cannot be projected for router policy
120    #[error("media transport RTP profile is invalid")]
121    InvalidRtpProfile,
122    /// one worker could not create its runtime or bind its assigned UDP range
123    #[error("media transport worker {worker_index} failed to start")]
124    WorkerStartup { worker_index: usize },
125}