Skip to main content

o_sfu_core/options/
codecs.rs

1//! codec policy shared by server config and RTC transport construction
2//!
3//! server configuration parses operator input into these values then
4//! `MediaTransport::build` compiles them into one private RTP profile
5
6use bitflags::bitflags;
7use o_sfu_rfc::rtp::codec_name;
8
9bitflags! {
10    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
11    struct MediaCodecSet: u16 {
12        const OPUS = 1 << 0;
13        const PCMU = 1 << 1;
14        const PCMA = 1 << 2;
15        const VP8 = 1 << 3;
16        const H264 = 1 << 4;
17        const H265 = 1 << 5;
18        const VP9 = 1 << 6;
19        const AV1 = 1 << 7;
20    }
21}
22
23/// Codec enablement for RTP profile compilation.
24///
25/// [`Default`] enables Opus and VP8. [`CodecPreferences`] determines their
26/// compilation order.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct MediaCodecFlags {
29    enabled: MediaCodecSet,
30}
31
32macro_rules! media_codec_accessors {
33    ($($enabled:ident => $with:ident => $flag:ident),+ $(,)?) => {
34        $(
35            #[doc = concat!("returns whether `", stringify!($flag), "` may enter the compiled RTP profile")]
36            #[must_use]
37            pub fn $enabled(self) -> bool {
38                self.enabled.contains(MediaCodecSet::$flag)
39            }
40
41            #[doc = concat!("returns a copy with `", stringify!($flag), "` enabled or disabled for profile compilation")]
42            #[must_use]
43            pub fn $with(self, enabled: bool) -> Self {
44                self.with_flag(MediaCodecSet::$flag, enabled)
45            }
46        )+
47    };
48}
49
50impl MediaCodecFlags {
51    #[must_use]
52    pub const fn empty() -> Self {
53        Self {
54            enabled: MediaCodecSet::empty(),
55        }
56    }
57
58    #[must_use]
59    fn with_flag(mut self, flag: MediaCodecSet, enabled: bool) -> Self {
60        if enabled {
61            self.enabled.insert(flag);
62        } else {
63            self.enabled.remove(flag);
64        }
65        self
66    }
67
68    media_codec_accessors!(
69        opus_enabled => with_opus => OPUS,
70        pcmu_enabled => with_pcmu => PCMU,
71        pcma_enabled => with_pcma => PCMA,
72        vp8_enabled => with_vp8 => VP8,
73        h264_enabled => with_h264 => H264,
74        h265_enabled => with_h265 => H265,
75        vp9_enabled => with_vp9 => VP9,
76        av1_enabled => with_av1 => AV1,
77    );
78}
79
80impl Default for MediaCodecFlags {
81    fn default() -> Self {
82        Self {
83            enabled: MediaCodecSet::OPUS | MediaCodecSet::VP8,
84        }
85    }
86}
87
88/// audio codec entry used to rank the negotiated audio capability surface
89///
90/// the private RTP profile compiler filters this order through
91/// [`MediaCodecFlags`]
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub enum AudioCodecPreference {
94    /// default audio codec for browser RTC sessions
95    Opus,
96    /// g.711 mu-law compatibility codec
97    Pcmu,
98    /// g.711 a-law compatibility codec
99    Pcma,
100}
101
102impl AudioCodecPreference {
103    /// canonical operator-configuration token for this codec
104    #[must_use]
105    pub const fn wire_name(self) -> &'static str {
106        match self {
107            Self::Opus => codec_name::OPUS,
108            Self::Pcmu => codec_name::PCMU,
109            Self::Pcma => codec_name::PCMA,
110        }
111    }
112
113    /// returns whether this preference enters the compiled RTP profile
114    #[must_use]
115    pub fn enabled_by(self, flags: MediaCodecFlags) -> bool {
116        match self {
117            Self::Opus => flags.opus_enabled(),
118            Self::Pcmu => flags.pcmu_enabled(),
119            Self::Pcma => flags.pcma_enabled(),
120        }
121    }
122}
123
124/// video codec entry used to rank the negotiated video capability surface
125///
126/// the private RTP profile compiler filters this order through
127/// [`MediaCodecFlags`] before installing concrete payload configurations
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum VideoCodecPreference {
130    /// default video codec for browser RTC sessions
131    Vp8,
132    /// browser-compatible h264 path with the shared payload contract
133    H264,
134    /// optional h265 capability controlled by runtime flags
135    H265,
136    /// optional vp9 capability controlled by runtime flags
137    Vp9,
138    /// optional av1 capability controlled by runtime flags
139    Av1,
140}
141
142impl VideoCodecPreference {
143    /// canonical operator-configuration token for this codec
144    #[must_use]
145    pub const fn wire_name(self) -> &'static str {
146        match self {
147            Self::Vp8 => codec_name::VP8,
148            Self::H264 => codec_name::H264,
149            Self::H265 => codec_name::H265,
150            Self::Vp9 => codec_name::VP9,
151            Self::Av1 => codec_name::AV1,
152        }
153    }
154
155    /// returns whether this preference enters the compiled RTP profile
156    #[must_use]
157    pub fn enabled_by(self, flags: MediaCodecFlags) -> bool {
158        match self {
159            Self::Vp8 => flags.vp8_enabled(),
160            Self::H264 => flags.h264_enabled(),
161            Self::H265 => flags.h265_enabled(),
162            Self::Vp9 => flags.vp9_enabled(),
163            Self::Av1 => flags.av1_enabled(),
164        }
165    }
166}
167
168/// Audio and video codec ordering for RTP profile compilation.
169///
170/// Partial orders should use [`Self::with_audio_order`] and
171/// [`Self::with_video_order`].
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct CodecPreferences {
174    audio: [AudioCodecPreference; 3],
175    video: [VideoCodecPreference; 5],
176}
177
178impl CodecPreferences {
179    /// canonical audio order used when operators do not override preferences
180    pub const DEFAULT_AUDIO: [AudioCodecPreference; 3] = [
181        AudioCodecPreference::Opus,
182        AudioCodecPreference::Pcmu,
183        AudioCodecPreference::Pcma,
184    ];
185    /// canonical video order used when operators do not override preferences
186    pub const DEFAULT_VIDEO: [VideoCodecPreference; 5] = [
187        VideoCodecPreference::Vp8,
188        VideoCodecPreference::H264,
189        VideoCodecPreference::H265,
190        VideoCodecPreference::Vp9,
191        VideoCodecPreference::Av1,
192    ];
193
194    /// Stores caller-supplied complete audio and video orders.
195    ///
196    /// Each array must contain every corresponding codec exactly once. This
197    /// constructor does not validate the permutation.
198    #[must_use]
199    pub const fn new(audio: [AudioCodecPreference; 3], video: [VideoCodecPreference; 5]) -> Self {
200        Self { audio, video }
201    }
202
203    /// Places each distinct `preferred` codec first in encounter order.
204    ///
205    /// Every omitted codec follows in canonical default order. The previous
206    /// audio order is not used.
207    #[must_use]
208    pub fn with_audio_order(self, preferred: &[AudioCodecPreference]) -> Self {
209        Self {
210            audio: complete_codec_order(preferred, Self::DEFAULT_AUDIO),
211            ..self
212        }
213    }
214
215    /// Places each distinct `preferred` codec first in encounter order.
216    ///
217    /// Every omitted codec follows in canonical default order. The previous
218    /// video order is not used.
219    #[must_use]
220    pub fn with_video_order(self, preferred: &[VideoCodecPreference]) -> Self {
221        Self {
222            video: complete_codec_order(preferred, Self::DEFAULT_VIDEO),
223            ..self
224        }
225    }
226
227    /// complete audio order after defaults filled any omitted codecs
228    #[must_use]
229    pub const fn audio_order(self) -> [AudioCodecPreference; 3] {
230        self.audio
231    }
232
233    /// complete video order after defaults filled any omitted codecs
234    #[must_use]
235    pub const fn video_order(self) -> [VideoCodecPreference; 5] {
236        self.video
237    }
238}
239
240impl Default for CodecPreferences {
241    fn default() -> Self {
242        Self::new(Self::DEFAULT_AUDIO, Self::DEFAULT_VIDEO)
243    }
244}
245
246fn complete_codec_order<T, const N: usize>(preferred: &[T], default: [T; N]) -> [T; N]
247where
248    T: Copy + Eq,
249{
250    let mut output = default;
251    let mut len = 0;
252    // `default` must contain every codec exactly once. Once `len == N`, every
253    // remaining item is therefore a duplicate.
254    for codec in preferred.iter().copied().chain(default) {
255        if contains_codec(&output, len, codec) {
256            continue;
257        }
258        if let Some(slot) = output.get_mut(len) {
259            *slot = codec;
260            len += 1;
261        }
262    }
263    output
264}
265
266fn contains_codec<T, const N: usize>(codecs: &[T; N], len: usize, needle: T) -> bool
267where
268    T: Copy + Eq,
269{
270    codecs.iter().take(len).any(|codec| *codec == needle)
271}