Skip to main content

VideoDecoder

Struct VideoDecoder 

Source
pub struct VideoDecoder { /* private fields */ }
Expand description

Hardware-accelerated video decoder.

Hardware-only — there is no software fallback inside this crate. If every hardware backend in the platform’s probe order fails to open, open returns Error::AllBackendsFailed and the caller is responsible for falling back to a software decoder of their choice (e.g. ffmpeg::decoder::Video).

Mirrors ffmpeg::decoder::Video’s send_packet/receive_frame interface. Decoded frames are returned through crate::Frame, a CPU-side wrapper whose accessors avoid the AVPixelFormat-enum UB that an unvalidated read of FFmpeg’s raw integer pixel formats can trigger.

open does a true probe: each backend opens with a strict get_format callback. On the first non-transient error from a backend the decoder is torn down and the next backend in probe order is tried, with all packets seen so far replayed through it. The advance is transactional — the candidate backend must successfully build and accept the replayed packets before any probe state is consumed, so a failing backend in the middle of the order does not strand the caller without history. Once the first frame is delivered the probe collapses and subsequent calls go straight to the active (committed) backend.

The committed backend can still fail at runtime — e.g. VideoToolbox can decode a clip’s first frames and then hit content its kernel can’t handle (H.264 High 4:2:2 10-bit), surfacing AVERROR_EXTERNAL. Post-commit a non-transient, non-EOF error from the committed backend is reclassified to Error::AllBackendsFailed (see the is_hw_decode_failure predicate), so the crate::FfmpegVideoStreamDecoder wrapper still recognises it as a HW-path exhaustion and falls back to software. The post-commit unconsumed_packets is empty (the probe buffer is gone); the wrapper’s rolling since-last-keyframe buffer supplies the replay set.

Implementations§

Source§

impl VideoDecoder

Source

pub fn open(parameters: Parameters) -> Result<Self>

Auto-probe hardware backends in the platform’s default order.

Each backend opens with a strict get_format callback. The first backend whose avcodec_open2 succeeds becomes active; if its first frame is unusable (decode error, transfer failure, or a CPU-format frame from a HW context) the decoder is torn down and the next backend is tried — packets sent so far are replayed through the new decoder transparently. The probe advance is transactional: the next backend must build and accept the replayed history before any probe state is consumed, so a misbehaving middle backend cannot strand the caller.

Self::backend reflects whichever backend ultimately produced the first frame.

Error::AllBackendsFailed surfaces in two places, with the same meaning (“no hardware backend can decode this stream — fall back to software yourself”):

  • From open itself, when no backend even opens.
  • From Self::send_packet / Self::send_eof / Self::receive_frame, when the initially-opened backend fails at decode time and every remaining backend in the probe order either also fails or doesn’t exist. On single-backend platforms (e.g. macOS, where the order is [VideoToolbox]), this is the only place a HW-only failure surfaces.

In both cases, attempts carries the per-backend error log. When the runtime path fires, unconsumed_packets also contains the packets the decoder consumed from the caller before the probe exhausted (refcounted shallow clones); for non-seekable inputs (live streams, pipes) the caller can replay these directly into a software decoder of their choice without re-demuxing. From the open-time path the vec is empty since no packets have been sent.

On Ok, the returned decoder always has an active probe rescue safety net. If a parameters clone fails under memory pressure before the probe state can be set up, open returns Err(Error::Ffmpeg(Other { errno: ENOMEM })) rather than handing back a live decoder with no fallback contract. No packets have been sent yet, so the caller can retry or fall back to software with the original parameters directly.

Source

pub fn open_with_frame_limits( parameters: Parameters, limits: DecoderLimits, ) -> Result<Self>

Self::open, with the frame ceilings named.

Taken at open for the reason Self::open_with_limits gives: [FrameLimits::max_pixels] is written into every AVCodecContext this decoder opens — including the ones a later probe advance opens — and a context’s ceiling cannot be moved after avcodec_open2.

Source

pub fn open_with(parameters: Parameters, backend: Backend) -> Result<Self>

Open the decoder with a specific backend. No probe, no fallback.

