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