Skip to main content

oxideav_core/
capabilities.rs

1//! Codec capability description.
2//!
3//! Each codec implementation registered with the codec registry attaches one
4//! of these structs to declare what it can do, what its constraints are, and
5//! how the registry should rank it against alternative implementations of
6//! the same codec id.
7//!
8//! The flag layout is a 6-column capability string (one letter per
9//! capability, `.` when absent):
10//!
11//! ```text
12//!  D..... = Decoding supported
13//!  .E.... = Encoding supported
14//!  ..V... = Video codec       ..A... = Audio       ..S... = Subtitle
15//!  ..D... = Data              ..T... = Attachment
16//!  ...I.. = Intra-frame-only codec
17//!  ....L. = Lossy compression
18//!  .....S = Lossless compression
19//! ```
20
21use std::fmt;
22
23use crate::format::{MediaType, PixelFormat, SampleFormat};
24
25/// Default priority for software implementations. Lower numbers are preferred
26/// at resolution time, so register hardware impls with a smaller value (e.g.
27/// `10`) and software fallbacks with the default `100`.
28///
29/// The same value and the same "lower is preferred" convention serve as
30/// the default *resolution* priority for every registry tie-break
31/// (container probes, codec tag / payload-magic claims) — see the
32/// crate-level "Resolution order" section.
33pub const DEFAULT_PRIORITY: i32 = 100;
34
35/// What an implementation can do plus how it ranks vs alternatives.
36///
37/// `#[non_exhaustive]`: build it with [`audio`](Self::audio),
38/// [`video`](Self::video) or [`subtitle`](Self::subtitle) and the
39/// `with_*` builders, then read the public fields. Fields are added
40/// over time (0.1.38 added the accepted sample rates / formats) and a
41/// struct literal in a downstream crate would stop compiling each time.
42#[derive(Clone, Debug)]
43#[non_exhaustive]
44pub struct CodecCapabilities {
45    /// Decoding supported by this implementation.
46    pub decode: bool,
47    /// Encoding supported by this implementation.
48    pub encode: bool,
49    /// Media type this implementation handles (audio, video, ...).
50    pub media_type: MediaType,
51    /// Every coded unit is independently decodable (no inter-frame
52    /// prediction).
53    pub intra_only: bool,
54    /// Supports lossy compression.
55    pub lossy: bool,
56    /// Supports lossless compression. `lossy` and `lossless` may both
57    /// be set for codecs that offer both modes.
58    pub lossless: bool,
59    /// Hardware-accelerated implementation (VAAPI/NVENC/QSV/VideoToolbox/...).
60    pub hardware_accelerated: bool,
61    /// Short identifier for this implementation, e.g. "flac_sw", "h264_qsv".
62    pub implementation: String,
63    /// Restrictions — `None` means "no constraint".
64    pub max_width: Option<u32>,
65    /// Maximum supported frame height in pixels; `None` = unconstrained.
66    pub max_height: Option<u32>,
67    /// Maximum supported bit rate in bits per second; `None` =
68    /// unconstrained.
69    pub max_bitrate: Option<u64>,
70    /// Maximum supported audio sample rate in Hz; `None` = unconstrained.
71    pub max_sample_rate: Option<u32>,
72    /// Maximum supported audio channel count; `None` = unconstrained.
73    pub max_channels: Option<u16>,
74    /// Lower numbers are preferred. HW impls should be ~10, SW impls ~100.
75    pub priority: i32,
76    /// Pixel formats this implementation accepts (video only). An empty
77    /// `Vec` means "any format" — resolution won't filter on it. When
78    /// populated, the registry can skip impls whose accepted set does not
79    /// include the format requested by the caller.
80    pub accepted_pixel_formats: Vec<PixelFormat>,
81    /// Audio sample rates (Hz) this implementation accepts as encoder
82    /// input (audio only). An empty `Vec` means "any rate". When
83    /// populated, a pipeline feeding the encoder is expected to resample
84    /// a stream whose rate is not listed to one of these rates (the
85    /// listed order is not a preference — consumers pick the closest
86    /// rate that does not discard bandwidth).
87    pub accepted_sample_rates: Vec<u32>,
88    /// Audio sample formats this implementation accepts as encoder input
89    /// (audio only). An empty `Vec` means "any format". When populated,
90    /// the first entry is the preferred conversion target for a stream
91    /// whose format is not listed.
92    pub accepted_sample_formats: Vec<SampleFormat>,
93}
94
95impl CodecCapabilities {
96    /// Construct a software audio decoder/encoder capability set with sensible
97    /// defaults — adjust fields after creation.
98    pub fn audio(implementation: impl Into<String>) -> Self {
99        Self {
100            decode: false,
101            encode: false,
102            media_type: MediaType::Audio,
103            intra_only: true, // audio packets are independently decodable in most codecs
104            lossy: false,
105            lossless: false,
106            hardware_accelerated: false,
107            implementation: implementation.into(),
108            max_width: None,
109            max_height: None,
110            max_bitrate: None,
111            max_sample_rate: None,
112            max_channels: None,
113            priority: DEFAULT_PRIORITY,
114            accepted_pixel_formats: Vec::new(),
115            accepted_sample_rates: Vec::new(),
116            accepted_sample_formats: Vec::new(),
117        }
118    }
119
120    /// Construct a software video decoder/encoder capability set with
121    /// sensible defaults — adjust fields after creation.
122    pub fn video(implementation: impl Into<String>) -> Self {
123        Self {
124            decode: false,
125            encode: false,
126            media_type: MediaType::Video,
127            intra_only: false,
128            lossy: false,
129            lossless: false,
130            hardware_accelerated: false,
131            implementation: implementation.into(),
132            max_width: None,
133            max_height: None,
134            max_bitrate: None,
135            max_sample_rate: None,
136            max_channels: None,
137            priority: DEFAULT_PRIORITY,
138            accepted_pixel_formats: Vec::new(),
139            accepted_sample_rates: Vec::new(),
140            accepted_sample_formats: Vec::new(),
141        }
142    }
143
144    /// Construct a software subtitle decoder/encoder capability set with
145    /// sensible defaults (intra-only, lossless) — adjust with the
146    /// `with_*` builders.
147    pub fn subtitle(implementation: impl Into<String>) -> Self {
148        let mut caps = Self::audio(implementation);
149        caps.media_type = MediaType::Subtitle;
150        caps.intra_only = true;
151        caps.lossless = true;
152        caps
153    }
154
155    /// Override the media type (for capability sets built from the
156    /// closest constructor, e.g. data or attachment streams).
157    pub fn with_media_type(mut self, media_type: MediaType) -> Self {
158        self.media_type = media_type;
159        self
160    }
161
162    /// 6-character capability flag string (see the module docs for the
163    /// column layout). Useful for `oxideav list`-style
164    /// output.
165    pub fn flag_string(&self) -> String {
166        let mut s = String::with_capacity(6);
167        s.push(if self.decode { 'D' } else { '.' });
168        s.push(if self.encode { 'E' } else { '.' });
169        s.push(match self.media_type {
170            MediaType::Video => 'V',
171            MediaType::Audio => 'A',
172            MediaType::Subtitle => 'S',
173            MediaType::Data => 'D',
174            MediaType::Unknown => '.',
175        });
176        s.push(if self.intra_only { 'I' } else { '.' });
177        s.push(if self.lossy { 'L' } else { '.' });
178        s.push(if self.lossless { 'S' } else { '.' });
179        s
180    }
181
182    // Builder-style helpers so registrations stay compact.
183
184    /// Mark this implementation as supporting decode.
185    pub fn with_decode(mut self) -> Self {
186        self.decode = true;
187        self
188    }
189    /// Mark this implementation as supporting encode.
190    pub fn with_encode(mut self) -> Self {
191        self.encode = true;
192        self
193    }
194    /// Set the intra-frame-only flag.
195    pub fn with_intra_only(mut self, v: bool) -> Self {
196        self.intra_only = v;
197        self
198    }
199    /// Set the lossy-compression flag.
200    pub fn with_lossy(mut self, v: bool) -> Self {
201        self.lossy = v;
202        self
203    }
204    /// Set the lossless-compression flag.
205    pub fn with_lossless(mut self, v: bool) -> Self {
206        self.lossless = v;
207        self
208    }
209    /// Set the hardware-accelerated flag.
210    pub fn with_hardware(mut self, v: bool) -> Self {
211        self.hardware_accelerated = v;
212        self
213    }
214    /// Set the registry ranking priority (lower is preferred; HW ~10,
215    /// SW ~100).
216    pub fn with_priority(mut self, p: i32) -> Self {
217        self.priority = p;
218        self
219    }
220    /// Constrain the maximum frame size to `w` × `h` pixels.
221    pub fn with_max_size(mut self, w: u32, h: u32) -> Self {
222        self.max_width = Some(w);
223        self.max_height = Some(h);
224        self
225    }
226    /// Constrain the maximum bit rate (bits per second).
227    pub fn with_max_bitrate(mut self, br: u64) -> Self {
228        self.max_bitrate = Some(br);
229        self
230    }
231    /// Constrain the maximum audio sample rate (Hz).
232    pub fn with_max_sample_rate(mut self, sr: u32) -> Self {
233        self.max_sample_rate = Some(sr);
234        self
235    }
236    /// Constrain the maximum audio channel count.
237    pub fn with_max_channels(mut self, ch: u16) -> Self {
238        self.max_channels = Some(ch);
239        self
240    }
241
242    /// Add one accepted pixel format. Appends — call multiple times to
243    /// list several.
244    pub fn with_pixel_format(mut self, fmt: PixelFormat) -> Self {
245        self.accepted_pixel_formats.push(fmt);
246        self
247    }
248
249    /// Replace the accepted pixel-format set wholesale.
250    pub fn with_pixel_formats(mut self, fmts: Vec<PixelFormat>) -> Self {
251        self.accepted_pixel_formats = fmts;
252        self
253    }
254
255    /// Replace the accepted encoder-input sample-rate set wholesale
256    /// (see [`Self::accepted_sample_rates`]).
257    pub fn with_sample_rates(mut self, rates: Vec<u32>) -> Self {
258        self.accepted_sample_rates = rates;
259        self
260    }
261
262    /// Replace the accepted encoder-input sample-format set wholesale
263    /// (see [`Self::accepted_sample_formats`]; the first entry is the
264    /// preferred conversion target).
265    pub fn with_sample_formats(mut self, fmts: Vec<SampleFormat>) -> Self {
266        self.accepted_sample_formats = fmts;
267        self
268    }
269
270    /// Pick the encoder-input sample rate for a stream at `rate` Hz:
271    /// `rate` itself when it is accepted (or every rate is), otherwise
272    /// the smallest accepted rate above it (no bandwidth is lost), and
273    /// failing that the largest accepted rate.
274    pub fn pick_sample_rate(&self, rate: u32) -> u32 {
275        if self.accepted_sample_rates.is_empty() || self.accepted_sample_rates.contains(&rate) {
276            return rate;
277        }
278        let above = self
279            .accepted_sample_rates
280            .iter()
281            .copied()
282            .filter(|&r| r > rate)
283            .min();
284        above.unwrap_or_else(|| {
285            self.accepted_sample_rates
286                .iter()
287                .copied()
288                .max()
289                .unwrap_or(rate)
290        })
291    }
292}
293
294impl fmt::Display for CodecCapabilities {
295    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296        write!(f, "{} {}", self.flag_string(), self.implementation)
297    }
298}
299
300impl CodecCapabilities {
301    /// Whether this implementation's max-* restrictions are compatible
302    /// with the requested codec parameters. `for_encode` is reserved
303    /// for restrictions that apply asymmetrically. Used by the
304    /// registry's `make_decoder` / `make_encoder` walker and by
305    /// out-of-tree selection layers (e.g. `oxideav-pipeline`'s
306    /// `CodecPreferences` filter).
307    pub fn fits_params(&self, p: &crate::CodecParameters, for_encode: bool) -> bool {
308        let _ = for_encode;
309        if let (Some(max), Some(w)) = (self.max_width, p.width) {
310            if w > max {
311                return false;
312            }
313        }
314        if let (Some(max), Some(h)) = (self.max_height, p.height) {
315            if h > max {
316                return false;
317            }
318        }
319        if let (Some(max), Some(br)) = (self.max_bitrate, p.bit_rate) {
320            if br > max {
321                return false;
322            }
323        }
324        if let (Some(max), Some(sr)) = (self.max_sample_rate, p.sample_rate) {
325            if sr > max {
326                return false;
327            }
328        }
329        if let (Some(max), Some(ch)) = (self.max_channels, p.channels) {
330            if ch > max {
331                return false;
332            }
333        }
334        true
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::*;
341
342    #[test]
343    fn pick_sample_rate_prefers_exact_then_next_above_then_max() {
344        let any = CodecCapabilities::audio("any");
345        assert_eq!(any.pick_sample_rate(44_100), 44_100);
346        let opus = CodecCapabilities::audio("opus")
347            .with_sample_rates(vec![8_000, 12_000, 16_000, 24_000, 48_000]);
348        assert_eq!(opus.pick_sample_rate(16_000), 16_000);
349        assert_eq!(opus.pick_sample_rate(44_100), 48_000);
350        assert_eq!(opus.pick_sample_rate(11_025), 12_000);
351        assert_eq!(opus.pick_sample_rate(96_000), 48_000);
352    }
353
354    #[test]
355    fn sample_format_builder_replaces_set() {
356        let c = CodecCapabilities::audio("x")
357            .with_sample_formats(vec![SampleFormat::S16, SampleFormat::F32]);
358        assert_eq!(
359            c.accepted_sample_formats,
360            vec![SampleFormat::S16, SampleFormat::F32]
361        );
362    }
363}