If backend cannot actually decode this stream, the failure surfaces from Self::receive_frame (the strict get_format callback returns AV_PIX_FMT_NONE, the decoder errors out). The caller is responsible for retrying with another hardware backend or falling back to a software decoder of their choice (e.g. ffmpeg::decoder::Video).

Source

pub fn open_with_limits( parameters: Parameters, backend: Backend, limits: DecoderLimits, ) -> Result<Self>

Self::open_with, with the frame ceilings named.

The limits are taken at open, not through a with_* builder, because [FrameLimits::max_pixels] is written straight into the AVCodecContext this call opens — that is the layer that makes libavcodec refuse an oversized picture before allocating it, and a context’s ceiling cannot be moved after avcodec_open2. A builder would have silently applied to only half the enforcement.

Source

pub fn with_max_probe_pending_bytes(self, bytes: usize) -> Self

Override the byte budget for probe-replay queued frames. Defaults to [DEFAULT_MAX_PROBE_PENDING_BYTES]. Use a higher value when targeting 8K+ workloads where 16 frames at full size could exceed the default; use a lower value in memory-constrained services to bound peak allocation more tightly.

Setting after the first frame has been delivered is harmless but has no observable effect — the probe has already collapsed and the cap only applies during replay drain.

Returns self for builder-style chaining:

let decoder = VideoDecoder::open(params)?
    .with_max_probe_pending_bytes(1024 * 1024 * 1024); // 1 GiB
Source

pub fn backend(&self) -> Backend

The backend currently producing frames. While the probe is still in progress (no frame received yet) this returns the optimistically selected backend; after the first frame, it is the backend that actually produced it. Once stable, never changes again.

Source

pub fn scaled_output_capability(&self) -> ScaledOutputCapability

Whether this decoder can emit pictures at a caller-requested size instead of full coded size.

ScaledOutputCapability::Supported on exactly one road: the VideoToolbox backend on an Apple target, where [crate::vtscale]’s VTPixelTransferSession sits between the decoded hardware frame and the CPU download. Every other backend this crate wires — Backend::Vaapi, Backend::Cuda, Backend::D3d11va — answers Unsupported, each with its own filed native scaling seam.

§And it stops saying Supported the moment the promise breaks

The trait’s contract is that a Supported answer lets a caller skip its own resampler, so a session that quietly went back to full-size pictures under that answer would hand the caller mixed extents with nothing to notice them by. The stage can stand down per frame — a padded pixel buffer, a crop rectangle, side data a resize would strand, a resolution change that turns the standing request into an upscale, a transfer that fails — and the first frame that goes out at anything other than the requested extent flips this answer to ScaledOutputCapability::Unsupported. That is the explicit transition a caller is told to look for: query again and learn that resampling is theirs once more. A fresh Self::request_scaled_output buys a fresh promise.

A request the stream already satisfies is not a broken promise: the picture arrives at exactly the size asked for.

A pure query: it neither requests anything nor changes what Self::receive_frame delivers.

Source

pub fn request_scaled_output( &mut self, size: (u32, u32), ) -> ScaledOutputCapability

Asks this decoder to emit pictures at size from the next frame on, and reports whether the request was recorded.

§What “from the next frame on” means, exactly

The stage is consulted per frame, on the way out of the decoder and before the GPU→CPU download. So a request placed mid-stream takes effect on the next picture receive_frame produces — never retroactively on one already decoded and queued, and never later than that.

§Refusal is not an error

ScaledOutputCapability::Unsupported comes back when this road has no stage at all (see Self::scaled_output_capability), when either requested extent is zero, or when the request is an upscale of the stream’s coded size — the stage exists to move fewer bytes across the GPU→CPU bus, and enlarging a picture moves more. Nothing about Self::send_packet or Self::receive_frame can fail because of it.

A refusal returns the session to full coded size, dropping any request already standing. That is what the trait says this answer means, and the only reading a caller can act on: told Unsupported it resamples for itself, and a session that went on quietly fitting to an older request would have it resample an already-fitted picture. Like an acceptance, it takes effect from the next picture; one already decoded keeps the extent it was decoded at.

See ScaledOutputCapability for the determinism trade a caller takes on by acting on a Supported answer.

Source

pub fn cancel_scaled_output(&mut self)

Withdraws any standing scaled-output request, returning this decoder to full coded size from the next frame.

