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}