Skip to main content

oxideav_codec/
registry.rs

1//! In-process codec registry — supports multiple implementations per codec
2//! id, ranked by capability + priority + user preferences with init-time
3//! fallback.
4
5use std::collections::HashMap;
6
7use oxideav_core::{CodecCapabilities, CodecId, CodecParameters, CodecPreferences, Error, Result};
8
9use crate::{Decoder, DecoderFactory, Encoder, EncoderFactory};
10
11/// One registered implementation: capability description + factories.
12/// Either / both factories may be present depending on whether the impl
13/// can decode, encode, or both.
14#[derive(Clone)]
15pub struct CodecImplementation {
16    pub caps: CodecCapabilities,
17    pub make_decoder: Option<DecoderFactory>,
18    pub make_encoder: Option<EncoderFactory>,
19}
20
21#[derive(Default)]
22pub struct CodecRegistry {
23    impls: HashMap<CodecId, Vec<CodecImplementation>>,
24}
25
26impl CodecRegistry {
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Register a codec implementation. The same codec id may be registered
32    /// multiple times — for example a software FLAC decoder and (later) a
33    /// hardware one would both register under id `"flac"`.
34    pub fn register(&mut self, id: CodecId, implementation: CodecImplementation) {
35        self.impls.entry(id).or_default().push(implementation);
36    }
37
38    /// Convenience: register a decoder-only implementation built from a
39    /// caps + factory pair.
40    pub fn register_decoder_impl(
41        &mut self,
42        id: CodecId,
43        caps: CodecCapabilities,
44        factory: DecoderFactory,
45    ) {
46        self.register(
47            id,
48            CodecImplementation {
49                caps: caps.with_decode(),
50                make_decoder: Some(factory),
51                make_encoder: None,
52            },
53        );
54    }
55
56    /// Convenience: register an encoder-only implementation.
57    pub fn register_encoder_impl(
58        &mut self,
59        id: CodecId,
60        caps: CodecCapabilities,
61        factory: EncoderFactory,
62    ) {
63        self.register(
64            id,
65            CodecImplementation {
66                caps: caps.with_encode(),
67                make_decoder: None,
68                make_encoder: Some(factory),
69            },
70        );
71    }
72
73    /// Convenience: register a single implementation that handles both
74    /// decode and encode for a codec id.
75    pub fn register_both(
76        &mut self,
77        id: CodecId,
78        caps: CodecCapabilities,
79        decode: DecoderFactory,
80        encode: EncoderFactory,
81    ) {
82        self.register(
83            id,
84            CodecImplementation {
85                caps: caps.with_decode().with_encode(),
86                make_decoder: Some(decode),
87                make_encoder: Some(encode),
88            },
89        );
90    }
91
92    /// Backwards-compat shim: register a decoder-only impl with default
93    /// software capabilities. Prefer `register_decoder_impl` for new code.
94    pub fn register_decoder(&mut self, id: CodecId, factory: DecoderFactory) {
95        let caps = CodecCapabilities::audio(id.as_str()).with_decode();
96        self.register_decoder_impl(id, caps, factory);
97    }
98
99    /// Backwards-compat shim: register an encoder-only impl with default
100    /// software capabilities.
101    pub fn register_encoder(&mut self, id: CodecId, factory: EncoderFactory) {
102        let caps = CodecCapabilities::audio(id.as_str()).with_encode();
103        self.register_encoder_impl(id, caps, factory);
104    }
105
106    pub fn has_decoder(&self, id: &CodecId) -> bool {
107        self.impls
108            .get(id)
109            .map(|v| v.iter().any(|i| i.make_decoder.is_some()))
110            .unwrap_or(false)
111    }
112
113    pub fn has_encoder(&self, id: &CodecId) -> bool {
114        self.impls
115            .get(id)
116            .map(|v| v.iter().any(|i| i.make_encoder.is_some()))
117            .unwrap_or(false)
118    }
119
120    /// Build a decoder for `params`. Walks all implementations matching the
121    /// codec id in increasing priority order, skipping any excluded by the
122    /// caller's preferences. Init-time fallback: if a higher-priority impl's
123    /// constructor returns an error, the next candidate is tried.
124    pub fn make_decoder_with(
125        &self,
126        params: &CodecParameters,
127        prefs: &CodecPreferences,
128    ) -> Result<Box<dyn Decoder>> {
129        let candidates = self
130            .impls
131            .get(&params.codec_id)
132            .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
133        let mut ranked: Vec<&CodecImplementation> = candidates
134            .iter()
135            .filter(|i| i.make_decoder.is_some() && !prefs.excludes(&i.caps))
136            .filter(|i| caps_fit_params(&i.caps, params, false))
137            .collect();
138        ranked.sort_by_key(|i| prefs.effective_priority(&i.caps));
139        let mut last_err: Option<Error> = None;
140        for imp in ranked {
141            match (imp.make_decoder.unwrap())(params) {
142                Ok(d) => return Ok(d),
143                Err(e) => last_err = Some(e),
144            }
145        }
146        Err(last_err.unwrap_or_else(|| {
147            Error::CodecNotFound(format!(
148                "no decoder for {} accepts the requested parameters",
149                params.codec_id
150            ))
151        }))
152    }
153
154    /// Build an encoder, with the same priority + fallback semantics.
155    pub fn make_encoder_with(
156        &self,
157        params: &CodecParameters,
158        prefs: &CodecPreferences,
159    ) -> Result<Box<dyn Encoder>> {
160        let candidates = self
161            .impls
162            .get(&params.codec_id)
163            .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
164        let mut ranked: Vec<&CodecImplementation> = candidates
165            .iter()
166            .filter(|i| i.make_encoder.is_some() && !prefs.excludes(&i.caps))
167            .filter(|i| caps_fit_params(&i.caps, params, true))
168            .collect();
169        ranked.sort_by_key(|i| prefs.effective_priority(&i.caps));
170        let mut last_err: Option<Error> = None;
171        for imp in ranked {
172            match (imp.make_encoder.unwrap())(params) {
173                Ok(e) => return Ok(e),
174                Err(e) => last_err = Some(e),
175            }
176        }
177        Err(last_err.unwrap_or_else(|| {
178            Error::CodecNotFound(format!(
179                "no encoder for {} accepts the requested parameters",
180                params.codec_id
181            ))
182        }))
183    }
184
185    /// Default-preference shorthand for `make_decoder_with`.
186    pub fn make_decoder(&self, params: &CodecParameters) -> Result<Box<dyn Decoder>> {
187        self.make_decoder_with(params, &CodecPreferences::default())
188    }
189
190    /// Default-preference shorthand for `make_encoder_with`.
191    pub fn make_encoder(&self, params: &CodecParameters) -> Result<Box<dyn Encoder>> {
192        self.make_encoder_with(params, &CodecPreferences::default())
193    }
194
195    /// Iterate codec ids that have at least one decoder implementation.
196    pub fn decoder_ids(&self) -> impl Iterator<Item = &CodecId> {
197        self.impls
198            .iter()
199            .filter(|(_, v)| v.iter().any(|i| i.make_decoder.is_some()))
200            .map(|(id, _)| id)
201    }
202
203    pub fn encoder_ids(&self) -> impl Iterator<Item = &CodecId> {
204        self.impls
205            .iter()
206            .filter(|(_, v)| v.iter().any(|i| i.make_encoder.is_some()))
207            .map(|(id, _)| id)
208    }
209
210    /// All registered implementations of a given codec id.
211    pub fn implementations(&self, id: &CodecId) -> &[CodecImplementation] {
212        self.impls.get(id).map(|v| v.as_slice()).unwrap_or(&[])
213    }
214
215    /// Iterator over every (codec_id, impl) pair — useful for `oxideav list`
216    /// to show capability flags per implementation.
217    pub fn all_implementations(&self) -> impl Iterator<Item = (&CodecId, &CodecImplementation)> {
218        self.impls
219            .iter()
220            .flat_map(|(id, v)| v.iter().map(move |i| (id, i)))
221    }
222}
223
224/// Check whether an implementation's restrictions are compatible with the
225/// requested codec parameters. `for_encode` swaps the rare cases where a
226/// restriction only applies one way.
227fn caps_fit_params(caps: &CodecCapabilities, p: &CodecParameters, for_encode: bool) -> bool {
228    let _ = for_encode; // reserved for future use (e.g. encode-only bitrate caps)
229    if let (Some(max), Some(w)) = (caps.max_width, p.width) {
230        if w > max {
231            return false;
232        }
233    }
234    if let (Some(max), Some(h)) = (caps.max_height, p.height) {
235        if h > max {
236            return false;
237        }
238    }
239    if let (Some(max), Some(br)) = (caps.max_bitrate, p.bit_rate) {
240        if br > max {
241            return false;
242        }
243    }
244    if let (Some(max), Some(sr)) = (caps.max_sample_rate, p.sample_rate) {
245        if sr > max {
246            return false;
247        }
248    }
249    if let (Some(max), Some(ch)) = (caps.max_channels, p.channels) {
250        if ch > max {
251            return false;
252        }
253    }
254    true
255}