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
impl VideoDecoder
Sourcepub fn open(parameters: Parameters) -> Result<Self>
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
openitself, 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.
Sourcepub fn open_timed(parameters: Parameters, timebase: Timebase) -> Result<Self>
pub fn open_timed(parameters: Parameters, timebase: Timebase) -> Result<Self>
Self::open, with the stream’s packet timebase declared.
Prefer this one whenever the caller knows the timebase, which
is nearly always: the packets fed to a decoder carry timestamps in
their stream’s units, and AVCodecContext.pkt_timebase is how
libavcodec is told what those units are. Not owning the
AVStream does not mean not knowing its ruler.
The value reaches every AVCodecContext this decoder opens,
including the ones a later hardware probe advance opens.
Sourcepub fn open_with_frame_limits(
parameters: Parameters,
limits: DecoderLimits,
) -> Result<Self>
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.
Sourcepub fn open_with_frame_limits_timed(
parameters: Parameters,
limits: DecoderLimits,
timebase: Timebase,
) -> Result<Self>
pub fn open_with_frame_limits_timed( parameters: Parameters, limits: DecoderLimits, timebase: Timebase, ) -> Result<Self>
Self::open_with_frame_limits, with the stream’s packet
timebase declared — see Self::open_timed.
Sourcepub fn open_with(parameters: Parameters, backend: Backend) -> Result<Self>
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).
Sourcepub fn open_with_timed(
parameters: Parameters,
backend: Backend,
timebase: Timebase,
) -> Result<Self>
pub fn open_with_timed( parameters: Parameters, backend: Backend, timebase: Timebase, ) -> Result<Self>
Self::open_with, with the stream’s packet timebase declared —
see Self::open_timed.
Sourcepub fn open_with_limits(
parameters: Parameters,
backend: Backend,
limits: DecoderLimits,
) -> Result<Self>
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.
Sourcepub fn open_with_limits_timed(
parameters: Parameters,
backend: Backend,
limits: DecoderLimits,
timebase: Timebase,
) -> Result<Self>
pub fn open_with_limits_timed( parameters: Parameters, backend: Backend, limits: DecoderLimits, timebase: Timebase, ) -> Result<Self>
Self::open_with_limits, with the stream’s packet timebase
declared — see Self::open_timed.
Sourcepub fn with_max_probe_pending_bytes(self, bytes: usize) -> Self
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 GiBSourcepub fn backend(&self) -> Backend
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.
Sourcepub fn scaled_output_capability(&self) -> ScaledOutputCapability
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.
Sourcepub fn request_scaled_output(
&mut self,
size: (u32, u32),
) -> ScaledOutputCapability
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.
Sourcepub fn cancel_scaled_output(&mut self)
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.
Sourcepub fn frame_rate(&self) -> Option<Rational>
pub fn frame_rate(&self) -> Option<Rational>
Frame rate from the codec context, if known.
Sourcepub fn send_packet(&mut self, packet: &Packet) -> Result<Sent>
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.
Sourcepub fn send_eof(&mut self) -> Result<Sent>
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].
Sourcepub fn receive_frame(&mut self, frame: &mut Frame) -> Result<Received>
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.
Sourcepub fn flush(&mut self)
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.