1use std::collections::HashMap;
25
26use oxideav_core::{
27 CodecCapabilities, CodecId, CodecOptionsStruct, CodecParameters, CodecPreferences,
28 CodecResolver, CodecTag, Error, OptionField, ProbeContext, ProbeFn, Result,
29};
30
31use crate::{Decoder, DecoderFactory, Encoder, EncoderFactory};
32
33#[non_exhaustive]
43pub struct CodecInfo {
44 pub id: CodecId,
45 pub capabilities: CodecCapabilities,
46 pub decoder_factory: Option<DecoderFactory>,
47 pub encoder_factory: Option<EncoderFactory>,
48 pub probe: Option<ProbeFn>,
53 pub tags: Vec<CodecTag>,
57 pub encoder_options_schema: Option<&'static [OptionField]>,
62 pub decoder_options_schema: Option<&'static [OptionField]>,
64}
65
66impl CodecInfo {
67 pub fn new(id: CodecId) -> Self {
72 Self {
73 capabilities: CodecCapabilities::audio(id.as_str()),
74 id,
75 decoder_factory: None,
76 encoder_factory: None,
77 probe: None,
78 tags: Vec::new(),
79 encoder_options_schema: None,
80 decoder_options_schema: None,
81 }
82 }
83
84 pub fn capabilities(mut self, caps: CodecCapabilities) -> Self {
88 self.capabilities = caps;
89 self
90 }
91
92 pub fn decoder(mut self, factory: DecoderFactory) -> Self {
93 self.decoder_factory = Some(factory);
94 self
95 }
96
97 pub fn encoder(mut self, factory: EncoderFactory) -> Self {
98 self.encoder_factory = Some(factory);
99 self
100 }
101
102 pub fn probe(mut self, probe: ProbeFn) -> Self {
103 self.probe = Some(probe);
104 self
105 }
106
107 pub fn tag(mut self, tag: CodecTag) -> Self {
111 self.tags.push(tag);
112 self
113 }
114
115 pub fn tags(mut self, tags: impl IntoIterator<Item = CodecTag>) -> Self {
119 self.tags.extend(tags);
120 self
121 }
122
123 pub fn encoder_options<T: CodecOptionsStruct>(mut self) -> Self {
130 self.encoder_options_schema = Some(T::SCHEMA);
131 self
132 }
133
134 pub fn decoder_options<T: CodecOptionsStruct>(mut self) -> Self {
137 self.decoder_options_schema = Some(T::SCHEMA);
138 self
139 }
140}
141
142#[derive(Clone)]
146pub struct CodecImplementation {
147 pub caps: CodecCapabilities,
148 pub make_decoder: Option<DecoderFactory>,
149 pub make_encoder: Option<EncoderFactory>,
150 pub encoder_options_schema: Option<&'static [OptionField]>,
156 pub decoder_options_schema: Option<&'static [OptionField]>,
157}
158
159#[derive(Default)]
160pub struct CodecRegistry {
161 impls: HashMap<CodecId, Vec<CodecImplementation>>,
165 registrations: Vec<RegistrationRecord>,
168 tag_index: HashMap<CodecTag, Vec<usize>>,
172}
173
174struct RegistrationRecord {
177 id: CodecId,
178 probe: Option<ProbeFn>,
179}
180
181impl CodecRegistry {
182 pub fn new() -> Self {
183 Self::default()
184 }
185
186 pub fn register(&mut self, info: CodecInfo) {
196 let CodecInfo {
197 id,
198 capabilities,
199 decoder_factory,
200 encoder_factory,
201 probe,
202 tags,
203 encoder_options_schema,
204 decoder_options_schema,
205 } = info;
206
207 let caps = {
208 let mut c = capabilities;
209 if decoder_factory.is_some() {
210 c = c.with_decode();
211 }
212 if encoder_factory.is_some() {
213 c = c.with_encode();
214 }
215 c
216 };
217
218 if decoder_factory.is_some() || encoder_factory.is_some() {
223 self.impls
224 .entry(id.clone())
225 .or_default()
226 .push(CodecImplementation {
227 caps,
228 make_decoder: decoder_factory,
229 make_encoder: encoder_factory,
230 encoder_options_schema,
231 decoder_options_schema,
232 });
233 }
234
235 let record_idx = self.registrations.len();
236 self.registrations.push(RegistrationRecord {
237 id: id.clone(),
238 probe,
239 });
240 for tag in tags {
241 self.tag_index.entry(tag).or_default().push(record_idx);
242 }
243 }
244
245 pub fn has_decoder(&self, id: &CodecId) -> bool {
246 self.impls
247 .get(id)
248 .map(|v| v.iter().any(|i| i.make_decoder.is_some()))
249 .unwrap_or(false)
250 }
251
252 pub fn has_encoder(&self, id: &CodecId) -> bool {
253 self.impls
254 .get(id)
255 .map(|v| v.iter().any(|i| i.make_encoder.is_some()))
256 .unwrap_or(false)
257 }
258
259 pub fn make_decoder_with(
264 &self,
265 params: &CodecParameters,
266 prefs: &CodecPreferences,
267 ) -> Result<Box<dyn Decoder>> {
268 let candidates = self
269 .impls
270 .get(¶ms.codec_id)
271 .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
272 let mut ranked: Vec<&CodecImplementation> = candidates
273 .iter()
274 .filter(|i| i.make_decoder.is_some() && !prefs.excludes(&i.caps))
275 .filter(|i| caps_fit_params(&i.caps, params, false))
276 .collect();
277 ranked.sort_by_key(|i| prefs.effective_priority(&i.caps));
278 let mut last_err: Option<Error> = None;
279 for imp in ranked {
280 match (imp.make_decoder.unwrap())(params) {
281 Ok(d) => return Ok(d),
282 Err(e) => last_err = Some(e),
283 }
284 }
285 Err(last_err.unwrap_or_else(|| {
286 Error::CodecNotFound(format!(
287 "no decoder for {} accepts the requested parameters",
288 params.codec_id
289 ))
290 }))
291 }
292
293 pub fn make_encoder_with(
295 &self,
296 params: &CodecParameters,
297 prefs: &CodecPreferences,
298 ) -> Result<Box<dyn Encoder>> {
299 let candidates = self
300 .impls
301 .get(¶ms.codec_id)
302 .ok_or_else(|| Error::CodecNotFound(params.codec_id.to_string()))?;
303 let mut ranked: Vec<&CodecImplementation> = candidates
304 .iter()
305 .filter(|i| i.make_encoder.is_some() && !prefs.excludes(&i.caps))
306 .filter(|i| caps_fit_params(&i.caps, params, true))
307 .collect();
308 ranked.sort_by_key(|i| prefs.effective_priority(&i.caps));
309 let mut last_err: Option<Error> = None;
310 for imp in ranked {
311 match (imp.make_encoder.unwrap())(params) {
312 Ok(e) => return Ok(e),
313 Err(e) => last_err = Some(e),
314 }
315 }
316 Err(last_err.unwrap_or_else(|| {
317 Error::CodecNotFound(format!(
318 "no encoder for {} accepts the requested parameters",
319 params.codec_id
320 ))
321 }))
322 }
323
324 pub fn make_decoder(&self, params: &CodecParameters) -> Result<Box<dyn Decoder>> {
326 self.make_decoder_with(params, &CodecPreferences::default())
327 }
328
329 pub fn make_encoder(&self, params: &CodecParameters) -> Result<Box<dyn Encoder>> {
331 self.make_encoder_with(params, &CodecPreferences::default())
332 }
333
334 pub fn decoder_ids(&self) -> impl Iterator<Item = &CodecId> {
336 self.impls
337 .iter()
338 .filter(|(_, v)| v.iter().any(|i| i.make_decoder.is_some()))
339 .map(|(id, _)| id)
340 }
341
342 pub fn encoder_ids(&self) -> impl Iterator<Item = &CodecId> {
343 self.impls
344 .iter()
345 .filter(|(_, v)| v.iter().any(|i| i.make_encoder.is_some()))
346 .map(|(id, _)| id)
347 }
348
349 pub fn implementations(&self, id: &CodecId) -> &[CodecImplementation] {
351 self.impls.get(id).map(|v| v.as_slice()).unwrap_or(&[])
352 }
353
354 pub fn encoder_options_schema(&self, id: &CodecId) -> Option<&'static [OptionField]> {
359 self.impls
360 .get(id)?
361 .iter()
362 .find_map(|i| i.encoder_options_schema)
363 }
364
365 pub fn decoder_options_schema(&self, id: &CodecId) -> Option<&'static [OptionField]> {
368 self.impls
369 .get(id)?
370 .iter()
371 .find_map(|i| i.decoder_options_schema)
372 }
373
374 pub fn all_implementations(&self) -> impl Iterator<Item = (&CodecId, &CodecImplementation)> {
377 self.impls
378 .iter()
379 .flat_map(|(id, v)| v.iter().map(move |i| (id, i)))
380 }
381
382 pub fn all_tag_registrations(&self) -> impl Iterator<Item = (&CodecTag, &CodecId)> {
386 self.tag_index.iter().flat_map(move |(tag, idxs)| {
387 idxs.iter().map(move |&i| (tag, &self.registrations[i].id))
388 })
389 }
390
391 pub fn resolve_tag_ref(&self, ctx: &ProbeContext) -> Option<&CodecId> {
401 let idxs = self.tag_index.get(ctx.tag)?;
402 let mut best: Option<(f32, usize)> = None;
403 for &i in idxs {
404 let rec = &self.registrations[i];
405 let conf = match rec.probe {
406 Some(f) => f(ctx),
407 None => 1.0,
408 };
409 if conf <= 0.0 {
410 continue;
411 }
412 best = match best {
413 None => Some((conf, i)),
414 Some((bc, _)) if conf > bc => Some((conf, i)),
415 other => other,
416 };
417 }
418 best.map(|(_, i)| &self.registrations[i].id)
419 }
420}
421
422impl CodecResolver for CodecRegistry {
426 fn resolve_tag(&self, ctx: &ProbeContext) -> Option<CodecId> {
427 self.resolve_tag_ref(ctx).cloned()
428 }
429}
430
431fn caps_fit_params(caps: &CodecCapabilities, p: &CodecParameters, for_encode: bool) -> bool {
435 let _ = for_encode; if let (Some(max), Some(w)) = (caps.max_width, p.width) {
437 if w > max {
438 return false;
439 }
440 }
441 if let (Some(max), Some(h)) = (caps.max_height, p.height) {
442 if h > max {
443 return false;
444 }
445 }
446 if let (Some(max), Some(br)) = (caps.max_bitrate, p.bit_rate) {
447 if br > max {
448 return false;
449 }
450 }
451 if let (Some(max), Some(sr)) = (caps.max_sample_rate, p.sample_rate) {
452 if sr > max {
453 return false;
454 }
455 }
456 if let (Some(max), Some(ch)) = (caps.max_channels, p.channels) {
457 if ch > max {
458 return false;
459 }
460 }
461 true
462}
463
464#[cfg(test)]
465mod tag_tests {
466 use super::*;
467 use oxideav_core::CodecCapabilities;
468
469 fn probe_msmpeg4(ctx: &ProbeContext) -> f32 {
472 match ctx.packet {
473 Some(d) if !d.windows(3).take(6).any(|w| w == [0x00, 0x00, 0x01]) => 1.0,
474 Some(_) => 0.0,
475 None => 0.5, }
477 }
478
479 fn probe_mpeg4_part2(ctx: &ProbeContext) -> f32 {
482 match ctx.packet {
483 Some(d) if d.windows(3).take(6).any(|w| w == [0x00, 0x00, 0x01]) => 1.0,
484 Some(_) => 0.0,
485 None => 0.5,
486 }
487 }
488
489 fn info(id: &str) -> CodecInfo {
490 CodecInfo::new(CodecId::new(id)).capabilities(CodecCapabilities::audio(id))
491 }
492
493 #[test]
494 fn resolve_single_claim_no_probe() {
495 let mut reg = CodecRegistry::new();
496 reg.register(info("flac").tag(CodecTag::fourcc(b"FLAC")));
497 let t = CodecTag::fourcc(b"FLAC");
498 assert_eq!(
499 reg.resolve_tag_ref(&ProbeContext::new(&t))
500 .map(|c| c.as_str()),
501 Some("flac"),
502 );
503 }
504
505 #[test]
506 fn resolve_missing_tag_returns_none() {
507 let reg = CodecRegistry::new();
508 let t = CodecTag::fourcc(b"????");
509 assert!(reg.resolve_tag_ref(&ProbeContext::new(&t)).is_none());
510 }
511
512 #[test]
513 fn unprobed_claims_tie_first_registered_wins() {
514 let mut reg = CodecRegistry::new();
516 reg.register(info("first").tag(CodecTag::fourcc(b"TEST")));
517 reg.register(info("second").tag(CodecTag::fourcc(b"TEST")));
518 let t = CodecTag::fourcc(b"TEST");
519 assert_eq!(
520 reg.resolve_tag_ref(&ProbeContext::new(&t))
521 .map(|c| c.as_str()),
522 Some("first"),
523 );
524 }
525
526 #[test]
527 fn probe_picks_matching_bitstream() {
528 let mut reg = CodecRegistry::new();
531 reg.register(
532 info("msmpeg4v3")
533 .probe(probe_msmpeg4)
534 .tag(CodecTag::fourcc(b"DIV3")),
535 );
536 reg.register(
537 info("mpeg4video")
538 .probe(probe_mpeg4_part2)
539 .tag(CodecTag::fourcc(b"DIV3")),
540 );
541
542 let mpeg4_part2 = [0x00u8, 0x00, 0x01, 0xB0, 0x01, 0x00];
543 let ms_mpeg4 = [0x85u8, 0x3F, 0xD4, 0x80, 0x00, 0xA2];
544 let tag = CodecTag::fourcc(b"DIV3");
545
546 let ctx_part2 = ProbeContext::new(&tag).packet(&mpeg4_part2);
547 assert_eq!(
548 reg.resolve_tag_ref(&ctx_part2).map(|c| c.as_str()),
549 Some("mpeg4video"),
550 );
551 let ctx_ms = ProbeContext::new(&tag).packet(&ms_mpeg4);
552 assert_eq!(
553 reg.resolve_tag_ref(&ctx_ms).map(|c| c.as_str()),
554 Some("msmpeg4v3"),
555 );
556 }
557
558 #[test]
559 fn unprobed_claim_wins_against_low_confidence_probe() {
560 let mut reg = CodecRegistry::new();
565 reg.register(info("owner").tag(CodecTag::fourcc(b"OWN_")));
566 reg.register(
567 info("speculative")
568 .probe(|_| 0.3)
569 .tag(CodecTag::fourcc(b"OWN_")),
570 );
571 let t = CodecTag::fourcc(b"OWN_");
572 assert_eq!(
573 reg.resolve_tag_ref(&ProbeContext::new(&t))
574 .map(|c| c.as_str()),
575 Some("owner"),
576 );
577 }
578
579 #[test]
580 fn probe_returning_zero_is_skipped() {
581 let mut reg = CodecRegistry::new();
582 reg.register(
583 info("refuses")
584 .probe(|_| 0.0)
585 .tag(CodecTag::fourcc(b"MAYB")),
586 );
587 reg.register(info("fallback").tag(CodecTag::fourcc(b"MAYB")));
588 let t = CodecTag::fourcc(b"MAYB");
589 let ctx = ProbeContext::new(&t).packet(b"hello");
590 assert_eq!(
591 reg.resolve_tag_ref(&ctx).map(|c| c.as_str()),
592 Some("fallback"),
593 );
594 }
595
596 #[test]
597 fn fourcc_case_insensitive_lookup() {
598 let mut reg = CodecRegistry::new();
599 reg.register(info("vid").tag(CodecTag::fourcc(b"div3")));
600 let upper = CodecTag::fourcc(b"DIV3");
603 let lower = CodecTag::fourcc(b"div3");
604 let mixed = CodecTag::fourcc(b"DiV3");
605 assert!(reg.resolve_tag_ref(&ProbeContext::new(&upper)).is_some());
606 assert!(reg.resolve_tag_ref(&ProbeContext::new(&lower)).is_some());
607 assert!(reg.resolve_tag_ref(&ProbeContext::new(&mixed)).is_some());
608 }
609
610 #[test]
611 fn wave_format_and_matroska_tags_work() {
612 let mut reg = CodecRegistry::new();
613 reg.register(info("mp3").tag(CodecTag::wave_format(0x0055)));
614 reg.register(info("h264").tag(CodecTag::matroska("V_MPEG4/ISO/AVC")));
615 let wf = CodecTag::wave_format(0x0055);
616 let mk = CodecTag::matroska("V_MPEG4/ISO/AVC");
617 assert_eq!(
618 reg.resolve_tag_ref(&ProbeContext::new(&wf))
619 .map(|c| c.as_str()),
620 Some("mp3"),
621 );
622 assert_eq!(
623 reg.resolve_tag_ref(&ProbeContext::new(&mk))
624 .map(|c| c.as_str()),
625 Some("h264"),
626 );
627 }
628
629 #[test]
630 fn mp4_object_type_tag_works() {
631 let mut reg = CodecRegistry::new();
632 reg.register(info("aac").tag(CodecTag::mp4_object_type(0x40)));
633 let t = CodecTag::mp4_object_type(0x40);
634 assert_eq!(
635 reg.resolve_tag_ref(&ProbeContext::new(&t))
636 .map(|c| c.as_str()),
637 Some("aac"),
638 );
639 }
640
641 #[test]
642 fn multi_tag_claim_all_resolve() {
643 let mut reg = CodecRegistry::new();
644 reg.register(info("aac").tags([
645 CodecTag::fourcc(b"MP4A"),
646 CodecTag::wave_format(0x00FF),
647 CodecTag::mp4_object_type(0x40),
648 CodecTag::matroska("A_AAC"),
649 ]));
650 for t in [
651 CodecTag::fourcc(b"MP4A"),
652 CodecTag::wave_format(0x00FF),
653 CodecTag::mp4_object_type(0x40),
654 CodecTag::matroska("A_AAC"),
655 ] {
656 assert_eq!(
657 reg.resolve_tag_ref(&ProbeContext::new(&t))
658 .map(|c| c.as_str()),
659 Some("aac"),
660 "tag {t:?} did not resolve",
661 );
662 }
663 }
664}