mediadecode 0.5.0

Generic, no_std-friendly type-and-trait spine for media decoders (FFmpeg, WebCodecs, R3D, BRAW, ARRIRAW, X-OCN, ProRes RAW, Canon Cinema RAW Light).
Documentation

Generic, no_std-friendly type-and-trait spine for media decoders.

The backend-agnostic core of the mediadecode workspace. Defines the unified Packet / Frame types, VideoAdapter / AudioAdapter / SubtitleAdapter traits, and the matching push-style *StreamDecoder traits that concrete decoder backends implement.

This crate ships no decoder code and no FFmpeg dependency. It's no_std-clean (with optional alloc / std features) and zero heavy deps — downstream crates (colconv, scenesdetect, …) program against this vocabulary regardless of which backend produced the bytes. Adapter implementations live in sibling crates such as mediadecode-ffmpeg.

What's in the box

  • Pixel and sample formatsPixelFormat (~270 variants covering every FFmpeg n9.0 AVPixelFormat slug plus cinema-RAW additions; sourced from videoframe and re-exported here so consumers keep their mediadecode::PixelFormat import). Unknown(u32) preserves the raw wire identifier for lossless round-trip via from_u32 / to_u32. H.273-aligned color enums (ColorMatrix, ColorPrimaries, ColorTransfer, ColorRange, ChromaLocation) and BayerPattern for RAW are similarly re-exported from videoframe.
  • Generic packet / frame typesVideoPacket<A, B>, AudioPacket<A, B>, SubtitlePacket<A, B>, VideoFrame<A, B>, AudioFrame<A, B>, SubtitleFrame<A, B> parameterized over an adapter's per-item extras type A and buffer type B. Plane<B> is the generic plane carrier.
  • Adapter traitsVideoAdapter, AudioAdapter, SubtitleAdapter. A backend implements these on a zero-sized type to fix A and B once for the whole pipeline.
  • Decoder traitsVideoStreamDecoder, AudioStreamDecoder, SubtitleStreamDecoder. Push-style send_packet / receive_frame / send_eof / flush shape; mirrors FFmpeg's decoder API while staying backend-agnostic.
  • Time primitives — re-exported Timebase / Timestamp / TimeRange from mediatime, so consumers don't need a separate dep.

API style

Mirrors the mediatime idioms the rest of the findit-studio workspace uses:

  • All public fields are private; access is via field() getters, consuming with_field(value) builders, and in-place set_field(value) mutators that return &mut Self.
  • const fn everywhere the field type allows.
  • Panicking constructors come with try_* fallible counterparts (empty / try_empty, clone / try_clone, …).
  • Errors via thiserror over the stable core::error::Error, so failures still implement the Error trait under --no-default-features.

Cargo features

Feature Default Effect
std yes Enable the standard library and mediatime/default.
alloc Enable owning collections (Vec, String) without std.
serde Every type below on the wire, plus mediatime.
arbitrary Arbitrary impls for fuzzing, same coverage.
quickcheck The same coverage again, for quickcheck.

The three optional matrices cover the same types, and each type's wire shape follows what it is:

Type Tier serde wire shape
channel::ChannelLayoutKind any canonical slug — "5.1", "7.1-wide-back"
channel::AudioChannelOrderKind any canonical slug — "native", "ambisonic"
channel::AudioChannelSpec alloc map of its accessor names
channel::AudioChannelLayout alloc map of its accessor names
packet::PacketFlags any the raw bits, as a number

A name vocabulary travels as its name: an unrecognised slug is a deserialization error, where an unrecognised u32 code would decode to Unknown / Unspecified and invent a value. A bit set travels as a number, because every bit pattern is meaningful and bits this build has no constant for still have to survive the round trip. A record travels as its fields, each in its own shape.

no_std builds: disable defaults and pick alloc if you need Vec-backed payloads:

[dependencies]
mediadecode = { version = "0.5", default-features = false, features = ["alloc"] }

Usage

This crate defines the surface; concrete decoding happens in adapter crates. A backend-agnostic consumer programs against the traits:

use mediadecode::{
  decoder::VideoStreamDecoder,
  frame::VideoFrame,
  packet::VideoPacket,
};

fn decode_one<D: VideoStreamDecoder>(
  decoder: &mut D,
  packet: &VideoPacket<
    <D::Adapter as mediadecode::adapter::VideoAdapter>::PacketExtra,
    D::Buffer,
  >,
  dst: &mut VideoFrame<
    <D::Adapter as mediadecode::adapter::VideoAdapter>::PixelFormat,
    <D::Adapter as mediadecode::adapter::VideoAdapter>::FrameExtra,
    D::Buffer,
  >,
) -> Result<(), D::Error> {
  decoder.send_packet(packet)?;
  decoder.receive_frame(dst)
}

For an end-to-end example using the FFmpeg adapter, see mediadecode-ffmpeg.

Build requirements

  • Rust ≥ 1.95, edition 2024.
  • No system dependencies — mediadecode is FFmpeg-free and builds anywhere Rust does, including no_std targets with optional alloc.

License

mediadecode is under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-APACHE, LICENSE-MIT for details.

Copyright (c) 2026 FinDIT Studio authors.