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};
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`.
28pub const DEFAULT_PRIORITY: i32 = 100;
29
30/// What an implementation can do plus how it ranks vs alternatives.
31#[derive(Clone, Debug)]
32pub struct CodecCapabilities {
33 /// Decoding supported by this implementation.
34 pub decode: bool,
35 /// Encoding supported by this implementation.
36 pub encode: bool,
37 /// Media type this implementation handles (audio, video, ...).
38 pub media_type: MediaType,
39 /// Every coded unit is independently decodable (no inter-frame
40 /// prediction).
41 pub intra_only: bool,
42 /// Supports lossy compression.
43 pub lossy: bool,
44 /// Supports lossless compression. `lossy` and `lossless` may both
45 /// be set for codecs that offer both modes.
46 pub lossless: bool,
47 /// Hardware-accelerated implementation (VAAPI/NVENC/QSV/VideoToolbox/...).
48 pub hardware_accelerated: bool,
49 /// Short identifier for this implementation, e.g. "flac_sw", "h264_qsv".
50 pub implementation: String,
51 /// Restrictions — `None` means "no constraint".
52 pub max_width: Option<u32>,
53 /// Maximum supported frame height in pixels; `None` = unconstrained.
54 pub max_height: Option<u32>,
55 /// Maximum supported bit rate in bits per second; `None` =
56 /// unconstrained.
57 pub max_bitrate: Option<u64>,
58 /// Maximum supported audio sample rate in Hz; `None` = unconstrained.
59 pub max_sample_rate: Option<u32>,
60 /// Maximum supported audio channel count; `None` = unconstrained.
61 pub max_channels: Option<u16>,
62 /// Lower numbers are preferred. HW impls should be ~10, SW impls ~100.
63 pub priority: i32,
64 /// Pixel formats this implementation accepts (video only). An empty
65 /// `Vec` means "any format" — resolution won't filter on it. When
66 /// populated, the registry can skip impls whose accepted set does not
67 /// include the format requested by the caller.
68 pub accepted_pixel_formats: Vec<PixelFormat>,
69}
70
71impl CodecCapabilities {
72 /// Construct a software audio decoder/encoder capability set with sensible
73 /// defaults — adjust fields after creation.
74 pub fn audio(implementation: impl Into<String>) -> Self {
75 Self {
76 decode: false,
77 encode: false,
78 media_type: MediaType::Audio,
79 intra_only: true, // audio packets are independently decodable in most codecs
80 lossy: false,
81 lossless: false,
82 hardware_accelerated: false,
83 implementation: implementation.into(),
84 max_width: None,
85 max_height: None,
86 max_bitrate: None,
87 max_sample_rate: None,
88 max_channels: None,
89 priority: DEFAULT_PRIORITY,
90 accepted_pixel_formats: Vec::new(),
91 }
92 }
93
94 /// Construct a software video decoder/encoder capability set with
95 /// sensible defaults — adjust fields after creation.
96 pub fn video(implementation: impl Into<String>) -> Self {
97 Self {
98 decode: false,
99 encode: false,
100 media_type: MediaType::Video,
101 intra_only: false,
102 lossy: false,
103 lossless: false,
104 hardware_accelerated: false,
105 implementation: implementation.into(),
106 max_width: None,
107 max_height: None,
108 max_bitrate: None,
109 max_sample_rate: None,
110 max_channels: None,
111 priority: DEFAULT_PRIORITY,
112 accepted_pixel_formats: Vec::new(),
113 }
114 }
115
116 /// 6-character capability flag string (see the module docs for the
117 /// column layout). Useful for `oxideav list`-style
118 /// output.
119 pub fn flag_string(&self) -> String {
120 let mut s = String::with_capacity(6);
121 s.push(if self.decode { 'D' } else { '.' });
122 s.push(if self.encode { 'E' } else { '.' });
123 s.push(match self.media_type {
124 MediaType::Video => 'V',
125 MediaType::Audio => 'A',
126 MediaType::Subtitle => 'S',
127 MediaType::Data => 'D',
128 MediaType::Unknown => '.',
129 });
130 s.push(if self.intra_only { 'I' } else { '.' });
131 s.push(if self.lossy { 'L' } else { '.' });
132 s.push(if self.lossless { 'S' } else { '.' });
133 s
134 }
135
136 // Builder-style helpers so registrations stay compact.
137
138 /// Mark this implementation as supporting decode.
139 pub fn with_decode(mut self) -> Self {
140 self.decode = true;
141 self
142 }
143 /// Mark this implementation as supporting encode.
144 pub fn with_encode(mut self) -> Self {
145 self.encode = true;
146 self
147 }
148 /// Set the intra-frame-only flag.
149 pub fn with_intra_only(mut self, v: bool) -> Self {
150 self.intra_only = v;
151 self
152 }
153 /// Set the lossy-compression flag.
154 pub fn with_lossy(mut self, v: bool) -> Self {
155 self.lossy = v;
156 self
157 }
158 /// Set the lossless-compression flag.
159 pub fn with_lossless(mut self, v: bool) -> Self {
160 self.lossless = v;
161 self
162 }
163 /// Set the hardware-accelerated flag.
164 pub fn with_hardware(mut self, v: bool) -> Self {
165 self.hardware_accelerated = v;
166 self
167 }
168 /// Set the registry ranking priority (lower is preferred; HW ~10,
169 /// SW ~100).
170 pub fn with_priority(mut self, p: i32) -> Self {
171 self.priority = p;
172 self
173 }
174 /// Constrain the maximum frame size to `w` × `h` pixels.
175 pub fn with_max_size(mut self, w: u32, h: u32) -> Self {
176 self.max_width = Some(w);
177 self.max_height = Some(h);
178 self
179 }
180 /// Constrain the maximum bit rate (bits per second).
181 pub fn with_max_bitrate(mut self, br: u64) -> Self {
182 self.max_bitrate = Some(br);
183 self
184 }
185 /// Constrain the maximum audio sample rate (Hz).
186 pub fn with_max_sample_rate(mut self, sr: u32) -> Self {
187 self.max_sample_rate = Some(sr);
188 self
189 }
190 /// Constrain the maximum audio channel count.
191 pub fn with_max_channels(mut self, ch: u16) -> Self {
192 self.max_channels = Some(ch);
193 self
194 }
195
196 /// Add one accepted pixel format. Appends — call multiple times to
197 /// list several.
198 pub fn with_pixel_format(mut self, fmt: PixelFormat) -> Self {
199 self.accepted_pixel_formats.push(fmt);
200 self
201 }
202
203 /// Replace the accepted pixel-format set wholesale.
204 pub fn with_pixel_formats(mut self, fmts: Vec<PixelFormat>) -> Self {
205 self.accepted_pixel_formats = fmts;
206 self
207 }
208}
209
210impl fmt::Display for CodecCapabilities {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 write!(f, "{} {}", self.flag_string(), self.implementation)
213 }
214}
215
216impl CodecCapabilities {
217 /// Whether this implementation's max-* restrictions are compatible
218 /// with the requested codec parameters. `for_encode` is reserved
219 /// for restrictions that apply asymmetrically. Used by the
220 /// registry's `make_decoder` / `make_encoder` walker and by
221 /// out-of-tree selection layers (e.g. `oxideav-pipeline`'s
222 /// `CodecPreferences` filter).
223 pub fn fits_params(&self, p: &crate::CodecParameters, for_encode: bool) -> bool {
224 let _ = for_encode;
225 if let (Some(max), Some(w)) = (self.max_width, p.width) {
226 if w > max {
227 return false;
228 }
229 }
230 if let (Some(max), Some(h)) = (self.max_height, p.height) {
231 if h > max {
232 return false;
233 }
234 }
235 if let (Some(max), Some(br)) = (self.max_bitrate, p.bit_rate) {
236 if br > max {
237 return false;
238 }
239 }
240 if let (Some(max), Some(sr)) = (self.max_sample_rate, p.sample_rate) {
241 if sr > max {
242 return false;
243 }
244 }
245 if let (Some(max), Some(ch)) = (self.max_channels, p.channels) {
246 if ch > max {
247 return false;
248 }
249 }
250 true
251 }
252}