Expand description
mediadecode
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.VideoStreamDecoderalso 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 outTrackHandles — 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
futurefeature, every decoder face again withasync fnmethods, in a!Sendand aSend-bounded variant. - The vocabulary, re-exported — everything above is written against
mediaspine’s types, re-exported here:PixelFormatand the color and CFA vocabulary frommediaframe, the packet and frame households, the carrier amputation contract every backend’s buffer type obeys (on theadaptermodule), the demuxer’sTrackInfo/TrackParams/DemuxedPacket, the resampler face, andTimebase/Timestamp/TimeRangewith the rest ofmediatimeasmediadecode::mediatime.
§Cargo features
| Feature | Default | Effect |
|---|---|---|
std | yes | The 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 —
mediadecodeis FFmpeg-free and builds anywhere Rust does, includingno_stdtargets with optionalalloc.
§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 thecfamodule and movedBayerPatternunderframe::bayer, re-exported viaframe::*). - 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
Demuxersession face. - frame
- Frame types and supporting building blocks.
- future
future - Async variants of the decoder / frame-source traits — gated
behind the
futurefeature. - packet
- Compressed
Packettypes andPacketFlags. - 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§
- Inverted
Range - Returned when a change to a
TimeRangewould put itsendbefore itsstart. - Time
Range - A half-open time range
[start, end)in a givenTimebase. - 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§
- Pixel
Format - Pixel format identifier covering FFmpeg + Bayer + cinema-RAW.
- Received
- What a drain call answered.
- Sent
- What a submission answered.