Skip to main content

mediadecode_ffmpeg/
resampler.rs

1//! [`mediadecode::resampler::AudioResampler`] impl backed by
2//! `libswresample`.
3//!
4//! Converts rate, sample format and channel layout between two specs
5//! fixed at construction — [`FfmpegResampler::new`] takes both, because
6//! neither end is discoverable and neither is a constant. The source is
7//! whatever the file holds; the target is whatever the consumer wants,
8//! and consumers disagree (16 kHz mono for a speech model, 48 kHz for
9//! an audio-event one, from the same track at the same time).
10//!
11//! # Output timestamps
12//!
13//! `swr` is a delay line: it needs future input to produce present
14//! output, so at any moment a filter's worth of samples is inside it.
15//! Timestamps are therefore *counted*, not computed per call — the
16//! output timeline is anchored on the first input timestamp and
17//! advanced by the number of samples actually produced. The frames
18//! drained after EOF continue that same line rather than restarting it,
19//! and no arithmetic anywhere depends on how many samples a given
20//! `swr_convert_frame` happened to yield.
21
22use std::{
23  collections::VecDeque,
24  ptr::{addr_of, read_unaligned},
25};
26
27use crate::channel_layout::LayoutArm;
28use derive_more::{IsVariant, TryUnwrap, Unwrap};
29use ffmpeg_next::{
30  ChannelLayout,
31  codec::Parameters,
32  ffi::{
33    AV_NOPTS_VALUE, AVChannelOrder, AVMatrixEncoding, AVSampleFormat, av_channel_layout_from_mask,
34    av_frame_get_buffer, swr_build_matrix2,
35  },
36  format::Sample,
37  frame,
38  software::resampling,
39};
40use mediadecode::{
41  Received, Sent, Timebase, Timestamp,
42  frame::{AudioFrame, Plane},
43  resampler::AudioResampler,
44};
45use mediaframe::audio::ChannelLayoutDescription;
46
47use crate::{
48  Error, Ffmpeg, extras::AudioFrameExtra, limits::FrameLimits, sample_format::SampleFormat,
49};
50
51/// The frame type a resampler accepts and produces, on lane `C`.
52///
53/// Written as a projection rather than a bounded alias so the bound
54/// lives on the items that need it — `type_alias_bounds` is not
55/// enforced anyway, and a bound written where it is not enforced reads
56/// like a promise the compiler is keeping.
57type Frame<C> = AudioFrame<
58  SampleFormat,
59  ChannelLayoutDescription,
60  AudioFrameExtra,
61  <C as crate::FfmpegCarrier>::Buffer,
62>;
63
64/// One end of a conversion: sample rate, sample format, channel layout.
65///
66/// Spelled in FFmpeg's own vocabulary because construction is off the
67/// [`AudioResampler`] trait and this is the backend that has to be
68/// handed to `swr_alloc_set_opts2`. [`FfmpegResampler`] restates the
69/// source spec in the vocabulary a decoded frame carries, so the
70/// mid-stream check compares like with like without the caller ever
71/// seeing two dialects.
72#[derive(Copy, Clone)]
73pub struct ResampleSpec {
74  rate: u32,
75  format: Sample,
76  layout: ChannelLayout,
77}
78
79/// **`Debug` that never reads undefined storage.**
80///
81/// The derive delegated to `ffmpeg_next::ChannelLayout`'s own
82/// formatter, which prints `u.mask` unconditionally — and for an
83/// `UNSPEC` order that union is undefined per FFmpeg's own header, so
84/// merely *logging* a spec read storage nobody had written. The mask is
85/// printed here only for the orders that define it; see [`LayoutArm`].
86impl core::fmt::Debug for ResampleSpec {
87  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
88    let order = layout_order(&self.layout);
89    let mut out = f.debug_struct("ResampleSpec");
90    out
91      .field("rate", &self.rate)
92      .field("format", &self.format)
93      .field("order", &order)
94      .field("channels", &self.layout.channels());
95    match LayoutArm::of(order) {
96      // SAFETY: the order defines the `mask` arm.
97      LayoutArm::Mask => out.field("mask", &unsafe { self.layout.0.u.mask }),
98      // The map's *address* is what a reader can use here — its
99      // contents need an extent this type cannot establish.
100      // SAFETY: the order defines the `map` arm; the pointer is
101      // formatted, never dereferenced.
102      LayoutArm::Map => out.field("map", &unsafe { self.layout.0.u.map }),
103      LayoutArm::Undefined => out.field("union", &"undefined for this order"),
104    };
105    out.finish()
106  }
107}
108
109/// **Equality that never asks FFmpeg.**
110///
111/// The derive that stood here was a safe `==` with a raw pointer
112/// dereference behind it, and it is the worst shape this branch found.
113/// `ffmpeg_next::ChannelLayout`'s own `PartialEq` is
114/// `av_channel_layout_compare`, which walks `u.map[i]` for a `CUSTOM`
115/// order — so safe Rust could forge a layout with a positive
116/// `nb_channels` and a null or dangling map, wrap it in two specs,
117/// write `a == b`, and have FFmpeg dereference it. No preflight could
118/// dominate that: `==` is reachable without constructing a resampler at
119/// all, before any of this crate's code runs.
120///
121/// So the comparison is structural and reads nothing through a pointer:
122/// the raw `order` first, then the channel count, then — for every
123/// order that describes its channels through the union's `mask` arm —
124/// the mask itself, a `uint64_t`.
125///
126/// # What a `CUSTOM` order compares as, and why
127///
128/// **Pointer identity, never contents.** A safe function cannot
129/// establish that a map is as long as the count beside it, which is the
130/// same wall
131/// [`channel_layout_from_ffmpeg`](crate::channel_layout::channel_layout_from_ffmpeg)
132/// meets and refuses at; here the answer is the one comparison that
133/// needs no extent — are these the same map? Two custom layouts
134/// describing identical channels through different allocations
135/// therefore compare **unequal**.
136///
137/// That is conservative in the only direction that matters. The single
138/// product use of layout equality is [`check_pair`]'s
139/// `source == target` short-circuit, where "unequal" means the
140/// rematrix check *runs* rather than being skipped; an answer that was
141/// wrong the other way would skip a check. And the relation is still
142/// an equivalence — reflexive, symmetric, transitive — which is what
143/// [`Eq`] requires of it.
144impl PartialEq for ResampleSpec {
145  fn eq(&self, other: &Self) -> bool {
146    self.rate == other.rate
147      && self.format == other.format
148      && layouts_equal(&self.layout, &other.layout)
149  }
150}
151
152impl Eq for ResampleSpec {}
153
154/// Structural equality for two channel layouts, **without an FFmpeg
155/// call and without following a pointer**. See [`ResampleSpec`]'s
156/// [`PartialEq`] for why this exists and what a `CUSTOM` order answers.
157fn layouts_equal(a: &ChannelLayout, b: &ChannelLayout) -> bool {
158  let (order_a, order_b) = (layout_order(a), layout_order(b));
159  if order_a != order_b || a.channels() != b.channels() {
160    return false;
161  }
162  match LayoutArm::of(order_a) {
163    // SAFETY: these two orders name the `mask` arm — a `uint64_t` —
164    // and `AVChannelLayout`'s contract defines it for them.
165    LayoutArm::Mask => unsafe { a.0.u.mask == b.0.u.mask },
166    // SAFETY: this order names the `map` arm. The pointers are
167    // **compared**, never dereferenced — which is the whole point.
168    LayoutArm::Map => unsafe { core::ptr::eq(a.0.u.map, b.0.u.map) },
169    // **Nothing is read.** For `UNSPEC` — and for any order this build
170    // does not name, which folds here — FFmpeg's own header says the
171    // union is undefined and must not be used. Two unspecified layouts
172    // of the same width are the same layout, whatever bytes happen to
173    // sit in storage neither of them owns.
174    LayoutArm::Undefined => true,
175  }
176}
177
178impl ResampleSpec {
179  /// Constructs a spec from its three parts.
180  ///
181  /// Deliberately total and `const`: a spec is a description, and
182  /// describing something `swr` cannot convert is not itself an error.
183  /// [`FfmpegResampler::new`] is the choke point every construction
184  /// route passes through, and it is what refuses a rate, a format or a
185  /// channel layout this backend cannot honour — see
186  /// [`FfmpegResampler::new`] for the roster and
187  /// [`ResampleError::UnsupportedLayout`] for why a layout can be
188  /// refused at all.
189  #[inline]
190  pub const fn new(rate: u32, format: Sample, layout: ChannelLayout) -> Self {
191    Self {
192      rate,
193      format,
194      layout,
195    }
196  }
197
198  /// The spec a track *declares*, read off the owned codec ticket a
199  /// [`crate::FfmpegDemuxer`] track row carries
200  /// (`track.extra().ticket()`) — the "source from `TrackInfo`" path.
201  ///
202  /// Reads the ticket directly rather than rebuilding an
203  /// `AVCodecParameters` to read three fields out of: the seats are
204  /// already owned, and a rebuild here would allocate `extradata` a
205  /// resampler has no use for.
206  ///
207  /// Returns `None` for a non-audio track, for one whose declared
208  /// sample format is `AV_SAMPLE_FMT_NONE` (a codec whose format is
209  /// only known once its decoder opens), and for a custom or ambisonic
210  /// channel layout — see [`Self::from_decoder`] for the first case and
211  /// the note on [`unspecified_layout`] for the last.
212  pub fn from_ticket(ticket: &crate::ticket::CodecTicket) -> Option<Self> {
213    if !crate::boundary::media_kind_from_raw(ticket.codec_type()).is_audio() {
214      return None;
215    }
216    let rate = ticket.sample_rate().max(0) as u32;
217    if rate == 0 {
218      return None;
219    }
220    let format = SampleFormat::from_raw(ticket.format()).to_ffmpeg()?;
221    // Through the same decision [`layout_from_raw`] makes — the same
222    // function, in fact, so the two roads cannot drift into admitting
223    // different layouts from the same file.
224    let owned = ticket.ch_layout();
225    let layout = layout_from_parts(owned.order(), owned.channels(), owned.mask())?;
226    Some(Self::new(rate, format, layout))
227  }
228
229  /// [`Self::from_ticket`], over a live `AVCodecParameters` — the road
230  /// for a caller holding one straight off `stream.parameters()`.
231  ///
232  /// Returns `None` in every case [`Self::from_ticket`] does, and also
233  /// for null-backed parameters: `Parameters`' safe constructors hand
234  /// one back when FFmpeg's allocation failed and report nothing, so a
235  /// caller can arrive here holding one without ever having been told.
236  /// Parameters that were never allocated describe no audio, which
237  /// this function already has a word for.
238  pub fn from_parameters(parameters: &Parameters) -> Option<Self> {
239    // Before `medium()`, which dereferences the pointer inside
240    // ffmpeg-next. `Parameters`' safe constructors hand back a
241    // null-backed value when FFmpeg's allocation failed and report
242    // nothing, so a caller can arrive here holding one without ever
243    // having been told. Parameters that were never allocated describe
244    // no audio, which this function already has a word for.
245    // SAFETY: reading the pointer without dereferencing it.
246    if unsafe { parameters.as_ptr() }.is_null() {
247      return None;
248    }
249    if !crate::boundary::media_kind_of(parameters).is_audio() {
250      return None;
251    }
252    // SAFETY: `parameters` keeps the `AVCodecParameters` live; every
253    // read below goes through the raw pointer and none of them
254    // materialises a bindgen enum out of foreign memory.
255    let par = unsafe { parameters.as_ptr() };
256    let rate = unsafe { (*par).sample_rate }.max(0) as u32;
257    if rate == 0 {
258      return None;
259    }
260    let format = SampleFormat::from_raw(unsafe { (*par).format }).to_ffmpeg()?;
261    let layout = unsafe { layout_from_raw(addr_of!((*par).ch_layout)) }?;
262    Some(Self::new(rate, format, layout))
263  }
264
265  /// The spec an opened decoder will actually produce — its rate,
266  /// sample format and channel layout, straight off the codec context.
267  ///
268  /// Reach it through
269  /// [`FfmpegAudioStreamDecoder::inner`](crate::FfmpegAudioStreamDecoder::inner).
270  /// `None` on a custom or ambisonic layout, and on a context whose
271  /// sample format is still unset (a decoder that has not been opened).
272  pub fn from_decoder(decoder: &ffmpeg_next::decoder::Audio) -> Option<Self> {
273    // SAFETY: `decoder` keeps the `AVCodecContext` live. `sample_fmt`
274    // is read as the raw integer it is rather than through
275    // `decoder.format()`, which would construct an `AVSampleFormat`
276    // out of foreign memory.
277    let ctx = unsafe { decoder.as_ptr() };
278    // Same reason as `from_parameters`: `codec::Context::new()` is a
279    // safe constructor over an unchecked `avcodec_alloc_context3`, so a
280    // decoder can be null-backed without anyone having been told.
281    if ctx.is_null() {
282      return None;
283    }
284    let format =
285      SampleFormat::from_raw(unsafe { read_unaligned(addr_of!((*ctx).sample_fmt).cast::<i32>()) })
286        .to_ffmpeg()?;
287    let rate = unsafe { (*ctx).sample_rate }.max(0) as u32;
288    if rate == 0 {
289      return None;
290    }
291    let layout = unsafe { layout_from_raw(addr_of!((*ctx).ch_layout)) }?;
292    Some(Self::new(rate, format, layout))
293  }
294
295  /// A layout that names a channel *count* and nothing else —
296  /// `AV_CHANNEL_ORDER_UNSPEC`.
297  ///
298  /// Not a degenerate case: a WAV file without a `WAVE_FORMAT_EXTENSIBLE`
299  /// channel mask genuinely declares no layout, and FFmpeg faithfully
300  /// reports it as unspecified in the codec parameters, in the codec
301  /// context, and on every decoded frame. Substituting a default layout
302  /// would make the source spec disagree with the frames it is supposed
303  /// to describe, and every `send_frame` would be refused as a
304  /// mid-stream change. `swr` accepts an unspecified layout at either
305  /// end and maps the channels positionally.
306  #[inline]
307  pub fn unspecified_layout(channels: i32) -> ChannelLayout {
308    // SAFETY: a zeroed `AVChannelLayout` is a valid value — `order`
309    // reads as `AV_CHANNEL_ORDER_UNSPEC`, the zero discriminant, and
310    // the union is documented as unused for that order.
311    unsafe {
312      let mut layout: ffmpeg_next::ffi::AVChannelLayout = std::mem::zeroed();
313      layout.nb_channels = channels.max(0);
314      ChannelLayout(layout)
315    }
316  }
317
318  /// Sample rate in Hz.
319  #[inline]
320  pub const fn rate(&self) -> u32 {
321    self.rate
322  }
323  /// Sample format.
324  #[inline]
325  pub const fn format(&self) -> Sample {
326    self.format
327  }
328  /// Channel layout.
329  #[inline]
330  pub const fn layout(&self) -> ChannelLayout {
331    self.layout
332  }
333  /// Channel count, from the layout.
334  #[inline]
335  pub fn channels(&self) -> i32 {
336    self.layout.channels()
337  }
338
339  /// The timebase output frames carry — one tick per output sample.
340  fn timebase(&self) -> Timebase {
341    Timebase::new(
342      1,
343      std::num::NonZeroI32::new(self.rate.min(i32::MAX as u32) as i32).unwrap_or(
344        // A zero-rate spec never reaches here: `new` is the only way in
345        // and every caller of it names a real rate. Falling back to
346        // one tick per second keeps the arithmetic total rather than
347        // panicking on a value that cannot occur.
348        std::num::NonZeroI32::new(1).expect("1 is non-zero"),
349      ),
350    )
351  }
352}
353
354/// `mediadecode::resampler::AudioResampler` impl wrapping
355/// `swresample`.
356///
357/// Construction is [`Self::new`], off the trait, taking both specs —
358/// see the trait's own documentation for why the target can never be a
359/// constant.
360pub struct CarrierResampler<C: crate::FfmpegCarrier> {
361  ctx: resampling::Context,
362  source: ResampleSpec,
363  target: ResampleSpec,
364  /// The source spec restated in the vocabulary a decoded `AudioFrame`
365  /// carries. The mid-stream check compares against these, not against
366  /// FFmpeg's dialect, so it never has to translate a frame.
367  source_format: SampleFormat,
368  source_layout: ChannelLayoutDescription,
369  /// The target spec in the vocabulary an output frame carries,
370  /// computed once at construction. Assembling a converted frame after
371  /// `swr` has run must not have to ask FFmpeg anything, because asking
372  /// can fail — see [`FfmpegResampler::prepare_output`].
373  target_format: SampleFormat,
374  target_layout: ChannelLayoutDescription,
375  /// The layouts `swr` is really configured with — see
376  /// [`initialized_layout`]. Every `AVFrame` this type stages or
377  /// allocates carries these, not the declared ones.
378  staged_source_layout: ChannelLayout,
379  staged_target_layout: ChannelLayout,
380  target_timebase: Timebase,
381  ready: VecDeque<Frame<C>>,
382  /// Next output timestamp, in target-rate ticks. `None` until the
383  /// first input frame anchors it.
384  next_pts: Option<i64>,
385  eof: bool,
386  /// `true` once the post-EOF tail has been drained to its end.
387  ///
388  /// **The terminal answer has to be terminal.** Without this latch,
389  /// every poll past the end re-enters the flush road — allocating an
390  /// output frame and asking `swr` again — because `swr_get_delay` can
391  /// keep reporting residual samples that `swr_convert_frame` will
392  /// never emit (observed: 16 samples left standing after the tail is
393  /// genuinely exhausted). That is harmless while allocation succeeds
394  /// and wrong when it does not: a session that has already answered
395  /// `Ended` would start answering with an allocation error instead,
396  /// turning a settled protocol state back into a fault. The latch
397  /// makes the end cheap and unconditional. `flush` clears it with the
398  /// rest of the session.
399  drained: bool,
400  /// What one converted frame may cost. See
401  /// [`Self::check_output_bytes`] for why a resampler needs a ceiling
402  /// of its own even when its input already had one.
403  limits: FrameLimits,
404  /// The lane. Zero-sized: it selects how a produced plane is carried
405  /// — shared out of the output `AVFrame` or copied out of it — and
406  /// nothing else about the conversion.
407  _carrier: core::marker::PhantomData<C>,
408}
409
410impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierResampler<C> {
411  /// Opens a resampler between two explicit specs.
412  ///
413  /// Both are required and neither is inferred. The source is what the
414  /// decoder will hand over — read it off the track
415  /// ([`ResampleSpec::from_parameters`]) or off the opened decoder
416  /// ([`ResampleSpec::from_decoder`]). The target is the caller's, and
417  /// is options: 16 kHz mono for a speech model, 48 kHz for an
418  /// audio-event one, both from the same track.
419  ///
420  /// # The choke point
421  ///
422  /// [`ResampleSpec::new`] is `const` and total, so this is where both
423  /// ends are checked — every construction route (`from_parameters`,
424  /// `from_decoder`, the public constructor) passes through here, and
425  /// nothing hazardous reaches `swr` or a staged `AVFrame` behind it:
426  ///
427  /// - a rate of zero, or one past `c_int`
428  ///   ([`ResampleError::UnsupportedRate`]);
429  /// - `AV_SAMPLE_FMT_NONE` ([`ResampleError::UnsupportedFormat`]);
430  /// - a channel layout that is neither native nor unspecified, or one
431  ///   naming no channels ([`ResampleError::UnsupportedLayout`]).
432  ///
433  /// `limits` bounds what one **converted** frame may cost.
434  ///
435  /// [`FrameLimits`] rather than a seat of this seam's own: the
436  /// quantity is bytes of one produced audio frame, which is exactly
437  /// what [`FrameLimits::max_frame_bytes`] means everywhere else in
438  /// this crate, and one number for "what a frame may cost" is worth
439  /// more than a second vocabulary. [`FrameLimits::max_pixels`] is
440  /// unused here, as it is on the audio decode path, and for the same
441  /// reason: audio has no pixels.
442  pub(crate) fn new_impl(
443    source: ResampleSpec,
444    target: ResampleSpec,
445    limits: FrameLimits,
446  ) -> Result<Self, ResampleError> {
447    check_spec(&source, SpecEnd::Source)?;
448    check_spec(&target, SpecEnd::Target)?;
449    // **Both layouts, preflighted before `swr` is handed either.**
450    //
451    // `swr_alloc_set_opts2` and `swr_build_matrix2` take an
452    // `AVChannelLayout` and assume every invariant
453    // `av_channel_layout_check` states; `ffmpeg_next::ChannelLayout` is
454    // a public newtype over that public struct, so a caller can hand
455    // this constructor a layout breaking any of them — a `NATIVE` count
456    // that disagrees with its mask, an `AMBISONIC` layout whose
457    // channels form no ambisonic order, a count large enough to take
458    // FFmpeg's own `int` arithmetic somewhere it was not meant to go.
459    // One preflight, shared with the demux admission pass, the codec
460    // ticket and the decoder.
461    preflight_layout(&source.layout, SpecEnd::Source)?;
462    preflight_layout(&target.layout, SpecEnd::Target)?;
463
464    // The layouts `swr` is really configured with, resolved *before*
465    // the pair is judged — because the conversion that will run is
466    // between these two, not between the two that were declared. An
467    // unspecified layout becomes FFmpeg's default for its channel count
468    // (twenty-four unspecified channels are 22.2), so judging the
469    // declared pair let exactly the routing the explicit 22.2 refusal
470    // blocks walk in through the unspecified door.
471    let staged_source_layout = initialized_layout(source.layout);
472    let staged_target_layout = initialized_layout(target.layout);
473    check_pair(&staged_source_layout, &staged_target_layout)?;
474    let ctx = open_context(&source, &target, staged_source_layout, staged_target_layout)?;
475
476    let source_format = SampleFormat::from_ffmpeg(source.format);
477    let target_format = SampleFormat::from_ffmpeg(target.format);
478    // SAFETY: the layout is a live `ChannelLayout` owned by this scope.
479    let target_layout =
480      crate::channel_layout::channel_layout_description_from_ffmpeg(&staged_target_layout)
481        .map_err(|_| {
482          ResampleError::UnsupportedLayout(UnsupportedLayout::new(
483            SpecEnd::Target,
484            layout_order(&staged_target_layout),
485            staged_target_layout.channels(),
486          ))
487        })?;
488    // SAFETY: the layout is a live `ChannelLayout` owned by `source`
489    // for the duration of this call.
490    let source_layout = crate::channel_layout::channel_layout_description_from_ffmpeg(
491      &source.layout,
492    )
493    .map_err(|_| {
494      ResampleError::UnsupportedLayout(UnsupportedLayout::new(
495        SpecEnd::Source,
496        layout_order(&source.layout),
497        source.layout.channels(),
498      ))
499    })?;
500    let target_timebase = target.timebase();
501
502    Ok(Self {
503      ctx,
504      source,
505      target,
506      source_format,
507      source_layout,
508      target_format,
509      target_layout,
510      staged_source_layout,
511      staged_target_layout,
512      target_timebase,
513      ready: VecDeque::new(),
514      next_pts: None,
515      eof: false,
516      drained: false,
517      limits,
518      _carrier: core::marker::PhantomData,
519    })
520  }
521
522  /// The spec frames must arrive in.
523  #[inline]
524  pub(crate) const fn source_impl(&self) -> &ResampleSpec {
525    &self.source
526  }
527
528  /// The spec frames leave in.
529  #[inline]
530  pub(crate) const fn target_impl(&self) -> &ResampleSpec {
531    &self.target
532  }
533
534  /// Borrows the wrapped `swr` context.
535  #[inline]
536  pub(crate) const fn inner_impl(&self) -> &resampling::Context {
537    &self.ctx
538  }
539
540  /// Samples still inside the delay line, counted at the output rate.
541  #[inline]
542  pub(crate) fn delay_impl(&self) -> i64 {
543    self.ctx.delay().map_or(0, |d| d.output.max(0))
544  }
545
546  /// Refuses a frame whose shape is not the source spec.
547  fn check_source(&self, frame: &Frame<C>) -> Result<(), ResampleError> {
548    if frame.sample_rate() != self.source.rate
549      || *frame.sample_format() != self.source_format
550      || *frame.channel_layout() != self.source_layout
551    {
552      return Err(ResampleError::SourceChanged(SourceChanged::new(
553        self.source.rate,
554        self.source_format,
555        frame.sample_rate(),
556        *frame.sample_format(),
557      )));
558    }
559    Ok(())
560  }
561
562  /// Where a frame's timestamp lands on the output timeline, or `None`
563  /// when it carries none.
564  ///
565  /// Rescaled with the **checked** rung, and before anything is staged.
566  /// `Timestamp::rescale_to` saturates, and both ends of that clamp are
567  /// wrong here: a positive one reaches the counted timeline's checked
568  /// addition only after `swr` has consumed the input, leaving a
569  /// session no caller can retry; a negative one lands on `i64::MIN`,
570  /// which *is* `AV_NOPTS_VALUE`, so the conversion back reads the
571  /// frame as carrying no timestamp at all and an extreme timestamp is
572  /// silently erased. A timestamp that does not fit the output timeline
573  /// is refused by name, with the resampler untouched.
574  fn anchor_of(&self, frame: &Frame<C>) -> Result<Option<i64>, ResampleError> {
575    let Some(timestamp) = frame.pts() else {
576      return Ok(None);
577    };
578    let ticks = timestamp.pts();
579    let out_of_range = || ResampleError::TimestampOutOfRange(TimestampOutOfRange::new(ticks));
580    // `AV_NOPTS_VALUE` is a sentinel, not a time. A frame carrying it
581    // as a value says something contradictory, and anchoring on it
582    // would produce output frames that report no timestamp.
583    if ticks == AV_NOPTS_VALUE {
584      return Err(out_of_range());
585    }
586    let rescaled = timestamp
587      .timebase()
588      .checked_rescale(ticks, self.target_timebase)
589      .ok_or_else(out_of_range)?;
590    if rescaled == AV_NOPTS_VALUE {
591      return Err(out_of_range());
592    }
593    Ok(Some(rescaled))
594  }
595
596  /// Stages a decoded frame as an `AVFrame` swr can read.
597  ///
598  /// Geometry is settled **before** anything is allocated. A frame's
599  /// header is a claim, not a fact: `nb_samples` comes from the same
600  /// foreign memory as the planes it describes, and sizing an
601  /// allocation off it first would let a forged frame with a
602  /// twelve-byte plane ask for tens of gigabytes on its way to being
603  /// refused.
604  fn stage_input(&self, frame: &Frame<C>) -> Result<frame::Audio, ResampleError> {
605    let samples = frame.nb_samples() as usize;
606    let channels = self.source.channels();
607
608    // What the *format* requires, not what the allocated frame reports
609    // — the frame does not exist yet.
610    let planes = if self.source.format.is_planar() {
611      // `check_spec` proved this positive at construction.
612      channels as usize
613    } else {
614      1
615    };
616    let found = frame.plane_count() as usize;
617    if planes > found {
618      return Err(ResampleError::PlaneCount(PlaneCount::new(planes, found)));
619    }
620    let bytes = plane_bytes(self.source.format, samples, channels)
621      .ok_or(ResampleError::SampleCount(SampleCount::new(samples)))?;
622    for plane in frame.planes().iter().take(planes) {
623      let src = plane.data_ref().as_ref();
624      if src.len() < bytes {
625        return Err(ResampleError::PlaneCount(PlaneCount::new(bytes, src.len())));
626      }
627    }
628
629    // Only now, with every plane proved long enough for the sample
630    // count that sizes this allocation.
631    let mut input = new_audio_frame(
632      self.source.format,
633      samples,
634      self.source.rate,
635      self.staged_source_layout,
636    )?;
637    // What the allocation really produced. `data_mut` panics past its
638    // own plane count, and this crate does not put a panic on a path
639    // that reads foreign geometry.
640    let staged = input.planes();
641    if staged < planes {
642      return Err(ResampleError::PlaneCount(PlaneCount::new(planes, staged)));
643    }
644    for (index, plane) in frame.planes().iter().take(planes).enumerate() {
645      let src = plane.data_ref().as_ref();
646      let dst = input.data_mut(index);
647      if dst.len() < bytes {
648        return Err(ResampleError::PlaneCount(PlaneCount::new(bytes, dst.len())));
649      }
650      dst[..bytes].copy_from_slice(&src[..bytes]);
651    }
652    Ok(input)
653  }
654
655  /// The most samples the next conversion could produce: the delay
656  /// line's contents plus `in_samples` of new input, rescaled to the
657  /// output rate and rounded up.
658  ///
659  /// Separate from the allocation because it is also the preflight the
660  /// output timeline is checked against — *before* `swr` consumes
661  /// anything, so a refusal leaves the session where a caller can retry
662  /// it.
663  fn output_capacity(&self, in_samples: i64) -> Result<usize, ResampleError> {
664    let delay_in = self.ctx.delay().map_or(0, |d| d.input.max(0));
665    let total = delay_in.saturating_add(in_samples).max(0) as i128;
666    let scaled = (total * i128::from(self.target.rate) + i128::from(self.source.rate) - 1)
667      / i128::from(self.source.rate).max(1);
668    // One extra sample of headroom: swr rounds its own accounting, and
669    // an output frame one short would silently push the remainder into
670    // the internal FIFO where the pts accounting cannot see it until
671    // the next call.
672    let samples = scaled + 1;
673    // `av_frame_get_buffer` takes the count as a `c_int`. A request
674    // past that is refused by name rather than clamped: a silently
675    // shortened output frame is a stream that loses samples.
676    if samples > i128::from(i32::MAX) {
677      // Saturating only for a count past `usize` itself, which no
678      // machine could hold either way.
679      return Err(ResampleError::SampleCount(SampleCount::new(
680        usize::try_from(samples).unwrap_or(usize::MAX),
681      )));
682    }
683    Ok(samples.max(1) as usize)
684  }
685
686  /// Refuses a conversion whose output would not fit the frame ceiling.
687  ///
688  /// # Why a resampler needs one even though its input had one
689  ///
690  /// [`Self::output_capacity`] bounds the output **sample count**, and
691  /// only at `i32::MAX` — the structural limit of
692  /// `av_frame_get_buffer`. Nothing in it bounds the *bytes*, and the
693  /// two are related by a ratio the caller does not control: the
694  /// capacity is `input_samples × target_rate / source_rate`, and a
695  /// source spec read off an untrusted container can say 1 Hz. One
696  /// second of 1 Hz mono input converted to 48 kHz stereo `f32` is
697  /// 384 KiB from 4 bytes — and the same input against a 1 Hz source
698  /// claim and a 192 kHz target is multi-gigabyte. The frame that
699  /// arrived was within *its* ceiling; the frame that leaves need not
700  /// be, so it gets its own judgement.
701  ///
702  /// Both allocations are covered: `av_frame_get_buffer`'s, and the
703  /// [`FfmpegBytes`] copy [`Self::finish_output`] makes from it.
704  fn check_output_bytes(&self, capacity: usize, channels: i32) -> Result<(), ResampleError> {
705    // **Priced as the allocator prices it, not as the samples weigh.**
706    // This used to multiply the tight plane length by the plane count,
707    // which is the payload's arithmetic and not
708    // `av_frame_get_buffer`'s: a one-sample eight-channel planar `s16`
709    // frame is 16 bytes of samples and a **768-byte** allocation,
710    // because every plane is aligned and padded on its own. A 16-byte
711    // ceiling admitted it.
712    //
713    // The overhead is under one percent on any frame big enough to
714    // care about, which is exactly why this went unseen — see
715    // [`crate::footprint`] for the measured table and for the rule the
716    // whole crate now keeps: a judge must dominate the allocator's
717    // arithmetic, not the payload's.
718    let bytes = crate::footprint::audio_frame_bytes(
719      ffmpeg_next::ffi::AVSampleFormat::from(self.target.format) as libc::c_int,
720      capacity,
721      channels.max(0) as usize,
722    )
723    .ok_or(ResampleError::OutputTooLarge(OutputTooLarge::new(
724      usize::MAX,
725      self.limits.max_frame_bytes(),
726    )))?;
727    if bytes > self.limits.max_frame_bytes() {
728      return Err(ResampleError::OutputTooLarge(OutputTooLarge::new(
729        bytes,
730        self.limits.max_frame_bytes(),
731      )));
732    }
733    Ok(())
734  }
735
736  /// Refuses a conversion whose output could not be labelled: the
737  /// timeline plus everything this call might produce has to stay
738  /// inside `i64`.
739  ///
740  /// Asked before `swr` sees a sample, like everything else that can
741  /// fail. [`Self::finish_output`] performs the same addition against
742  /// the count actually produced, which cannot exceed the capacity
743  /// checked here — so once this passes, that one cannot fail.
744  fn check_timeline(&self, anchor: Option<i64>, capacity: usize) -> Result<(), ResampleError> {
745    let pts = self.next_pts.or(anchor).unwrap_or(0);
746    let samples = capacity as i64;
747    if pts.checked_add(samples).is_none() {
748      return Err(ResampleError::TimestampOverflow(TimestampOverflow::new(
749        pts, samples,
750      )));
751    }
752    Ok(())
753  }
754
755  /// Allocates the output frame **and proves every plane the converted
756  /// frame will be read out of**, before `swr` is allowed to touch a
757  /// sample.
758  ///
759  /// This is the shape the whole seam is built around. Anything
760  /// fallible that runs *after* `swr_convert_frame` has consumed input
761  /// leaves a session no caller can act on: retrying feeds the same
762  /// samples twice, continuing loses them, and the delay line has moved
763  /// either way. The failure kept relocating — the timestamp addition,
764  /// the tail drain, then the output wrapping — so the fix is not
765  /// another check in another place but an ordering that leaves nothing
766  /// on the far side: the frame, every plane pointer it will be read
767  /// through, and the queue slot are all taken here, where failing
768  /// costs nothing but an error.
769  ///
770  /// **What 0.9 changed, and what it did not.** Through 0.8 this
771  /// function also *acquired* one `AVBufferRef` view per plane, because
772  /// wrapping a plane could fail and so had to happen on this side of
773  /// the conversion; [`Self::finish_output`] then narrowed each view to
774  /// what `swr` produced. The amputation removes the views — the output
775  /// planes are copied out afterwards instead — and with them the
776  /// failure that forced the acquisition to be early. What stays early
777  /// is the *proof*: every plane pointer is checked non-null and
778  /// checked to address `plane_len` bytes inside one of the frame's own
779  /// buffers here, so the copy on the far side has nothing left to
780  /// judge and [`Self::finish_output`] remains infallible.
781  fn prepare_output(&self, capacity: usize) -> Result<PreparedOutput<C>, ResampleError> {
782    let channels = self.target.channels();
783    let plane_count = if self.target.format.is_planar() {
784      // `check_spec` proved this positive at construction.
785      channels as usize
786    } else {
787      1
788    };
789    let plane_len = plane_bytes(self.target.format, capacity, channels)
790      .ok_or(ResampleError::SampleCount(SampleCount::new(capacity)))?;
791    // Linear in the sample count, which is what lets the post-run
792    // trim be a multiplication rather than another fallible call.
793    let per_sample = plane_bytes(self.target.format, 1, channels)
794      .ok_or(ResampleError::SampleCount(SampleCount::new(1)))?;
795
796    // **The byte ceiling, before `av_frame_get_buffer` and before the
797    // carrier copy that follows it.** Measured first, allocated second:
798    // the three quantities above are arithmetic over the target spec
799    // and cost nothing, so there is no reason for the frame to exist
800    // before the answer does.
801    self.check_output_bytes(capacity, channels)?;
802
803    let frame = new_audio_frame(
804      self.target.format,
805      capacity,
806      self.target.rate,
807      self.staged_target_layout,
808    )?;
809    if frame.planes() < plane_count {
810      return Err(ResampleError::PlaneCount(PlaneCount::new(
811        plane_count,
812        frame.planes(),
813      )));
814    }
815
816    let mut reserved: [Option<C::Reserved>; 8] = core::array::from_fn(|_| None);
817    for (index, slot) in reserved.iter_mut().enumerate().take(plane_count) {
818      // SAFETY: `frame` owns a live `AVFrame` this call just
819      // allocated; `data` is a public field and `plane_count` is
820      // within the eight slots `data` has.
821      let data_ptr = unsafe { (*frame.as_ptr()).data[index] };
822      if data_ptr.is_null() {
823        return Err(ResampleError::OutputBuffer(OutputBuffer::new(index)));
824      }
825      // SAFETY: the frame is live, and the helper only reads `buf[]`'s
826      // ranges to find the one containing `data_ptr`. Proving it here
827      // is what lets the carry on the far side of the conversion be
828      // unconditional — a copy on the owned lane, a view on the other,
829      // and neither has a proof left to make.
830      let backing =
831        unsafe { crate::convert::find_audio_backing_buffer(frame.as_ptr(), data_ptr, plane_len) }
832          .ok_or(ResampleError::OutputBuffer(OutputBuffer::new(index)))?;
833      // The carrier's claim on the plane, taken **here** — before `swr`
834      // consumes anything. See [`FfmpegCarrier::reserve`].
835      //
836      // SAFETY: `backing` is a live buffer of this frame's, proved just
837      // above to cover `plane_len` bytes from `data_ptr`; the frame is
838      // moved into `PreparedOutput` below and so outlives the commit.
839      let offset = unsafe { (data_ptr as usize).wrapping_sub((*backing).data as usize) };
840      // SAFETY: as above — the extent lies inside `backing`.
841      *slot = Some(
842        unsafe { C::reserve(backing, offset, plane_len) }
843          .ok_or(ResampleError::OutputBuffer(OutputBuffer::new(index)))?,
844      );
845    }
846
847    Ok(PreparedOutput {
848      frame,
849      reserved,
850      plane_count,
851      plane_len,
852      per_sample,
853    })
854  }
855
856  /// Turns a converted frame into a `mediadecode` one. **Infallible**,
857  /// by construction: every check it could have made was made in
858  /// [`Self::prepare_output`], and everything left here is arithmetic
859  /// over values this type owns plus a copy that cannot be refused.
860  ///
861  /// `None` when the conversion produced nothing — the delay line
862  /// swallowed the input, which is ordinary and not a failure.
863  fn finish_output(&mut self, mut prepared: PreparedOutput<C>) -> Option<Frame<C>> {
864    let produced = prepared.frame.samples();
865    if produced == 0 {
866      return None;
867    }
868    let pts = self.next_pts.unwrap_or(0);
869    // `check_timeline` ran before `swr` did, against a capacity that is
870    // never smaller than what came out, so this addition cannot leave
871    // `i64`. It is stated rather than checked because a check here
872    // would be an error path on the wrong side of the conversion —
873    // exactly what this design exists to remove.
874    debug_assert!(
875      pts.checked_add(produced as i64).is_some(),
876      "the timeline was preflighted against a capacity >= produced",
877    );
878    self.next_pts = Some(pts.saturating_add(produced as i64));
879
880    let bytes = prepared
881      .per_sample
882      .saturating_mul(produced)
883      .min(prepared.plane_len);
884    let plane_count = prepared.plane_count;
885    let mut planes: [Plane<C::Buffer>; 8] = core::array::from_fn(|_| Plane::new(C::empty(), 0));
886    for (index, slot) in planes.iter_mut().enumerate().take(plane_count) {
887      // Present for every index below `plane_count`: `prepare_output`
888      // fills exactly that many and returns an error otherwise.
889      let Some(reserved) = prepared.reserved[index].take() else {
890        continue;
891      };
892      // **The valid prefix, not the plane.** `plane_len` is what
893      // capacity was allocated for; `bytes` is what `swr` produced.
894      // Both lanes stop at the latter, for the same reason the decode
895      // road does: the tail is allocator memory nothing wrote, and a
896      // carrier's span is a span a consumer may read.
897      //
898      // SAFETY: the reservation covers `plane_len` bytes inside one of
899      // `frame`'s own buffers, and `bytes <= plane_len` by the `min`
900      // above. `swr_convert_frame` has written those bytes and does not
901      // replace the buffers; `frame` has been owned by `prepared` — and
902      // so kept alive — across the whole conversion. A view committed
903      // here outlives `prepared` by refcount, and the writing is over
904      // before the sharing begins: this resampler allocates a fresh
905      // output frame per conversion, so nothing ever writes into a
906      // buffer a delivered frame is reading.
907      // Infallible: the owned lane took its allocation in `reserve`,
908      // before `swr` ran, so nothing here can refuse and no converted
909      // sample can be lost to an allocator.
910      *slot = Plane::new(unsafe { C::commit(reserved, bytes) }, bytes as u32);
911    }
912
913    Some(
914      AudioFrame::new(
915        self.target.rate,
916        produced as u32,
917        // Exact, not clipped: `check_spec` refused every spec outside
918        // `1..=MAX_FRAME_CHANNELS` before this resampler existed.
919        self.target.channels() as u8,
920        self.target_format,
921        self.target_layout.clone(),
922        planes,
923        plane_count as u8,
924        AudioFrameExtra::default(),
925      )
926      .with_pts(Some(Timestamp::new(pts, self.target_timebase)))
927      .with_duration(Some(Timestamp::new(produced as i64, self.target_timebase))),
928    )
929  }
930}
931
932/// Everything a converted frame needs, acquired before the conversion
933/// runs. See [`FfmpegResampler::prepare_output`].
934struct PreparedOutput<C: crate::FfmpegCarrier + crate::CarrierOps> {
935  /// The output `AVFrame`. Owning it here is what keeps the plane
936  /// pointers below valid across the conversion — moving this struct
937  /// moves a pointer to the `AVFrame`, never the `AVFrame` or the
938  /// buffers it addresses.
939  frame: frame::Audio,
940  /// One carrier claim per populated plane, taken before the
941  /// conversion ran and settled at its true length after — which is
942  /// what keeps this struct's whole reason for existing intact on the
943  /// view lane too. `None` for every unpopulated slot.
944  reserved: [Option<C::Reserved>; 8],
945  plane_count: usize,
946  /// Bytes one plane holds at full capacity — the ceiling every trim
947  /// stays under.
948  plane_len: usize,
949  /// Bytes one plane holds per sample.
950  per_sample: usize,
951}
952
953impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierResampler<C> {
954  /// **Always [`Sent::Accepted`] when it accepts at all.** The
955  /// converted-frame queue this type keeps is unbounded — every frame a
956  /// conversion produces is built before `swr` is asked and pushed
957  /// straight onto it — so there is no state in which draining first
958  /// would let a submission through that is refused now. A bounded
959  /// implementation of the same face would answer
960  /// [`Sent::MustDrain`] here; this one has nothing to say it about.
961  ///
962  /// [`ResampleError::AfterEof`] stays an error rather than becoming
963  /// that arm, and the line is the same one the decoders draw: a
964  /// resampler that has been told the stream ended will refuse this
965  /// frame however much is drained first, so sending the caller into a
966  /// drain loop would be sending it nowhere. `flush` is the way back,
967  /// and the message says so.
968  pub(crate) fn send_frame_impl(&mut self, frame: &Frame<C>) -> Result<Sent, ResampleError> {
969    if self.eof {
970      return Err(ResampleError::AfterEof);
971    }
972    self.check_source(frame)?;
973    // A frame carrying no samples is a header and nothing else. There
974    // is nothing to convert and nothing to stage: `av_frame_get_buffer`
975    // refuses a zero-sample allocation, so staging one would hand `swr`
976    // an unbacked `AVFrame` for no gain.
977    if frame.nb_samples() == 0 {
978      return Ok(Sent::Accepted);
979    }
980
981    // Nothing below touches the session's state until the conversion
982    // has succeeded. A refused frame must leave the timeline exactly
983    // where it was, or the next good frame inherits the rejected one's
984    // timestamp.
985    let anchor = self.anchor_of(frame)?;
986    let input = self.stage_input(frame)?;
987    let capacity = self.output_capacity(frame.nb_samples() as i64)?;
988    self.check_timeline(anchor, capacity)?;
989    let mut prepared = self.prepare_output(capacity)?;
990    // The last fallible thing before the conversion: room for the frame
991    // it will produce. `push_back` on a full queue allocates, and an
992    // allocation failure there aborts the process rather than
993    // unwinding — so the growth happens here, where it can be an error.
994    self
995      .ready
996      .try_reserve(1)
997      .map_err(|_| ResampleError::QueueAlloc)?;
998
999    // The only mutation. Everything above could fail and cost nothing;
1000    // nothing below can fail at all.
1001    self
1002      .ctx
1003      .run(&input, &mut prepared.frame)
1004      .map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))?;
1005
1006    // The frame is inside the filter now, so the timeline may be
1007    // anchored on it. Anchored on *input* rather than on the first
1008    // output, because a call that produces nothing but fills the delay
1009    // line still fixes where the stream starts.
1010    if self.next_pts.is_none() {
1011      self.next_pts = anchor;
1012    }
1013    if let Some(converted) = self.finish_output(prepared) {
1014      self.ready.push_back(converted);
1015    }
1016    Ok(Sent::Accepted)
1017  }
1018
1019  /// **No parked-frame seat here, and none is needed.** The queue holds
1020  /// frames that are already built: every fallible step of a
1021  /// conversion — the carrier's claim included — happens in
1022  /// [`Self::prepare_output`], before `swr` consumes anything, and
1023  /// `finish_output` is infallible by construction. There is no
1024  /// conversion left to fail after a frame has been taken out of the
1025  /// queue, so nothing can be lost between the two. That is the
1026  /// property the reserve-then-commit seam was built for, stated where
1027  /// the sibling roads state their seats.
1028  pub(crate) fn receive_frame_impl(
1029    &mut self,
1030    dst: &mut Frame<C>,
1031  ) -> Result<Received, ResampleError> {
1032    if let Some(frame) = self.ready.pop_front() {
1033      *dst = frame;
1034      return Ok(Received::Frame);
1035    }
1036    if !self.eof {
1037      return Ok(Received::NeedsInput);
1038    }
1039    if self.drained {
1040      return Ok(Received::Ended);
1041    }
1042    // EOF: drain the conversion tail. Without this every file loses the
1043    // tens of milliseconds sitting inside the filter.
1044    //
1045    // **This is where the two answers used to be one.** Pre-EOF nothing
1046    // ready and post-EOF tail exhausted both returned `Again`, so a
1047    // caller that did not itself remember whether it had called
1048    // `send_eof` could not tell "send more" from "there is no more" —
1049    // and a drain loop written against the seam alone spun forever on
1050    // a resampler that was already finished.
1051    let remaining = self.delay_impl();
1052    if remaining <= 0 {
1053      self.drained = true;
1054      return Ok(Received::Ended);
1055    }
1056    let capacity = remaining.min(i64::from(i32::MAX)) as usize;
1057    // Same discipline as `send_frame`, and for the same reason: the
1058    // tail is drained only once the timeline can hold it and every
1059    // reference the converted frame needs is already in hand, so a
1060    // failure leaves the delay line untouched instead of turning
1061    // samples into an error.
1062    self.check_timeline(None, capacity)?;
1063    let mut prepared = self.prepare_output(capacity)?;
1064    self
1065      .ctx
1066      .flush(&mut prepared.frame)
1067      .map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))?;
1068    match self.finish_output(prepared) {
1069      Some(frame) => {
1070        *dst = frame;
1071        Ok(Received::Frame)
1072      }
1073      // The delay line reported samples and the flush produced none.
1074      // Another flush would report the same and produce the same — this
1075      // really happens, `swr_get_delay` standing at a residue the
1076      // converter will not emit — so this is the end of the tail and
1077      // not a pause in it. Saying otherwise is the spin this reform
1078      // removes.
1079      None => {
1080        self.drained = true;
1081        Ok(Received::Ended)
1082      }
1083    }
1084  }
1085
1086  pub(crate) fn send_eof_impl(&mut self) -> Result<Sent, ResampleError> {
1087    self.eof = true;
1088    Ok(Sent::Accepted)
1089  }
1090
1091  /// Resets the resampler for another stream on the same two specs.
1092  ///
1093  /// The `swr` context is **rebuilt**, not drained. `swresample` has no
1094  /// reset call, and draining it dry cannot be verified from outside: a
1095  /// `swr_convert_frame` that makes no progress reports no error, so a
1096  /// drain loop that gives up and a drain loop that finished are
1097  /// indistinguishable — and a flush that returned `Ok` with the old
1098  /// delay line still inside would let one stream's tail contaminate
1099  /// the next. A fresh context is the only reset whose success is a
1100  /// fact.
1101  ///
1102  /// The new context is built before the old one is dropped, so a
1103  /// failure leaves the resampler exactly as it was: this call either
1104  /// resets everything or changes nothing.
1105  pub(crate) fn flush_impl(&mut self) -> Result<(), ResampleError> {
1106    let ctx = open_context(
1107      &self.source,
1108      &self.target,
1109      self.staged_source_layout,
1110      self.staged_target_layout,
1111    )?;
1112    self.ctx = ctx;
1113    self.ready.clear();
1114    self.next_pts = None;
1115    self.eof = false;
1116    self.drained = false;
1117    debug_assert_eq!(self.delay_impl(), 0, "a fresh swr context holds nothing");
1118    Ok(())
1119  }
1120}
1121
1122macro_rules! resampler_lane_face {
1123  ($($lane:ty),+ $(,)?) => { $(
1124    impl CarrierResampler<$lane> {
1125      /// Opens a resampler between two explicit specs. See
1126      /// [`CarrierResampler::new_impl`] for the full contract.
1127      pub fn new(
1128        source: ResampleSpec,
1129        target: ResampleSpec,
1130        limits: FrameLimits,
1131      ) -> Result<Self, ResampleError> {
1132        Self::new_impl(source, target, limits)
1133      }
1134
1135      /// The spec frames must arrive in.
1136      pub const fn source(&self) -> &ResampleSpec {
1137        self.source_impl()
1138      }
1139
1140      /// The spec frames leave in.
1141      pub const fn target(&self) -> &ResampleSpec {
1142        self.target_impl()
1143      }
1144
1145      /// The wrapped `swr` context.
1146      pub const fn inner(&self) -> &resampling::Context {
1147        self.inner_impl()
1148      }
1149
1150      /// Samples still inside the delay line, counted at the output
1151      /// rate.
1152      pub fn delay(&self) -> i64 {
1153        self.delay_impl()
1154      }
1155    }
1156
1157    impl AudioResampler for CarrierResampler<$lane> {
1158      type Adapter = Ffmpeg;
1159      type Buffer = <$lane as crate::FfmpegCarrier>::Buffer;
1160      type Error = ResampleError;
1161
1162      fn send_frame(&mut self, frame: &Frame<$lane>) -> Result<Sent, ResampleError> {
1163        self.send_frame_impl(frame)
1164      }
1165
1166      fn receive_frame(&mut self, dst: &mut Frame<$lane>) -> Result<Received, ResampleError> {
1167        self.receive_frame_impl(dst)
1168      }
1169
1170      fn send_eof(&mut self) -> Result<Sent, ResampleError> {
1171        self.send_eof_impl()
1172      }
1173
1174      fn flush(&mut self) -> Result<(), ResampleError> {
1175        self.flush_impl()
1176      }
1177    }
1178  )+ };
1179}
1180
1181resampler_lane_face!(crate::View, crate::Owned);
1182
1183/// Payload for [`ResampleError::SourceChanged`].
1184///
1185/// A frame arrived whose shape is not the source spec this resampler
1186/// was built with — the mid-stream refusal.
1187///
1188/// The face never silently reconfigures: doing so would resample the
1189/// two halves of a stream on different terms and hand back a single
1190/// unbroken timeline built out of them. Build a new resampler for the
1191/// new source spec.
1192#[derive(thiserror::Error, Debug, Clone)]
1193#[error(
1194  "source format changed mid-stream: expected {expected_rate} Hz {expected_format:?}, \
1195   got {found_rate} Hz {found_format:?}"
1196)]
1197pub struct SourceChanged {
1198  expected_rate: u32,
1199  expected_format: SampleFormat,
1200  found_rate: u32,
1201  found_format: SampleFormat,
1202}
1203
1204impl SourceChanged {
1205  /// Constructs a `SourceChanged` payload.
1206  #[inline]
1207  pub const fn new(
1208    expected_rate: u32,
1209    expected_format: SampleFormat,
1210    found_rate: u32,
1211    found_format: SampleFormat,
1212  ) -> Self {
1213    Self {
1214      expected_rate,
1215      expected_format,
1216      found_rate,
1217      found_format,
1218    }
1219  }
1220  /// Rate the resampler was built for.
1221  #[inline]
1222  pub const fn expected_rate(&self) -> u32 {
1223    self.expected_rate
1224  }
1225  /// Sample format the resampler was built for.
1226  #[inline]
1227  pub const fn expected_format(&self) -> SampleFormat {
1228    self.expected_format
1229  }
1230  /// Rate the offending frame carried.
1231  #[inline]
1232  pub const fn found_rate(&self) -> u32 {
1233    self.found_rate
1234  }
1235  /// Sample format the offending frame carried.
1236  #[inline]
1237  pub const fn found_format(&self) -> SampleFormat {
1238    self.found_format
1239  }
1240}
1241
1242/// Payload for [`ResampleError::PlaneCount`].
1243///
1244/// A frame's planes do not hold what its header claims — too few
1245/// planes for the format, or a plane shorter than its sample count
1246/// requires.
1247#[derive(thiserror::Error, Debug, Clone)]
1248#[error("frame plane geometry mismatch: expected {expected}, found {found}")]
1249pub struct PlaneCount {
1250  expected: usize,
1251  found: usize,
1252}
1253
1254impl PlaneCount {
1255  /// Constructs a `PlaneCount` payload.
1256  #[inline]
1257  pub const fn new(expected: usize, found: usize) -> Self {
1258    Self { expected, found }
1259  }
1260  /// What the format and sample count require.
1261  #[inline]
1262  pub const fn expected(&self) -> usize {
1263    self.expected
1264  }
1265  /// What the frame carries.
1266  #[inline]
1267  pub const fn found(&self) -> usize {
1268    self.found
1269  }
1270}
1271
1272/// Payload for [`ResampleError::OutputTooLarge`].
1273///
1274/// The converted frame would be larger than
1275/// [`FrameLimits::max_frame_bytes`] allows.
1276///
1277/// Distinct from [`SampleCount`], which is about a count no `AVFrame`
1278/// can express at all. This one is about a frame FFmpeg would happily
1279/// allocate and the caller has said it does not want: the amplification
1280/// a hostile source rate buys — one second at a declared 1 Hz becomes
1281/// gigabytes at 192 kHz — lands here.
1282#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
1283#[error("a converted frame of {bytes} bytes exceeds the {limit}-byte ceiling")]
1284pub struct OutputTooLarge {
1285  bytes: usize,
1286  limit: usize,
1287}
1288
1289impl OutputTooLarge {
1290  /// Constructs an `OutputTooLarge` payload.
1291  #[cfg_attr(not(tarpaulin), inline(always))]
1292  pub const fn new(bytes: usize, limit: usize) -> Self {
1293    Self { bytes, limit }
1294  }
1295  /// The bytes the conversion would have produced.
1296  #[cfg_attr(not(tarpaulin), inline(always))]
1297  pub const fn bytes(&self) -> usize {
1298    self.bytes
1299  }
1300  /// The ceiling in force.
1301  #[cfg_attr(not(tarpaulin), inline(always))]
1302  pub const fn limit(&self) -> usize {
1303    self.limit
1304  }
1305}
1306
1307/// Payload for [`ResampleError::SampleCount`].
1308///
1309/// A sample count no frame can hold: one whose byte size overflows, or
1310/// one past the `c_int` `av_frame_get_buffer` takes.
1311#[derive(thiserror::Error, Debug, Clone)]
1312#[error("{requested} samples is not a frame size")]
1313pub struct SampleCount {
1314  requested: usize,
1315}
1316
1317impl SampleCount {
1318  /// Constructs a `SampleCount` payload.
1319  #[inline]
1320  pub const fn new(requested: usize) -> Self {
1321    Self { requested }
1322  }
1323  /// The count that was asked for.
1324  #[inline]
1325  pub const fn requested(&self) -> usize {
1326    self.requested
1327  }
1328}
1329
1330/// Payload for [`ResampleError::UnsupportedRate`].
1331///
1332/// One end of the conversion declares a sample rate `swr` cannot be
1333/// driven with — zero, or past `c_int`.
1334#[derive(thiserror::Error, Debug, Clone)]
1335#[error("the {end} rate {rate} is not a sample rate swr can use")]
1336pub struct UnsupportedRate {
1337  end: SpecEnd,
1338  rate: u32,
1339}
1340
1341impl UnsupportedRate {
1342  /// Constructs an `UnsupportedRate` payload.
1343  #[inline]
1344  pub const fn new(end: SpecEnd, rate: u32) -> Self {
1345    Self { end, rate }
1346  }
1347  /// Which end of the conversion.
1348  #[inline]
1349  pub const fn end(&self) -> SpecEnd {
1350    self.end
1351  }
1352  /// The rate that was declared.
1353  #[inline]
1354  pub const fn rate(&self) -> u32 {
1355    self.rate
1356  }
1357}
1358
1359/// Payload for [`ResampleError::UnsupportedFormat`].
1360///
1361/// One end of the conversion declares no sample format
1362/// (`AV_SAMPLE_FMT_NONE`) — the state a codec context is in before its
1363/// decoder opens.
1364#[derive(thiserror::Error, Debug, Clone)]
1365#[error("the {end} spec names no sample format")]
1366pub struct UnsupportedFormat {
1367  end: SpecEnd,
1368}
1369
1370impl UnsupportedFormat {
1371  /// Constructs an `UnsupportedFormat` payload.
1372  #[inline]
1373  pub const fn new(end: SpecEnd) -> Self {
1374    Self { end }
1375  }
1376  /// Which end of the conversion.
1377  #[inline]
1378  pub const fn end(&self) -> SpecEnd {
1379    self.end
1380  }
1381}
1382
1383/// Payload for [`ResampleError::UnsupportedLayout`].
1384///
1385/// One end of the conversion declares a channel layout this backend
1386/// will not carry.
1387///
1388/// Native and unspecified layouts are the two it does. A **custom** or
1389/// **ambisonic** `AVChannelLayout` owns a heap-allocated channel map,
1390/// and FFmpeg documents that such a layout must be copied with
1391/// `av_channel_layout_copy` rather than assigned — while
1392/// `ffmpeg_next::ChannelLayout` is a `Copy` wrapper with no destructor.
1393/// Every `AVFrame` this type stages or allocates receives the layout by
1394/// assignment, and `av_frame_free` runs `av_channel_layout_uninit` on
1395/// it: the first staged frame to be dropped would free a map the spec,
1396/// the decoder and every later frame still point at. Refusing at
1397/// construction is what keeps that use-after-free unreachable; a
1398/// resampler over those layouts is a separate design, not a silent
1399/// approximation.
1400#[derive(thiserror::Error, Debug, Clone)]
1401#[error("the {end} channel layout is not supported: order {order}, {channels} channels")]
1402pub struct UnsupportedLayout {
1403  end: SpecEnd,
1404  order: i32,
1405  channels: i32,
1406}
1407
1408impl UnsupportedLayout {
1409  /// Constructs an `UnsupportedLayout` payload.
1410  #[inline]
1411  pub const fn new(end: SpecEnd, order: i32, channels: i32) -> Self {
1412    Self {
1413      end,
1414      order,
1415      channels,
1416    }
1417  }
1418  /// Which end of the conversion.
1419  #[inline]
1420  pub const fn end(&self) -> SpecEnd {
1421    self.end
1422  }
1423  /// `AVChannelOrder` as the raw integer it is on the wire.
1424  #[inline]
1425  pub const fn order(&self) -> i32 {
1426    self.order
1427  }
1428  /// The channel count the layout declares.
1429  #[inline]
1430  pub const fn channels(&self) -> i32 {
1431    self.channels
1432  }
1433}
1434
1435/// Payload for [`ResampleError::TooManyPlanes`].
1436///
1437/// A planar spec with more channels than a decoded frame has plane
1438/// slots.
1439///
1440/// `mediadecode`'s `AudioFrame` carries a fixed eight planes
1441/// (`AV_NUM_DATA_POINTERS`); planar audio past that lives in
1442/// `AVFrame.extended_data[]`, which this crate does not plumb through.
1443/// As a **source** no valid frame could ever arrive; as a **target**
1444/// `swr` would produce one this crate cannot hand back — and it would
1445/// fail only after the input had been consumed, leaving a session that
1446/// cannot be retried. Both are refused at construction, where nothing
1447/// has happened yet.
1448#[derive(thiserror::Error, Debug, Clone)]
1449#[error("the {end} spec is planar with {channels} channels; a frame carries {limit} planes")]
1450pub struct TooManyPlanes {
1451  end: SpecEnd,
1452  channels: i32,
1453  limit: i32,
1454}
1455
1456impl TooManyPlanes {
1457  /// Constructs a `TooManyPlanes` payload.
1458  #[inline]
1459  pub const fn new(end: SpecEnd, channels: i32, limit: i32) -> Self {
1460    Self {
1461      end,
1462      channels,
1463      limit,
1464    }
1465  }
1466  /// Which end of the conversion.
1467  #[inline]
1468  pub const fn end(&self) -> SpecEnd {
1469    self.end
1470  }
1471  /// The channel count the layout declares.
1472  #[inline]
1473  pub const fn channels(&self) -> i32 {
1474    self.channels
1475  }
1476  /// Plane slots a frame has.
1477  #[inline]
1478  pub const fn limit(&self) -> i32 {
1479    self.limit
1480  }
1481}
1482
1483/// Payload for [`ResampleError::UnsupportedChannelCount`].
1484///
1485/// A spec declaring more channels than a frame's channel seat can
1486/// carry. `mediadecode`'s `AudioFrame` states its channel count in a
1487/// `u8`, so 255 is the ceiling in both directions.
1488///
1489/// This is the **packed** sibling of [`TooManyPlanes`], which only ever
1490/// caught planar specs: packed audio declares one plane whatever its
1491/// channel count, so a 256-channel packed spec sailed past that check
1492/// and had its count clipped on the way into the frame — a frame whose
1493/// bytes were computed from 256 channels while advertising 255. Refused
1494/// here, at the same choke point, for both ends.
1495#[derive(thiserror::Error, Debug, Clone)]
1496#[error("the {end} spec declares {channels} channels; a frame carries at most {limit}")]
1497pub struct UnsupportedChannelCount {
1498  end: SpecEnd,
1499  channels: i32,
1500  limit: i32,
1501}
1502
1503impl UnsupportedChannelCount {
1504  /// Constructs an `UnsupportedChannelCount` payload.
1505  #[inline]
1506  pub const fn new(end: SpecEnd, channels: i32, limit: i32) -> Self {
1507    Self {
1508      end,
1509      channels,
1510      limit,
1511    }
1512  }
1513  /// Which end of the conversion.
1514  #[inline]
1515  pub const fn end(&self) -> SpecEnd {
1516    self.end
1517  }
1518  /// The channel count the layout declares.
1519  #[inline]
1520  pub const fn channels(&self) -> i32 {
1521    self.channels
1522  }
1523  /// Channels a frame can state.
1524  #[inline]
1525  pub const fn limit(&self) -> i32 {
1526    self.limit
1527  }
1528}
1529
1530/// Payload for [`ResampleError::TimestampOutOfRange`].
1531///
1532/// A frame's timestamp does not land on the output timeline: it does
1533/// not survive the rescale as an `i64`, or it is `AV_NOPTS_VALUE`,
1534/// which is a sentinel rather than a time.
1535///
1536/// Raised before anything is staged, so a refused frame leaves the
1537/// resampler exactly as it was.
1538#[derive(thiserror::Error, Debug, Clone)]
1539#[error("the frame timestamp {pts} does not land on the output timeline")]
1540pub struct TimestampOutOfRange {
1541  pts: i64,
1542}
1543
1544impl TimestampOutOfRange {
1545  /// Constructs a `TimestampOutOfRange` payload.
1546  #[inline]
1547  pub const fn new(pts: i64) -> Self {
1548    Self { pts }
1549  }
1550  /// The timestamp the frame carried, in its own timebase.
1551  #[inline]
1552  pub const fn pts(&self) -> i64 {
1553    self.pts
1554  }
1555}
1556
1557/// Payload for [`ResampleError::ChannelDropped`].
1558///
1559/// The conversion between these two layouts would silently drop a
1560/// source channel: FFmpeg's own mixing matrix routes it to no output.
1561///
1562/// `swr` mixes the channel positions its rematrix table knows and
1563/// processes the rest of the input as though it were absent — a log
1564/// line at most. Measured against FFmpeg 9, packed 22.2 → mono loses
1565/// fifteen of twenty-four channels and `cube` → stereo loses two of
1566/// eight, so this is not a matter of channel count. Installing an
1567/// explicit mix matrix is how such a conversion would be accepted
1568/// deliberately; until this crate has a seat for one, the pair is
1569/// refused.
1570#[derive(thiserror::Error, Debug, Clone)]
1571#[error(
1572  "converting {source_channels} channels to {target_channels} would drop source channel \
1573   {channel}: FFmpeg's mixing matrix routes it to no output"
1574)]
1575pub struct ChannelDropped {
1576  source_channels: i32,
1577  target_channels: i32,
1578  channel: i32,
1579}
1580
1581impl ChannelDropped {
1582  /// Constructs a `ChannelDropped` payload.
1583  #[inline]
1584  pub const fn new(source_channels: i32, target_channels: i32, channel: i32) -> Self {
1585    Self {
1586      source_channels,
1587      target_channels,
1588      channel,
1589    }
1590  }
1591  /// Channels the source layout declares.
1592  #[inline]
1593  pub const fn source_channels(&self) -> i32 {
1594    self.source_channels
1595  }
1596  /// Channels the target layout declares.
1597  #[inline]
1598  pub const fn target_channels(&self) -> i32 {
1599    self.target_channels
1600  }
1601  /// The first source channel that reaches no output channel.
1602  #[inline]
1603  pub const fn channel(&self) -> i32 {
1604    self.channel
1605  }
1606}
1607
1608/// Payload for [`ResampleError::RematrixUnsupported`].
1609///
1610/// FFmpeg will not build a mixing matrix between these two layouts at
1611/// all.
1612#[derive(thiserror::Error, Debug, Clone)]
1613#[error("FFmpeg builds no mixing matrix from {source_channels} channels to {target_channels}")]
1614pub struct RematrixUnsupported {
1615  source_channels: i32,
1616  target_channels: i32,
1617}
1618
1619impl RematrixUnsupported {
1620  /// Constructs a `RematrixUnsupported` payload.
1621  #[inline]
1622  pub const fn new(source_channels: i32, target_channels: i32) -> Self {
1623    Self {
1624      source_channels,
1625      target_channels,
1626    }
1627  }
1628  /// Channels the source layout declares.
1629  #[inline]
1630  pub const fn source_channels(&self) -> i32 {
1631    self.source_channels
1632  }
1633  /// Channels the target layout declares.
1634  #[inline]
1635  pub const fn target_channels(&self) -> i32 {
1636    self.target_channels
1637  }
1638}
1639
1640/// Payload for [`ResampleError::TimestampOverflow`].
1641///
1642/// The output timeline would leave `i64`. Counted timestamps are exact
1643/// or they are nothing, so this is named rather than saturated.
1644#[derive(thiserror::Error, Debug, Clone)]
1645#[error("the output timeline overflows: {pts} + {samples} samples")]
1646pub struct TimestampOverflow {
1647  pts: i64,
1648  samples: i64,
1649}
1650
1651impl TimestampOverflow {
1652  /// Constructs a `TimestampOverflow` payload.
1653  #[inline]
1654  pub const fn new(pts: i64, samples: i64) -> Self {
1655    Self { pts, samples }
1656  }
1657  /// Where the timeline stood.
1658  #[inline]
1659  pub const fn pts(&self) -> i64 {
1660    self.pts
1661  }
1662  /// How many samples were produced.
1663  #[inline]
1664  pub const fn samples(&self) -> i64 {
1665    self.samples
1666  }
1667}
1668
1669/// Payload for [`ResampleError::OutputBuffer`].
1670///
1671/// A reference to one of the output frame's planes could not be taken.
1672///
1673/// Raised while preparing the conversion, never after it: that is the
1674/// point of preparing.
1675#[derive(thiserror::Error, Debug, Clone)]
1676#[error("the output frame's plane {plane} could not be referenced")]
1677pub struct OutputBuffer {
1678  plane: usize,
1679}
1680
1681impl OutputBuffer {
1682  /// Constructs an `OutputBuffer` payload.
1683  #[inline]
1684  pub const fn new(plane: usize) -> Self {
1685    Self { plane }
1686  }
1687  /// Which plane slot.
1688  #[inline]
1689  pub const fn plane(&self) -> usize {
1690    self.plane
1691  }
1692}
1693
1694/// Errors from [`FfmpegResampler`] — **faults and the two send-side
1695/// refusals** ([`Self::SourceChanged`], [`Self::AfterEof`]).
1696///
1697/// `Again` used to be here and meant two opposite things: pre-EOF
1698/// "nothing ready, send more" and post-EOF "the tail is exhausted".
1699/// A caller polling the seam could tell them apart only by remembering
1700/// whether it had itself called `send_eof`; one that did not spun
1701/// forever. Both are [`Received`] states now, and they are distinct.
1702///
1703/// **Open fault taxonomy, so it is `#[non_exhaustive]`.** New ways to
1704/// fail are discovered — a backend, a ceiling, a corruption a codec
1705/// learns to report — and a consumer that meets one it has never heard
1706/// of should take its generic-fault path. That is exactly what the
1707/// wildcard arm this attribute forces is for. The two status
1708/// vocabularies opposite it,
1709/// [`Sent`](mediadecode::Sent) and [`Received`](mediadecode::Received),
1710/// are exhaustive for the mirror-image reason: their arms are the
1711/// substrate's fixed state set, and there the wildcard would be dead
1712/// weight hiding a state a consumer forgot.
1713#[derive(thiserror::Error, Debug, IsVariant, Unwrap, TryUnwrap)]
1714#[unwrap(ref, ref_mut)]
1715#[try_unwrap(ref, ref_mut)]
1716#[non_exhaustive]
1717pub enum ResampleError {
1718  /// The conversion would produce a frame larger than the ceiling
1719  /// allows. Refused **before** the output frame is allocated.
1720  #[error(transparent)]
1721  OutputTooLarge(#[from] OutputTooLarge),
1722
1723  /// A frame arrived whose shape is not the source spec this resampler
1724  /// was built with — the mid-stream refusal.
1725  #[error(transparent)]
1726  SourceChanged(#[from] SourceChanged),
1727
1728  /// [`send_frame`](AudioResampler::send_frame) was called after
1729  /// [`send_eof`](AudioResampler::send_eof). Call
1730  /// [`flush`](AudioResampler::flush) first to reuse the resampler for
1731  /// another stream.
1732  #[error("send_frame after send_eof; flush() first to start another stream")]
1733  AfterEof,
1734
1735  /// A frame's planes do not hold what its header claims — too few
1736  /// planes for the format, or a plane shorter than its sample count
1737  /// requires.
1738  #[error(transparent)]
1739  PlaneCount(#[from] PlaneCount),
1740
1741  /// A sample count no frame can hold: one whose byte size overflows,
1742  /// or one past the `c_int` `av_frame_get_buffer` takes.
1743  #[error(transparent)]
1744  SampleCount(#[from] SampleCount),
1745
1746  /// One end of the conversion declares a sample rate `swr` cannot be
1747  /// driven with — zero, or past `c_int`.
1748  #[error(transparent)]
1749  UnsupportedRate(#[from] UnsupportedRate),
1750
1751  /// One end of the conversion declares no sample format
1752  /// (`AV_SAMPLE_FMT_NONE`) — the state a codec context is in before
1753  /// its decoder opens.
1754  #[error(transparent)]
1755  UnsupportedFormat(#[from] UnsupportedFormat),
1756
1757  /// One end of the conversion declares a channel layout this backend
1758  /// will not carry.
1759  #[error(transparent)]
1760  UnsupportedLayout(#[from] UnsupportedLayout),
1761
1762  /// A planar spec with more channels than a decoded frame has plane
1763  /// slots.
1764  #[error(transparent)]
1765  TooManyPlanes(#[from] TooManyPlanes),
1766
1767  /// A spec with more channels than a frame's channel seat can state.
1768  #[error(transparent)]
1769  UnsupportedChannelCount(#[from] UnsupportedChannelCount),
1770
1771  /// A frame's timestamp does not land on the output timeline.
1772  #[error(transparent)]
1773  TimestampOutOfRange(#[from] TimestampOutOfRange),
1774
1775  /// The conversion between these two layouts would silently drop a
1776  /// source channel.
1777  #[error(transparent)]
1778  ChannelDropped(#[from] ChannelDropped),
1779
1780  /// FFmpeg will not build a mixing matrix between these two layouts at
1781  /// all.
1782  #[error(transparent)]
1783  RematrixUnsupported(#[from] RematrixUnsupported),
1784
1785  /// The output timeline would leave `i64`. Counted timestamps are
1786  /// exact or they are nothing, so this is named rather than saturated.
1787  #[error(transparent)]
1788  TimestampOverflow(#[from] TimestampOverflow),
1789
1790  /// The wrapped `swresample` call reported an error.
1791  #[error(transparent)]
1792  Resample(#[from] Error),
1793
1794  /// A reference to one of the output frame's planes could not be
1795  /// taken.
1796  #[error(transparent)]
1797  OutputBuffer(#[from] OutputBuffer),
1798
1799  /// The queue of converted frames could not be grown to hold one more.
1800  #[error("out of memory reserving room for a converted frame")]
1801  QueueAlloc,
1802}
1803
1804/// Which end of a conversion a refusal is about.
1805#[derive(Copy, Clone, Debug, PartialEq, Eq, IsVariant)]
1806pub enum SpecEnd {
1807  /// The spec frames must arrive in.
1808  Source,
1809  /// The spec frames leave in.
1810  Target,
1811}
1812
1813impl core::fmt::Display for SpecEnd {
1814  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1815    f.write_str(match self {
1816      Self::Source => "source",
1817      Self::Target => "target",
1818    })
1819  }
1820}
1821
1822/// Plane slots a `mediadecode::frame::AudioFrame` has — the fixed array
1823/// matching `AV_NUM_DATA_POINTERS`. Planar audio past this many
1824/// channels lives in `AVFrame.extended_data[]` / `extended_buf[]`,
1825/// which this crate does not plumb through: `convert` refuses such a
1826/// frame and `AudioFrame::new` will not build one.
1827const MAX_AUDIO_PLANES: i32 = 8;
1828
1829/// Channels a `mediadecode::frame::AudioFrame` can state — its channel
1830/// seat is a `u8`. A spec past this is refused rather than clipped.
1831const MAX_FRAME_CHANNELS: i32 = u8::MAX as i32;
1832
1833/// Refuses a spec `swr` cannot be driven with, or whose channel layout
1834/// cannot be carried by value — see [`ResampleError::UnsupportedLayout`]
1835/// for that one, which is the whole reason this check exists at the
1836/// choke point rather than in the `const` constructor.
1837fn check_spec(spec: &ResampleSpec, end: SpecEnd) -> Result<(), ResampleError> {
1838  if spec.rate == 0 || spec.rate > i32::MAX as u32 {
1839    return Err(ResampleError::UnsupportedRate(UnsupportedRate::new(
1840      end, spec.rate,
1841    )));
1842  }
1843  if spec.format == Sample::None {
1844    return Err(ResampleError::UnsupportedFormat(UnsupportedFormat::new(
1845      end,
1846    )));
1847  }
1848  let order = layout_order(&spec.layout);
1849  let channels = spec.layout.channels();
1850  let carried = order == AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32
1851    || order == AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32;
1852  if !carried || channels <= 0 {
1853    return Err(ResampleError::UnsupportedLayout(UnsupportedLayout::new(
1854      end, order, channels,
1855    )));
1856  }
1857  // A planar spec with more channels than the frame model has plane
1858  // slots is a resampler that cannot work in either direction, and
1859  // saying so here is the difference between a refusal at construction
1860  // and a refusal on every frame — the target one arriving *after*
1861  // `swr` has already consumed the input, which is not a state a caller
1862  // can retry from.
1863  if spec.format.is_planar() && channels > MAX_AUDIO_PLANES {
1864    return Err(ResampleError::TooManyPlanes(TooManyPlanes::new(
1865      end,
1866      channels,
1867      MAX_AUDIO_PLANES,
1868    )));
1869  }
1870  // And the packed sibling, which the plane check above cannot see: a
1871  // packed spec declares one plane at any channel count, so it reached
1872  // the frame with its count clipped to 255 instead of refused. That is
1873  // the same silent truncation the decode path was carrying, on the
1874  // other audio road — closed here, at the same choke point, so that
1875  // every channel count downstream is exact by construction.
1876  if channels > MAX_FRAME_CHANNELS {
1877    return Err(ResampleError::UnsupportedChannelCount(
1878      UnsupportedChannelCount::new(end, channels, MAX_FRAME_CHANNELS),
1879    ));
1880  }
1881  Ok(())
1882}
1883
1884/// A layout's `AVChannelOrder` as the integer it is on the wire.
1885///
1886/// Read raw rather than matched as an `AVChannelOrder`, the discipline
1887/// this crate keeps everywhere it touches a bindgen enum: a value
1888/// outside this build's discriminant set would be undefined behaviour
1889/// the moment it existed as one.
1890/// Runs [`crate::channel_layout::layout_preflight`] over a caller's
1891/// layout and refuses a `CUSTOM` order outright.
1892///
1893/// The refusal is not squeamishness about custom layouts; it is what
1894/// makes the rest of this type's story true. A `CUSTOM` layout carries
1895/// a raw `u.map` pointer whose extent no signature here can establish —
1896/// the same wall [`crate::channel_layout::channel_layout_from_ffmpeg`]
1897/// meets — and a [`ResampleSpec`] holding one would be a struct with a
1898/// bare pointer inside, which is the thing that would have to be
1899/// reasoned about for every use of this value on another thread. Every
1900/// layout a live resampler holds is mask-only, and therefore a plain
1901/// value.
1902fn preflight_layout(layout: &ChannelLayout, end: SpecEnd) -> Result<(), ResampleError> {
1903  let unsupported = || {
1904    ResampleError::UnsupportedLayout(UnsupportedLayout::new(
1905      end,
1906      layout_order(layout),
1907      layout.channels(),
1908    ))
1909  };
1910  if layout_order(layout) == AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM as i32 {
1911    return Err(unsupported());
1912  }
1913  // SAFETY: `layout` is a live `ChannelLayout` for the duration of this
1914  // call, and the custom order — the only one whose contract mentions a
1915  // map — was just refused.
1916  unsafe { crate::channel_layout::layout_preflight(&layout.0 as *const _) }
1917    .map_err(|_| unsupported())
1918}
1919
1920/// **`Send`, and deliberately not `Sync`.**
1921///
1922/// # Safety
1923///
1924/// The `SwrContext` behind `ctx` is owned by this value: it is
1925/// allocated in [`open_context`], never shared, never cloned, and
1926/// dropped with the resampler. Every operation that touches it goes
1927/// through `&mut self` — `swr_convert_frame`, `swr_get_delay`,
1928/// `swr_init` on the rebuild — so no two threads can be inside
1929/// libswresample for this context at once, which is the whole of what
1930/// that library requires: it keeps no thread-local or process-global
1931/// state of its own, and a context is a plain heap allocation whose
1932/// address is meaningful on any thread.
1933///
1934/// The other fields are ordinary owned values. The two
1935/// `ffmpeg_next::ChannelLayout`s and the two [`ResampleSpec`]s wrap an
1936/// `AVChannelLayout`, which holds a pointer **only for
1937/// `AV_CHANNEL_ORDER_CUSTOM`** — and [`preflight_layout`] refuses that
1938/// order at the one construction choke point, so every layout a live
1939/// resampler holds is the `u.mask` arm: a `uint64_t`, a plain value.
1940/// The staged layouts come from [`initialized_layout`], which produces
1941/// `av_channel_layout_default`'s mask-only answer. `ready` holds
1942/// `Frame<C>` values, whose carriers are `Send` by the carrier
1943/// contract.
1944///
1945/// **Not `Sync`, and that is not an oversight.** `&self` would let two
1946/// threads hold the context at once, and nothing in this type
1947/// serialises that; the `&mut` receiver is the serialisation. Moving
1948/// the whole resampler to another thread is sound; sharing it is not
1949/// offered.
1950unsafe impl<C: crate::FfmpegCarrier> Send for CarrierResampler<C> {}
1951
1952fn layout_order(layout: &ChannelLayout) -> i32 {
1953  // SAFETY: `layout` is a live `ChannelLayout` for the duration of this
1954  // call; `addr_of!` reaches its `order` field without forming a
1955  // reference to the enum.
1956  unsafe { read_unaligned(addr_of!(layout.0.order).cast::<i32>()) }
1957}
1958
1959/// FFmpeg's `SWR_CH_MAX`: the square its own matrix builder writes,
1960/// whatever the two layouts' channel counts are.
1961///
1962/// Not a convenience. `swr_build_matrix2` copies its internal
1963/// `[SWR_CH_MAX][SWR_CH_MAX]` block out at the caller's stride, so a
1964/// buffer sized to the actual channel counts is written far past its
1965/// end — measured, and the measurement is a killed process.
1966const SWR_CH_MAX: usize = 64;
1967
1968/// Refuses an **effective pair** whose rematrixing would silently drop
1969/// input channels.
1970///
1971/// Takes the layouts `swr` is configured with, not the ones the caller
1972/// declared. The two differ exactly where it matters: an unspecified
1973/// layout is resolved to FFmpeg's default for its channel count before
1974/// the context is opened, and twenty-four unspecified channels resolve
1975/// to 22.2 — so the declared pair says "unspecified, nothing to
1976/// rematrix" while the conversion that runs is the lossy one.
1977///
1978/// This is the second half of the crate's two-layout bookkeeping, and
1979/// the halves answer different questions. The **declared** layout is
1980/// what decoded frames carry (a WAV without a channel mask hands out
1981/// unspecified frames forever) and stays the yardstick for the
1982/// mid-stream refusal: *is this frame the stream I was built for?* The
1983/// **effective** layout is what `swr` and every staged `AVFrame` use,
1984/// and it is the one judged here: *what will `swr` actually do?*
1985///
1986/// Each end can be perfectly valid on its own and the conversion
1987/// between them still lose whole channels: `swr` mixes only the channel
1988/// positions its rematrix table knows, and quietly processes the rest
1989/// of the input as though it were not there. Measured against the
1990/// linked FFmpeg 9 with a tone isolated in each source channel: packed
1991/// 22.2 → mono drops fifteen of twenty-four (`swr` says as much in a
1992/// log line and converts anyway), `cube` → stereo drops two of *eight*
1993/// — so a channel-count threshold is both too strict and too loose to
1994/// be the rule.
1995///
1996/// The rule is asked of FFmpeg instead: build the mixing matrix its own
1997/// builder would use, and refuse when any input channel reaches no
1998/// output at all. `lfe_mix_level` is deliberately non-zero, so the
1999/// question is "can this channel reach the output" rather than "does
2000/// FFmpeg's default downmix policy include it" — the default leaves LFE
2001/// out of a downmix on purpose, and refusing an everyday 5.1 → stereo
2002/// over that would be absurd. The predicate matched the tone sweep
2003/// exactly on every pair measured.
2004///
2005/// A pair `swr` cannot matrix at all is refused too. Accepting these
2006/// deliberately is a *mix matrix* seat on the spec — a real design, not
2007/// something to mint in passing; until it exists, refusal is the honest
2008/// answer.
2009fn check_pair(source: &ChannelLayout, target: &ChannelLayout) -> Result<(), ResampleError> {
2010  let native = AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32;
2011  // A layout still unspecified *after* resolution — a channel count
2012  // FFmpeg has no default for — is mapped positionally by `swr` with no
2013  // rematrixing at all, and identical layouts need no matrix: neither
2014  // can drop a channel, and neither is what the builder describes.
2015  if layout_order(source) != native || layout_order(target) != native || source == target {
2016    return Ok(());
2017  }
2018  let source_channels = source.channels();
2019  let target_channels = target.channels();
2020
2021  let mut matrix = vec![0f64; SWR_CH_MAX * SWR_CH_MAX];
2022  // SAFETY: both layouts are live for the call; `matrix` is the full
2023  // `SWR_CH_MAX` square the builder writes, passed with the matching
2024  // stride; the encoding is a compile-time constant of this build; and
2025  // a null log context is documented as allowed.
2026  let rc = unsafe {
2027    swr_build_matrix2(
2028      &source.0,
2029      &target.0,
2030      core::f64::consts::FRAC_1_SQRT_2,
2031      core::f64::consts::FRAC_1_SQRT_2,
2032      1.0,
2033      1.0,
2034      1.0,
2035      matrix.as_mut_ptr(),
2036      SWR_CH_MAX as isize,
2037      AVMatrixEncoding::AV_MATRIX_ENCODING_NONE,
2038      core::ptr::null_mut(),
2039    )
2040  };
2041  if rc < 0 {
2042    return Err(ResampleError::RematrixUnsupported(
2043      RematrixUnsupported::new(source_channels, target_channels),
2044    ));
2045  }
2046  for channel in 0..source_channels.min(SWR_CH_MAX as i32) {
2047    let index = channel as usize;
2048    if (0..target_channels.min(SWR_CH_MAX as i32) as usize)
2049      .all(|out| matrix[index + SWR_CH_MAX * out] == 0.0)
2050    {
2051      return Err(ResampleError::ChannelDropped(ChannelDropped::new(
2052        source_channels,
2053        target_channels,
2054        channel,
2055      )));
2056    }
2057  }
2058  Ok(())
2059}
2060
2061/// Opens a `swr` context for the two specs. Shared by
2062/// [`FfmpegResampler::new`] and the rebuild
2063/// [`AudioResampler::flush`] performs.
2064fn open_context(
2065  source: &ResampleSpec,
2066  target: &ResampleSpec,
2067  staged_source_layout: ChannelLayout,
2068  staged_target_layout: ChannelLayout,
2069) -> Result<resampling::Context, ResampleError> {
2070  resampling::Context::get(
2071    source.format,
2072    staged_source_layout,
2073    source.rate,
2074    target.format,
2075    staged_target_layout,
2076    target.rate,
2077  )
2078  .map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))
2079}
2080
2081/// Allocates an audio `AVFrame`, checking every step the dependency's
2082/// own `frame::Audio::new` does not.
2083///
2084/// `ffmpeg_next`'s constructor dereferences `av_frame_alloc`'s result
2085/// without a null check and discards `av_frame_get_buffer`'s return
2086/// value, so an allocation failure there yields a frame whose planes
2087/// are not backed — which is then handed to FFI. Both are checked here;
2088/// on failure the caller gets a named error and no frame at all. The
2089/// null check is the crate's existing one
2090/// ([`crate::frame::alloc_av_audio_frame`], which the decoders already
2091/// allocate through), so there is one answer to `av_frame_alloc`
2092/// returning null rather than two.
2093fn new_audio_frame(
2094  format: Sample,
2095  samples: usize,
2096  rate: u32,
2097  layout: ChannelLayout,
2098) -> Result<frame::Audio, ResampleError> {
2099  if samples == 0 || samples > i32::MAX as usize {
2100    return Err(ResampleError::SampleCount(SampleCount::new(samples)));
2101  }
2102  let mut out = crate::frame::alloc_av_audio_frame()?;
2103  out.set_format(format);
2104  out.set_samples(samples);
2105  // The layout is assigned by value, which is sound only because
2106  // `check_spec` refused every layout that owns a heap channel map.
2107  out.set_channel_layout(layout);
2108  out.set_rate(rate);
2109  // SAFETY: `out` is a live `AVFrame` whose format, sample count and
2110  // layout were just set; `av_frame_get_buffer` allocates its planes
2111  // and reports failure in its return value, which is checked.
2112  let rc = unsafe { av_frame_get_buffer(out.as_mut_ptr(), 0) };
2113  if rc < 0 {
2114    return Err(ResampleError::Resample(Error::Ffmpeg(
2115      ffmpeg_next::Error::from(rc),
2116    )));
2117  }
2118  Ok(out)
2119}
2120
2121/// Bytes one plane holds for `samples` samples of `format`, or `None`
2122/// when that product does not fit a `usize`. Packed formats keep every
2123/// channel in the single plane; planar formats give each channel its
2124/// own.
2125fn plane_bytes(format: Sample, samples: usize, channels: i32) -> Option<usize> {
2126  // Refused, not floored. `check_spec` already proves the count is in
2127  // `1..=MAX_FRAME_CHANNELS` before any resampler exists, so this is a
2128  // restatement of an invariant rather than a live branch — but it is
2129  // stated as a refusal because the alternative was a substituted `1`,
2130  // which invents a channel the caller never declared and makes the
2131  // byte product disagree with the frame it sizes.
2132  let channels = usize::try_from(channels).ok().filter(|count| *count > 0)?;
2133  let bytes = samples.checked_mul(format.bytes())?;
2134  if format.is_planar() {
2135    Some(bytes)
2136  } else {
2137    bytes.checked_mul(channels)
2138  }
2139}
2140
2141/// Builds a native-order [`ChannelLayout`] from a channel bitmask,
2142/// without ever forming an `AVChannelLayout` out of foreign memory:
2143/// the struct starts zeroed (`AV_CHANNEL_ORDER_UNSPEC` is `0`, a valid
2144/// discriminant) and FFmpeg fills it.
2145fn layout_from_mask(mask: u64) -> ChannelLayout {
2146  // SAFETY: a zeroed `AVChannelLayout` is a valid value — its `order`
2147  // field reads as `AV_CHANNEL_ORDER_UNSPEC`, the zero discriminant —
2148  // and `av_channel_layout_from_mask` overwrites it wholesale.
2149  unsafe {
2150    let mut layout = std::mem::zeroed();
2151    if av_channel_layout_from_mask(&mut layout, mask) < 0 {
2152      return ChannelLayout::default(mask.count_ones() as i32);
2153    }
2154    ChannelLayout(layout)
2155  }
2156}
2157
2158/// The layout `swr` will actually be configured with.
2159///
2160/// `swr_init` replaces an unspecified input or output layout with
2161/// FFmpeg's default for that channel count, and from then on compares
2162/// every frame handed to it against *that* layout — a staged frame
2163/// still carrying the unspecified one is refused with
2164/// `AVERROR_INPUT_CHANGED`. Applying the same rule here, once, keeps
2165/// the frames this type builds in step with the context it built.
2166///
2167/// The declared layout is kept separately and is what the mid-stream
2168/// check compares against, because it is what decoded frames really
2169/// carry: a WAV without a channel mask hands out unspecified frames
2170/// forever, whatever `swr` decided internally.
2171fn initialized_layout(layout: ChannelLayout) -> ChannelLayout {
2172  if layout.is_empty() {
2173    ChannelLayout::default(layout.channels())
2174  } else {
2175    layout
2176  }
2177}
2178
2179/// Reads an `AVChannelLayout` out of FFmpeg memory into a layout this
2180/// spec can own, or `None` for one it does not represent.
2181///
2182/// The `order` field is read as the integer it is on the wire: an
2183/// out-of-range value would be undefined behaviour the instant it
2184/// existed as an `AVChannelOrder`, which is the hazard this crate
2185/// keeps out everywhere it touches a bindgen enum.
2186///
2187/// A **custom** or **ambisonic** layout returns `None`. Both keep a
2188/// heap-allocated channel map inside the layout, and `ChannelLayout` is
2189/// a plain `Copy` wrapper with no destructor: owning one here would
2190/// either alias a map the decoder still frees or leak the copy. A
2191/// resampler over one of those layouts is a separate design, not a
2192/// silent approximation.
2193///
2194/// # Safety
2195///
2196/// `ptr` must be a live `*const AVChannelLayout` for the duration of
2197/// this call.
2198unsafe fn layout_from_raw(ptr: *const ffmpeg_next::ffi::AVChannelLayout) -> Option<ChannelLayout> {
2199  let order = unsafe { read_unaligned(addr_of!((*ptr).order).cast::<i32>()) };
2200  let channels = unsafe { (*ptr).nb_channels };
2201  // The union's `mask` arm is only read for the order that names it —
2202  // for `CUSTOM` those eight bytes are a pointer. `layout_from_parts`
2203  // refuses that order anyway, so a zero is the honest thing to hand
2204  // it and reading the pointer would be the dishonest one.
2205  let mask = if matches!(LayoutArm::of(order), LayoutArm::Mask) {
2206    // SAFETY: the arm names exactly the orders whose contract defines
2207    // `u.mask`. Through the shared dispatcher rather than a local
2208    // comparison, which had named `NATIVE` alone and so dropped an
2209    // ambisonic layout's non-diegetic channels on the floor.
2210    unsafe { (*ptr).u.mask }
2211  } else {
2212    0
2213  };
2214  layout_from_parts(order, channels, mask)
2215}
2216
2217/// The layout roster, over parts rather than a pointer.
2218///
2219/// One function so the two roads into a source spec —
2220/// [`ResampleSpec::from_parameters`] over a live `AVCodecParameters`
2221/// and [`ResampleSpec::from_ticket`] over an owned mirror — cannot
2222/// drift into admitting different layouts from the same file.
2223fn layout_from_parts(order: i32, channels: i32, mask: u64) -> Option<ChannelLayout> {
2224  if channels <= 0 {
2225    return None;
2226  }
2227  if order == AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32 {
2228    if mask != 0 {
2229      return Some(layout_from_mask(mask));
2230    }
2231    // Native in name with no channels named: unspecified in substance.
2232    return Some(ResampleSpec::unspecified_layout(channels));
2233  }
2234  if order == AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32 {
2235    return Some(ResampleSpec::unspecified_layout(channels));
2236  }
2237  None
2238}
2239
2240/// Compile-time assurance that `SampleFormat`'s round trip through
2241/// FFmpeg's vocabulary is the identity on the closed set. Both
2242/// directions are hand-written tables, and a table that disagreed with
2243/// its inverse would silently mislabel every sample.
2244const _: () = {
2245  assert!(
2246    SampleFormat::from_raw(AVSampleFormat::AV_SAMPLE_FMT_NONE as i32)
2247      .to_ffmpeg()
2248      .is_none()
2249  );
2250};
2251
2252#[cfg(test)]
2253mod tests {
2254  use super::*;
2255
2256  use mediadecode::resampler::AudioResampler;
2257
2258  // These exercise the conversion arithmetic — rates, layouts, the
2259  // counted timeline, the byte ceiling — which is lane-independent, so
2260  // they run on the owned lane exactly as they did before the second
2261  // lane existed. The view lane's own road through this type (reserve
2262  // before `swr`, commit after) is proved in `tests/view_carriers.rs`,
2263  // where a produced plane can be shown to point into the output
2264  // frame's buffer.
2265  use crate::{FfmpegBytes, FfmpegOwnedResampler as FfmpegResampler};
2266
2267  type Frame = super::Frame<crate::Owned>;
2268
2269  /// **The resampler moves between threads, and does not share.**
2270  ///
2271  /// Stated as a compile-time fact rather than a comment because the
2272  /// `unsafe impl Send` on this type rests on an invariant a later
2273  /// change could quietly break: every layout a live resampler holds is
2274  /// the mask arm of its union, because [`preflight_layout`] refuses a
2275  /// `CUSTOM` order at the one construction choke point. If that
2276  /// refusal is ever removed, a `ResampleSpec` starts carrying a raw
2277  /// `u.map` pointer and the argument behind the `unsafe impl` is no
2278  /// longer true — this lane will still compile, so the refusal's own
2279  /// lane below is what guards it, and this one guards the `Send`
2280  /// itself against being dropped.
2281  ///
2282  /// `Sync` is deliberately absent and deliberately not asserted here:
2283  /// the `SwrContext` is serialised by `&mut self`, which `&self` would
2284  /// not provide.
2285  #[test]
2286  fn the_resampler_is_send_on_both_lanes() {
2287    const fn assert_send<T: Send>() {}
2288    assert_send::<CarrierResampler<crate::Owned>>();
2289    assert_send::<CarrierResampler<crate::View>>();
2290  }
2291
2292  /// **`==` on a spec never reaches FFmpeg — and this lane's *passing*
2293  /// is the proof.**
2294  ///
2295  /// `ResampleSpec` derived `PartialEq`, and `ffmpeg_next::ChannelLayout`
2296  /// implements it as `av_channel_layout_compare`, which walks
2297  /// `u.map[i]` for a `CUSTOM` order. So a safe caller could forge a
2298  /// layout declaring channels it has no map for, wrap it in two specs,
2299  /// and write `a == b` — reaching a dereference of null inside FFmpeg
2300  /// without constructing a resampler or touching a single line of this
2301  /// crate's own code. No preflight can dominate an operator.
2302  ///
2303  /// The layouts below are exactly that shape. If the hand-written
2304  /// comparison is ever replaced by a derive again, this lane does not
2305  /// fail — it **crashes**, because FFmpeg reads from the null map. That
2306  /// is the honest signal for this defect, and the same one the
2307  /// null-map lanes in `channel_layout` carry.
2308  #[test]
2309  fn spec_equality_never_follows_a_channel_map() {
2310    // SAFETY: a zeroed `AVChannelLayout` is a valid value; the fields
2311    // below declare two channels under a custom order and leave `u.map`
2312    // null — a shape safe Rust can build and FFmpeg would dereference.
2313    let forged = || {
2314      let mut inner: ffmpeg_next::ffi::AVChannelLayout = unsafe { std::mem::zeroed() };
2315      inner.order = AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM;
2316      inner.nb_channels = 2;
2317      ChannelLayout(inner)
2318    };
2319    let format = Sample::I16(ffmpeg_next::format::sample::Type::Packed);
2320    let one = ResampleSpec::new(48_000, format, forged());
2321    let two = ResampleSpec::new(48_000, format, forged());
2322
2323    // Two *separate* null maps: equal by every field this comparison
2324    // reads, and the answer is the conservative one because the maps
2325    // are compared by identity rather than by contents.
2326    assert!(one == two, "two null maps are the same map");
2327    // Reflexive, which `Eq` requires of it.
2328    assert!(one == one);
2329
2330    // A different map pointer is a different layout, without either
2331    // being read. `data` outlives the comparison.
2332    let data = [0u8; 64];
2333    let mut other_inner: ffmpeg_next::ffi::AVChannelLayout = unsafe { std::mem::zeroed() };
2334    other_inner.order = AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM;
2335    other_inner.nb_channels = 2;
2336    other_inner.u.map = data.as_ptr().cast_mut().cast();
2337    let three = ResampleSpec::new(48_000, format, ChannelLayout(other_inner));
2338    assert!(one != three, "two different maps are different layouts");
2339
2340    // And the ordinary road still answers as it always did: mask-only
2341    // orders compare by their mask.
2342    let stereo = ResampleSpec::new(48_000, format, ChannelLayout::STEREO);
2343    assert!(stereo == ResampleSpec::new(48_000, format, ChannelLayout::STEREO));
2344    assert!(stereo != ResampleSpec::new(48_000, format, ChannelLayout::MONO));
2345    assert!(stereo != ResampleSpec::new(44_100, format, ChannelLayout::STEREO));
2346    assert!(stereo != one, "a native layout is not a custom one");
2347  }
2348
2349  /// **An `UNSPEC` layout's union is undefined, so nothing reads it.**
2350  ///
2351  /// `AVChannelLayout`'s own header says the union must not be used for
2352  /// `AV_CHANNEL_ORDER_UNSPEC`, and this crate had been reading
2353  /// `u.mask` for every order that was not `CUSTOM`. Two valid
2354  /// unspecified layouts of the same width therefore compared
2355  /// **unequal** whenever whatever bytes happened to sit in that
2356  /// storage differed — a wrong answer derived from memory neither
2357  /// layout owns.
2358  ///
2359  /// The same was true of merely *printing* one: the derived `Debug`
2360  /// delegated to `ffmpeg_next`'s formatter, which prints `u.mask`
2361  /// unconditionally. Both are order-aware now; see `LayoutArm`.
2362  #[test]
2363  fn an_unspecified_layout_ignores_its_union() {
2364    let unspec = |channels: i32, junk: u64| {
2365      // SAFETY: a zeroed `AVChannelLayout` is a valid value. `UNSPEC`
2366      // is the zero discriminant, and the union is written with bytes
2367      // the order does not define — which is exactly the state under
2368      // test.
2369      let mut inner: ffmpeg_next::ffi::AVChannelLayout = unsafe { std::mem::zeroed() };
2370      inner.order = AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC;
2371      inner.nb_channels = channels;
2372      inner.u.mask = junk;
2373      ChannelLayout(inner)
2374    };
2375    let format = Sample::I16(ffmpeg_next::format::sample::Type::Packed);
2376
2377    let plain = ResampleSpec::new(48_000, format, unspec(6, 0));
2378    let junked = ResampleSpec::new(48_000, format, unspec(6, 0xDEAD_BEEF_DEAD_BEEF));
2379    assert!(
2380      plain == junked,
2381      "two unspecified layouts of one width are one layout, whatever sits in storage the \
2382       order does not define",
2383    );
2384    // The width is still the value, so a different one is a different
2385    // layout.
2386    assert!(plain != ResampleSpec::new(48_000, format, unspec(2, 0)));
2387
2388    // And `Debug` says the union is undefined rather than printing it.
2389    let rendered = format!("{plain:?}");
2390    assert!(
2391      rendered.contains("undefined for this order"),
2392      "an unspecified layout must not have its union printed: {rendered}",
2393    );
2394    assert!(
2395      !rendered.contains("dead") && !rendered.contains("DEAD"),
2396      "the junk must not reach a log line: {rendered}",
2397    );
2398    // A native layout does print its mask, which is what makes the
2399    // line above a statement about the order rather than about `Debug`.
2400    let native = ResampleSpec::new(48_000, format, ChannelLayout::STEREO);
2401    assert!(format!("{native:?}").contains("mask"));
2402  }
2403
2404  /// A `CUSTOM` layout is refused at construction — the invariant the
2405  /// `unsafe impl Send` above rests on, and the reason a live
2406  /// resampler's layouts are plain values.
2407  #[test]
2408  fn a_custom_layout_is_refused_at_construction() {
2409    // SAFETY: a zeroed `AVChannelLayout` is a valid value; the fields
2410    // below are set to a custom order declaring channels, and the
2411    // pointer arm stays null — a shape a safe caller can build.
2412    let mut inner: ffmpeg_next::ffi::AVChannelLayout = unsafe { std::mem::zeroed() };
2413    inner.order = AVChannelOrder::AV_CHANNEL_ORDER_CUSTOM;
2414    inner.nb_channels = 2;
2415    let custom = ChannelLayout(inner);
2416
2417    let spec = ResampleSpec::new(
2418      48_000,
2419      Sample::I16(ffmpeg_next::format::sample::Type::Packed),
2420      custom,
2421    );
2422    let target = ResampleSpec::new(
2423      48_000,
2424      Sample::I16(ffmpeg_next::format::sample::Type::Packed),
2425      ChannelLayout::STEREO,
2426    );
2427    // Matched rather than formatted: the resampler is deliberately not
2428    // `Debug`, so only the error side can be printed.
2429    match FfmpegResampler::new(spec, target, FrameLimits::default()) {
2430      Err(ResampleError::UnsupportedLayout(fault)) => {
2431        assert_eq!(fault.end(), SpecEnd::Source);
2432        assert_eq!(fault.channels(), 2);
2433      }
2434      Err(other) => panic!("a custom layout must be an unsupported layout, got {other:?}"),
2435      Ok(_) => panic!("a custom layout must be refused at construction"),
2436    }
2437  }
2438
2439  /// A 48 kHz packed-s16 stereo frame of silence, with the plane its
2440  /// header claims.
2441  fn stereo_frame(samples: u32) -> Frame {
2442    let plane = FfmpegBytes::copy_from_slice(&vec![0u8; samples as usize * 2 * 2]);
2443    let planes = std::array::from_fn(|index| {
2444      Plane::new(
2445        if index == 0 {
2446          plane.clone()
2447        } else {
2448          FfmpegBytes::empty()
2449        },
2450        0,
2451      )
2452    });
2453    AudioFrame::new(
2454      48_000,
2455      samples,
2456      2,
2457      SampleFormat::S16,
2458      crate::channel_layout::channel_layout_description_from_ffmpeg(&ChannelLayout::STEREO)
2459        .expect("a stereo layout describes"),
2460      planes,
2461      1,
2462      AudioFrameExtra::default(),
2463    )
2464    .with_pts(Some(Timestamp::new(
2465      0,
2466      Timebase::new(1, std::num::NonZeroI32::new(48_000).expect("a real rate")),
2467    )))
2468  }
2469
2470  /// Takes every frame that is ready *right now* and stops — the
2471  /// pre-EOF half of a drain, written as the exhaustive match the face
2472  /// now requires. A `.is_ok()` loop here would never end: "needs
2473  /// input" is a success.
2474  fn drain_ready(resampler: &mut FfmpegResampler, dst: &mut Frame) {
2475    loop {
2476      match resampler.receive_frame(dst).expect("a fault-free drain") {
2477        Received::Frame => {}
2478        Received::NeedsInput | Received::Ended => return,
2479      }
2480    }
2481  }
2482
2483  fn stereo_to_mono() -> FfmpegResampler {
2484    FfmpegResampler::new(
2485      ResampleSpec::new(
2486        48_000,
2487        Sample::I16(ffmpeg_next::format::sample::Type::Packed),
2488        ChannelLayout::STEREO,
2489      ),
2490      ResampleSpec::new(
2491        16_000,
2492        Sample::I16(ffmpeg_next::format::sample::Type::Packed),
2493        ChannelLayout::MONO,
2494      ),
2495      FrameLimits::default(),
2496    )
2497    .expect("open resampler")
2498  }
2499
2500  #[test]
2501  fn resample_error_carries_the_derived_accessor_face() {
2502    // `IsVariant` / `Unwrap` / `TryUnwrap` — one arm per derive family,
2503    // mirroring the mediadecode-side proof for this crate's own
2504    // newly-wired `derive_more` dependency.
2505    let err = ResampleError::OutputBuffer(OutputBuffer::new(2));
2506    assert!(err.is_output_buffer());
2507    assert!(!err.is_queue_alloc());
2508    assert_eq!(err.unwrap_output_buffer_ref().plane(), 2);
2509    assert!(err.try_unwrap_queue_alloc().is_err());
2510  }
2511
2512  #[test]
2513  fn an_allocation_fault_while_sending_leaves_the_session_untouched() {
2514    // The class this design exists to end: a failure on the far side of
2515    // `swr_convert_frame` leaves a session no caller can act on —
2516    // retrying feeds the same samples twice, continuing loses them, and
2517    // the delay line has moved either way. Every allocation the
2518    // conversion needs is taken before `swr` runs, so an allocator that
2519    // refuses everything can only produce an error that cost nothing.
2520    crate::fault_subprocess::in_subprocess(
2521      "resampler::tests::an_allocation_fault_while_sending_leaves_the_session_untouched",
2522      || {
2523        let mut resampler = stereo_to_mono();
2524        let frame = stereo_frame(4_800);
2525        let mut dst = crate::boundary::empty_owned_audio_frame();
2526        crate::accepted(resampler.send_frame(&frame), "a first frame");
2527        drain_ready(&mut resampler, &mut dst);
2528        let delay = resampler.delay();
2529        assert!(delay > 0, "the filter has to be holding something");
2530
2531        crate::fault_subprocess::cap_ffmpeg_allocations(1);
2532        let refused = resampler.send_frame(&frame);
2533        crate::fault_subprocess::uncap_ffmpeg_allocations();
2534
2535        assert!(
2536          refused.is_err(),
2537          "an allocator that refuses everything must not look like success",
2538        );
2539        assert_eq!(
2540          resampler.delay(),
2541          delay,
2542          "the frame went into the filter anyway",
2543        );
2544        assert!(
2545          matches!(resampler.receive_frame(&mut dst), Ok(Received::NeedsInput)),
2546          "a failed send left output ready",
2547        );
2548
2549        // And the session is still a session: the same frame converts.
2550        crate::accepted(resampler.send_frame(&frame), "the failure cost nothing");
2551        assert!(matches!(
2552          resampler.receive_frame(&mut dst),
2553          Ok(Received::Frame)
2554        ));
2555      },
2556    );
2557  }
2558
2559  #[test]
2560  fn an_allocation_fault_while_draining_keeps_the_tail() {
2561    // The same property one call along, where the samples at risk are
2562    // the ones already inside the filter: a drain that fails must leave
2563    // the tail where it was, not turn it into an error.
2564    crate::fault_subprocess::in_subprocess(
2565      "resampler::tests::an_allocation_fault_while_draining_keeps_the_tail",
2566      || {
2567        let mut resampler = stereo_to_mono();
2568        let frame = stereo_frame(4_800);
2569        let mut dst = crate::boundary::empty_owned_audio_frame();
2570        for _ in 0..3 {
2571          crate::accepted(resampler.send_frame(&frame), "send_frame");
2572          drain_ready(&mut resampler, &mut dst);
2573        }
2574        crate::accepted(resampler.send_eof(), "eof");
2575        let tail = resampler.delay();
2576        assert!(tail > 0, "there has to be a tail to lose");
2577
2578        crate::fault_subprocess::cap_ffmpeg_allocations(1);
2579        let refused = resampler.receive_frame(&mut dst);
2580        crate::fault_subprocess::uncap_ffmpeg_allocations();
2581
2582        let refused = refused.expect_err("the drain cannot have succeeded");
2583        // No arm of this enum can say "send me more input" any more —
2584        // that answer left the error type — so an allocation failure has
2585        // nowhere to be mistaken for one.
2586        assert!(
2587          matches!(
2588            refused,
2589            ResampleError::Resample(_) | ResampleError::OutputBuffer(_)
2590          ),
2591          "an allocation failure surfaced as something else: {refused:?}",
2592        );
2593        assert_eq!(
2594          resampler.delay(),
2595          tail,
2596          "the tail was consumed by a drain that failed",
2597        );
2598
2599        // And it is still drainable, which is the whole point.
2600        assert_eq!(
2601          resampler
2602            .receive_frame(&mut dst)
2603            .expect("the tail survived the failure"),
2604          Received::Frame,
2605        );
2606      },
2607    );
2608  }
2609
2610  #[test]
2611  fn the_sample_format_table_round_trips() {
2612    for format in [
2613      SampleFormat::U8,
2614      SampleFormat::S16,
2615      SampleFormat::S32,
2616      SampleFormat::S64,
2617      SampleFormat::FLT,
2618      SampleFormat::DBL,
2619      SampleFormat::U8P,
2620      SampleFormat::S16P,
2621      SampleFormat::S32P,
2622      SampleFormat::S64P,
2623      SampleFormat::FLTP,
2624      SampleFormat::DBLP,
2625    ] {
2626      let ffmpeg = format.to_ffmpeg().expect("a named format");
2627      assert_eq!(
2628        SampleFormat::from_ffmpeg(ffmpeg),
2629        format,
2630        "{format:?} does not survive the round trip",
2631      );
2632      assert_eq!(ffmpeg.is_planar(), format.is_planar());
2633    }
2634    assert!(SampleFormat::NONE.to_ffmpeg().is_none());
2635    assert!(SampleFormat::from_raw(9999).to_ffmpeg().is_none());
2636  }
2637
2638  #[test]
2639  fn a_mask_rebuilds_the_layout_it_names() {
2640    let stereo = layout_from_mask(ChannelLayout::STEREO.bits());
2641    assert_eq!(stereo.channels(), 2);
2642    assert_eq!(stereo.bits(), ChannelLayout::STEREO.bits());
2643
2644    let five_one = layout_from_mask(ChannelLayout::_5POINT1.bits());
2645    assert_eq!(five_one.channels(), 6);
2646    assert_eq!(
2647      five_one.bits(),
2648      ChannelLayout::_5POINT1.bits(),
2649      "the side-vs-back distinction is exactly what a default layout would lose",
2650    );
2651  }
2652
2653  #[test]
2654  fn plane_geometry_follows_packed_versus_planar() {
2655    use ffmpeg_next::format::sample::Type;
2656    // Packed: one plane holding every channel.
2657    assert_eq!(
2658      plane_bytes(Sample::I16(Type::Packed), 1024, 2),
2659      Some(1024 * 2 * 2)
2660    );
2661    // Planar: one plane per channel, so the count does not multiply in.
2662    assert_eq!(
2663      plane_bytes(Sample::I16(Type::Planar), 1024, 2),
2664      Some(1024 * 2)
2665    );
2666    assert_eq!(
2667      plane_bytes(Sample::F32(Type::Planar), 1024, 6),
2668      Some(1024 * 4)
2669    );
2670    // A sample count whose byte size does not fit is not a size. This
2671    // is the arithmetic that used to run before the allocation it
2672    // feeds, and it wrapped.
2673    assert_eq!(
2674      plane_bytes(Sample::F32(Type::Packed), usize::MAX / 2, 8),
2675      None,
2676      "an overflowing plane size is refused, not wrapped",
2677    );
2678  }
2679
2680  #[test]
2681  fn the_target_timebase_is_one_tick_per_output_sample() {
2682    let spec = ResampleSpec::new(
2683      16_000,
2684      Sample::I16(ffmpeg_next::format::sample::Type::Packed),
2685      ChannelLayout::MONO,
2686    );
2687    let tb = spec.timebase();
2688    assert_eq!((tb.num(), tb.den().get()), (1, 16_000));
2689  }
2690}