Skip to main content

Crate mediadecode

Crate mediadecode 

Source
Expand description

mediadecode

The read faces of the mediaio family: the decoders and the demuxer, no_std-friendly.

github LoC Build codecov

docs.rs crates.io crates.io license

The read faces of the mediaio family: the push-style decoder traits, the pull-style frame sources, the one-shot image decoder, the Demuxer session, and their async mirrors under future. A backend implements them; a consumer programs against them and never names the backend.

The vocabulary the faces speak — frames, packets, subtitles, the push rhythm’s Sent and Received, the demuxer’s track types, the adapter traits, the resampler face, the time primitives — is mediaspine’s. This crate re-exports it under the module names it has always used (mediadecode::frame, mediadecode::packet, mediadecode::demuxer, mediadecode::Received, …), so every path a consumer wrote still resolves, to the same item a mediaspine:: path names. The write faces are mediaencode’s.

This crate ships no decoder code and no FFmpeg dependency. It is no_std-clean (with optional alloc / std features). Adapter implementations live in sibling crates: mediadecode-ffmpeg and mediadecode-webcodecs.

§What’s in the box

  • Decoder traits — VideoStreamDecoder, AudioStreamDecoder, SubtitleDecoder, ImageDecoder. The two *Stream* faces are push-style (send_packet / receive_frame / send_eof / flush), mirroring FFmpeg’s decoder API while staying backend-agnostic; the other two are not, and their names say so — a subtitle cue and a still image each come out of exactly the packet that went in. VideoStreamDecoder also states whether it can decode to a smaller size (ScaledOutputCapability).
  • Frame sources — VideoFrameSource, AudioFrameSource: the pull-style faces of clip-oriented SDKs (R3D, BRAW, ARRIRAW, X-OCN, Canon Cinema RAW Light), which hand out frames by index.
  • The demux face — Demuxer, the pull session over an opened container (tracks() / next_packet() / seek(), with the chapter table, the container duration and the measured end provided). Opening is each backend’s own, so the trait covers only the opened session. A session keeps its track table for life: tracks() hands out TrackHandles — the backend’s own row carrier, Arc / Rc / a plain borrow — so a consumer that needs a row past the pull loop clones a refcount rather than taking the table away from the session.
  • Async faces — under the future feature, every decoder face again with async fn methods, in a !Send and a Send-bounded variant.
  • The vocabulary, re-exported — everything above is written against mediaspine’s types, re-exported here: PixelFormat and the color and CFA vocabulary from mediaframe, the packet and frame households, the carrier amputation contract every backend’s buffer type obeys (on the adapter module), the demuxer’s TrackInfo / TrackParams / DemuxedPacket, the resampler face, and Timebase / Timestamp / TimeRange with the rest of mediatime as mediadecode::mediatime.

§Cargo features

FeatureDefaultEffect
stdyesThe standard library, here and in mediaspine.
alloc—Owning collections without std, here and in mediaspine.
future—The async faces under mediadecode::future.
serde—mediaspine’s wire formats.
arbitrary—mediaspine’s Arbitrary impls, for fuzzing.
quickcheck—mediaspine’s quickcheck impls.

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

[dependencies]
mediadecode = { version = "0.19", 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, and both faces answer states: send_packet answers Sent — accepted or must drain — and receive_frame answers Received — produced, needs input, or ended. Err means a fault and nothing else:

use mediadecode::{
  Received, Sent,
  adapter::VideoAdapter,
  decoder::VideoStreamDecoder,
  frame::VideoFrame,
  packet::VideoPacket,
};

type Frame<D> = VideoFrame<
  <<D as VideoStreamDecoder>::Adapter as VideoAdapter>::PixelFormat,
  <<D as VideoStreamDecoder>::Adapter as VideoAdapter>::FrameExtra,
  <D as VideoStreamDecoder>::Buffer,
>;

/// Feeds one packet and delivers every frame it made ready.
/// Answers `true` once the stream is over.
fn decode_one<D: VideoStreamDecoder>(
  decoder: &mut D,
  packet: &VideoPacket<
    <D::Adapter as VideoAdapter>::PacketExtra,
    D::Buffer,
  >,
  dst: &mut Frame<D>,
  mut on_frame: impl FnMut(&Frame<D>),
) -> Result<bool, D::Error> {
  // Offer until the decoder takes it. `MustDrain` promises nothing was
  // consumed, so the *same* packet is re-offered after a drain.
  loop {
    // `?` gives up on a real failure — and only on a real failure.
    match decoder.send_packet(packet)? {
      Sent::Accepted => break,
      Sent::MustDrain => {
        while let Received::Produced = decoder.receive_frame(dst)? {
          on_frame(dst);
        }
      }
    }
  }
  loop {
    match decoder.receive_frame(dst)? {
      Received::Produced => on_frame(dst),
      Received::NeedsInput => return Ok(false),
      Received::Ended => return Ok(true),
    }
  }
}

The compiler is what makes this correct: a consumer that forgets end-of-stream does not compile, a receive-side failure can no longer be mistaken for a drained decoder, and back pressure no longer has to be guessed at by offering every packet twice.

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.

Re-exports§

pub use mediaspine::mediatime;
pub use mediaspine;

Modules§

adapter
Adapter traits — the per-kind backend “vocabulary.”
cfa
Color-filter-array (Bayer) descriptions: re-exported from mediaframe::frame (mediaframe 0.2 dropped the cfa module and moved BayerPattern under frame::bayer, re-exported via frame::*).
color
Color metadata: re-exported from mediaframe::color.
decoder
Decoder traits — push-style streams (FFmpeg / WebCodecs / ProRes RAW via VTDecompressionSession), pull-style frame sources (R3D / BRAW / ARRIRAW / X-OCN / Canon RAW Light), and the one-shot ImageDecoder.
demuxer
The demux tier — the track table, the five-arm packet envelope, and the Demuxer session face.
frame
Frame types and supporting building blocks.
futurefuture
Async variants of the decoder / frame-source traits — gated behind the future feature.
packet
Compressed Packet types and PacketFlags.
pixel_format
Pixel format identifier: re-exported from mediaframe::pixel_format.
resampler
The resample seam — sample-rate, sample-format and channel-layout conversion for decoded audio.
rhythm
The push rhythm’s vocabulary — what a submission answered (Sent) and what a drain answered (Received), shared by every push session written against this vocabulary: the decoders, the resampler, the encoders.
subtitle
Decoded subtitle payload.

Structs§

InvertedRange
Returned when a change to a TimeRange would put its end before its start.
TimeRange
A half-open time range [start, end) in a given Timebase.
Timebase
A media timebase represented as a rational number: a non-negative numerator over a strictly positive denominator.
Timestamp
A presentation timestamp, expressed as a PTS value in units of an associated Timebase.

Enums§

PixelFormat
Pixel format identifier covering FFmpeg + Bayer + cinema-RAW.
Received
What a drain call answered.
Sent
What a submission answered.