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