The refusal road that does not go through Self::request_scaled_output: the wrapper refuses a request placed while a decoded picture is parked, and a refusal has to mean the same thing there as everywhere else — the session is returning to full coded size. Without this the old request would stay armed behind an Unsupported answer, and a caller acting on that answer would resample pictures that were already fitted.

Source

pub fn width(&self) -> u32

Decoder width in pixels.

Source

pub fn height(&self) -> u32

Decoder height in pixels.

Source

pub fn time_base(&self) -> Rational

Codec context time base.

Source

pub fn frame_rate(&self) -> Option<Rational>

Frame rate from the codec context, if known.

Source

pub fn send_packet(&mut self, packet: &Packet) -> Result<Sent>

Submit a packet to the decoder.

On success — and only on success — the packet is buffered for potential replay through a fallback backend while the probe is active. EAGAIN (the decoder needs receive_frame to drain output first) is Sent::MustDrain: nothing was consumed, so the caller drains and offers the same packet again. AVERROR_EOF is not back pressure on this face — it means this decoder was already told the stream ended — so it stays a fault. See [send_status].

While the probe is active, a non-transient error (e.g. the active HW backend rejecting this stream’s geometry on first packet) advances the probe to the next candidate and retries the packet there. The caller observes only the eventual success or, if the probe is exhausted, the final error.

Atomic probe rescue. While the probe is active, the rescue invariant is that everything FFmpeg has consumed since open is reflected in buffered_packets (so a future Error::AllBackendsFailed can hand a complete replay history back to the caller for software fallback on a non-seekable input). If we cannot prove this packet is buffer-able — its side-data entry count exceeds [MAX_PROBE_PACKET_SIDE_DATA_ENTRIES], its bytes would push the probe past [MAX_PROBE_PACKETS] or [MAX_PROBE_PACKET_BYTES], or av_packet_ref fails ENOMEM — send_packet returns Error::AllBackendsFailed without invoking state.inner.send_packet on this packet. The caller’s packet stays in their hand and unconsumed_packets carries the pre-existing buffered history, so they can replay unconsumed_packets plus the current packet through their software decoder of choice. The post-probe path (after the first frame, when self.probe is None) skips this pre-flight entirely.

Source

pub fn send_eof(&mut self) -> Result<Sent>

Signal end-of-stream to the decoder.

Recorded for replay only if the underlying send_eof succeeds. While the probe is active, non-transient errors trigger probe advance and retry, matching send_packet’s behaviour.

Answers Sent::MustDrain on EAGAIN — the end-of-stream was not recorded, so drain and signal again. A second EOF is a caller fault and stays one; see [send_status].

Source

pub fn receive_frame(&mut self, frame: &mut Frame) -> Result<Received>

Receive a CPU-side decoded frame.

The frame is downloaded with av_hwframe_transfer_data and metadata is copied via av_frame_copy_props. The caller’s frame is always unref’d first, so reuse across resolution changes or different decoders is safe.

While the probe window is open, any non-transient failure (decode error, transfer error, copy_props error, or a CPU-format frame from a HW-opened context) tears down the current decoder and advances to the next hardware backend in probe order, replaying buffered packets through it. Frames the candidate produced during replay (drained when send_packet returned EAGAIN) are queued and delivered FIFO via this method, so the caller never loses initial frames after a fallback.

This crate is hardware-only: there is no software fallback inside the decoder. When every backend in the probe order has been exhausted — including the case of a single-backend platform whose only backend failed — this returns Error::AllBackendsFailed with the per- backend attempt log so the caller can branch into a software decoder of their choice.

Answers the same three states ffmpeg::decoder::Video does, in the shape the trait tier publishes: Received::NeedsInput where libavcodec says EAGAIN, Received::Ended where it says EOF, and Received::Frame when frame was written. The errno stops here — the two flow signals never leave this crate as Error::Ffmpeg, so a caller has nothing to decode.

Source

pub fn flush(&mut self)

Flush internal buffers (e.g. after a seek).

Discards every frame buffered by the decoder, every frame queued during probe replay (pending_frames), and the residual hw_frame scratch buffer. Probe-time replay state (buffered packets, EOF marker) is also cleared since post-seek packets do not align with the previously captured history. After a flush, the next receive_frame waits for new post-seek input.

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more