Expand description
mediadecode-ffmpeg
Implements mediadecode’s VideoAdapter / AudioAdapter /
SubtitleAdapter traits and the matching push-style *StreamDecoder
traits. Frame payloads are zero-copy refcounted views over FFmpeg’s
AVBufferRef via the FfmpegBuffer type — receiving a frame does
not memcpy the pixel data.
FfmpegVideoStreamDecoder mirrors the send_packet / receive_frame
shape of ffmpeg::decoder::Video, auto-probes the host’s HW backends,
and falls through to a software decoder when none open. Audio and
subtitles use parallel FfmpegAudioStreamDecoder /
FfmpegSubtitleStreamDecoder types.
§Backends
FfmpegVideoStreamDecoder::open walks this probe order, opening the
first backend that accepts the stream:
| Target | Probe order |
|---|---|
| macOS / iOS / tvOS | VideoToolbox → software |
| Linux | VAAPI → CUDA → software |
| Windows | D3D11VA → CUDA → software |
| other | software |
Output frames are CPU-side, downloaded with av_hwframe_transfer_data
(NV12 for 8-bit, P010/P012/P016/P210/P212/P216/P410/P412/P416 for
10/12/16-bit). Pixel-format conversion is intentionally out of scope
— downstream
colconv handles it.
If every HW backend opens but later fails at decode time and the
software backend is also unavailable, the error surfaces as
VideoDecodeError::Decode(Error::AllBackendsFailed(p)) carrying any
packets the decoder had already accepted from the demuxer (accessible
via p.unconsumed_packets() / p.into_unconsumed_packets()) — so
non-seekable callers (live streams, pipes, network sources) can replay
them through their own software decoder without re-demuxing.
§Usage
use ffmpeg_next as ffmpeg;
use ffmpeg::{format, media};
use mediadecode::{Timebase, decoder::VideoStreamDecoder};
use mediadecode_ffmpeg::{
Error as FfmpegError, FfmpegVideoStreamDecoder, VideoDecodeError,
empty_video_frame, video_packet_from_ffmpeg,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
ffmpeg::init()?;
let path = std::env::args().nth(1).expect("usage: <input-file>");
let mut input = format::input(&path)?;
let stream = input.streams().best(media::Type::Video).unwrap();
let stream_index = stream.index();
let time_base = Timebase::new(
stream.time_base().numerator() as u32,
std::num::NonZeroU32::new(stream.time_base().denominator() as u32).unwrap(),
);
// Probes HW backends in order, falls back to software.
let mut decoder = match FfmpegVideoStreamDecoder::open(stream.parameters(), time_base) {
Ok(d) => d,
Err(FfmpegError::AllBackendsFailed(p)) => {
// No backend at all could open this stream — including software.
// `unconsumed_packets` is empty at open-time. Caller decides.
let _unconsumed_packets = p.into_unconsumed_packets();
return Ok(());
}
Err(e) => return Err(e.into()),
};
let mut frame = empty_video_frame();
for (s, av_packet) in input.packets() {
if s.index() != stream_index { continue; }
let Some(pkt) = video_packet_from_ffmpeg(&av_packet) else { continue };
match decoder.send_packet(&pkt) {
Ok(()) => {}
Err(VideoDecodeError::Decode(FfmpegError::AllBackendsFailed(p))) => {
// Runtime exhaustion: rescued packets are the bytes the decoder
// already consumed from `input`. Replay them through your own
// software decoder before the current packet so non-seekable
// sources recover cleanly.
let _unconsumed_packets = p.into_unconsumed_packets();
return Ok(());
}
Err(e) => return Err(e.into()),
}
while decoder.receive_frame(&mut frame).is_ok() {
// frame.pixel_format(), frame.width(), frame.height(),
// frame.planes() — zero-copy views over AVBufferRef.
}
}
decoder.send_eof()?;
while decoder.receive_frame(&mut frame).is_ok() { /* drain */ }
Ok(())
}Audio and subtitle decoding share the shape — see
examples/decode_via_trait.rs and
tests/audio_subtitle_via_trait.rs
for end-to-end demuxer-driven runs that cover all three streams.
§Public surface map
- Decoders:
FfmpegVideoStreamDecoder,FfmpegAudioStreamDecoder,FfmpegSubtitleStreamDecoder. Plus their error types:VideoDecodeError,AudioDecodeError,SubtitleDecodeError. - Type aliases:
VideoPacket,AudioPacket,SubtitlePacket,VideoFrame,AudioFrame,SubtitleFrame— themediadecodegeneric types pre-parameterized with this crate’s adapter / buffer / extras, so you don’t have to spell them out. - Buffer:
FfmpegBuffer— refcounted view over anAVBufferRefwith safe constructors (empty,from_packet,try_*panic-free counterparts). - Boundary helpers:
video_packet_from_ffmpeg,audio_packet_from_ffmpeg,subtitle_packet_from_ffmpeg— convert a borrowedffmpeg::Packetinto the matchingmediadecodepacket without copying the compressed payload. - Empty-frame builders:
empty_video_frame,empty_audio_frame,empty_subtitle_frame— well-formed destinations forreceive_frame.
§Running tests and benches
The integration test and benchmark expect a real video file. Set
MEDIADECODE_SAMPLE_VIDEO to enable them:
MEDIADECODE_SAMPLE_VIDEO=/path/to/clip.mp4 cargo test
MEDIADECODE_SAMPLE_VIDEO=/path/to/clip.mp4 cargo test --test hw_smoke -- --ignored
MEDIADECODE_SAMPLE_VIDEO=/path/to/clip.mp4 cargo benchWithout the env var the integration tests skip with a notice; unit tests run unconditionally.
§Build requirements
- A system FFmpeg ≥ 5.1 linkable via
pkg-config(we referenceAV_PIX_FMT_P212LE/AV_PIX_FMT_P412LE, which were added in 5.1). Tested against 8.1. Verify withffmpeg -hwaccelsthat your build has the backends you expect compiled in (e.g.videotoolboxon macOS,vaapi/cudaon Linux,d3d11va/cudaon Windows). - Rust ≥ 1.95, edition 2024.
§License
mediadecode-ffmpeg 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 boundary::audio_packet_from_ffmpeg;pub use boundary::empty_audio_frame;pub use boundary::empty_subtitle_frame;pub use boundary::empty_video_frame;pub use boundary::from_av_pixel_format;pub use boundary::is_hardware_pix_fmt;pub use boundary::subtitle_packet_from_ffmpeg;pub use boundary::try_empty_audio_frame;pub use boundary::try_empty_subtitle_frame;pub use boundary::try_empty_video_frame;pub use boundary::video_packet_from_ffmpeg;pub use channel_layout::audio_channel_layout_from_ffmpeg;pub use channel_layout::audio_channel_order_kind_from_ffmpeg;pub use channel_layout::channel_layout_kind_from_ffmpeg;
Modules§
- boundary
- Boundary conversions between FFmpeg’s bindgen integers and the
unified
mediadecodevocabulary. - channel_
layout - Conversions from FFmpeg’s
ffmpeg_next::ChannelLayout/ffmpeg_next::ffi::AVChannelOrderto the channel-layout types mediadecode owns (mediadecode::channel::ChannelLayoutKind,mediadecode::channel::AudioChannelOrderKind,mediadecode::channel::AudioChannelSpec,mediadecode::channel::AudioChannelLayout). - convert
- Conversion helpers from FFmpeg
AVFrame/AVPacketto themediadecodetypes parameterized bycrate::Ffmpegandcrate::FfmpegBuffer. - extras
- Backend-specific
*Extracarriers used as themediadecode::*Adapter::*Extraassociated types.
Structs§
- CodecId
- Codec identifier. Wraps the integer value of an
AVCodecIDenum variant; comparisons and storage work without ever transmuting back into the bindgen enum. - Ffmpeg
- Zero-sized type carrying the FFmpeg adapter’s vocabulary.
- Ffmpeg
Audio Stream Decoder mediadecode::AudioStreamDecoderimpl wrappingffmpeg::decoder::Audio.- Ffmpeg
Buffer - Owned, refcounted handle to a contiguous byte range inside an
AVBufferRef. - Ffmpeg
Subtitle Stream Decoder mediadecode::SubtitleDecoderimpl wrappingffmpeg::decoder::Subtitle.- Ffmpeg
Video Stream Decoder mediadecode::VideoStreamDecoderimpl with transparent HW → SW fallback.- Frame
- CPU-side decoded video frame produced by
crate::VideoDecoder. - Sample
Format - Audio sample format identifier.
- Video
Decoder - Hardware-accelerated video decoder.
Enums§
- Audio
Decode Error - Errors from
FfmpegAudioStreamDecoder. - Backend
- Hardware decoding backend.
- Error
- Errors returned from
crate::VideoDecoder. - Subtitle
Decode Error - Errors from
FfmpegSubtitleStreamDecoder. - Video
Decode Error - Error type for
FfmpegVideoStreamDecoder.
Type Aliases§
- Audio
Frame - Decoded audio frame pre-parameterized with this crate’s sample format / channel layout / extras / refcounted buffer.
- Audio
Packet - Compressed audio packet pre-parameterized with this crate’s extras and refcounted buffer.
- Result
- Crate result alias.
- Subtitle
Frame - Decoded subtitle frame pre-parameterized with this crate’s extras / refcounted buffer.
- Subtitle
Packet - Compressed subtitle packet pre-parameterized with this crate’s extras and refcounted buffer.
- Video
Frame - Decoded video frame pre-parameterized with this crate’s pixel format / extras / refcounted buffer.
- Video
Packet - Compressed video packet pre-parameterized with this crate’s
extras and refcounted buffer — the type
FfmpegVideoStreamDecoderconsumes viamediadecode::decoder::VideoStreamDecoder::send_packet.