# oxideav-codec
Codec traits + registry for the
[oxideav](https://github.com/OxideAV/oxideav-workspace) pure-Rust media
framework. Every per-format codec crate (MP3, AAC, H.264, GIF, …)
implements `Decoder` and/or `Encoder` and registers itself via a
`CodecImplementation`; the aggregator builds one `CodecRegistry` with
the union of every enabled feature.
* **`Decoder`** — `send_packet` → `receive_frame`, plus `flush` (EOS
drain) and `reset` (state wipe after seek).
* **`Encoder`** — `send_frame` → `receive_packet`, plus `flush`.
* **`CodecCapabilities`** — per-impl flags (lossless, intra-only,
accepted pixel formats, supported sample rates, priority) so
registries can pick the right implementation for a given request.
* **`CodecRegistry`** — factory lookup by `CodecId`, with fallback when
the first-choice implementation refuses the input. Also implements
`oxideav_core::CodecResolver`, so containers can delegate
tag-to-codec resolution (AVI FourCC, WAVEFORMATEX `wFormatTag`, MP4
OTI, Matroska CodecID) to the registry without a direct dep on this
crate.
* **Tag claims** — a codec attaches one or more `CodecTag`s to its id
via `CodecInfo::tag(...)` / `.tags([...])`, optionally with a probe
function that returns an `f32` confidence in `0.0..=1.0` for the
tag-collision cases (e.g. `DIV3` that's actually MPEG-4 Part 2 — both
codecs claim the tag, and their probes pick the right one per file).
Zero C dependencies. Zero FFI.
## Usage
```toml
[dependencies]
oxideav-codec = "0.1"
```
## Using a codec directly (standalone, no container, no pipeline)
OxideAV's codecs are designed to be usable on their own. If you already
have raw encoded packets (from disk, the network, or another parser),
you only need three crates: `oxideav-core` for the `Packet` / `Frame`
types, `oxideav-codec` for the `Decoder` / `Encoder` traits + registry,
and the specific codec crate you want.
Example — decoding G.711 µ-law:
```toml
[dependencies]
oxideav-core = "0.1"
oxideav-codec = "0.1"
oxideav-g711 = "0.0"
```
```rust
use oxideav_codec::CodecRegistry;
use oxideav_core::{CodecId, CodecParameters, Frame, Packet, TimeBase};
// 1. Register the codec with a registry (one-time setup).
let mut reg = CodecRegistry::new();
oxideav_g711::register(&mut reg);
// 2. Describe the stream — here 8 kHz mono µ-law.
let mut params = CodecParameters::audio(CodecId::new("pcm_mulaw"));
params.sample_rate = Some(8_000);
params.channels = Some(1);
// 3. Build a decoder.
let mut dec = reg.make_decoder(¶ms)?;
// 4. Feed packets + pull frames in a loop.
let pkt = Packet::new(/* stream_index */ 0, TimeBase::new(1, 8_000), mulaw_bytes);
dec.send_packet(&pkt)?;
match dec.receive_frame()? {
Frame::Audio(a) => {
// `a.data[0]` is interleaved S16 PCM; `a.channels` / `a.samples`
// / `a.sample_rate` describe the shape.
}
_ => unreachable!("G.711 yields audio"),
}
# Ok::<(), oxideav_core::Error>(())
```
### The packet → frame loop
`send_packet` / `receive_frame` is a two-step state machine. Most
codecs accept exactly one packet per frame (a 1:1 mapping), so the
pattern is:
```rust
for pkt in stream_of_packets {
dec.send_packet(&pkt)?;
let frame = dec.receive_frame()?;
// ... use frame ...
}
```
Some codecs buffer internally (e.g. a video codec waiting for reference
frames). In that case `receive_frame` returns `Error::NeedMore` until
the decoder has enough input; keep sending packets until you get a
frame, and keep pulling frames after each send (a single packet may
produce multiple frames for some codecs). The canonical loop:
```rust
loop {
match dec.receive_frame() {
Ok(frame) => { /* consume */ }
Err(oxideav_core::Error::NeedMore) => break, // send more packets
Err(oxideav_core::Error::Eof) => return Ok(()),
Err(e) => return Err(e),
}
}
```
### Signalling end-of-stream
When you've sent every packet and want to flush any still-buffered
output:
```rust
dec.flush()?;
while let Ok(frame) = dec.receive_frame() { /* trailing frames */ }
```
### Resetting after a seek
If you reposition the input mid-stream (seek), call `reset()` instead
of `flush()`. `reset` drops buffered packets *and* wipes codec-internal
state (LPC memory, IMDCT overlap, reference pictures) so the first
frame after the seek decodes cleanly rather than glitching:
```rust
// After repositioning your byte stream:
dec.reset()?;
// ...resume feeding packets from the new position...
```
### Encoding is symmetric
The `Encoder` trait mirrors `Decoder` — build via
`reg.make_encoder(¶ms)`, feed raw `Frame`s with `send_frame`, pull
encoded `Packet`s with `receive_packet`. See each codec's README for
per-format parameter requirements (sample rate, channel count,
bitrate, pixel format, etc.).
## Extending the registry
Codec crates expose a single `register(reg)` function that hands one
`CodecInfo` per codec id to the registry. The struct is
`#[non_exhaustive]` with a builder, so new optional fields can be added
in future releases without breaking existing codec crates. The registry
supports multiple implementations per codec id with priority +
capability fallback, so you can register a fast-but-lossy impl and a
slow-but-accurate one and let the registry pick based on
`CodecPreferences`:
```rust
use oxideav_codec::{CodecInfo, CodecRegistry};
use oxideav_core::{CodecCapabilities, CodecId};
pub fn register(reg: &mut CodecRegistry) {
reg.register(
CodecInfo::new(CodecId::new("my-codec"))
.capabilities(CodecCapabilities::audio("my_codec_sw").with_lossless(true))
.decoder(make_decoder)
.encoder(make_encoder),
);
}
```
## Claiming container tags
A codec attaches container-level tags to its registration via
`.tag(...)` / `.tags([...])`. Tag kinds live in
`oxideav_core::CodecTag` — AVI FourCC, WAVEFORMATEX `wFormatTag`, MP4
`ObjectTypeIndication`, and Matroska CodecID strings. An optional
`.probe(fn)` returns a `f32` confidence in `0.0..=1.0` so the registry
can disambiguate genuine collisions (the famous
`DIV3`-tagged-as-MPEG-4-Part-2 case). The highest-confidence claim
wins; no probe means "confidence 1.0 for every claimed tag".
```rust
use oxideav_codec::{CodecInfo, CodecRegistry};
use oxideav_core::{CodecId, CodecTag, ProbeContext};
fn probe_is_mpeg4_part2(ctx: &ProbeContext) -> f32 {
match ctx.packet {
Some(d) if d.windows(3).take(8).any(|w| w == [0, 0, 1]) => 1.0,
Some(_) => 0.0,
None => 0.5,
}
}
pub fn register(reg: &mut CodecRegistry) {
let id = CodecId::new("mpeg4video");
// Unambiguous claims.
reg.register(
CodecInfo::new(id.clone())
.capabilities(/* ... */ CodecCapabilities::video("mpeg4_sw"))
.decoder(make_decoder)
.encoder(make_encoder)
.tags([CodecTag::fourcc(b"XVID"), CodecTag::fourcc(b"DX50")]),
);
// Ambiguous: DIV3 is usually msmpeg4v3 but sometimes real Part 2.
// Tag-only (no factories — already registered above) with a probe
// that inspects the bitstream.
reg.register(
CodecInfo::new(id)
.probe(probe_is_mpeg4_part2)
.tag(CodecTag::fourcc(b"DIV3")),
);
}
```
Demuxers resolve via `registry.resolve_tag(&ProbeContext)` — which is
what `oxideav-avi`, `oxideav-mp4`, and `oxideav-mkv` do internally,
filling in hints like `bits_per_sample`, `channels`, and `width` /
`height` from the container metadata they've already parsed.
## License
MIT — see [LICENSE](LICENSE).