use std::collections::HashMap;
use crate::arena;
use crate::{
CodecCapabilities, CodecId, CodecOptionsStruct, CodecParameters, CodecResolver, CodecTag,
Error, ExecutionContext, Frame, OptionField, Packet, PixelFormat, ProbeContext, ProbeFn,
Result,
};
pub trait Decoder: Send {
fn codec_id(&self) -> &CodecId;
fn send_packet(&mut self, packet: &Packet) -> Result<()>;
fn receive_frame(&mut self) -> Result<Frame>;
fn receive_arena_frame(&mut self) -> Result<arena::sync::Frame> {
let frame = self.receive_frame()?;
match frame {
Frame::Video(v) => video_frame_to_arena_sync_frame(&v),
Frame::Audio(_) => Err(Error::unsupported(
"receive_arena_frame: audio frames not yet supported by default impl",
)),
Frame::Subtitle(_) => Err(Error::unsupported(
"receive_arena_frame: subtitle frames have no arena-backed representation",
)),
Frame::Vector(_) => Err(Error::unsupported(
"receive_arena_frame: vector frames have no arena-backed representation",
)),
}
}
fn flush(&mut self) -> Result<()>;
fn reset(&mut self) -> Result<()> {
self.flush()?;
loop {
match self.receive_frame() {
Ok(_) => {}
Err(Error::NeedMore) | Err(Error::Eof) => return Ok(()),
Err(e) => return Err(e),
}
}
}
fn set_execution_context(&mut self, _ctx: &ExecutionContext) {}
fn output_pixel_format(&self) -> Option<crate::PixelFormat> {
None
}
}
pub trait Encoder: Send {
fn codec_id(&self) -> &CodecId;
fn output_params(&self) -> &CodecParameters;
fn send_frame(&mut self, frame: &Frame) -> Result<()>;
fn receive_packet(&mut self) -> Result<Packet>;
fn flush(&mut self) -> Result<()>;
fn set_execution_context(&mut self, _ctx: &ExecutionContext) {}
}
fn video_frame_to_arena_sync_frame(v: &crate::VideoFrame) -> Result<arena::sync::Frame> {
if v.planes.is_empty() {
return Err(Error::invalid(
"receive_arena_frame: video frame has no planes",
));
}
let total_bytes: usize = v.planes.iter().map(|p| p.data.len()).sum();
if total_bytes == 0 {
return Err(Error::invalid(
"receive_arena_frame: video frame planes are empty",
));
}
let pool = arena::sync::ArenaPool::with_alloc_count_cap(
1,
total_bytes,
(v.planes.len() as u32).saturating_add(4),
);
let arena = pool.lease()?;
let mut plane_offsets: Vec<(usize, usize)> = Vec::with_capacity(v.planes.len());
let mut cursor = 0usize;
for plane in &v.planes {
let dst = arena.alloc::<u8>(plane.data.len())?;
dst.copy_from_slice(&plane.data);
plane_offsets.push((cursor, plane.data.len()));
cursor += plane.data.len();
}
let stride0 = v.planes[0].stride.max(1);
let width = stride0 as u32;
let height = (v.planes[0].data.len() / stride0) as u32;
let pixel_format = match v.image_plane_count() {
1 => PixelFormat::Gray8,
3 => PixelFormat::Yuv420P,
_ => PixelFormat::Yuv444P,
};
let header = arena::sync::FrameHeader::new(width, height, pixel_format, v.pts);
arena::sync::FrameInner::new(arena, &plane_offsets, header)
}
pub type DecoderFactory = fn(params: &CodecParameters) -> Result<Box<dyn Decoder>>;
pub type EncoderFactory = fn(params: &CodecParameters) -> Result<Box<dyn Encoder>>;
#[non_exhaustive]
pub struct CodecInfo {
pub id: CodecId,
pub capabilities: CodecCapabilities,
pub decoder_factory: Option<DecoderFactory>,
pub encoder_factory: Option<EncoderFactory>,
pub probe: Option<ProbeFn>,
pub tags: Vec<CodecTag>,
pub payload_magics: Vec<Vec<u8>>,
pub encoder_options_schema: Option<&'static [OptionField]>,
pub decoder_options_schema: Option<&'static [OptionField]>,
pub engine_id: Option<&'static str>,
pub engine_probe: Option<crate::engine::EngineProbeFn>,
pub resolution_priority: i32,
}
impl CodecInfo {
pub fn new(id: CodecId) -> Self {
Self {
capabilities: CodecCapabilities::audio(id.as_str()),
id,
decoder_factory: None,
encoder_factory: None,
probe: None,
tags: Vec::new(),
payload_magics: Vec::new(),
encoder_options_schema: None,
decoder_options_schema: None,
engine_id: None,
engine_probe: None,
resolution_priority: crate::DEFAULT_PRIORITY,
}
}
pub fn with_resolution_priority(mut self, priority: i32) -> Self {
self.resolution_priority = priority;
self
}
pub fn capabilities(mut self, caps: CodecCapabilities) -> Self {
self.capabilities = caps;
self
}
pub fn decoder(mut self, factory: DecoderFactory) -> Self {
self.decoder_factory = Some(factory);
self
}
pub fn encoder(mut self, factory: EncoderFactory) -> Self {
self.encoder_factory = Some(factory);
self
}
pub fn probe(mut self, probe: ProbeFn) -> Self {
self.probe = Some(probe);
self
}
pub fn tag(mut self, tag: CodecTag) -> Self {
self.tags.push(tag);
self
}
pub fn tags(mut self, tags: impl IntoIterator<Item = CodecTag>) -> Self {
self.tags.extend(tags);
self
}
pub fn payload_magic(mut self, magic: impl Into<Vec<u8>>) -> Self {
self.payload_magics.push(magic.into());
self
}
pub fn payload_magics<I>(mut self, magics: I) -> Self
where
I: IntoIterator,
I::Item: Into<Vec<u8>>,
{
self.payload_magics
.extend(magics.into_iter().map(Into::into));
self
}
pub fn encoder_options<T: CodecOptionsStruct>(mut self) -> Self {
self.encoder_options_schema = Some(T::SCHEMA);
self
}
pub fn decoder_options<T: CodecOptionsStruct>(mut self) -> Self {
self.decoder_options_schema = Some(T::SCHEMA);
self
}
pub fn with_engine_id(mut self, engine_id: &'static str) -> Self {
self.engine_id = Some(engine_id);
self
}
pub fn with_engine_probe(mut self, probe: crate::engine::EngineProbeFn) -> Self {
self.engine_probe = Some(probe);
self
}
}
#[derive(Clone)]
pub struct CodecImplementation {
pub caps: CodecCapabilities,
pub make_decoder: Option<DecoderFactory>,
pub make_encoder: Option<EncoderFactory>,
pub encoder_options_schema: Option<&'static [OptionField]>,
pub decoder_options_schema: Option<&'static [OptionField]>,
pub engine_id: Option<&'static str>,
pub engine_probe: Option<crate::engine::EngineProbeFn>,
}
#[derive(Default)]
pub struct CodecRegistry {
impls: HashMap<CodecId, Vec<CodecImplementation>>,
impl_order: Vec<CodecId>,
registrations: Vec<RegistrationRecord>,
tag_index: HashMap<CodecTag, Vec<usize>>,
magic_index: Vec<(Vec<u8>, usize)>,
}
struct RegistrationRecord {
id: CodecId,
probe: Option<ProbeFn>,
priority: i32,
tags: Vec<CodecTag>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TagCandidate<'a> {
pub id: &'a CodecId,
pub confidence: crate::Confidence,
pub priority: i32,
pub order: usize,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PayloadMagicCandidate<'a> {
pub id: &'a CodecId,
pub magic: &'a [u8],
pub priority: i32,
pub order: usize,
}
impl CodecRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, info: CodecInfo) {
let CodecInfo {
id,
capabilities,
decoder_factory,
encoder_factory,
probe,
tags,
payload_magics,
encoder_options_schema,
decoder_options_schema,
engine_id,
engine_probe,
resolution_priority,
} = info;
let caps = {
let mut c = capabilities;
if decoder_factory.is_some() {
c = c.with_decode();
}
if encoder_factory.is_some() {
c = c.with_encode();
}
c
};
if decoder_factory.is_some() || encoder_factory.is_some() {
let slot = self.impls.entry(id.clone()).or_default();
if slot.is_empty() {
self.impl_order.push(id.clone());
}
slot.push(CodecImplementation {
caps,
make_decoder: decoder_factory,
make_encoder: encoder_factory,
encoder_options_schema,
decoder_options_schema,
engine_id,
engine_probe,
});
}
let record_idx = self.registrations.len();
for tag in &tags {
self.tag_index
.entry(tag.clone())
.or_default()
.push(record_idx);
}
self.registrations.push(RegistrationRecord {
id: id.clone(),
probe,
priority: resolution_priority,
tags,
});
for magic in payload_magics {
if !magic.is_empty() {
self.magic_index.push((magic, record_idx));
}
}
}
pub fn has_decoder(&self, id: &CodecId) -> bool {
self.impls
.get(id)
.map(|v| v.iter().any(|i| i.make_decoder.is_some()))
.unwrap_or(false)
}
pub fn has_encoder(&self, id: &CodecId) -> bool {
self.impls
.get(id)
.map(|v| v.iter().any(|i| i.make_encoder.is_some()))
.unwrap_or(false)
}
pub fn first_decoder(&self, params: &CodecParameters) -> Result<Box<dyn Decoder>> {
let imp = self
.implementations(¶ms.codec_id)
.iter()
.find(|i| i.make_decoder.is_some())
.ok_or_else(|| {
Error::CodecNotFound(format!("no decoder for codec {}", params.codec_id))
})?;
(imp.make_decoder.expect("checked above"))(params)
}
pub fn first_encoder(&self, params: &CodecParameters) -> Result<Box<dyn Encoder>> {
let imp = self
.implementations(¶ms.codec_id)
.iter()
.find(|i| i.make_encoder.is_some())
.ok_or_else(|| {
Error::CodecNotFound(format!("no encoder for codec {}", params.codec_id))
})?;
(imp.make_encoder.expect("checked above"))(params)
}
pub fn decoder_by_impl(
&self,
impl_name: &str,
params: &CodecParameters,
) -> Result<Box<dyn Decoder>> {
let imp = self
.implementations(¶ms.codec_id)
.iter()
.find(|i| i.caps.implementation == impl_name)
.ok_or_else(|| {
Error::CodecNotFound(format!(
"no implementation `{impl_name}` for codec {}",
params.codec_id
))
})?;
let factory = imp
.make_decoder
.ok_or_else(|| Error::CodecNotFound(format!("`{impl_name}` is encoder-only")))?;
factory(params)
}
pub fn encoder_by_impl(
&self,
impl_name: &str,
params: &CodecParameters,
) -> Result<Box<dyn Encoder>> {
let imp = self
.implementations(¶ms.codec_id)
.iter()
.find(|i| i.caps.implementation == impl_name)
.ok_or_else(|| {
Error::CodecNotFound(format!(
"no implementation `{impl_name}` for codec {}",
params.codec_id
))
})?;
let factory = imp
.make_encoder
.ok_or_else(|| Error::CodecNotFound(format!("`{impl_name}` is decoder-only")))?;
factory(params)
}
pub fn decoder_ids(&self) -> impl Iterator<Item = &CodecId> {
self.impl_order.iter().filter(|id| self.has_decoder(id))
}
pub fn encoder_ids(&self) -> impl Iterator<Item = &CodecId> {
self.impl_order.iter().filter(|id| self.has_encoder(id))
}
pub fn implementations(&self, id: &CodecId) -> &[CodecImplementation] {
self.impls.get(id).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn encoder_options_schema(&self, id: &CodecId) -> Option<&'static [OptionField]> {
self.impls
.get(id)?
.iter()
.find_map(|i| i.encoder_options_schema)
}
pub fn decoder_options_schema(&self, id: &CodecId) -> Option<&'static [OptionField]> {
self.impls
.get(id)?
.iter()
.find_map(|i| i.decoder_options_schema)
}
pub fn all_implementations(&self) -> impl Iterator<Item = (&CodecId, &CodecImplementation)> {
self.impl_order
.iter()
.flat_map(move |id| self.implementations(id).iter().map(move |i| (id, i)))
}
pub fn all_tag_registrations(&self) -> impl Iterator<Item = (&CodecTag, &CodecId)> {
self.registrations
.iter()
.flat_map(|rec| rec.tags.iter().map(move |tag| (tag, &rec.id)))
}
pub fn resolve_tag_ref(&self, ctx: &ProbeContext) -> Option<&CodecId> {
self.resolve_tag_candidates(ctx).first().map(|c| c.id)
}
pub fn resolve_tag_candidates(&self, ctx: &ProbeContext) -> Vec<TagCandidate<'_>> {
let Some(idxs) = self.tag_index.get(ctx.tag) else {
return Vec::new();
};
let mut out: Vec<TagCandidate<'_>> = idxs
.iter()
.filter_map(|&i| {
let rec = &self.registrations[i];
let confidence = match rec.probe {
Some(f) => f(ctx),
None => 1.0,
};
(confidence > 0.0).then_some(TagCandidate {
id: &rec.id,
confidence,
priority: rec.priority,
order: i,
})
})
.collect();
out.sort_by(|a, b| {
b.confidence
.partial_cmp(&a.confidence)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.priority.cmp(&b.priority))
.then(a.order.cmp(&b.order))
});
out
}
pub fn resolve_payload_magic_ref(&self, first_bytes: &[u8]) -> Option<&CodecId> {
self.resolve_payload_magic_candidates(first_bytes)
.first()
.map(|c| c.id)
}
pub fn resolve_payload_magic_candidates(
&self,
first_bytes: &[u8],
) -> Vec<PayloadMagicCandidate<'_>> {
let mut out: Vec<PayloadMagicCandidate<'_>> = self
.magic_index
.iter()
.filter(|(magic, _)| first_bytes.starts_with(magic))
.map(|(magic, i)| {
let rec = &self.registrations[*i];
PayloadMagicCandidate {
id: &rec.id,
magic: magic.as_slice(),
priority: rec.priority,
order: *i,
}
})
.collect();
out.sort_by(|a, b| {
b.magic
.len()
.cmp(&a.magic.len())
.then(a.priority.cmp(&b.priority))
.then(a.order.cmp(&b.order))
});
out
}
pub fn all_payload_magic_registrations(&self) -> impl Iterator<Item = (&[u8], &CodecId)> {
self.magic_index
.iter()
.map(move |(magic, i)| (magic.as_slice(), &self.registrations[*i].id))
}
}
impl CodecResolver for CodecRegistry {
fn resolve_tag(&self, ctx: &ProbeContext) -> Option<CodecId> {
self.resolve_tag_ref(ctx).cloned()
}
fn resolve_payload_magic(&self, first_packet: &[u8]) -> Option<CodecId> {
self.resolve_payload_magic_ref(first_packet).cloned()
}
}
#[cfg(test)]
mod tag_tests {
use super::*;
use crate::CodecCapabilities;
fn probe_msmpeg4(ctx: &ProbeContext) -> f32 {
match ctx.packet {
Some(d) if !d.windows(3).take(6).any(|w| w == [0x00, 0x00, 0x01]) => 1.0,
Some(_) => 0.0,
None => 0.5, }
}
fn probe_mpeg4_part2(ctx: &ProbeContext) -> f32 {
match ctx.packet {
Some(d) if d.windows(3).take(6).any(|w| w == [0x00, 0x00, 0x01]) => 1.0,
Some(_) => 0.0,
None => 0.5,
}
}
fn info(id: &str) -> CodecInfo {
CodecInfo::new(CodecId::new(id)).capabilities(CodecCapabilities::audio(id))
}
#[test]
fn resolve_single_claim_no_probe() {
let mut reg = CodecRegistry::new();
reg.register(info("flac").tag(CodecTag::fourcc(b"FLAC")));
let t = CodecTag::fourcc(b"FLAC");
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&t))
.map(|c| c.as_str()),
Some("flac"),
);
}
#[test]
fn resolve_missing_tag_returns_none() {
let reg = CodecRegistry::new();
let t = CodecTag::fourcc(b"????");
assert!(reg.resolve_tag_ref(&ProbeContext::new(&t)).is_none());
}
#[test]
fn unprobed_claims_tie_first_registered_wins() {
let mut reg = CodecRegistry::new();
reg.register(info("first").tag(CodecTag::fourcc(b"TEST")));
reg.register(info("second").tag(CodecTag::fourcc(b"TEST")));
let t = CodecTag::fourcc(b"TEST");
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&t))
.map(|c| c.as_str()),
Some("first"),
);
}
#[test]
fn probe_picks_matching_bitstream() {
let mut reg = CodecRegistry::new();
reg.register(
info("msmpeg4v3")
.probe(probe_msmpeg4)
.tag(CodecTag::fourcc(b"DIV3")),
);
reg.register(
info("mpeg4video")
.probe(probe_mpeg4_part2)
.tag(CodecTag::fourcc(b"DIV3")),
);
let mpeg4_part2 = [0x00u8, 0x00, 0x01, 0xB0, 0x01, 0x00];
let ms_mpeg4 = [0x85u8, 0x3F, 0xD4, 0x80, 0x00, 0xA2];
let tag = CodecTag::fourcc(b"DIV3");
let ctx_part2 = ProbeContext::new(&tag).packet(&mpeg4_part2);
assert_eq!(
reg.resolve_tag_ref(&ctx_part2).map(|c| c.as_str()),
Some("mpeg4video"),
);
let ctx_ms = ProbeContext::new(&tag).packet(&ms_mpeg4);
assert_eq!(
reg.resolve_tag_ref(&ctx_ms).map(|c| c.as_str()),
Some("msmpeg4v3"),
);
}
#[test]
fn unprobed_claim_wins_against_low_confidence_probe() {
let mut reg = CodecRegistry::new();
reg.register(info("owner").tag(CodecTag::fourcc(b"OWN_")));
reg.register(
info("speculative")
.probe(|_| 0.3)
.tag(CodecTag::fourcc(b"OWN_")),
);
let t = CodecTag::fourcc(b"OWN_");
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&t))
.map(|c| c.as_str()),
Some("owner"),
);
}
#[test]
fn probe_returning_zero_is_skipped() {
let mut reg = CodecRegistry::new();
reg.register(
info("refuses")
.probe(|_| 0.0)
.tag(CodecTag::fourcc(b"MAYB")),
);
reg.register(info("fallback").tag(CodecTag::fourcc(b"MAYB")));
let t = CodecTag::fourcc(b"MAYB");
let ctx = ProbeContext::new(&t).packet(b"hello");
assert_eq!(
reg.resolve_tag_ref(&ctx).map(|c| c.as_str()),
Some("fallback"),
);
}
#[test]
fn fourcc_case_insensitive_lookup() {
let mut reg = CodecRegistry::new();
reg.register(info("vid").tag(CodecTag::fourcc(b"div3")));
let upper = CodecTag::fourcc(b"DIV3");
let lower = CodecTag::fourcc(b"div3");
let mixed = CodecTag::fourcc(b"DiV3");
assert!(reg.resolve_tag_ref(&ProbeContext::new(&upper)).is_some());
assert!(reg.resolve_tag_ref(&ProbeContext::new(&lower)).is_some());
assert!(reg.resolve_tag_ref(&ProbeContext::new(&mixed)).is_some());
}
#[test]
fn wave_format_and_matroska_tags_work() {
let mut reg = CodecRegistry::new();
reg.register(info("mp3").tag(CodecTag::wave_format(0x0055)));
reg.register(info("h264").tag(CodecTag::matroska("V_MPEG4/ISO/AVC")));
let wf = CodecTag::wave_format(0x0055);
let mk = CodecTag::matroska("V_MPEG4/ISO/AVC");
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&wf))
.map(|c| c.as_str()),
Some("mp3"),
);
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&mk))
.map(|c| c.as_str()),
Some("h264"),
);
}
#[test]
fn mp4_object_type_tag_works() {
let mut reg = CodecRegistry::new();
reg.register(info("aac").tag(CodecTag::mp4_object_type(0x40)));
let t = CodecTag::mp4_object_type(0x40);
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&t))
.map(|c| c.as_str()),
Some("aac"),
);
}
#[test]
fn multi_tag_claim_all_resolve() {
let mut reg = CodecRegistry::new();
reg.register(info("aac").tags([
CodecTag::fourcc(b"MP4A"),
CodecTag::wave_format(0x00FF),
CodecTag::mp4_object_type(0x40),
CodecTag::matroska("A_AAC"),
]));
for t in [
CodecTag::fourcc(b"MP4A"),
CodecTag::wave_format(0x00FF),
CodecTag::mp4_object_type(0x40),
CodecTag::matroska("A_AAC"),
] {
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&t))
.map(|c| c.as_str()),
Some("aac"),
"tag {t:?} did not resolve",
);
}
}
}
#[cfg(test)]
mod payload_magic_tests {
use super::*;
use crate::CodecCapabilities;
fn info(id: &str) -> CodecInfo {
CodecInfo::new(CodecId::new(id)).capabilities(CodecCapabilities::audio(id))
}
fn ogg_family_registry() -> CodecRegistry {
let mut reg = CodecRegistry::new();
reg.register(info("vorbis").payload_magic(b"\x01vorbis"));
reg.register(info("opus").payload_magic(b"OpusHead"));
reg.register(info("theora").payload_magic(b"\x80theora"));
reg.register(info("flac").payload_magic(b"\x7fFLAC"));
reg
}
#[test]
fn resolve_payload_magic_matches_first_packet_prefix() {
let reg = ogg_family_registry();
let vorbis_id_header = b"\x01vorbis\x00\x00\x00\x00\x02\x44\xac\x00\x00";
assert_eq!(
reg.resolve_payload_magic_ref(vorbis_id_header)
.map(|c| c.as_str()),
Some("vorbis"),
);
assert_eq!(
reg.resolve_payload_magic_ref(b"OpusHead\x01\x02\x38\x01")
.map(|c| c.as_str()),
Some("opus"),
);
assert_eq!(
reg.resolve_payload_magic_ref(b"\x80theora\x03\x02\x01")
.map(|c| c.as_str()),
Some("theora"),
);
assert_eq!(
reg.resolve_payload_magic_ref(b"\x7fFLAC\x01\x00")
.map(|c| c.as_str()),
Some("flac"),
);
}
#[test]
fn resolve_payload_magic_exact_length_packet_matches() {
let reg = ogg_family_registry();
assert_eq!(
reg.resolve_payload_magic_ref(b"OpusHead")
.map(|c| c.as_str()),
Some("opus"),
);
}
#[test]
fn resolve_payload_magic_unknown_or_short_packet_is_none() {
let reg = ogg_family_registry();
assert!(reg.resolve_payload_magic_ref(b"Speex 1.2.0").is_none());
assert!(reg.resolve_payload_magic_ref(b"Opus").is_none());
assert!(reg.resolve_payload_magic_ref(b"").is_none());
}
#[test]
fn resolve_payload_magic_longest_prefix_wins_regardless_of_order() {
let mut reg = CodecRegistry::new();
reg.register(info("generic").payload_magic(b"Opus"));
reg.register(info("opus").payload_magic(b"OpusHead"));
assert_eq!(
reg.resolve_payload_magic_ref(b"OpusHead\x01")
.map(|c| c.as_str()),
Some("opus"),
);
assert_eq!(
reg.resolve_payload_magic_ref(b"OpusTags")
.map(|c| c.as_str()),
Some("generic"),
);
let mut reg = CodecRegistry::new();
reg.register(info("opus").payload_magic(b"OpusHead"));
reg.register(info("generic").payload_magic(b"Opus"));
assert_eq!(
reg.resolve_payload_magic_ref(b"OpusHead\x01")
.map(|c| c.as_str()),
Some("opus"),
);
}
#[test]
fn resolve_payload_magic_equal_length_tie_first_registered_wins() {
let mut reg = CodecRegistry::new();
reg.register(info("first").payload_magic(b"SameMagic"));
reg.register(info("second").payload_magic(b"SameMagic"));
assert_eq!(
reg.resolve_payload_magic_ref(b"SameMagic\x00")
.map(|c| c.as_str()),
Some("first"),
);
}
#[test]
fn empty_payload_magic_is_ignored_at_registration() {
let mut reg = CodecRegistry::new();
reg.register(info("greedy").payload_magic(b""));
assert!(reg.resolve_payload_magic_ref(b"anything at all").is_none());
assert_eq!(reg.all_payload_magic_registrations().count(), 0);
}
#[test]
fn payload_magics_plural_builder_and_diagnostics() {
let mut reg = CodecRegistry::new();
reg.register(info("speex").payload_magics([b"Speex ".to_vec(), b"speex-alt".to_vec()]));
assert_eq!(
reg.resolve_payload_magic_ref(b"Speex 1.2")
.map(|c| c.as_str()),
Some("speex"),
);
assert_eq!(
reg.resolve_payload_magic_ref(b"speex-alt\x00")
.map(|c| c.as_str()),
Some("speex"),
);
let all: Vec<(&[u8], &str)> = reg
.all_payload_magic_registrations()
.map(|(m, id)| (m, id.as_str()))
.collect();
assert_eq!(
all,
vec![
(b"Speex ".as_slice(), "speex"),
(b"speex-alt".as_slice(), "speex"),
],
);
}
#[test]
fn magic_claims_compose_with_tag_claims_on_one_registration() {
let mut reg = CodecRegistry::new();
reg.register(
info("vorbis")
.tag(CodecTag::matroska("A_VORBIS"))
.payload_magic(b"\x01vorbis"),
);
let mk = CodecTag::matroska("A_VORBIS");
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&mk))
.map(|c| c.as_str()),
Some("vorbis"),
);
assert_eq!(
reg.resolve_payload_magic_ref(b"\x01vorbis\x00")
.map(|c| c.as_str()),
Some("vorbis"),
);
}
#[test]
fn resolver_trait_payload_magic_surface() {
let reg = ogg_family_registry();
let resolver: &dyn CodecResolver = ®
assert_eq!(
resolver
.resolve_payload_magic(b"OpusHead\x01")
.map(|c| c.0.clone()),
Some("opus".to_owned()),
);
assert!(resolver.resolve_payload_magic(b"unknown").is_none());
let null = crate::NullCodecResolver;
assert!(null.resolve_payload_magic(b"OpusHead\x01").is_none());
}
#[test]
fn payload_magic_serves_non_ogg_carriage() {
let mut reg = CodecRegistry::new();
reg.register(info("flac").payload_magic(b"fLaC"));
reg.register(info("shorten").payload_magic(b"ajkg"));
assert_eq!(
reg.resolve_payload_magic_ref(b"fLaC\x00\x00\x00\x22"),
Some(&CodecId::new("flac"))
);
assert_eq!(
reg.resolve_payload_magic_ref(b"ajkg\x02"),
Some(&CodecId::new("shorten"))
);
assert_eq!(reg.resolve_payload_magic_ref(b"RIFF"), None);
}
}
#[cfg(test)]
mod resolution_order_tests {
use super::*;
use crate::CodecCapabilities;
fn info(id: &str) -> CodecInfo {
CodecInfo::new(CodecId::new(id)).capabilities(CodecCapabilities::audio(id))
}
fn tag_ids<'a>(cands: &[TagCandidate<'a>]) -> Vec<&'a str> {
cands.iter().map(|c| c.id.as_str()).collect()
}
fn magic_ids<'a>(cands: &[PayloadMagicCandidate<'a>]) -> Vec<&'a str> {
cands.iter().map(|c| c.id.as_str()).collect()
}
#[test]
fn codec_info_resolution_priority_defaults_and_builder() {
assert_eq!(info("x").resolution_priority, crate::DEFAULT_PRIORITY);
assert_eq!(info("x").with_resolution_priority(7).resolution_priority, 7);
}
#[test]
fn equal_tag_claims_resolve_by_registration_order_across_fresh_contexts() {
const ORDER: [&str; 5] = ["zeta", "alpha", "omicron", "beta", "kappa"];
let tag = CodecTag::fourcc(b"SYNT");
for _ in 0..1000 {
let mut ctx = crate::RuntimeContext::new();
for id in ORDER {
ctx.codecs.register(info(id).tag(tag.clone()));
}
let pc = ProbeContext::new(&tag);
assert_eq!(
ctx.codecs.resolve_tag_ref(&pc).map(|c| c.as_str()),
Some("zeta")
);
let cands = ctx.codecs.resolve_tag_candidates(&pc);
assert_eq!(tag_ids(&cands), ORDER.to_vec());
assert_eq!(
cands.iter().map(|c| c.order).collect::<Vec<_>>(),
vec![0, 1, 2, 3, 4]
);
}
}
#[test]
fn equal_magic_claims_resolve_by_registration_order_across_fresh_contexts() {
const ORDER: [&str; 4] = ["zeta", "alpha", "omicron", "beta"];
for _ in 0..1000 {
let mut ctx = crate::RuntimeContext::new();
for id in ORDER {
ctx.codecs.register(info(id).payload_magic(b"SYNTHMAGIC"));
}
assert_eq!(
ctx.codecs
.resolve_payload_magic_ref(b"SYNTHMAGIC\x00")
.map(|c| c.as_str()),
Some("zeta")
);
let cands = ctx
.codecs
.resolve_payload_magic_candidates(b"SYNTHMAGIC\x00");
assert_eq!(magic_ids(&cands), ORDER.to_vec());
}
}
#[test]
fn tag_priority_beats_registration_order() {
let tag = CodecTag::wave_format(0x7777);
let mut reg = CodecRegistry::new();
reg.register(info("earlier").tag(tag.clone()));
reg.register(
info("later-preferred")
.with_resolution_priority(10)
.tag(tag.clone()),
);
let pc = ProbeContext::new(&tag);
assert_eq!(
reg.resolve_tag_ref(&pc).map(|c| c.as_str()),
Some("later-preferred")
);
let cands = reg.resolve_tag_candidates(&pc);
assert_eq!(tag_ids(&cands), vec!["later-preferred", "earlier"]);
assert_eq!(cands[0].priority, 10);
assert_eq!(cands[0].order, 1);
assert_eq!(cands[1].priority, crate::DEFAULT_PRIORITY);
assert_eq!(cands[1].order, 0);
assert_eq!(cands[0].confidence, 1.0);
}
#[test]
fn tag_confidence_beats_priority() {
let tag = CodecTag::fourcc(b"AMBI");
let mut reg = CodecRegistry::new();
reg.register(
info("prioritised")
.with_resolution_priority(i32::MIN)
.probe(|_| 0.6)
.tag(tag.clone()),
);
reg.register(info("certain").tag(tag.clone()));
let pc = ProbeContext::new(&tag);
assert_eq!(
reg.resolve_tag_ref(&pc).map(|c| c.as_str()),
Some("certain")
);
let cands = reg.resolve_tag_candidates(&pc);
assert_eq!(tag_ids(&cands), vec!["certain", "prioritised"]);
}
#[test]
fn tag_candidates_drop_refusing_and_non_finite_probes() {
let tag = CodecTag::fourcc(b"NANP");
let mut reg = CodecRegistry::new();
reg.register(info("refuses").probe(|_| 0.0).tag(tag.clone()));
reg.register(info("broken").probe(|_| f32::NAN).tag(tag.clone()));
reg.register(info("negative").probe(|_| -1.0).tag(tag.clone()));
reg.register(info("accepts").probe(|_| 0.2).tag(tag.clone()));
let pc = ProbeContext::new(&tag);
assert_eq!(tag_ids(®.resolve_tag_candidates(&pc)), vec!["accepts"]);
assert_eq!(
reg.resolve_tag_ref(&pc).map(|c| c.as_str()),
Some("accepts")
);
let other = CodecTag::fourcc(b"NONE");
assert!(reg
.resolve_tag_candidates(&ProbeContext::new(&other))
.is_empty());
}
#[test]
fn duplicate_tag_claim_by_one_id_is_visible_not_folded() {
let tag = CodecTag::fourcc(b"TWIC");
let mut reg = CodecRegistry::new();
reg.register(info("dup").tag(tag.clone()));
reg.register(info("other").tag(tag.clone()));
reg.register(info("dup").tag(tag.clone()));
let pc = ProbeContext::new(&tag);
let cands = reg.resolve_tag_candidates(&pc);
assert_eq!(tag_ids(&cands), vec!["dup", "other", "dup"]);
assert_eq!(
cands.iter().map(|c| c.order).collect::<Vec<_>>(),
vec![0, 1, 2]
);
assert_eq!(reg.resolve_tag_ref(&pc).map(|c| c.as_str()), Some("dup"));
}
#[test]
fn magic_priority_beats_registration_order_but_not_length() {
let mut reg = CodecRegistry::new();
reg.register(info("earlier").payload_magic(b"MAGIC"));
reg.register(
info("later-preferred")
.with_resolution_priority(1)
.payload_magic(b"MAGIC"),
);
reg.register(info("longer").payload_magic(b"MAGICAL"));
let cands = reg.resolve_payload_magic_candidates(b"MAGIC\x00");
assert_eq!(magic_ids(&cands), vec!["later-preferred", "earlier"]);
assert_eq!(cands[0].order, 1);
assert_eq!(cands[0].magic, b"MAGIC");
let cands = reg.resolve_payload_magic_candidates(b"MAGICAL\x00");
assert_eq!(
magic_ids(&cands),
vec!["longer", "later-preferred", "earlier"]
);
assert_eq!(
reg.resolve_payload_magic_ref(b"MAGICAL\x00")
.map(|c| c.as_str()),
Some("longer")
);
assert!(reg.resolve_payload_magic_candidates(b"other").is_empty());
assert!(reg.resolve_payload_magic_ref(b"other").is_none());
}
fn dec(_p: &crate::CodecParameters) -> crate::Result<Box<dyn Decoder>> {
Err(Error::unsupported("dummy"))
}
fn enc(_p: &crate::CodecParameters) -> crate::Result<Box<dyn Encoder>> {
Err(Error::unsupported("dummy"))
}
#[test]
fn enumerations_follow_registration_order() {
for _ in 0..200 {
let mut reg = CodecRegistry::new();
reg.register(
info("zeta")
.decoder(dec)
.tags([CodecTag::fourcc(b"ZET1"), CodecTag::fourcc(b"ZET2")]),
);
reg.register(info("alpha").encoder(enc).tag(CodecTag::fourcc(b"ALPH")));
reg.register(info("omicron").decoder(dec).encoder(enc));
reg.register(info("zeta").encoder(enc).tag(CodecTag::fourcc(b"ZET3")));
reg.register(info("beta").tag(CodecTag::fourcc(b"BETA")));
let ids: Vec<&str> = reg
.all_implementations()
.map(|(id, _)| id.as_str())
.collect();
assert_eq!(ids, vec!["zeta", "zeta", "alpha", "omicron"]);
let dec_ids: Vec<&str> = reg.decoder_ids().map(|i| i.as_str()).collect();
assert_eq!(dec_ids, vec!["zeta", "omicron"]);
let enc_ids: Vec<&str> = reg.encoder_ids().map(|i| i.as_str()).collect();
assert_eq!(enc_ids, vec!["zeta", "alpha", "omicron"]);
let tags: Vec<(String, &str)> = reg
.all_tag_registrations()
.map(|(t, id)| (format!("{t:?}"), id.as_str()))
.collect();
let want: Vec<(String, &str)> = [
(CodecTag::fourcc(b"ZET1"), "zeta"),
(CodecTag::fourcc(b"ZET2"), "zeta"),
(CodecTag::fourcc(b"ALPH"), "alpha"),
(CodecTag::fourcc(b"ZET3"), "zeta"),
(CodecTag::fourcc(b"BETA"), "beta"),
]
.into_iter()
.map(|(t, id)| (format!("{t:?}"), id))
.collect();
assert_eq!(tags, want);
}
}
#[test]
fn resolution_priority_is_independent_of_capabilities_priority() {
let tag = CodecTag::fourcc(b"SHRD");
let mut reg = CodecRegistry::new();
reg.register(info("sw-id").tag(tag.clone()));
reg.register(
CodecInfo::new(CodecId::new("hw-id"))
.capabilities(CodecCapabilities::video("hw-id").with_priority(10))
.tag(tag.clone()),
);
let pc = ProbeContext::new(&tag);
assert_eq!(reg.resolve_tag_ref(&pc).map(|c| c.as_str()), Some("sw-id"));
let cands = reg.resolve_tag_candidates(&pc);
assert!(cands.iter().all(|c| c.priority == crate::DEFAULT_PRIORITY));
}
}
#[cfg(test)]
mod engine_tests {
use super::*;
use crate::engine::HwDeviceInfo;
#[test]
fn codec_info_engine_id_and_probe_default_to_none() {
let ci = CodecInfo::new(CodecId::new("h264"));
assert!(ci.engine_id.is_none());
assert!(ci.engine_probe.is_none());
}
#[test]
fn codec_info_engine_builder_methods_set_fields() {
fn dummy_probe() -> Vec<HwDeviceInfo> {
vec![]
}
let ci = CodecInfo::new(CodecId::new("h264"))
.with_engine_id("nvidia")
.with_engine_probe(dummy_probe);
assert_eq!(ci.engine_id, Some("nvidia"));
assert!(ci.engine_probe.is_some());
let probe = ci.engine_probe.unwrap();
let result = probe();
assert!(result.is_empty());
}
#[test]
fn registering_codec_with_engine_metadata_does_not_panic() {
fn dummy_probe() -> Vec<HwDeviceInfo> {
vec![]
}
let mut reg = CodecRegistry::new();
reg.register(
CodecInfo::new(CodecId::new("h264"))
.capabilities(CodecCapabilities::audio("h264_nvdec"))
.tag(CodecTag::fourcc(b"H264"))
.with_engine_id("nvidia")
.with_engine_probe(dummy_probe),
);
let t = CodecTag::fourcc(b"H264");
assert_eq!(
reg.resolve_tag_ref(&ProbeContext::new(&t))
.map(|c| c.as_str()),
Some("h264"),
);
}
fn dummy_decoder_factory(
_params: &crate::CodecParameters,
) -> crate::Result<Box<dyn super::Decoder>> {
Err(crate::Error::unsupported("dummy decoder"))
}
#[test]
fn engine_metadata_propagates_through_register() {
fn dummy_probe() -> Vec<HwDeviceInfo> {
vec![]
}
let mut reg = CodecRegistry::default();
reg.register(
CodecInfo::new(CodecId::new("h264"))
.capabilities(CodecCapabilities::video("h264_test"))
.decoder(dummy_decoder_factory)
.with_engine_id("test-backend")
.with_engine_probe(dummy_probe),
);
let impls = reg.implementations(&CodecId::new("h264"));
assert_eq!(impls.len(), 1);
assert_eq!(impls[0].engine_id, Some("test-backend"));
assert!(impls[0].engine_probe.is_some());
}
#[test]
fn engine_metadata_absent_for_sw_codecs() {
let mut reg = CodecRegistry::default();
reg.register(
CodecInfo::new(CodecId::new("flac"))
.capabilities(CodecCapabilities::audio("flac_sw"))
.decoder(dummy_decoder_factory),
);
let impls = reg.implementations(&CodecId::new("flac"));
assert_eq!(impls.len(), 1);
assert!(impls[0].engine_id.is_none());
assert!(impls[0].engine_probe.is_none());
}
}