Skip to main content

mediadecode_ffmpeg/video/
mod.rs

1//! `mediadecode::VideoStreamDecoder` impl over FFmpeg's hardware and
2//! software decoders.
3//!
4//! [`FfmpegVideoStreamDecoder`] opens on the [`DecodePath`] it is given:
5//!
6//! * **`Auto`** — what `open` does — probes the platform's hardware
7//!   backends in order through [`crate::VideoDecoder`]. When none of them
8//!   takes the stream before its first picture, it falls back to a
9//!   **software** `ffmpeg::decoder::Video` opened from the same
10//!   `Parameters`, and that fallback is lossless: the probe keeps every
11//!   packet it consumed and hands them back in
12//!   [`AllBackendsFailed`](crate::Error::AllBackendsFailed), and they are
13//!   replayed into the software decoder — then the packet the probe
14//!   refused — before anything else, so a non-seekable input loses
15//!   nothing. A probe that exhausts at `open` opens software at once.
16//! * **`AnyHardware`** is the same probe with software taken away: when no
17//!   backend takes the stream before its first picture, the session
18//!   reports [`AllBackendsFailed`](crate::Error::AllBackendsFailed) with
19//!   the packets the probe took (none, when no backend opens at all), and
20//!   the caller decides what to replay them into.
21//! * **`Hardware(b)`** is that one backend or nothing, committed at open.
22//! * **`Software`** is libavcodec's own decoder, with no probe.
23//!
24//! # After the first picture, nothing changes the road
25//!
26//! Once a backend has committed — at its first picture, or at open when
27//! it was named — the session stays on it, and nothing is classified.
28//! Every decoder failure ([`VideoDecodeError::Decode`]) is that picture's
29//! own error, reported as it was minted, and nothing of it is remembered,
30//! so the next call reaches libavcodec. Whether a hardware session
31//! recovers is FFmpeg's: after a VideoToolbox restart that fails, every
32//! picture fails the same way until a new parameter set re-arms the
33//! restart, and `flush` rebuilds no hardware session (see
34//! [`crate::VideoDecoder`] for the lines). This wrapper neither waits for
35//! a recovery nor rules one out.
36//!
37//! A [`VideoDecodeError::Convert`] is this wrapper's own failure to
38//! convert a decoded picture into a frame, not the decoder's. One that
39//! failed on an allocation parks the picture, and until a `receive_frame`
40//! delivers it, `send_packet` and `send_eof` answer `Sent::MustDrain`
41//! without reaching libavcodec: back pressure, not a failure.
42//!
43//! **`Auto` does not go on in software at that point.** A software
44//! decoder that starts mid-stream holds no reference pictures, and after
45//! the first picture this session keeps no packets to give it, so it
46//! cannot switch without a gap. Whether to switch, and what gap to
47//! accept, is the caller's: it sees the failures and what it has
48//! delivered, and it decides what to keep. [`DecodePath`] describes the
49//! simplest policy.
50//!
51//! Frames produced by either decoder are converted via
52//! [`crate::convert::av_frame_to_video_frame`] so the consumer sees the
53//! same `mediadecode::VideoFrame<PixelFormat, VideoFrameExtra,
54//! FfmpegBytes>` shape regardless of which backend produced it.
55
56use std::collections::VecDeque;
57
58/// Maximum number of frames the SW fallback replay path will buffer
59/// while draining the new SW decoder during packet/EOF replay.
60/// Replaying many compressed packets through SW can produce hundreds
61/// of decoded frames before the fallback commits; with no cap the
62/// resident memory grows unbounded (e.g. 4K frames at ~12 MB each ×
63/// 100s of frames). 64 frames is enough room to absorb every
64/// realistic codec's reorder/lookahead window without becoming a
65/// resource sink.
66const SW_REPLAY_FRAME_CAP: usize = 64;
67
68use derive_more::{IsVariant, TryUnwrap, Unwrap};
69use ffmpeg_next::{Packet, codec::Parameters, frame};
70use mediadecode::{
71  Received, Sent, Timebase,
72  decoder::{ScaledOutputCapability, VideoStreamDecoder},
73  frame::VideoFrame,
74  packet::VideoPacket,
75};
76
77use crate::{
78  Backend, DecoderLimits, Error, Ffmpeg, Frame, VideoDecoder, boundary,
79  convert::{self, ConvertError},
80  decoder::{build_codec_context, try_clone_parameters},
81  error::FallbackFailed,
82  extras::{VideoFrameExtra, VideoPacketExtra},
83  frame::alloc_av_video_frame,
84};
85
86/// Which decode path a video session takes — the choice
87/// [`CarrierVideoStreamDecoder::open_as`] is given.
88///
89/// # Three words and a pin, and what each permits
90///
91/// - [`Auto`](Self::Auto): before the first picture, the platform's
92///   hardware backends in probe order, the packets taken so far replayed
93///   across them; when none takes the stream, software, the same packets
94///   replayed into it. After it, the decoder that produced it.
95/// - [`AnyHardware`](Self::AnyHardware): the same probe, and never
96///   software — when no backend takes the stream,
97///   [`Error::AllBackendsFailed`] hands back the packets the probe took.
98///   After the first picture, the backend that produced it.
99/// - [`Software`](Self::Software): libavcodec's own decoder, with no
100///   probe, before and after.
101/// - [`Hardware(b)`](Self::Hardware), the pin: backend `b`, committed at
102///   open; nothing else is tried, before or after.
103///
104/// **After the first picture nothing changes the road, on any path, and
105/// nothing is classified.** A decoder failure
106/// ([`VideoDecodeError::Decode`]) is that picture's own error, reported as
107/// it was minted, and nothing of it is remembered, so the next call
108/// reaches libavcodec. Whether a hardware session recovers is FFmpeg's:
109/// after a VideoToolbox restart that fails, every picture fails the same
110/// way until a new parameter set re-arms the restart, and `flush` rebuilds
111/// no hardware session. See [`VideoDecoder`](crate::VideoDecoder) for the
112/// lines.
113///
114/// # When to stop trusting a hardware session
115///
116/// A caller that sees a hardware session's decoder failures persist
117/// rebuilds: it opens a session on [`Software`](Self::Software) from the
118/// same parameters and feeds it forward. When to do so is the caller's
119/// policy, not this crate's, because it is the caller that sees the
120/// failures on all three roads, the packets' key flags and what it has
121/// delivered. What the new session can be fed depends on the road the
122/// failure came from:
123///
124/// - **`send_packet`**: the failure names the packet in hand, and the new
125///   session is given that packet.
126/// - **`receive_frame`**: the failure may concern a packet accepted
127///   earlier. FFmpeg decouples input from output and may hold several
128///   pictures (`libavcodec/avcodec.h` 90–139 in FFmpeg 9.0.1), so no
129///   packet is named: the new session is given the next packet, and the
130///   caller accepts the gap.
131/// - **`send_eof`**: the end has no packet to give a new session. What
132///   the hardware session still held is recoverable only from packets
133///   kept from before, or by a seek.
134///
135/// What a policy counts is the hardware session's decoder failures,
136/// [`VideoDecodeError::Decode`]. A [`VideoDecodeError::Convert`] is this
137/// wrapper's own: a decoded picture it could not convert into a frame (a
138/// frame ceiling, a pixel format or plane layout it cannot carry, an
139/// allocation). It is reported, not counted. One that failed on an
140/// allocation parks the picture: the next `receive_frame` converts it
141/// again, and until one delivers it `send_packet` and `send_eof` answer
142/// [`Sent::MustDrain`] without reaching libavcodec. That is the wrapper's
143/// back pressure, not a failure.
144///
145/// The example in the [crate documentation](crate) is the simplest such
146/// policy:
147///
148/// - It counts the hardware session's decoder failures from all three
149///   roads, and only a delivered picture ends the count.
150/// - At a threshold of its own it opens [`Software`](Self::Software) from
151///   the same parameters and feeds it forward, road by road as above.
152///   Every picture the new session delivers is delivered as it is.
153/// - On software its failures are reported and the stream goes on:
154///   software is where the policy ends.
155///
156/// It keeps no packets, so the pictures from a failure to the next
157/// keyframe are lost: the new session holds no reference pictures, and
158/// libavcodec drops or conceals what comes before one. A caller that
159/// cannot afford that gap keeps packets and replays them with a picture
160/// identity of its own; that bookkeeping is the caller's design, and the
161/// example does not show it.
162///
163/// The words differ in what they **permit**, not only where they start.
164/// That is the difference the consumers of this door need. A determinism
165/// comparison decodes *one stream* both ways and compares the pixels; a
166/// run that silently swapped paths halfway would compare nothing and say
167/// it had. An operator turning hardware off for a lane over a driver that
168/// produces wrong pixels needs it to stay off. And a node that sends
169/// hardware and software sessions to separate pools needs a hardware
170/// session that never quietly becomes a software one — which is what
171/// [`AnyHardware`](Self::AnyHardware) is for.
172///
173/// # Observability
174///
175/// [`is_hardware`](CarrierVideoStreamDecoder::is_hardware) and
176/// [`is_software`](CarrierVideoStreamDecoder::is_software) read where a
177/// session **is**, which stays a live reading — under
178/// [`Auto`](Self::Auto) it can change once, during the probe, and under
179/// the others it answers what was chosen because nothing can move it.
180///
181/// This type deliberately grows **no** `is_*` predicates of its own,
182/// where most vocabularies in this crate do. They would spell the
183/// decoder's two questions a second time with a different meaning —
184/// `path.is_software()` is *what was asked for* and
185/// `decoder.is_software()` is *where it ended up*, and under
186/// [`Auto`](Self::Auto) those genuinely differ. A caller that needs to
187/// branch on the choice it made already holds the value and can
188/// `match` it.
189#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
190pub enum DecodePath {
191  /// Probe the platform's hardware backends in order and fall back to
192  /// software when none takes the stream before its first picture, the
193  /// packets the probe consumed replayed into it so nothing is lost.
194  /// After the first picture the session stays on the decoder that
195  /// produced it.
196  ///
197  /// What [`CarrierVideoStreamDecoder::open`] does.
198  Auto,
199  /// **Any of the platform's hardware backends, and never software.**
200  ///
201  /// The same probe as [`Auto`](Self::Auto): the backends in the
202  /// platform's probe order, the packets taken before the first picture
203  /// replayed across them as each one is tried. Where `Auto` would then
204  /// open software, this arm refuses — at
205  /// [`open_as`](CarrierVideoStreamDecoder::open_as) when no backend
206  /// opens (on a platform with no hardware backend at all, at once), and
207  /// on the road that met it when the probe exhausts later — with
208  /// [`Error::AllBackendsFailed`] carrying every backend's attempt and the
209  /// packets the probe took, so the caller can replay them into a
210  /// software session of its own. After the first picture it is the
211  /// backend that produced it, as on every path.
212  ///
213  /// [`is_hardware`](CarrierVideoStreamDecoder::is_hardware) answers
214  /// `true` for the session's whole life.
215  AnyHardware,
216  /// **This hardware backend, or nothing.** No other backend is probed
217  /// and software is never opened.
218  ///
219  /// A backend that cannot be opened for the stream fails the
220  /// [`open_as`](CarrierVideoStreamDecoder::open_as) call. The backend is
221  /// committed from open, so every failure after it is that picture's
222  /// own error, as on every path.
223  Hardware(Backend),
224  /// **Software, with no probe at all.**
225  ///
226  /// Opens `libavcodec`'s own decoder for the stream directly. There is
227  /// no hardware in this session to fail, so there is nothing for it to
228  /// fall back from — the state [`Auto`](Self::Auto) reaches when its
229  /// probe exhausts, entered on purpose.
230  Software,
231}
232
233/// `mediadecode::VideoStreamDecoder` impl over FFmpeg's hardware and
234/// software decoders, on the [`DecodePath`] it was opened on — under
235/// [`DecodePath::Auto`], hardware with a lossless fallback to software
236/// before the first picture.
237pub struct CarrierVideoStreamDecoder<C: crate::FfmpegCarrier> {
238  state: DecodeState,
239  /// The path this session was opened on — see [`DecodePath`].
240  ///
241  /// Read for exactly one question, [`Self::may_open_software`]: whether
242  /// a probe's exhaustion is this session's cue to open software or its
243  /// cue to report. Kept as the whole choice rather than reduced to that
244  /// bit so a session can say what it *is*, not only what it allows.
245  path: DecodePath,
246  /// Codec parameters retained so we can open a software
247  /// `ffmpeg::decoder::Video` if the HW probe exhausts.
248  parameters: Parameters,
249  /// HW-side scratch frame (filled by [`VideoDecoder::receive_frame`]).
250  hw_scratch: Frame,
251  /// SW-side scratch frame (filled by `ffmpeg::decoder::Video::receive_frame`).
252  sw_scratch: frame::Video,
253  /// Frames produced while draining the SW decoder during fallback
254  /// replay (see [`Self::fall_back_to_sw`]). The trait's
255  /// `receive_frame` delivers from this queue before pulling new
256  /// frames from the SW decoder. Empty in steady-state operation.
257  sw_replay_frames: VecDeque<frame::Video>,
258  /// Resource ceilings for the frames this decoder exports, and for the
259  /// `AVCodecContext`s it opens — HW candidates, the SW fallback, and
260  /// any decoder a later probe advance builds all get the same number.
261  limits: DecoderLimits,
262  /// `true` once `send_eof` has been called on the active decoder.
263  /// Used to propagate EOF to the SW decoder when fallback fires
264  /// during the drain phase — without this, codecs that hold tail
265  /// frames at EOF would hang waiting for an EOF they already saw on
266  /// the HW path.
267  eof_sent: bool,
268  /// Source-stream time base, used to label produced frames.
269  time_base: Timebase,
270  /// The lane this decoder captures into. A marker: the carrier
271  /// appears in the frames it produces, not in its own state.
272  /// `true` when the scratch frame holds a decoded frame whose
273  /// conversion has **not committed** — see
274  /// [`CarrierAudioStreamDecoder::scratch_pending`](crate::audio::CarrierAudioStreamDecoder)
275  /// for the reasoning, which is the same on both roads.
276  ///
277  /// **This decoder has two scratches and can change which one is
278  /// current, so the seat is enforced rather than merely recorded.**
279  /// While it is set, `send_packet` and `send_eof` answer
280  /// [`Sent::MustDrain`]: both are the roads that commit a
281  /// hardware-to-software fallback, and a fallback under a parked frame
282  /// would leave the retry reading the *other* scratch — delivering a
283  /// stale frame, or refusing permanently and stranding a decoded one.
284  /// Refusing makes the retry's state the state that parked it **by
285  /// construction**, which is a stronger guarantee than remembering
286  /// which road produced it.
287  ///
288  /// **The discipline is unchanged; only its spelling moved.** It was
289  /// `VideoDecodeError::FramePending`, and the escape was already
290  /// documented as "call `receive_frame`, or `flush` to abandon it" —
291  /// which is to say it was back pressure wearing an error's clothes.
292  /// Now it says so, and a caller can act on it without inspecting a
293  /// backend-specific error type. The subtitle decoder keeps the same
294  /// seat one road over, spelled the same way.
295  scratch_pending: bool,
296  _carrier: core::marker::PhantomData<C>,
297}
298
299/// Hardware-decode seam behind [`DecodeState::Hw`]. In production this is
300/// the real [`VideoDecoder`]; tests substitute a fake to drive the
301/// probe-era fallback and the committed road without a live GPU. Mirrors
302/// the subset of `VideoDecoder`'s surface the wrapper drives on the HW
303/// path.
304pub(crate) trait HwInner: Send {
305  /// See [`VideoDecoder::send_packet`].
306  fn send_packet(&mut self, packet: &Packet) -> Result<Sent, Error>;
307  /// See [`VideoDecoder::receive_frame`].
308  fn receive_frame(&mut self, frame: &mut Frame) -> Result<Received, Error>;
309  /// See [`VideoDecoder::send_eof`].
310  fn send_eof(&mut self) -> Result<Sent, Error>;
311  /// See [`VideoDecoder::flush`]. Returns `Result` for a uniform seam even
312  /// though the inherent method is infallible.
313  fn flush(&mut self) -> Result<(), Error>;
314  /// Downcast to the concrete [`VideoDecoder`] when this seam is the real
315  /// HW decoder, so [`FfmpegVideoStreamDecoder::hardware_inner`] can keep
316  /// exposing it. Returns `None` for a test fake.
317  fn as_video_decoder(&self) -> Option<&VideoDecoder>;
318
319  /// See [`VideoDecoder::scaled_output_capability`].
320  ///
321  /// Defaulted to the refusal so a test fake — which has no
322  /// VideoToolbox road behind it, and therefore no stage — answers
323  /// honestly without having to say so.
324  fn scaled_output_capability(&self) -> ScaledOutputCapability {
325    ScaledOutputCapability::Unsupported
326  }
327
328  /// See [`VideoDecoder::request_scaled_output`]. Defaulted to the
329  /// refusal, for the same reason as above.
330  fn request_scaled_output(&mut self, size: (u32, u32)) -> ScaledOutputCapability {
331    let _ = size;
332    ScaledOutputCapability::Unsupported
333  }
334
335  /// See [`VideoDecoder::cancel_scaled_output`]. Defaulted to nothing,
336  /// because a seat that never accepts a request has none to withdraw.
337  fn cancel_scaled_output(&mut self) {}
338}
339
340impl HwInner for VideoDecoder {
341  #[inline]
342  fn send_packet(&mut self, packet: &Packet) -> Result<Sent, Error> {
343    VideoDecoder::send_packet(self, packet)
344  }
345  #[inline]
346  fn receive_frame(&mut self, frame: &mut Frame) -> Result<Received, Error> {
347    VideoDecoder::receive_frame(self, frame)
348  }
349  #[inline]
350  fn send_eof(&mut self) -> Result<Sent, Error> {
351    VideoDecoder::send_eof(self)
352  }
353  #[inline]
354  fn flush(&mut self) -> Result<(), Error> {
355    VideoDecoder::flush(self);
356    Ok(())
357  }
358  #[inline]
359  fn as_video_decoder(&self) -> Option<&VideoDecoder> {
360    Some(self)
361  }
362  #[inline]
363  fn scaled_output_capability(&self) -> ScaledOutputCapability {
364    VideoDecoder::scaled_output_capability(self)
365  }
366  #[inline]
367  fn request_scaled_output(&mut self, size: (u32, u32)) -> ScaledOutputCapability {
368    VideoDecoder::request_scaled_output(self, size)
369  }
370  #[inline]
371  fn cancel_scaled_output(&mut self) {
372    VideoDecoder::cancel_scaled_output(self);
373  }
374}
375
376/// Internal: which backend is currently driving the decode.
377enum DecodeState {
378  /// Hardware-backed decoder. Under [`DecodePath::Auto`] it may
379  /// transition to `Sw` on its probe's `AllBackendsFailed`, before the
380  /// first picture, and at no other time. Boxed behind [`HwInner`] so
381  /// tests can inject a fake HW decoder.
382  Hw(Box<dyn HwInner>),
383  /// Software decoder. Terminal state.
384  Sw(SwDecoder),
385}
386
387/// A software decoder and the callback state its codec context points
388/// at.
389///
390/// The state carries the allocator judge's byte budget and the
391/// `get_format` declination; it has to outlive the `AVCodecContext`
392/// that references it, which is why it is a field here rather than a
393/// value dropped at the end of `open_sw_decoder`.
394///
395/// `Deref` so that every call site keeps talking to the decoder and
396/// only the construction changed — this pairing is a lifetime fact, not
397/// a new abstraction.
398pub(crate) struct SwDecoder {
399  decoder: ffmpeg_next::decoder::Video,
400  /// Declared **after** the decoder: fields drop in declaration order,
401  /// so the codec context is freed before the state it points at.
402  _callback_state: Box<crate::ffi::CallbackState>,
403}
404
405impl SwDecoder {
406  /// The callback state this decoder's codec context points at.
407  ///
408  /// Handed out as a raw pointer so an error closure can consult it
409  /// while the decoder itself is mutably borrowed — every software send
410  /// / receive / EOF failure on this road goes through
411  /// [`crate::decoder::software_exit`] with it, so a frame the
412  /// allocator judge refused surfaces named instead of as the `EINVAL`
413  /// libavcodec also uses for corrupt input.
414  ///
415  /// `Deref` alone was not enough: it exposes the decoder and hides the
416  /// state, so every call site kept wrapping raw and the budget refusal
417  /// had no way out on the whole software road — including the replay
418  /// helper, which drops the state when it finishes.
419  pub(crate) fn state(&self) -> *const crate::ffi::CallbackState {
420    &*self._callback_state
421  }
422}
423
424impl core::ops::Deref for SwDecoder {
425  type Target = ffmpeg_next::decoder::Video;
426  fn deref(&self) -> &Self::Target {
427    &self.decoder
428  }
429}
430
431impl core::ops::DerefMut for SwDecoder {
432  fn deref_mut(&mut self) -> &mut Self::Target {
433    &mut self.decoder
434  }
435}
436
437impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierVideoStreamDecoder<C> {
438  /// Opens a decoder for the given codec parameters with the default
439  /// HW backend probe order. If the HW probe can't open any backend,
440  /// falls back to a software `ffmpeg::decoder::Video` immediately —
441  /// `open` only returns `Err` when both paths fail.
442  ///
443  /// A probe that exhausts later, before the first picture, triggers the
444  /// same software fallback with its rescued packets replayed. After the
445  /// first picture nothing changes the road — see [`DecodePath`].
446  ///
447  /// `limits` bounds what one decoded frame may cost. It is taken here
448  /// rather than through a builder because half of it —
449  /// [`DecoderLimits::max_pixels`] — is written into every
450  /// `AVCodecContext` this decoder opens, and a context's ceiling
451  /// cannot be moved after `avcodec_open2`. That includes the contexts
452  /// opened later, by a probe-era fallback or a probe advance: the
453  /// limits are retained for exactly that reason.
454  pub(crate) fn open_impl(
455    parameters: Parameters,
456    time_base: Timebase,
457    limits: DecoderLimits,
458  ) -> Result<Self, Error> {
459    Self::open_as_impl(parameters, time_base, limits, DecodePath::Auto)
460  }
461
462  /// [`Self::open_impl`], with the decode path chosen rather than
463  /// probed. `DecodePath::Auto` is the constructor above, verbatim.
464  pub(crate) fn open_as_impl(
465    parameters: Parameters,
466    time_base: Timebase,
467    limits: DecoderLimits,
468    path: DecodePath,
469  ) -> Result<Self, Error> {
470    Self::open_as_in(
471      parameters,
472      time_base,
473      limits,
474      path,
475      crate::backend::probe_order(),
476    )
477  }
478
479  /// [`Self::open_as_impl`] with the probe order named — the platform's
480  /// own for every public constructor, and an empty one for the lane
481  /// that stands on a platform with no hardware backend at all.
482  pub(crate) fn open_as_in(
483    parameters: Parameters,
484    time_base: Timebase,
485    limits: DecoderLimits,
486    path: DecodePath,
487    order: &[Backend],
488  ) -> Result<Self, Error> {
489    // ffmpeg-next's `Parameters` carries an optional `owner: Rc<dyn Any>`
490    // (when constructed from `stream.parameters()` it points back at
491    // the demuxer's `AVStream`). Upstream marks the type `Send`
492    // anyway, which is unsound the moment a non-`None` owner is in
493    // play — moving such a value across threads moves the `Rc`. We
494    // sidestep this by always storing a deep-cloned `Parameters`
495    // (`avcodec_parameters_copy` produces an owner-free copy), so
496    // the `FfmpegVideoStreamDecoder`'s `Send` reachability never
497    // depends on the caller's owner discipline.
498    //
499    // Use `try_clone_parameters` instead of `Parameters::clone` —
500    // ffmpeg-next's `clone` calls `Parameters::new()` which can
501    // return a `Parameters` whose inner pointer is null on OOM
502    // (`avcodec_parameters_alloc` returns null without indication);
503    // the subsequent `avcodec_parameters_copy` against that null
504    // destination is C UB. Our checked helper surfaces the OOM as
505    // an error instead.
506    let owned_parameters = try_clone_parameters(&parameters, limits.max_codec_parameter_bytes())?;
507    let hw_scratch = Frame::empty()?;
508    let sw_scratch = alloc_av_video_frame()?;
509    let state = match path {
510      DecodePath::Auto => match VideoDecoder::open_probing(
511        try_clone_parameters(&owned_parameters, limits.max_codec_parameter_bytes())?,
512        limits,
513        Some(time_base),
514        order,
515      ) {
516        Ok(hw) => DecodeState::Hw(Box::new(hw)),
517        Err(Error::AllBackendsFailed(_)) => {
518          // Open-time HW exhaustion: no rescued packets (open didn't
519          // see any). Just open SW directly from our owned copy.
520          let sw = open_sw_decoder(&owned_parameters, limits, Some(time_base))?;
521          DecodeState::Sw(sw)
522        }
523        Err(other) => return Err(other),
524      },
525      // **The same probe, and no software behind it.** An exhaustion at
526      // open is the answer — `AllBackendsFailed` with every backend's
527      // attempt and no packet, since none was sent — where `Auto` would
528      // have read it as its cue to open software.
529      DecodePath::AnyHardware => DecodeState::Hw(Box::new(VideoDecoder::open_probing(
530        try_clone_parameters(&owned_parameters, limits.max_codec_parameter_bytes())?,
531        limits,
532        Some(time_base),
533        order,
534      )?)),
535      // **The named backend, and no probe order at all.** Nothing is
536      // tried before it and nothing after it, which is what makes the
537      // arm a pin: an open that fails is the answer, where `Auto` would
538      // have read the same failure as a reason to look elsewhere.
539      DecodePath::Hardware(backend) => {
540        DecodeState::Hw(Box::new(VideoDecoder::open_with_limits_timed(
541          try_clone_parameters(&owned_parameters, limits.max_codec_parameter_bytes())?,
542          backend,
543          limits,
544          time_base,
545        )?))
546      }
547      // The software decoder, opened on purpose rather than reached by
548      // degrading. `DecodeState::Sw` is terminal, so this session has
549      // nothing to keep it on its path but the shape of the state
550      // machine itself.
551      DecodePath::Software => {
552        DecodeState::Sw(open_sw_decoder(&owned_parameters, limits, Some(time_base))?)
553      }
554    };
555    Ok(Self {
556      state,
557      path,
558      parameters: owned_parameters,
559      hw_scratch,
560      sw_scratch,
561      sw_replay_frames: VecDeque::new(),
562      eof_sent: false,
563      time_base,
564      limits,
565      scratch_pending: false,
566      _carrier: core::marker::PhantomData,
567    })
568  }
569
570  /// Returns `true` when this decoder has fallen back to the software
571  /// path. `false` while still on the HW probe (the initial state).
572  #[cfg_attr(not(tarpaulin), inline(always))]
573  pub(crate) const fn is_software_impl(&self) -> bool {
574    matches!(self.state, DecodeState::Sw(_))
575  }
576
577  /// Returns `true` while the HW probe is still active.
578  #[cfg_attr(not(tarpaulin), inline(always))]
579  pub(crate) const fn is_hardware_impl(&self) -> bool {
580    matches!(self.state, DecodeState::Hw(_))
581  }
582
583  /// Whether this session can currently honor a
584  /// [`Self::request_scaled_output_impl`] request. See
585  /// [`ScaledOutputCapability`] for the determinism trade a caller
586  /// takes on by requesting one.
587  ///
588  /// **[`ScaledOutputCapability::Supported`] on exactly one road: a
589  /// live VideoToolbox session on an Apple target.** There, a
590  /// `VTPixelTransferSession` sits between the decoded hardware frame
591  /// and `av_hwframe_transfer_data` and resizes the `CVPixelBuffer` on
592  /// the GPU, so the fitted picture is what crosses to the CPU — see
593  /// [`crate::vtscale`] for the design, and
594  /// [mediadecode#55](https://github.com/findit-studio/mediadecode/issues/55)
595  /// for the ruling that chose it. Everything else answers
596  /// `Unsupported`, and each refusal has its own reason rather than a
597  /// shared shrug:
598  ///
599  /// - **A session that fell back to software during its probe.** The
600  ///   stage is the hardware road's; this answer follows the session, so
601  ///   it flips to `Unsupported` the moment the fallback commits, and a
602  ///   caller that asks again learns it.
603  /// - **The other hardware backends.** [`Backend::Vaapi`],
604  ///   [`Backend::Cuda`] and [`Backend::D3d11va`] are wired in source
605  ///   (`Backend::av_hwdevice_type`, `probe_order`) but cannot be
606  ///   compiled, run or verified on a non-Linux, non-Windows host, and
607  ///   each has a native scaling seam of its own that this crate has
608  ///   not built: NVDEC/CUVID in-decode scaling
609  ///   ([#56](https://github.com/findit-studio/mediadecode/issues/56)),
610  ///   VAAPI VPP
611  ///   ([#57](https://github.com/findit-studio/mediadecode/issues/57)),
612  ///   the D3D11 Video Processor
613  ///   ([#58](https://github.com/findit-studio/mediadecode/issues/58)).
614  ///   Filed rather than fabricated.
615  /// - **Software.** See [`Self::request_scaled_output_impl`] for the
616  ///   software road's own, separate refusal.
617  ///
618  /// What the VideoToolbox road did **not** get is decode-time
619  /// scaling, and the distinction is worth keeping: inter prediction
620  /// needs full-resolution reference frames, so every road decodes full
621  /// size internally. What this seam saves is the GPU→CPU crossing and
622  /// the CPU frame at the end of it — roughly thirtyfold on a 4K stream
623  /// fitted to a 512-class box. A caller-owned `VTDecompressionSession`
624  /// would save the same crossing and no more, which is why it stays
625  /// #55's standing future enhancement rather than this release's work.
626  ///
627  /// A pure query: calling it requests nothing and changes nothing
628  /// about what [`Self::receive_frame`] delivers.
629  #[cfg_attr(not(tarpaulin), inline(always))]
630  pub(crate) fn scaled_output_capability_impl(&self) -> ScaledOutputCapability {
631    match &self.state {
632      DecodeState::Hw(hw) => hw.scaled_output_capability(),
633      DecodeState::Sw(_) => ScaledOutputCapability::Unsupported,
634    }
635  }
636
637  /// Requests that this session emit pictures at `size` from the next
638  /// frame on. See [`Self::scaled_output_capability_impl`] for which
639  /// road can honor it at all.
640  ///
641  /// # A parked frame refuses
642  ///
643  /// While [`Self::scratch_pending`] holds a decoded picture whose
644  /// conversion did not commit, this refuses. That frame is already
645  /// decided — the retry delivers it from the scratch without
646  /// consulting the stage — so accepting a new size would promise an
647  /// extent the very next frame cannot have. Drain it and ask again;
648  /// the same escape every other seat guarded by that flag offers.
649  ///
650  /// The refusal carries the same meaning as every other: the session
651  /// returns to full coded size, so any request already standing is
652  /// withdrawn. The parked picture keeps the extent it was decoded at.
653  ///
654  /// # When a mid-stream request takes effect
655  ///
656  /// On the **next** picture [`Self::receive_frame`] produces. The
657  /// stage is consulted per frame, on the way out of the hardware
658  /// decoder and before the GPU→CPU download, so a request never
659  /// reaches back to a picture already decoded and never waits longer
660  /// than the one being decoded now.
661  ///
662  /// # The two refusals this seat mints itself
663  ///
664  /// Neither is an error, and each **returns the session to full coded
665  /// size**, dropping any request already standing — what the trait
666  /// says this answer means, and the only reading a caller can act on
667  /// without risking a second resample of an already-fitted picture:
668  ///
669  /// - **A zero extent.** A zero-extent picture is not a smaller
670  ///   picture.
671  /// - **An upscale.** The stage exists to move fewer bytes across the
672  ///   GPU→CPU bus; enlarging moves more, and inventing detail the
673  ///   decoder did not produce is the caller's business rather than a
674  ///   decode session's. An *equal* size is not an upscale: it is
675  ///   accepted, and the stage simply has nothing to do.
676  ///
677  /// # The software road's refusal has its own, different shape
678  ///
679  /// Worth naming rather than folding into "no backend does this yet":
680  /// FFmpeg's software decoders have no *general* decode-time scaling
681  /// seam. The one option that comes close — `AVCodecContext.lowres`
682  /// (the CLI's `-lowres`) — falls short on three separate counts, any
683  /// one of which would disqualify it as this seam's software answer:
684  ///
685  /// 1. **Narrow codec coverage.** `lowres` is wired only into the
686  ///    legacy MPEG-family decoders (MPEG-1/2/4 part 2, H.263) that
687  ///    still carry the low-resolution IDCT machinery it depends on.
688  ///    HEVC, AV1 and VP9 — the codecs a modern HDR pipeline actually
689  ///    decodes — implement no `lowres` support at all.
690  /// 2. **The one codec that is wired is broken.** `lowres` on H.264
691  ///    (also nominally covered) has been non-functional for years —
692  ///    the decoder does not honor it correctly — so even the "old
693  ///    family" half of the promise does not hold across the board.
694  /// 3. **It is not a resize, it is reduced reconstruction.** Where it
695  ///    does work, `lowres` decodes at a coarser IDCT precision
696  ///    (`1<<lowres`), skipping reconstruction detail rather than
697  ///    decoding in full and scaling the result — later inter frames
698  ///    drift from a reference the decoder itself degraded, which is a
699  ///    different (and worse) contract than "the same picture, smaller".
700  ///
701  /// So the software road's answer is not "unimplemented" the way the
702  /// other hardware backends' is — it is "full-size decode, then the
703  /// fused conform walk downstream", by design, on every codec this
704  /// crate decodes in software.
705  #[cfg_attr(not(tarpaulin), inline(always))]
706  pub(crate) fn request_scaled_output_impl(&mut self, size: (u32, u32)) -> ScaledOutputCapability {
707    // **A parked frame is already decided, so it may not be
708    // re-promised.** [`Self::scratch_pending`] means a picture came out
709    // of the decoder and its conversion did not commit; the retry
710    // delivers *that* frame from the scratch without going back through
711    // the stage, so it will arrive at whatever extent it already has.
712    // Accepting a new size here would answer `Supported` and then hand
713    // the caller a frame the new request never touched — the exact
714    // silent mismatch [`Self::scaled_output_capability_impl`]'s promise
715    // exists to rule out. Refusing changes nothing, which is the
716    // contract for a refusal, and the caller's escape is the one this
717    // seat already documents everywhere else: drain the frame, then ask
718    // again.
719    if self.scratch_pending {
720      tracing::debug!(
721        requested_width = size.0,
722        requested_height = size.1,
723        "mediadecode-ffmpeg: scaled-output request refused while a decoded frame is parked; \
724         the session returns to full size — drain it and ask again"
725      );
726      // **A refusal means the same thing here as anywhere else.**
727      // Returning `Unsupported` while an earlier request stayed armed
728      // would leave the caller resampling pictures this session went on
729      // fitting — the very double-scale the word exists to prevent. So
730      // the standing request goes, and with it what was built for it.
731      // The parked picture keeps the extent it was decoded at, which is
732      // the same rule an *acceptance* has always carried.
733      if let DecodeState::Hw(hw) = &mut self.state {
734        hw.cancel_scaled_output();
735      }
736      return ScaledOutputCapability::Unsupported;
737    }
738    match &mut self.state {
739      DecodeState::Hw(hw) => hw.request_scaled_output(size),
740      DecodeState::Sw(_) => ScaledOutputCapability::Unsupported,
741    }
742  }
743
744  /// Borrow the inner [`VideoDecoder`] when this decoder is still on the
745  /// real HW path. Returns `None` after the SW fallback has fired (or, in
746  /// tests, when the HW seam is a fake rather than a real decoder).
747  #[cfg_attr(not(tarpaulin), inline(always))]
748  pub(crate) fn hardware_inner_impl(&self) -> Option<&VideoDecoder> {
749    match &self.state {
750      DecodeState::Hw(hw) => hw.as_video_decoder(),
751      DecodeState::Sw(_) => None,
752    }
753  }
754
755  /// Returns the time base associated with the source stream.
756  #[cfg_attr(not(tarpaulin), inline(always))]
757  pub(crate) const fn time_base_impl(&self) -> Timebase {
758    self.time_base
759  }
760
761  /// Whether this session may open a software decoder in answer to its
762  /// probe's exhaustion.
763  ///
764  /// **The one place a path that excludes software is enforced**,
765  /// consulted by all three roads that can meet a probe-era
766  /// [`Error::AllBackendsFailed`] — the two send arms and the receive
767  /// arm. It is one predicate rather than three conditions because the
768  /// promise is one: a session opened on [`DecodePath::AnyHardware`] or
769  /// [`DecodePath::Hardware`] ends on hardware or ends in an error, and
770  /// a road that forgot to ask would break that promise silently, which
771  /// is the failure mode a caller cannot see.
772  ///
773  /// It is a question for the probe era only. After the first picture no
774  /// path opens software: a failure there is that picture's own error, on
775  /// every path.
776  ///
777  /// [`DecodePath::Software`] answers `true` and it costs nothing:
778  /// `DecodeState::Sw` is terminal, so no hardware exhaustion can
779  /// reach a road that asks. Answering for it by state rather than by
780  /// path would make the predicate say something it does not mean.
781  #[cfg_attr(not(tarpaulin), inline(always))]
782  const fn may_open_software(&self) -> bool {
783    matches!(self.path, DecodePath::Auto | DecodePath::Software)
784  }
785
786  /// Internal: **probe-era** transition from HW to SW. Replays the rescued
787  /// packets (the inner decoder's buffered history, already accepted by the HW
788  /// probe but not yet decoded) through the new SW decoder so the stream resumes
789  /// seamlessly. No frame was delivered on the HW path yet, so replaying the
790  /// history is lossless.
791  ///
792  /// It is the only transition from hardware to software, and only
793  /// [`DecodePath::Auto`] takes it: after the first picture nothing
794  /// changes the road.
795  ///
796  /// **Transactional**: drained replay frames accumulate in a local
797  /// queue; we only commit them to `self.sw_replay_frames` and switch
798  /// `self.state` to `Sw` after the replay (and EOF re-forwarding, if
799  /// needed) succeed. On failure, the SW decoder, the local frame
800  /// queue, and (where reachable) any consumed packets are dropped —
801  /// `self` is left in its prior state.
802  ///
803  /// **EOF-aware**: when EOF was already accepted on the HW path
804  /// (`self.eof_sent`), the new SW decoder also receives `send_eof()`
805  /// after replay. Without this, codecs that delay tail frames hang
806  /// forever in the drain phase.
807  ///
808  /// **EAGAIN-aware**: if SW's `send_packet` returns EAGAIN during
809  /// replay, drain produced frames into the local queue and retry.
810  ///
811  /// `eof_pending` is passed as a **local** argument rather than read from
812  /// `self.eof_sent`: callers must not mutate `self.eof_sent` before this
813  /// transaction commits (see [`VideoStreamDecoder::send_eof`]), so the
814  /// in-transaction SW EOF re-forward keys off the local flag and `self`'s
815  /// EOF state is updated only after a clean commit.
816  fn fall_back_to_sw(
817    &mut self,
818    unconsumed_packets: std::vec::Vec<ffmpeg_next::Packet>,
819    eof_pending: bool,
820  ) -> Result<(), Error> {
821    tracing::info!(
822      packets_replayed = unconsumed_packets.len(),
823      eof_pending,
824      "mediadecode-ffmpeg: HW probe exhausted, falling back to software decode",
825    );
826    // Wrap the internal worker so any failure path returns the
827    // rescued packets to the caller via `Error::FallbackFailed`.
828    // Without this, non-seekable streams (live feeds, pipes) would
829    // lose every compressed byte the HW path had consumed when a
830    // fallback transition fails partway.
831    match self.fall_back_to_sw_inner(&unconsumed_packets, eof_pending) {
832      Ok(()) => Ok(()),
833      Err(source) => Err(Error::FallbackFailed(FallbackFailed::new(
834        Box::new(source),
835        unconsumed_packets,
836      ))),
837    }
838  }
839
840  /// Worker for [`Self::fall_back_to_sw`]. Returns the rescued packets
841  /// untouched on the borrowed slice; the wrapper takes ownership of
842  /// them and surfaces them in `FallbackFailed` if this returns Err.
843  fn fall_back_to_sw_inner(
844    &mut self,
845    unconsumed_packets: &[ffmpeg_next::Packet],
846    eof_pending: bool,
847  ) -> Result<(), Error> {
848    let mut sw = open_sw_decoder(&self.parameters, self.limits, Some(self.time_base))?;
849    // Bound before the decoder is mutably borrowed, so the error
850    // closures below can still consult it.
851    let sw_state = sw.state();
852    let mut local_replay: VecDeque<frame::Video> = VecDeque::new();
853    // Helper: drain SW into the local replay queue, capped at
854    // `SW_REPLAY_FRAME_CAP`.
855    //
856    // Error discipline: stop the drain **only** on the transient
857    // backpressure signals EAGAIN / EOF (the decoder has no more output for
858    // now). Every other `ffmpeg_next::Error` — e.g. `InvalidData` from a
859    // corrupt replayed packet — is a real decode failure and is propagated,
860    // so a non-recoverable error surfaces as `FallbackFailed` (carrying the
861    // replay packets) instead of being silently swallowed and the fallback
862    // committed over corruption.
863    fn drain_into(
864      sw: &mut ffmpeg_next::decoder::Video,
865      state: *const crate::ffi::CallbackState,
866      local_replay: &mut VecDeque<frame::Video>,
867    ) -> std::result::Result<(), Error> {
868      loop {
869        let mut tmp = alloc_av_video_frame()?;
870        match sw.receive_frame(&mut tmp) {
871          Ok(()) => {
872            if local_replay.len() >= SW_REPLAY_FRAME_CAP {
873              tracing::error!(
874                cap = SW_REPLAY_FRAME_CAP,
875                "mediadecode-ffmpeg: SW fallback replay produced more frames than the \
876                 replay cap allows; aborting fallback (no frames dropped — they're \
877                 still in the SW decoder's internal queue and will be released when \
878                 it drops)",
879              );
880              return Err(Error::Ffmpeg(ffmpeg_next::Error::Other {
881                errno: libc::ENOMEM,
882              }));
883            }
884            local_replay.push_back(tmp);
885          }
886          // EAGAIN / EOF: no more output for now — stop draining, success.
887          Err(ffmpeg_next::Error::Other { errno }) if errno == ffmpeg_next::error::EAGAIN => {
888            break;
889          }
890          Err(ffmpeg_next::Error::Eof) => break,
891          // Any other error is a genuine decode failure on a replayed
892          // packet — surface it so it is not masked as a clean fallback.
893          Err(other) => return Err(crate::decoder::software_exit(state, other)),
894        }
895      }
896      Ok(())
897    }
898
899    for pkt in unconsumed_packets {
900      let mut attempts: u32 = 0;
901      loop {
902        match sw.send_packet(pkt) {
903          Ok(()) => break,
904          Err(ffmpeg_next::Error::Other { errno }) if errno == ffmpeg_next::error::EAGAIN => {
905            drain_into(&mut sw, sw_state, &mut local_replay)?;
906            attempts += 1;
907            if attempts > 16 {
908              return Err(Error::Ffmpeg(ffmpeg_next::Error::Other {
909                errno: ffmpeg_next::error::EAGAIN,
910              }));
911            }
912          }
913          Err(other) => return Err(crate::decoder::software_exit(sw_state, other)),
914        }
915      }
916    }
917    // Re-forward EOF if the HW path already saw it. SW EOF can also
918    // return EAGAIN until prior output is drained — mirror the
919    // packet-replay loop.
920    if eof_pending {
921      let mut attempts: u32 = 0;
922      loop {
923        match sw.send_eof() {
924          Ok(()) => break,
925          Err(ffmpeg_next::Error::Other { errno }) if errno == ffmpeg_next::error::EAGAIN => {
926            drain_into(&mut sw, sw_state, &mut local_replay)?;
927            attempts += 1;
928            if attempts > 16 {
929              return Err(Error::Ffmpeg(ffmpeg_next::Error::Other {
930                errno: ffmpeg_next::error::EAGAIN,
931              }));
932            }
933          }
934          Err(other) => return Err(crate::decoder::software_exit(sw_state, other)),
935        }
936      }
937    }
938    // Final drain BEFORE commit — the transactional commit boundary. The
939    // EAGAIN-triggered drains above only fire when SW exerts backpressure mid
940    // replay; a SW decoder that ACCEPTS every replayed packet (and the EOF)
941    // without one then surfaces a non-transient error — `InvalidData` from a
942    // corrupt replayed packet, or any other decode failure — only on the *next*
943    // `receive_frame`. Without this drain that error would land after the
944    // commit (frames appended, `state` flipped to `Sw`, rescued packets
945    // dropped) and reach the caller as a plain decode failure, not
946    // `FallbackFailed` — breaking probe-era recovery on non-seekable input.
947    // Draining to EAGAIN/EOF here forces any such error to surface now, so it is
948    // wrapped as `FallbackFailed` (retaining the rescued packets) and the
949    // decoder stays on HW — nothing is committed.
950    drain_into(&mut sw, sw_state, &mut local_replay)?;
951    // Commit: only after replay, any EOF forwarding, AND the final drain
952    // succeeded do we move the new SW decoder and queue into `self`.
953    self.sw_replay_frames.append(&mut local_replay);
954    self.state = DecodeState::Sw(sw);
955    Ok(())
956  }
957
958  /// The one place a delivered frame is committed.
959  ///
960  /// Every road that hands a frame to the caller passes through here —
961  /// the hardware scratch, the software scratch, both replay-queue
962  /// entries, and the retry of a parked frame — so the bookkeeping a
963  /// delivery owes cannot be attached to some of them and forgotten on
964  /// others.
965  fn commit_delivery(
966    &mut self,
967    frame: VideoFrame<mediadecode::PixelFormat, VideoFrameExtra, C::Buffer>,
968    dst: &mut VideoFrame<mediadecode::PixelFormat, VideoFrameExtra, C::Buffer>,
969  ) {
970    // The scratch is free once a carrier exists for what it held.
971    self.scratch_pending = false;
972    *dst = frame;
973  }
974
975  /// Where this session is. See
976  /// [`SessionPhase`](crate::decoder::SessionPhase).
977  ///
978  /// The wrapper never sees a probe — that lives inside the hardware
979  /// seam, which derives its own — so only the committed pair is
980  /// reachable from here.
981  const fn phase(&self) -> crate::decoder::SessionPhase {
982    if self.eof_sent {
983      crate::decoder::SessionPhase::Draining
984    } else {
985      crate::decoder::SessionPhase::Streaming
986    }
987  }
988
989  /// Internal: convert the active scratch frame into a
990  /// `mediadecode::VideoFrame` and write into `dst`.
991  fn deliver_frame(
992    &mut self,
993    dst: &mut VideoFrame<mediadecode::PixelFormat, VideoFrameExtra, C::Buffer>,
994  ) -> Result<Received, VideoDecodeError> {
995    let av_frame = match &mut self.state {
996      DecodeState::Hw(_) => unsafe { self.hw_scratch.as_inner_mut().as_ptr() },
997      DecodeState::Sw(_) => unsafe { self.sw_scratch.as_ptr() },
998    };
999    // SAFETY: the scratch frame is live — either just filled by the
1000    // inner decoder's `receive_frame`, or left holding a frame whose
1001    // conversion did not commit. Convert takes what it needs out of it,
1002    // so the scratch can be reused once this has committed.
1003    let converted = unsafe {
1004      convert::av_frame_to_video_frame_as::<C>(av_frame, self.time_base, self.limits.frame())
1005    };
1006    match converted {
1007      Ok(new_frame) => {
1008        self.commit_delivery(new_frame, dst);
1009        Ok(Received::Frame)
1010      }
1011      Err(e) => {
1012        // Park only what another attempt could survive.
1013        self.scratch_pending = e.parks_in_decode();
1014        Err(VideoDecodeError::Convert(e))
1015      }
1016    }
1017  }
1018}
1019
1020#[cfg(test)]
1021impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierVideoStreamDecoder<C> {
1022  /// Build a decoder around an injected HW seam, bypassing the real probe.
1023  /// Lets tests drive the probe-era fallback and the committed road with a
1024  /// [`HwInner`] fake instead of a live GPU. The SW fallback still opens
1025  /// the **real** `ffmpeg::decoder::Video` from `parameters`, so a fallback
1026  /// in these tests genuinely decodes.
1027  pub(crate) fn from_hw_inner_for_test(
1028    hw: Box<dyn HwInner>,
1029    parameters: Parameters,
1030    time_base: Timebase,
1031  ) -> Result<Self, Error> {
1032    Self::from_hw_inner_for_test_as(hw, parameters, time_base, DecodePath::Auto)
1033  }
1034
1035  /// [`Self::from_hw_inner_for_test`], with the session's
1036  /// [`DecodePath`] named.
1037  ///
1038  /// The seam a **pinned** session's mid-stream behaviour is driven
1039  /// through: a pin's promise is about what happens when the hardware
1040  /// fails after opening, and the only way to reach that on a machine
1041  /// whose GPU works is to inject a seam that fails on demand. See
1042  /// `a_hardware_pin_reports_its_probes_exhaustion_instead_of_falling_back`.
1043  pub(crate) fn from_hw_inner_for_test_as(
1044    hw: Box<dyn HwInner>,
1045    parameters: Parameters,
1046    time_base: Timebase,
1047    path: DecodePath,
1048  ) -> Result<Self, Error> {
1049    let limits = DecoderLimits::default();
1050    let owned_parameters = try_clone_parameters(&parameters, limits.max_codec_parameter_bytes())?;
1051    Ok(Self {
1052      state: DecodeState::Hw(hw),
1053      path,
1054      parameters: owned_parameters,
1055      hw_scratch: Frame::empty()?,
1056      sw_scratch: alloc_av_video_frame()?,
1057      sw_replay_frames: VecDeque::new(),
1058      eof_sent: false,
1059      time_base,
1060      limits,
1061      scratch_pending: false,
1062      _carrier: core::marker::PhantomData,
1063    })
1064  }
1065
1066  /// Whether `send_eof` has been committed on the active decoder. Lets the
1067  /// rollback tests assert that a failed EOF fallback restores (never
1068  /// half-mutates) `eof_sent`.
1069  pub(crate) const fn eof_sent_for_test(&self) -> bool {
1070    self.eof_sent
1071  }
1072
1073  /// Whether the probe-era replay queue is empty — so a lane can tell a
1074  /// frame parked in the scratch from one still waiting in the queue.
1075  pub(crate) fn sw_replay_frames_is_empty_for_test(&self) -> bool {
1076    self.sw_replay_frames.is_empty()
1077  }
1078}
1079
1080impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierVideoStreamDecoder<C> {
1081  /// The fault a submission after end-of-stream earns on this face.
1082  ///
1083  /// **Censused from the empty-seat road rather than invented.** With
1084  /// the seat free, a post-EOF `send_packet` or a repeated `send_eof`
1085  /// reaches libavcodec, which answers `AVERROR_EOF`, and all four
1086  /// roads through this wrapper — hardware and software, packet and
1087  /// EOF — surface it as exactly this value. The gates below short
1088  /// out to the same one so a parked seat cannot change *which* answer
1089  /// a caller gets, only how quickly. `the_post_eof_fault_is_the_one_the_substrate_gives`
1090  /// pins the two against each other.
1091  ///
1092  /// Deliberately **not** a new `VideoDecodeError` arm. The subtitle
1093  /// seam had to mint `AfterEof` because `avcodec_decode_subtitle2` has
1094  /// no state machine to refuse for it; this face already has an answer
1095  /// for the condition, and a second spelling for one fault on one
1096  /// surface is the disease this release is curing.
1097  fn after_eof() -> VideoDecodeError {
1098    VideoDecodeError::Decode(Error::Ffmpeg(ffmpeg_next::Error::Eof))
1099  }
1100
1101  pub(crate) fn send_packet_impl(
1102    &mut self,
1103    packet: &VideoPacket<VideoPacketExtra, C::Buffer>,
1104  ) -> Result<Sent, VideoDecodeError> {
1105    // **The end of the stream outranks the parked seat, and the order
1106    // is the whole point.**
1107    //
1108    // `Sent::MustDrain` is a promise: drain the output and this same
1109    // offer becomes acceptable. Past end-of-stream that promise is
1110    // false — draining empties the seat and the retry still faults,
1111    // until `flush`. Checking the seat first made the wrapper answer
1112    // `MustDrain` for a submission nothing could ever accept, which is
1113    // the same fault-under-back-pressure inversion the subtitle seam
1114    // carried: a caller obeying the contract loops, drains, re-offers,
1115    // and is refused anyway.
1116    //
1117    // It is reachable: `send_eof` is accepted and sets `eof_sent`, a
1118    // delayed tail frame comes out of the decoder, its carrier
1119    // allocation fails parkably, and the seat is taken on a session
1120    // that is already over.
1121    if !self.phase().accepts_input() {
1122      return Err(Self::after_eof());
1123    }
1124    // **Nothing is sent while a frame is parked.** Both send roads can
1125    // commit a hardware-to-software fallback, and a fallback under a
1126    // parked frame would leave the retry reading the other scratch. See
1127    // [`Self::scratch_pending`]. Nothing was consumed, so this is back
1128    // pressure and the packet is still the caller's to re-offer — which
1129    // is true precisely because the stream is not over, checked above.
1130    if self.scratch_pending {
1131      return Ok(Sent::MustDrain);
1132    }
1133    let phase = self.phase();
1134    // Scoped submission: the rebuilt `AVPacket` never leaves this call,
1135    // which is what lets the view lane share its buffer with libavcodec
1136    // rather than copy into it. See `boundary::with_ffmpeg_video_packet`.
1137    let limits = self.limits.packet_limits();
1138    // **One route, on every road.** The submission may share its
1139    // carrier's buffer — wherever `boundary::share_or_copy` can prove
1140    // the padding — in the probe window as after it, hardware or
1141    // software. libavcodec may keep a reference past the call and does
1142    // not write through it (`libavcodec/avcodec.h` 2333–2335 in FFmpeg
1143    // 9.0.1). The one thing that keeps what it is sent *and hands it
1144    // back* is the hardware probe, whose rescue history goes to the
1145    // caller as owned, mutable `Packet`s
1146    // (`AllBackendsFailed::into_unconsumed_packets`); it records copies
1147    // of its own (`decoder::try_clone_packet`), so nothing it hands back
1148    // addresses a carrier.
1149    let route = crate::carrier::BodyRoute::Submission;
1150    boundary::with_ffmpeg_video_packet::<C, _>(packet, limits, route, |av_pkt| {
1151      match &mut self.state {
1152        DecodeState::Hw(hw) => match hw.send_packet(av_pkt) {
1153          // The seam already classified libavcodec's back pressure, so
1154          // both states travel on unchanged.
1155          Ok(status) => Ok(status),
1156          Err(Error::AllBackendsFailed(p)) => {
1157            // **A pinned hardware session reports rather than falls back.**
1158            // See [`Self::may_open_software`]: this is the exhaustion
1159            // `DecodePath::Auto` reads as its cue to open software, and
1160            // the pin's whole content is that it is not that cue here.
1161            // Reported with the payload intact, so the caller keeps the
1162            // backend, its error, and any rescued packets.
1163            if !self.may_open_software() {
1164              return Err(VideoDecodeError::Decode(Error::AllBackendsFailed(p)));
1165            }
1166            // The probe exhausted before the first picture: replay the
1167            // inner decoder's buffered history (lossless — no frame was
1168            // delivered yet), then forward the still-unconsumed current
1169            // packet to SW.
1170            let rescued = p.into_unconsumed_packets();
1171            // `eof_pending` is the committed EOF state — never pre-mutated here.
1172            let eof_pending = self.eof_sent;
1173            self
1174              .fall_back_to_sw(rescued, eof_pending)
1175              .map_err(VideoDecodeError::Decode)?;
1176            // Forward the new (still-unconsumed) current packet to the
1177            // freshly-opened SW decoder — the HW decoder REFUSED it, so it was not
1178            // in the replay set. A failure here surfaces (it is not silently
1179            // dropped), and back pressure from the fresh decoder is reported as
1180            // such rather than mistaken for one: the fallback committed either
1181            // way, and the caller re-offers the packet.
1182            if let DecodeState::Sw(sw) = &mut self.state {
1183              let st = sw.state();
1184              if let Err(e) = sw.send_packet(av_pkt) {
1185                return crate::decoder::software_send(st, e, phase)
1186                  .map_err(VideoDecodeError::Decode);
1187              }
1188            }
1189            Ok(Sent::Accepted)
1190          }
1191          Err(other) => Err(VideoDecodeError::Decode(other)),
1192        },
1193        DecodeState::Sw(sw) => {
1194          let st = sw.state();
1195          match sw.send_packet(av_pkt) {
1196            Ok(()) => Ok(Sent::Accepted),
1197            // Funnel, then gate: a refusal the allocator judge latched
1198            // comes back named, and back pressure is `MustDrain`.
1199            Err(e) => crate::decoder::software_send(st, e, phase).map_err(VideoDecodeError::Decode),
1200          }
1201        }
1202      }
1203    })
1204    .map_err(|e| VideoDecodeError::Decode(Error::PacketBuild(e)))?
1205  }
1206
1207  pub(crate) fn receive_frame_impl(
1208    &mut self,
1209    dst: &mut VideoFrame<mediadecode::PixelFormat, VideoFrameExtra, C::Buffer>,
1210  ) -> Result<Received, VideoDecodeError> {
1211    // Deliver any frames produced during SW fallback replay before
1212    // pulling new ones from the SW decoder. This is the queue
1213    // populated by `fall_back_to_sw` when SW returned EAGAIN during
1214    // packet replay.
1215    // **Peeked, not popped.** A replayed frame is the rescue history's
1216    // only copy: popping it before the conversion committed lost it to
1217    // any allocation failure, which is the one thing this queue exists
1218    // to prevent. It leaves the queue when a carrier exists for it.
1219    if let Some(replayed) = self.sw_replay_frames.front() {
1220      // SAFETY: `replayed` is a live AVFrame owned by this queue;
1221      // convert takes what it needs out of it.
1222      let converted = unsafe {
1223        convert::av_frame_to_video_frame_as::<C>(
1224          replayed.as_ptr(),
1225          self.time_base,
1226          self.limits.frame(),
1227        )
1228      };
1229      let new_frame = match converted {
1230        Ok(new_frame) => new_frame,
1231        Err(e) if e.parks_in_decode() => return Err(VideoDecodeError::Convert(e)),
1232        // A frame nothing can carry is dropped rather than re-offered
1233        // forever — the same rule the scratch seat follows.
1234        Err(e) => {
1235          self.sw_replay_frames.pop_front();
1236          return Err(VideoDecodeError::Convert(e));
1237        }
1238      };
1239      self.sw_replay_frames.pop_front();
1240      self.commit_delivery(new_frame, dst);
1241      return Ok(Received::Frame);
1242    }
1243    // A frame whose conversion did not commit is converted again before
1244    // the decoder is asked for another — see [`Self::scratch_pending`].
1245    // The scratch still holds it, and `deliver_frame` reads whichever
1246    // scratch the current state uses.
1247    if self.scratch_pending {
1248      return self.deliver_frame(dst);
1249    }
1250    let phase = self.phase();
1251    loop {
1252      match &mut self.state {
1253        DecodeState::Hw(hw) => match hw.receive_frame(&mut self.hw_scratch) {
1254          Ok(Received::Frame) => {
1255            // The frame is out of the decoder's queue from here; the
1256            // seat is what keeps it if the conversion cannot commit.
1257            self.scratch_pending = true;
1258            return self.deliver_frame(dst);
1259          }
1260          // The hardware seam already classified the two flow signals.
1261          Ok(status) => return Ok(status),
1262          Err(Error::AllBackendsFailed(p)) => {
1263            // The same gate, on the receive road — see
1264            // [`Self::may_open_software`] and the identical gate on the
1265            // two send roads.
1266            if !self.may_open_software() {
1267              return Err(VideoDecodeError::Decode(Error::AllBackendsFailed(p)));
1268            }
1269            // The probe exhausted at frame time, before the first picture:
1270            // replay the buffered history (lossless). There is no current
1271            // packet here.
1272            let rescued = p.into_unconsumed_packets();
1273            // `eof_pending` is the committed EOF state — never pre-mutated here.
1274            let eof_pending = self.eof_sent;
1275            self
1276              .fall_back_to_sw(rescued, eof_pending)
1277              .map_err(VideoDecodeError::Decode)?;
1278            // If the replay produced any drained frames, return one
1279            // immediately — preserves stream order vs. whatever the
1280            // SW decoder will produce next.
1281            // **Peeked, not popped** — the second delivery path onto
1282            // this queue, and it owes the same discipline as the first
1283            // (see the head of `receive_frame_impl`). The replay queue
1284            // is the rescue history's only copy of these frames, so a
1285            // conversion that cannot commit must leave the head where
1286            // it is rather than advance past it.
1287            if let Some(replayed) = self.sw_replay_frames.front() {
1288              // SAFETY: `replayed` is a live AVFrame owned by this
1289              // queue; convert takes what it needs out of it.
1290              let converted = unsafe {
1291                convert::av_frame_to_video_frame_as::<C>(
1292                  replayed.as_ptr(),
1293                  self.time_base,
1294                  self.limits.frame(),
1295                )
1296              };
1297              let new_frame = match converted {
1298                Ok(new_frame) => new_frame,
1299                Err(e) if e.parks_in_decode() => return Err(VideoDecodeError::Convert(e)),
1300                // A frame nothing can carry is dropped rather than
1301                // re-offered forever.
1302                Err(e) => {
1303                  self.sw_replay_frames.pop_front();
1304                  return Err(VideoDecodeError::Convert(e));
1305                }
1306              };
1307              self.sw_replay_frames.pop_front();
1308              self.commit_delivery(new_frame, dst);
1309              return Ok(Received::Frame);
1310            }
1311            // Fall through to the loop; next iteration takes the Sw arm.
1312          }
1313          Err(other) => return Err(VideoDecodeError::Decode(other)),
1314        },
1315        DecodeState::Sw(sw) => {
1316          // Convert inline (rather than via `deliver_frame`, which borrows all
1317          // of `self`) so only the disjoint fields `sw_scratch` / `time_base`
1318          // are touched alongside the `self.state` borrow `sw` holds.
1319          let st = sw.state();
1320          match sw.receive_frame(&mut self.sw_scratch) {
1321            Ok(()) => {
1322              // The frame is out of the decoder's queue from here; the
1323              // seat is what keeps it if the conversion cannot commit.
1324              self.scratch_pending = true;
1325              // SAFETY: the scratch frame is live (just filled by
1326              // `receive_frame`); convert takes what it needs out of
1327              // it, so the scratch can be reused once this commits.
1328              let converted = unsafe {
1329                convert::av_frame_to_video_frame_as::<C>(
1330                  self.sw_scratch.as_ptr(),
1331                  self.time_base,
1332                  self.limits.frame(),
1333                )
1334              };
1335              let new_frame = match converted {
1336                Ok(new_frame) => new_frame,
1337                Err(e) => {
1338                  self.scratch_pending = e.parks_in_decode();
1339                  return Err(VideoDecodeError::Convert(e));
1340                }
1341              };
1342              self.commit_delivery(new_frame, dst);
1343              return Ok(Received::Frame);
1344            }
1345            // Funnel first — so a recorded budget refusal is named
1346            // rather than laundered — and read as a status second:
1347            // `EAGAIN` is `NeedsInput`, `Eof` is `Ended`, and the errno
1348            // stops inside this crate either way.
1349            Err(e) => {
1350              return crate::decoder::software_receive(st, e, phase)
1351                .map_err(VideoDecodeError::Decode);
1352            }
1353          }
1354        }
1355      }
1356    }
1357  }
1358
1359  pub(crate) fn send_eof_impl(&mut self) -> Result<Sent, VideoDecodeError> {
1360    // The same two gates in the same order, for the same reason: a
1361    // repeated end-of-stream past a committed one is refused however
1362    // much is drained, so answering back pressure would be a promise
1363    // this face cannot keep. See [`Self::after_eof`].
1364    if !self.phase().accepts_input() {
1365      return Err(Self::after_eof());
1366    }
1367    // As `send_packet`: EOF can commit a fallback too. Nothing was
1368    // recorded, so drain and signal again.
1369    if self.scratch_pending {
1370      return Ok(Sent::MustDrain);
1371    }
1372    let phase = self.phase();
1373    let outcome = match &mut self.state {
1374      DecodeState::Hw(hw) => match hw.send_eof() {
1375        // The seam classified libavcodec's back pressure already.
1376        Ok(status) => Ok(status),
1377        Err(Error::AllBackendsFailed(p)) => {
1378          // The same gate, on the EOF road — see
1379          // [`Self::may_open_software`]. Returned rather than folded into
1380          // `outcome`: the commit below fires only on `Ok(Sent::Accepted)`,
1381          // so the two roads agree.
1382          if !self.may_open_software() {
1383            return Err(VideoDecodeError::Decode(Error::AllBackendsFailed(p)));
1384          }
1385          // EOF is pending for this transaction, so the SW decoder must also
1386          // receive `send_eof` (codecs that delay tail frames hang otherwise).
1387          // We pass that intent locally rather than pre-setting `self.eof_sent`:
1388          // a fallback that fails returns `FallbackFailed` and stays on HW, and a
1389          // half-mutated `self.eof_sent = true` would then make a *later*
1390          // fallback inject an EOF into SW even though this `send_eof` errored.
1391          // `self.eof_sent` is committed only after the whole operation succeeds
1392          // (the `outcome` check below), keeping the fallback all-or-nothing.
1393          //
1394          // Replay the buffered history (lossless), re-forwarding EOF inside
1395          // the transaction.
1396          let rescued = p.into_unconsumed_packets();
1397          self
1398            .fall_back_to_sw(rescued, true)
1399            .map(|()| Sent::Accepted)
1400            .map_err(VideoDecodeError::Decode)
1401        }
1402        Err(other) => Err(VideoDecodeError::Decode(other)),
1403      },
1404      DecodeState::Sw(sw) => {
1405        let st = sw.state();
1406        match sw.send_eof() {
1407          Ok(()) => Ok(Sent::Accepted),
1408          Err(e) => crate::decoder::software_send(st, e, phase).map_err(VideoDecodeError::Decode),
1409        }
1410      }
1411    };
1412    // Commit EOF state only when the EOF was actually **taken** — a failed
1413    // fallback left `self.eof_sent` untouched (restored-by-construction: we
1414    // never mutated it), so HW stays EOF-not-yet-sent and a retry behaves
1415    // correctly.
1416    //
1417    // **`is_ok()` is not the test any more, and that is not a stylistic
1418    // change.** `Ok(Sent::MustDrain)` means the decoder did not take the
1419    // end-of-stream; recording `eof_sent` there would make a later fallback
1420    // inject an EOF into the software decoder for a signal that was never
1421    // accepted — the exact half-mutation the local `eof_pending` argument
1422    // exists to prevent on the failure road.
1423    if matches!(outcome, Ok(Sent::Accepted)) {
1424      self.eof_sent = true;
1425    }
1426    outcome
1427  }
1428
1429  pub(crate) fn flush_impl(&mut self) -> Result<(), VideoDecodeError> {
1430    // Drop any frames buffered during SW fallback replay before
1431    // flushing the inner decoder — otherwise a seek/reset would
1432    // surface stale pre-flush frames on the next `receive_frame`.
1433    self.sw_replay_frames.clear();
1434    // And a parked frame belongs to the position being abandoned.
1435    self.scratch_pending = false;
1436    // Flush ends the drain phase; the decoder accepts new packets
1437    // after this, so reset EOF tracking.
1438    self.eof_sent = false;
1439    match &mut self.state {
1440      // The HW seam's `flush` returns `Result` for a uniform trait; the
1441      // real `VideoDecoder::flush` is infallible (always `Ok`).
1442      DecodeState::Hw(hw) => hw.flush().map_err(VideoDecodeError::Decode)?,
1443      DecodeState::Sw(sw) => sw.flush(),
1444    }
1445    Ok(())
1446  }
1447}
1448
1449macro_rules! video_lane_face {
1450  ($($lane:ty),+ $(,)?) => { $(
1451    impl CarrierVideoStreamDecoder<$lane> {
1452      /// Opens a video decoder for `parameters`, probing hardware
1453      /// backends in order and falling back to software.
1454      ///
1455      /// [`open_as`](Self::open_as)`(.., DecodePath::Auto)`.
1456      pub fn open(
1457        parameters: Parameters,
1458        time_base: Timebase,
1459        limits: DecoderLimits,
1460      ) -> Result<Self, Error> {
1461        Self::open_impl(parameters, time_base, limits)
1462      }
1463
1464      /// Opens a video decoder on a **named decode path**.
1465      ///
1466      /// [`DecodePath::Auto`] is [`open`](Self::open) exactly;
1467      /// [`DecodePath::AnyHardware`] keeps the session to the platform's
1468      /// hardware backends and the other two arms pin it to one backend or
1469      /// to software, each for its whole life. See [`DecodePath`] for what
1470      /// each permits and what it costs.
1471      ///
1472      /// Everything else about the session is unchanged — the same
1473      /// [`VideoStreamDecoder`] face, the same frames, the same
1474      /// [`is_hardware`](Self::is_hardware) / [`is_software`](Self::is_software)
1475      /// readings. The choice is *which decoder is behind them*, which
1476      /// is what a determinism comparison and a deployment policy each
1477      /// need and neither could reach.
1478      ///
1479      /// # Errors
1480      ///
1481      /// [`DecodePath::AnyHardware`] fails here with
1482      /// [`Error::AllBackendsFailed`] when no hardware backend opens for
1483      /// the stream — at once on a platform with none — carrying every
1484      /// backend's attempt and no packet, since none was sent.
1485      /// [`DecodePath::Hardware`] fails here when the named backend
1486      /// cannot be opened for the stream. [`DecodePath::Auto`] would have
1487      /// gone on to software in both cases. [`DecodePath::Software`] fails
1488      /// only where libavcodec has no decoder for the stream, or the
1489      /// context cannot be built.
1490      ///
1491      /// # Examples
1492      ///
1493      /// ```no_run
1494      /// use mediadecode_ffmpeg::{DecodePath, DecoderLimits, FfmpegVideoStreamDecoder};
1495      /// # fn f(parameters: ffmpeg_next::codec::Parameters, time_base: mediadecode::Timebase)
1496      /// # -> Result<(), Box<dyn std::error::Error>> {
1497      /// // The same stream, decoded without a GPU anywhere in the story.
1498      /// let decoder = FfmpegVideoStreamDecoder::open_as(
1499      ///   parameters,
1500      ///   time_base,
1501      ///   DecoderLimits::default(),
1502      ///   DecodePath::Software,
1503      /// )?;
1504      /// assert!(decoder.is_software());
1505      /// # Ok(())
1506      /// # }
1507      /// ```
1508      pub fn open_as(
1509        parameters: Parameters,
1510        time_base: Timebase,
1511        limits: DecoderLimits,
1512        path: DecodePath,
1513      ) -> Result<Self, Error> {
1514        Self::open_as_impl(parameters, time_base, limits, path)
1515      }
1516
1517      /// Whether this decoder is currently running on software.
1518      pub const fn is_software(&self) -> bool {
1519        self.is_software_impl()
1520      }
1521
1522      /// Whether this decoder is currently running on hardware.
1523      pub const fn is_hardware(&self) -> bool {
1524        self.is_hardware_impl()
1525      }
1526
1527      /// Whether this session can currently emit pictures at a
1528      /// caller-requested output size. See
1529      /// [`ScaledOutputCapability`] and
1530      /// [`Self::request_scaled_output`].
1531      ///
1532      /// `Supported` on a live VideoToolbox session on an Apple
1533      /// target, `Unsupported` everywhere else — including on a
1534      /// session that fell back to software during its probe, which is
1535      /// why this reads the session's live state rather than a fact
1536      /// recorded once at open.
1537      pub fn scaled_output_capability(&self) -> ScaledOutputCapability {
1538        self.scaled_output_capability_impl()
1539      }
1540
1541      /// Requests that this session emit pictures at `size` (width,
1542      /// height) from the next frame on, and reports whether the
1543      /// request was recorded. See
1544      /// [`VideoStreamDecoder::request_scaled_output`] for the full
1545      /// contract (never an error) and
1546      /// [`Self::scaled_output_capability`]'s documentation for which
1547      /// road can honor one, what a mid-stream request means, and the
1548      /// zero / upscale refusals this seat mints itself.
1549      pub fn request_scaled_output(&mut self, size: (u32, u32)) -> ScaledOutputCapability {
1550        self.request_scaled_output_impl(size)
1551      }
1552
1553      /// The hardware wrapper, when one is in use.
1554      pub fn hardware_inner(&self) -> Option<&VideoDecoder> {
1555        self.hardware_inner_impl()
1556      }
1557
1558      /// The stream timebase every produced timestamp is stamped with.
1559      pub const fn time_base(&self) -> Timebase {
1560        self.time_base_impl()
1561      }
1562    }
1563
1564    impl VideoStreamDecoder for CarrierVideoStreamDecoder<$lane> {
1565      type Adapter = Ffmpeg;
1566      type Buffer = <$lane as crate::FfmpegCarrier>::Buffer;
1567      type Error = VideoDecodeError;
1568
1569      fn send_packet(
1570        &mut self,
1571        packet: &VideoPacket<VideoPacketExtra, Self::Buffer>,
1572      ) -> Result<Sent, Self::Error> {
1573        self.send_packet_impl(packet)
1574      }
1575
1576      fn receive_frame(
1577        &mut self,
1578        dst: &mut VideoFrame<mediadecode::PixelFormat, VideoFrameExtra, Self::Buffer>,
1579      ) -> Result<Received, Self::Error> {
1580        self.receive_frame_impl(dst)
1581      }
1582
1583      fn send_eof(&mut self) -> Result<Sent, Self::Error> {
1584        self.send_eof_impl()
1585      }
1586
1587      fn flush(&mut self) -> Result<(), Self::Error> {
1588        self.flush_impl()
1589      }
1590
1591      fn scaled_output_capability(&self) -> ScaledOutputCapability {
1592        self.scaled_output_capability_impl()
1593      }
1594
1595      fn request_scaled_output(&mut self, size: (u32, u32)) -> ScaledOutputCapability {
1596        self.request_scaled_output_impl(size)
1597      }
1598    }
1599  )+ };
1600}
1601
1602video_lane_face!(crate::View, crate::Owned);
1603
1604fn open_sw_decoder(
1605  parameters: &Parameters,
1606  limits: DecoderLimits,
1607  pkt_timebase: Option<Timebase>,
1608) -> Result<SwDecoder, Error> {
1609  // Use the checked codec-context builder — ffmpeg-next's
1610  // `Context::from_parameters` calls `Context::new()` which doesn't
1611  // null-check `avcodec_alloc_context3`'s return value before
1612  // running `avcodec_parameters_to_context` against it. Under
1613  // memory pressure that's C-level UB; `build_codec_context`
1614  // surfaces the OOM as an error instead.
1615  let (ctx, callback_state) = build_codec_context(parameters, limits, pkt_timebase)?;
1616  // Opened without forming a bindgen enum from FFmpeg memory: the codec
1617  // is resolved off a raw `codec_id`, and the medium is proved off a raw
1618  // `codec_type`. See `crate::decoder::ensure_codec_type`.
1619  let codec = crate::decoder::find_decoder(parameters)?;
1620  let opened = ctx.decoder().open_as(codec).map_err(Error::Ffmpeg)?;
1621  crate::decoder::ensure_video_codec_type(&opened)?;
1622  Ok(SwDecoder {
1623    decoder: ffmpeg_next::decoder::Video(opened),
1624    _callback_state: callback_state,
1625  })
1626}
1627
1628/// Error type for [`FfmpegVideoStreamDecoder`] — **faults and the
1629/// send-side refusal**.
1630///
1631/// Every arm here is something that went wrong or something the push
1632/// face declined. The drain's *needs input* and *ended* are
1633/// [`Received`] states out of `receive_frame`; they used to arrive as
1634/// `Decode(Ffmpeg(Other { errno: EAGAIN }))` and `Decode(Ffmpeg(Eof))`,
1635/// which is to say they had no name at this tier at all.
1636///
1637/// **Open fault taxonomy, so it is `#[non_exhaustive]`.** New ways to
1638/// fail are discovered — a backend, a ceiling, a corruption a codec
1639/// learns to report — and a consumer that meets one it has never heard
1640/// of should take its generic-fault path. That is exactly what the
1641/// wildcard arm this attribute forces is for. The two status
1642/// vocabularies opposite it,
1643/// [`Sent`](mediadecode::Sent) and [`Received`](mediadecode::Received),
1644/// are exhaustive for the mirror-image reason: their arms are the
1645/// substrate's fixed state set, and there the wildcard would be dead
1646/// weight hiding a state a consumer forgot.
1647#[derive(thiserror::Error, Debug, IsVariant, Unwrap, TryUnwrap)]
1648#[unwrap(ref, ref_mut)]
1649#[try_unwrap(ref, ref_mut)]
1650#[non_exhaustive]
1651pub enum VideoDecodeError {
1652  /// The wrapped decoder (HW or SW) reported an error.
1653  #[error(transparent)]
1654  Decode(#[from] Error),
1655  /// Frame conversion from FFmpeg's native types to mediadecode's
1656  /// types failed.
1657  #[error(transparent)]
1658  Convert(#[from] ConvertError),
1659}
1660
1661#[cfg(test)]
1662mod tests;