ff-decode 0.7.1

Video and audio decoding - the Rust way
Documentation

ff-decode

Decode video and audio frames without managing codec contexts, packet queues, or timestamp conversions. Open a file, call decode_one in a loop, and receive VideoFrame objects with their position already expressed as a Timestamp.

Installation

[dependencies]

ff-decode = "0.7"

ff-format = "0.7"

Video Decoding

use ff_decode::VideoDecoder;
use ff_format::PixelFormat;

let mut decoder = VideoDecoder::open("video.mp4")
    .output_format(PixelFormat::Rgba)
    .build()?;

while let Some(frame) = decoder.decode_one()? {
    // frame.data()      — raw pixel bytes in RGBA order
    // frame.width()     — frame width in pixels
    // frame.height()    — frame height in pixels
    // frame.timestamp() — position as Timestamp
    process(&frame);
}

Iterator API

VideoDecoder and AudioDecoder implement Iterator and FusedIterator:

for frame in decoder {
    let frame = frame?;   // Iterator<Item = Result<VideoFrame, DecodeError>>
    process(&frame);
}

Audio Decoding

use ff_decode::AudioDecoder;
use ff_format::SampleFormat;

let mut decoder = AudioDecoder::open("audio.flac")
    .output_format(SampleFormat::Fltp)
    .output_sample_rate(44_100)
    .output_channels(2)   // downmix to stereo
    .build()?;

while let Some(frame) = decoder.decode_one()? {
    // frame.to_f32_interleaved() — interleaved f32 samples
    process(&frame);
}

Image Sequence Decoding

When a path contains % (printf-style pattern), VideoDecoder automatically uses the image2 demuxer. Supported extensions: .png, .jpg, .bmp, .tiff.

use ff_decode::{VideoDecoder, HardwareAccel};

// Decode a numbered PNG sequence at 25 fps.
let mut decoder = VideoDecoder::open("frames/frame%04d.png")
    .hardware_accel(HardwareAccel::None)  // recommended for still images
    .frame_rate(25)
    .build()?;

while let Some(frame) = decoder.decode_one()? {
    process(&frame);
}

OpenEXR Sequence Decoding

OpenEXR sequences use the same %-pattern mechanism. EXR frames decode as gbrpf32le (32-bit float, three planes ordered G/B/R):

use ff_decode::{VideoDecoder, HardwareAccel};
use ff_format::PixelFormat;

// Hardware decoders do not support EXR; always use HardwareAccel::None.
let mut decoder = VideoDecoder::open("frames/frame%04d.exr")
    .hardware_accel(HardwareAccel::None)
    .frame_rate(24)
    .build()?;  // returns DecodeError::DecoderUnavailable if --enable-decoder=exr
                // was omitted from the FFmpeg build

while let Some(frame) = decoder.decode_one()? {
    assert_eq!(frame.format(), PixelFormat::Gbrpf32le);
    // Access individual colour planes: plane(0)=G, plane(1)=B, plane(2)=R
    let green_plane = frame.plane(0).unwrap();
    // Each element is a 4-byte IEEE 754 f32 in native byte order.
}

10-bit and High-Bit-Depth Formats

HDR and professional content often uses 10-bit pixel formats. Request conversion via .output_format() or leave unset to receive frames in the native format:

use ff_format::PixelFormat;

// Receive frames in the native 10-bit format (no conversion).
let mut decoder = VideoDecoder::open("hdr.mkv").build()?;

// Or convert to a specific format for processing.
let mut decoder = VideoDecoder::open("hdr.mkv")
    .output_format(PixelFormat::Yuv420p10le)
    .build()?;

Common 10-bit formats: Yuv420p10le, Yuv422p10le, Yuv444p10le, P010Le.

Scaled Output

use ff_decode::VideoDecoder;
use ff_format::PixelFormat;

let mut decoder = VideoDecoder::open("4k.mp4")
    .output_format(PixelFormat::Rgb24)
    .output_size(1280, 720)   // scale + pixel-format conversion in one pass
    .build()?;

Seeking

use ff_decode::{VideoDecoder, SeekMode};
use std::time::Duration;

let mut decoder = VideoDecoder::open("video.mp4").build()?;

// Jump to the nearest keyframe at or before 30 seconds.
decoder.seek(Duration::from_secs(30), SeekMode::Keyframe)?;

// Jump to the exact position (may decode additional frames internally).
decoder.seek(Duration::from_secs(30), SeekMode::Exact)?;

Seeking does not re-open the file. The existing codec context is flushed and reused.

Hardware Acceleration

use ff_decode::{VideoDecoder, HardwareAccel};

let mut decoder = VideoDecoder::open("video.mp4")
    .hardware_accel(HardwareAccel::Auto)
    .build()?;

HardwareAccel::Auto probes for NVDEC, DXVA2, VideoToolbox, and VAAPI in that order, and falls back to software decoding if none is available.

Error Handling

Variant When it occurs
DecodeError::FileNotFound The input path does not exist
DecodeError::CannotOpen FFmpeg could not open the container or codec
DecodeError::UnsupportedCodec No decoder available for the stream's codec
DecodeError::DecoderUnavailable Codec is known but not compiled into FFmpeg
DecodeError::InvalidConfig Builder options are inconsistent or unsupported
DecodeError::Io Read error on the underlying file

What the Crate Handles for You

  • Codec context allocation and lifetime
  • PTS-to-Timestamp conversion using the stream's time base
  • Packet queue management and buffering
  • EOF signalled as Ok(None) rather than a special error variant
  • Pixel format and sample format negotiation via swscale / swresample
  • image2 demuxer selection for %-pattern paths (image sequences)

Feature Flags

Flag Description Default
tokio Enables AsyncVideoDecoder and AsyncAudioDecoder. Wraps each blocking FFmpeg call in tokio::task::spawn_blocking and exposes a futures::Stream interface via into_stream(). Requires a tokio 1.x runtime. disabled
[dependencies]

ff-decode = { version = "0.7", features = ["tokio"] }

When the tokio feature is disabled, only the synchronous VideoDecoder and AudioDecoder APIs are compiled. No tokio dependency is pulled in.

MSRV

Rust 1.93.0 (edition 2024).

License

MIT OR Apache-2.0