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