1use std::collections::HashMap;
6
7use oxideav_core::{
8 CodecCapabilities, CodecId, CodecParameters, CodecPreferences, CodecTag, Error, Result,
9};
10
11use crate::{Decoder, DecoderFactory, Encoder, EncoderFactory};
12
13pub type CodecProbe = fn(&[u8]) -> bool;
19
20#[derive(Clone, Copy)]
24pub struct TagClaim {
25 pub priority: u8,
27 pub probe: Option<CodecProbe>,
30}
31
32#[derive(Clone)]
36pub struct CodecImplementation {
37 pub caps: CodecCapabilities,
38 pub make_decoder: Option<DecoderFactory>,
39 pub make_encoder: Option<EncoderFactory>,
40}
41
42#[derive(Default)]
43pub struct CodecRegistry {
44 impls: HashMap<CodecId, Vec<CodecImplementation>>,
45 tag_claims: HashMap<CodecTag, Vec<(CodecId, TagClaim)>>,
51}
52
53impl CodecRegistry {
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn register(&mut self, id: CodecId, implementation: CodecImplementation) {
62 self.impls.entry(id).or_default().push(implementation);
63 }
64
65 pub fn register_decoder_impl(
68 &mut self,
69 id: CodecId,
70 caps: CodecCapabilities,
71 factory: DecoderFactory,
72 ) {
73 self.register(
74 id,
75 CodecImplementation {
76 caps: caps.with_decode(),
77 make_decoder: Some(factory),
78 make_encoder: None,
79 },
80 );
81 }
82
83 pub fn register_encoder_impl(
85 &mut self,
86 id: CodecId,
87 caps: CodecCapabilities,
88 factory: EncoderFactory,
89 ) {
90 self.register(
91 id,
92 CodecImplementation {
93 caps: caps.with_encode(),
94 make_decoder: None,
95 make_encoder: Some(factory),
96 },
97 );
98 }
99
100 pub fn register_both(
103 &mut self,
104 id: CodecId,
105 caps: CodecCapabilities,
106 decode: DecoderFactory,
107 encode: EncoderFactory,
108 ) {
109 self.register(
110 id,
111 CodecImplementation {
112 caps: caps.with_decode().with_encode(),
113 make_decoder: Some(decode),
114 make_encoder: Some(encode),
115 },
116 );
117 }
118
119 pub fn register_decoder(&mut self, id: CodecId, factory: DecoderFactory) {
122 let caps = CodecCapabilities::audio(id.as_str()).with_decode();
123 self.register_decoder_impl(id, caps, factory);
124 }
125
126 pub fn register_encoder(&mut self, id: CodecId, factory: EncoderFactory) {
129 let caps = CodecCapabilities::audio(id.as_str()).with_encode();
130 self.register_encoder_impl(id, caps, factory);
131 }
132
133 pub fn has_decoder(&self, id: &CodecId) -> bool {
134 self.impls
135 .get(id)
136 .map(|v| v.iter().any(|i| i.make_decoder.is_some()))
137 .unwrap_or(false)
138 }
139
140 pub fn has_encoder(&self, id: &CodecId) -> bool {
141 self.impls
142 .get(id)
143 .map(|v| v.iter().any(|i| i.make_encoder.is_some()))
144 .unwrap_or(false)
145 }
146
147 pub fn make_decoder_with(
152 &self,
153 params: &CodecParameters,
154 prefs: &CodecPreferences,
155 ) -> Result<Box<dyn Decoder>> {
156 let candidates = self
157 .impls
158 .get(¶ms.codec_id)
159 .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
160 let mut ranked: Vec<&CodecImplementation> = candidates
161 .iter()
162 .filter(|i| i.make_decoder.is_some() && !prefs.excludes(&i.caps))
163 .filter(|i| caps_fit_params(&i.caps, params, false))
164 .collect();
165 ranked.sort_by_key(|i| prefs.effective_priority(&i.caps));
166 let mut last_err: Option<Error> = None;
167 for imp in ranked {
168 match (imp.make_decoder.unwrap())(params) {
169 Ok(d) => return Ok(d),
170 Err(e) => last_err = Some(e),
171 }
172 }
173 Err(last_err.unwrap_or_else(|| {
174 Error::CodecNotFound(format!(
175 "no decoder for {} accepts the requested parameters",
176 params.codec_id
177 ))
178 }))
179 }
180
181 pub fn make_encoder_with(
183 &self,
184 params: &CodecParameters,
185 prefs: &CodecPreferences,
186 ) -> Result<Box<dyn Encoder>> {
187 let candidates = self
188 .impls
189 .get(¶ms.codec_id)
190 .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
191 let mut ranked: Vec<&CodecImplementation> = candidates
192 .iter()
193 .filter(|i| i.make_encoder.is_some() && !prefs.excludes(&i.caps))
194 .filter(|i| caps_fit_params(&i.caps, params, true))
195 .collect();
196 ranked.sort_by_key(|i| prefs.effective_priority(&i.caps));
197 let mut last_err: Option<Error> = None;
198 for imp in ranked {
199 match (imp.make_encoder.unwrap())(params) {
200 Ok(e) => return Ok(e),
201 Err(e) => last_err = Some(e),
202 }
203 }
204 Err(last_err.unwrap_or_else(|| {
205 Error::CodecNotFound(format!(
206 "no encoder for {} accepts the requested parameters",
207 params.codec_id
208 ))
209 }))
210 }
211
212 pub fn make_decoder(&self, params: &CodecParameters) -> Result<Box<dyn Decoder>> {
214 self.make_decoder_with(params, &CodecPreferences::default())
215 }
216
217 pub fn make_encoder(&self, params: &CodecParameters) -> Result<Box<dyn Encoder>> {
219 self.make_encoder_with(params, &CodecPreferences::default())
220 }
221
222 pub fn decoder_ids(&self) -> impl Iterator<Item = &CodecId> {
224 self.impls
225 .iter()
226 .filter(|(_, v)| v.iter().any(|i| i.make_decoder.is_some()))
227 .map(|(id, _)| id)
228 }
229
230 pub fn encoder_ids(&self) -> impl Iterator<Item = &CodecId> {
231 self.impls
232 .iter()
233 .filter(|(_, v)| v.iter().any(|i| i.make_encoder.is_some()))
234 .map(|(id, _)| id)
235 }
236
237 pub fn implementations(&self, id: &CodecId) -> &[CodecImplementation] {
239 self.impls.get(id).map(|v| v.as_slice()).unwrap_or(&[])
240 }
241
242 pub fn all_implementations(&self) -> impl Iterator<Item = (&CodecId, &CodecImplementation)> {
245 self.impls
246 .iter()
247 .flat_map(|(id, v)| v.iter().map(move |i| (id, i)))
248 }
249
250 pub fn claim_tag(
265 &mut self,
266 id: CodecId,
267 tag: CodecTag,
268 priority: u8,
269 probe: Option<CodecProbe>,
270 ) {
271 let entry = self.tag_claims.entry(tag).or_default();
272 entry.push((id, TagClaim { priority, probe }));
273 entry.sort_by_key(|(_, claim)| std::cmp::Reverse(claim.priority));
277 }
278
279 pub fn resolve_tag(&self, tag: &CodecTag, probe_data: Option<&[u8]>) -> Option<&CodecId> {
303 let claims = self.tag_claims.get(tag)?;
304 for (id, claim) in claims {
305 match (claim.probe, probe_data) {
306 (None, _) => return Some(id),
307 (Some(_), None) => return Some(id),
308 (Some(p), Some(d)) => {
309 if p(d) {
310 return Some(id);
311 }
312 }
313 }
314 }
315 None
316 }
317
318 pub fn claims_for_tag(&self, tag: &CodecTag) -> &[(CodecId, TagClaim)] {
322 self.tag_claims
323 .get(tag)
324 .map(|v| v.as_slice())
325 .unwrap_or(&[])
326 }
327
328 pub fn all_tag_claims(&self) -> impl Iterator<Item = (&CodecTag, &CodecId, &TagClaim)> {
332 self.tag_claims
333 .iter()
334 .flat_map(|(tag, claims)| claims.iter().map(move |(id, c)| (tag, id, c)))
335 }
336}
337
338fn caps_fit_params(caps: &CodecCapabilities, p: &CodecParameters, for_encode: bool) -> bool {
342 let _ = for_encode; if let (Some(max), Some(w)) = (caps.max_width, p.width) {
344 if w > max {
345 return false;
346 }
347 }
348 if let (Some(max), Some(h)) = (caps.max_height, p.height) {
349 if h > max {
350 return false;
351 }
352 }
353 if let (Some(max), Some(br)) = (caps.max_bitrate, p.bit_rate) {
354 if br > max {
355 return false;
356 }
357 }
358 if let (Some(max), Some(sr)) = (caps.max_sample_rate, p.sample_rate) {
359 if sr > max {
360 return false;
361 }
362 }
363 if let (Some(max), Some(ch)) = (caps.max_channels, p.channels) {
364 if ch > max {
365 return false;
366 }
367 }
368 true
369}
370
371#[cfg(test)]
372mod tag_tests {
373 use super::*;
374 use oxideav_core::CodecCapabilities;
375
376 fn looks_like_msmpeg4(data: &[u8]) -> bool {
377 !data.windows(3).take(6).any(|w| w == [0x00, 0x00, 0x01])
379 }
380
381 fn looks_like_mpeg4_part2(data: &[u8]) -> bool {
382 data.windows(3).take(6).any(|w| w == [0x00, 0x00, 0x01])
383 }
384
385 #[test]
386 fn resolve_single_claim_no_probe() {
387 let mut reg = CodecRegistry::new();
388 reg.claim_tag(CodecId::new("flac"), CodecTag::fourcc(b"FLAC"), 10, None);
389 assert_eq!(
390 reg.resolve_tag(&CodecTag::fourcc(b"FLAC"), None)
391 .map(|c| c.as_str()),
392 Some("flac"),
393 );
394 }
395
396 #[test]
397 fn resolve_missing_tag_returns_none() {
398 let reg = CodecRegistry::new();
399 assert!(reg.resolve_tag(&CodecTag::fourcc(b"????"), None).is_none());
400 }
401
402 #[test]
403 fn priority_highest_wins() {
404 let mut reg = CodecRegistry::new();
405 reg.claim_tag(CodecId::new("low"), CodecTag::fourcc(b"TEST"), 1, None);
406 reg.claim_tag(CodecId::new("high"), CodecTag::fourcc(b"TEST"), 10, None);
407 reg.claim_tag(CodecId::new("mid"), CodecTag::fourcc(b"TEST"), 5, None);
408 assert_eq!(
409 reg.resolve_tag(&CodecTag::fourcc(b"TEST"), None)
410 .map(|c| c.as_str()),
411 Some("high"),
412 );
413 }
414
415 #[test]
416 fn probe_chooses_matching_bitstream() {
417 let mut reg = CodecRegistry::new();
422 reg.claim_tag(
423 CodecId::new("msmpeg4v3"),
424 CodecTag::fourcc(b"DIV3"),
425 10,
426 Some(looks_like_msmpeg4),
427 );
428 reg.claim_tag(
429 CodecId::new("mpeg4video"),
430 CodecTag::fourcc(b"DIV3"),
431 5,
432 Some(looks_like_mpeg4_part2),
433 );
434
435 let mpeg4_part2 = [0x00u8, 0x00, 0x01, 0xB0, 0x01, 0x00];
436 let ms_mpeg4 = [0x85u8, 0x3F, 0xD4, 0x80, 0x00, 0xA2];
437
438 assert_eq!(
439 reg.resolve_tag(&CodecTag::fourcc(b"DIV3"), Some(&mpeg4_part2))
440 .map(|c| c.as_str()),
441 Some("mpeg4video"),
442 );
443 assert_eq!(
444 reg.resolve_tag(&CodecTag::fourcc(b"DIV3"), Some(&ms_mpeg4))
445 .map(|c| c.as_str()),
446 Some("msmpeg4v3"),
447 );
448 }
449
450 #[test]
451 fn probed_claims_without_probe_data_fall_back_to_priority() {
452 let mut reg = CodecRegistry::new();
453 reg.claim_tag(
454 CodecId::new("msmpeg4v3"),
455 CodecTag::fourcc(b"DIV3"),
456 10,
457 Some(looks_like_msmpeg4),
458 );
459 reg.claim_tag(
460 CodecId::new("mpeg4video"),
461 CodecTag::fourcc(b"DIV3"),
462 5,
463 Some(looks_like_mpeg4_part2),
464 );
465 assert_eq!(
467 reg.resolve_tag(&CodecTag::fourcc(b"DIV3"), None)
468 .map(|c| c.as_str()),
469 Some("msmpeg4v3"),
470 );
471 }
472
473 #[test]
474 fn fallback_no_probe_catches_everything() {
475 let mut reg = CodecRegistry::new();
476 reg.claim_tag(
477 CodecId::new("picky"),
478 CodecTag::fourcc(b"MAYB"),
479 10,
480 Some(|_| false), );
482 reg.claim_tag(CodecId::new("fallback"), CodecTag::fourcc(b"MAYB"), 1, None);
483 assert_eq!(
484 reg.resolve_tag(&CodecTag::fourcc(b"MAYB"), Some(b"hello"))
485 .map(|c| c.as_str()),
486 Some("fallback"),
487 );
488 }
489
490 #[test]
491 fn claims_for_tag_returns_ordered_list() {
492 let mut reg = CodecRegistry::new();
493 reg.claim_tag(CodecId::new("a"), CodecTag::fourcc(b"XYZ1"), 1, None);
494 reg.claim_tag(CodecId::new("b"), CodecTag::fourcc(b"XYZ1"), 9, None);
495 reg.claim_tag(CodecId::new("c"), CodecTag::fourcc(b"XYZ1"), 5, None);
496 let claims: Vec<_> = reg
497 .claims_for_tag(&CodecTag::fourcc(b"XYZ1"))
498 .iter()
499 .map(|(id, c)| (id.as_str().to_string(), c.priority))
500 .collect();
501 assert_eq!(
502 claims,
503 vec![
504 ("b".to_string(), 9),
505 ("c".to_string(), 5),
506 ("a".to_string(), 1),
507 ],
508 );
509 }
510
511 #[test]
512 fn fourcc_case_insensitive_lookup() {
513 let mut reg = CodecRegistry::new();
514 reg.claim_tag(CodecId::new("vid"), CodecTag::fourcc(b"div3"), 10, None);
515 assert!(reg.resolve_tag(&CodecTag::fourcc(b"DIV3"), None).is_some());
518 assert!(reg.resolve_tag(&CodecTag::fourcc(b"div3"), None).is_some());
519 assert!(reg.resolve_tag(&CodecTag::fourcc(b"DiV3"), None).is_some());
520 }
521
522 #[test]
523 fn wave_format_and_matroska_tags_work() {
524 let mut reg = CodecRegistry::new();
525 reg.claim_tag(CodecId::new("mp3"), CodecTag::wave_format(0x0055), 10, None);
526 reg.claim_tag(
527 CodecId::new("h264"),
528 CodecTag::matroska("V_MPEG4/ISO/AVC"),
529 10,
530 None,
531 );
532 assert_eq!(
533 reg.resolve_tag(&CodecTag::wave_format(0x0055), None)
534 .map(|c| c.as_str()),
535 Some("mp3"),
536 );
537 assert_eq!(
538 reg.resolve_tag(&CodecTag::matroska("V_MPEG4/ISO/AVC"), None)
539 .map(|c| c.as_str()),
540 Some("h264"),
541 );
542 }
543
544 #[test]
545 fn mp4_object_type_tag_works() {
546 let mut reg = CodecRegistry::new();
547 reg.claim_tag(
549 CodecId::new("aac"),
550 CodecTag::mp4_object_type(0x40),
551 10,
552 None,
553 );
554 assert_eq!(
555 reg.resolve_tag(&CodecTag::mp4_object_type(0x40), None)
556 .map(|c| c.as_str()),
557 Some("aac"),
558 );
559 }
560
561 #[test]
562 fn suppress_unused_caps() {
563 let _ = CodecCapabilities::audio("dummy");
564 }
565}