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 ffmpeg_next::{
28  ChannelLayout,
29  codec::Parameters,
30  ffi::{
31    AV_NOPTS_VALUE, AVChannelOrder, AVMatrixEncoding, AVSampleFormat, av_channel_layout_from_mask,
32    av_frame_get_buffer, swr_build_matrix2,
33  },
34  format::Sample,
35  frame,
36  software::resampling,
37};
38use mediadecode::{
39  Timebase, Timestamp,
40  frame::{AudioFrame, Plane},
41  resampler::AudioResampler,
42};
43use mediaframe::audio::ChannelLayoutDescription;
44
45use crate::{Error, Ffmpeg, FfmpegBuffer, extras::AudioFrameExtra, sample_format::SampleFormat};
46
47/// The frame type [`FfmpegResampler`] accepts and produces.
48type Frame = AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, FfmpegBuffer>;
49
50/// One end of a conversion: sample rate, sample format, channel layout.
51///
52/// Spelled in FFmpeg's own vocabulary because construction is off the
53/// [`AudioResampler`] trait and this is the backend that has to be
54/// handed to `swr_alloc_set_opts2`. [`FfmpegResampler`] restates the
55/// source spec in the vocabulary a decoded frame carries, so the
56/// mid-stream check compares like with like without the caller ever
57/// seeing two dialects.
58#[derive(Copy, Clone, Debug, PartialEq, Eq)]
59pub struct ResampleSpec {
60  rate: u32,
61  format: Sample,
62  layout: ChannelLayout,
63}
64
65impl ResampleSpec {
66  /// Constructs a spec from its three parts.
67  ///
68  /// Deliberately total and `const`: a spec is a description, and
69  /// describing something `swr` cannot convert is not itself an error.
70  /// [`FfmpegResampler::new`] is the choke point every construction
71  /// route passes through, and it is what refuses a rate, a format or a
72  /// channel layout this backend cannot honour — see
73  /// [`FfmpegResampler::new`] for the roster and
74  /// [`ResampleError::UnsupportedLayout`] for why a layout can be
75  /// refused at all.
76  #[inline]
77  pub const fn new(rate: u32, format: Sample, layout: ChannelLayout) -> Self {
78    Self {
79      rate,
80      format,
81      layout,
82    }
83  }
84
85  /// The spec a track *declares*, read off the codec parameters a
86  /// [`crate::FfmpegDemuxer`] track row carries
87  /// (`track.extra().parameters()`) — the "source from `TrackInfo`"
88  /// path.
89  ///
90  /// Returns `None` for a non-audio track, for one whose declared
91  /// sample format is `AV_SAMPLE_FMT_NONE` (a codec whose format is
92  /// only known once its decoder opens), and for a custom or ambisonic
93  /// channel layout — see [`Self::from_decoder`] for the first case and
94  /// the note on [`unspecified_layout`] for the last.
95  pub fn from_parameters(parameters: &Parameters) -> Option<Self> {
96    // Before `medium()`, which dereferences the pointer inside
97    // ffmpeg-next. `Parameters`' safe constructors hand back a
98    // null-backed value when FFmpeg's allocation failed and report
99    // nothing, so a caller can arrive here holding one without ever
100    // having been told. Parameters that were never allocated describe
101    // no audio, which this function already has a word for.
102    // SAFETY: reading the pointer without dereferencing it.
103    if unsafe { parameters.as_ptr() }.is_null() {
104      return None;
105    }
106    if parameters.medium() != ffmpeg_next::media::Type::Audio {
107      return None;
108    }
109    // SAFETY: `parameters` keeps the `AVCodecParameters` live; every
110    // read below goes through the raw pointer and none of them
111    // materialises a bindgen enum out of foreign memory.
112    let par = unsafe { parameters.as_ptr() };
113    let rate = unsafe { (*par).sample_rate }.max(0) as u32;
114    if rate == 0 {
115      return None;
116    }
117    let format = SampleFormat::from_raw(unsafe { (*par).format }).to_ffmpeg()?;
118    let layout = unsafe { layout_from_raw(addr_of!((*par).ch_layout)) }?;
119    Some(Self::new(rate, format, layout))
120  }
121
122  /// The spec an opened decoder will actually produce — its rate,
123  /// sample format and channel layout, straight off the codec context.
124  ///
125  /// Reach it through
126  /// [`FfmpegAudioStreamDecoder::inner`](crate::FfmpegAudioStreamDecoder::inner).
127  /// `None` on a custom or ambisonic layout, and on a context whose
128  /// sample format is still unset (a decoder that has not been opened).
129  pub fn from_decoder(decoder: &ffmpeg_next::decoder::Audio) -> Option<Self> {
130    // SAFETY: `decoder` keeps the `AVCodecContext` live. `sample_fmt`
131    // is read as the raw integer it is rather than through
132    // `decoder.format()`, which would construct an `AVSampleFormat`
133    // out of foreign memory.
134    let ctx = unsafe { decoder.as_ptr() };
135    // Same reason as `from_parameters`: `codec::Context::new()` is a
136    // safe constructor over an unchecked `avcodec_alloc_context3`, so a
137    // decoder can be null-backed without anyone having been told.
138    if ctx.is_null() {
139      return None;
140    }
141    let format =
142      SampleFormat::from_raw(unsafe { read_unaligned(addr_of!((*ctx).sample_fmt).cast::<i32>()) })
143        .to_ffmpeg()?;
144    let rate = unsafe { (*ctx).sample_rate }.max(0) as u32;
145    if rate == 0 {
146      return None;
147    }
148    let layout = unsafe { layout_from_raw(addr_of!((*ctx).ch_layout)) }?;
149    Some(Self::new(rate, format, layout))
150  }
151
152  /// A layout that names a channel *count* and nothing else —
153  /// `AV_CHANNEL_ORDER_UNSPEC`.
154  ///
155  /// Not a degenerate case: a WAV file without a `WAVE_FORMAT_EXTENSIBLE`
156  /// channel mask genuinely declares no layout, and FFmpeg faithfully
157  /// reports it as unspecified in the codec parameters, in the codec
158  /// context, and on every decoded frame. Substituting a default layout
159  /// would make the source spec disagree with the frames it is supposed
160  /// to describe, and every `send_frame` would be refused as a
161  /// mid-stream change. `swr` accepts an unspecified layout at either
162  /// end and maps the channels positionally.
163  #[inline]
164  pub fn unspecified_layout(channels: i32) -> ChannelLayout {
165    // SAFETY: a zeroed `AVChannelLayout` is a valid value — `order`
166    // reads as `AV_CHANNEL_ORDER_UNSPEC`, the zero discriminant, and
167    // the union is documented as unused for that order.
168    unsafe {
169      let mut layout: ffmpeg_next::ffi::AVChannelLayout = std::mem::zeroed();
170      layout.nb_channels = channels.max(0);
171      ChannelLayout(layout)
172    }
173  }
174
175  /// Sample rate in Hz.
176  #[inline]
177  pub const fn rate(&self) -> u32 {
178    self.rate
179  }
180  /// Sample format.
181  #[inline]
182  pub const fn format(&self) -> Sample {
183    self.format
184  }
185  /// Channel layout.
186  #[inline]
187  pub const fn layout(&self) -> ChannelLayout {
188    self.layout
189  }
190  /// Channel count, from the layout.
191  #[inline]
192  pub fn channels(&self) -> i32 {
193    self.layout.channels()
194  }
195
196  /// The timebase output frames carry — one tick per output sample.
197  fn timebase(&self) -> Timebase {
198    Timebase::new(
199      1,
200      std::num::NonZeroI32::new(self.rate.min(i32::MAX as u32) as i32).unwrap_or(
201        // A zero-rate spec never reaches here: `new` is the only way in
202        // and every caller of it names a real rate. Falling back to
203        // one tick per second keeps the arithmetic total rather than
204        // panicking on a value that cannot occur.
205        std::num::NonZeroI32::new(1).expect("1 is non-zero"),
206      ),
207    )
208  }
209}
210
211/// `mediadecode::resampler::AudioResampler` impl wrapping
212/// `swresample`.
213///
214/// Construction is [`Self::new`], off the trait, taking both specs —
215/// see the trait's own documentation for why the target can never be a
216/// constant.
217pub struct FfmpegResampler {
218  ctx: resampling::Context,
219  source: ResampleSpec,
220  target: ResampleSpec,
221  /// The source spec restated in the vocabulary a decoded `AudioFrame`
222  /// carries. The mid-stream check compares against these, not against
223  /// FFmpeg's dialect, so it never has to translate a frame.
224  source_format: SampleFormat,
225  source_layout: ChannelLayoutDescription,
226  /// The target spec in the vocabulary an output frame carries,
227  /// computed once at construction. Assembling a converted frame after
228  /// `swr` has run must not have to ask FFmpeg anything, because asking
229  /// can fail — see [`FfmpegResampler::prepare_output`].
230  target_format: SampleFormat,
231  target_layout: ChannelLayoutDescription,
232  /// The layouts `swr` is really configured with — see
233  /// [`initialized_layout`]. Every `AVFrame` this type stages or
234  /// allocates carries these, not the declared ones.
235  staged_source_layout: ChannelLayout,
236  staged_target_layout: ChannelLayout,
237  target_timebase: Timebase,
238  ready: VecDeque<Frame>,
239  /// Next output timestamp, in target-rate ticks. `None` until the
240  /// first input frame anchors it.
241  next_pts: Option<i64>,
242  eof: bool,
243}
244
245impl FfmpegResampler {
246  /// Opens a resampler between two explicit specs.
247  ///
248  /// Both are required and neither is inferred. The source is what the
249  /// decoder will hand over — read it off the track
250  /// ([`ResampleSpec::from_parameters`]) or off the opened decoder
251  /// ([`ResampleSpec::from_decoder`]). The target is the caller's, and
252  /// is options: 16 kHz mono for a speech model, 48 kHz for an
253  /// audio-event one, both from the same track.
254  ///
255  /// # The choke point
256  ///
257  /// [`ResampleSpec::new`] is `const` and total, so this is where both
258  /// ends are checked — every construction route (`from_parameters`,
259  /// `from_decoder`, the public constructor) passes through here, and
260  /// nothing hazardous reaches `swr` or a staged `AVFrame` behind it:
261  ///
262  /// - a rate of zero, or one past `c_int`
263  ///   ([`ResampleError::UnsupportedRate`]);
264  /// - `AV_SAMPLE_FMT_NONE` ([`ResampleError::UnsupportedFormat`]);
265  /// - a channel layout that is neither native nor unspecified, or one
266  ///   naming no channels ([`ResampleError::UnsupportedLayout`]).
267  pub fn new(source: ResampleSpec, target: ResampleSpec) -> Result<Self, ResampleError> {
268    check_spec(&source, SpecEnd::Source)?;
269    check_spec(&target, SpecEnd::Target)?;
270
271    // The layouts `swr` is really configured with, resolved *before*
272    // the pair is judged — because the conversion that will run is
273    // between these two, not between the two that were declared. An
274    // unspecified layout becomes FFmpeg's default for its channel count
275    // (twenty-four unspecified channels are 22.2), so judging the
276    // declared pair let exactly the routing the explicit 22.2 refusal
277    // blocks walk in through the unspecified door.
278    let staged_source_layout = initialized_layout(source.layout);
279    let staged_target_layout = initialized_layout(target.layout);
280    check_pair(&staged_source_layout, &staged_target_layout)?;
281    let ctx = open_context(&source, &target, staged_source_layout, staged_target_layout)?;
282
283    let source_format = SampleFormat::from_ffmpeg(source.format);
284    let target_format = SampleFormat::from_ffmpeg(target.format);
285    // SAFETY: the layout is a live `ChannelLayout` owned by this scope.
286    let target_layout =
287      crate::channel_layout::channel_layout_description_from_ffmpeg(&staged_target_layout);
288    // SAFETY: the layout is a live `ChannelLayout` owned by `source`
289    // for the duration of this call.
290    let source_layout =
291      crate::channel_layout::channel_layout_description_from_ffmpeg(&source.layout);
292    let target_timebase = target.timebase();
293
294    Ok(Self {
295      ctx,
296      source,
297      target,
298      source_format,
299      source_layout,
300      target_format,
301      target_layout,
302      staged_source_layout,
303      staged_target_layout,
304      target_timebase,
305      ready: VecDeque::new(),
306      next_pts: None,
307      eof: false,
308    })
309  }
310
311  /// The spec frames must arrive in.
312  #[inline]
313  pub const fn source(&self) -> &ResampleSpec {
314    &self.source
315  }
316
317  /// The spec frames leave in.
318  #[inline]
319  pub const fn target(&self) -> &ResampleSpec {
320    &self.target
321  }
322
323  /// Borrows the wrapped `swr` context.
324  #[inline]
325  pub const fn inner(&self) -> &resampling::Context {
326    &self.ctx
327  }
328
329  /// Samples still inside the delay line, counted at the output rate.
330  #[inline]
331  pub fn delay(&self) -> i64 {
332    self.ctx.delay().map_or(0, |d| d.output.max(0))
333  }
334
335  /// Refuses a frame whose shape is not the source spec.
336  fn check_source(&self, frame: &Frame) -> Result<(), ResampleError> {
337    if frame.sample_rate() != self.source.rate
338      || *frame.sample_format() != self.source_format
339      || *frame.channel_layout() != self.source_layout
340    {
341      return Err(ResampleError::SourceChanged {
342        expected_rate: self.source.rate,
343        expected_format: self.source_format,
344        found_rate: frame.sample_rate(),
345        found_format: *frame.sample_format(),
346      });
347    }
348    Ok(())
349  }
350
351  /// Where a frame's timestamp lands on the output timeline, or `None`
352  /// when it carries none.
353  ///
354  /// Rescaled with the **checked** rung, and before anything is staged.
355  /// `Timestamp::rescale_to` saturates, and both ends of that clamp are
356  /// wrong here: a positive one reaches the counted timeline's checked
357  /// addition only after `swr` has consumed the input, leaving a
358  /// session no caller can retry; a negative one lands on `i64::MIN`,
359  /// which *is* `AV_NOPTS_VALUE`, so the conversion back reads the
360  /// frame as carrying no timestamp at all and an extreme timestamp is
361  /// silently erased. A timestamp that does not fit the output timeline
362  /// is refused by name, with the resampler untouched.
363  fn anchor_of(&self, frame: &Frame) -> Result<Option<i64>, ResampleError> {
364    let Some(timestamp) = frame.pts() else {
365      return Ok(None);
366    };
367    let ticks = timestamp.pts();
368    let out_of_range = || ResampleError::TimestampOutOfRange { pts: ticks };
369    // `AV_NOPTS_VALUE` is a sentinel, not a time. A frame carrying it
370    // as a value says something contradictory, and anchoring on it
371    // would produce output frames that report no timestamp.
372    if ticks == AV_NOPTS_VALUE {
373      return Err(out_of_range());
374    }
375    let rescaled = timestamp
376      .timebase()
377      .checked_rescale(ticks, self.target_timebase)
378      .ok_or_else(out_of_range)?;
379    if rescaled == AV_NOPTS_VALUE {
380      return Err(out_of_range());
381    }
382    Ok(Some(rescaled))
383  }
384
385  /// Stages a decoded frame as an `AVFrame` swr can read.
386  ///
387  /// Geometry is settled **before** anything is allocated. A frame's
388  /// header is a claim, not a fact: `nb_samples` comes from the same
389  /// foreign memory as the planes it describes, and sizing an
390  /// allocation off it first would let a forged frame with a
391  /// twelve-byte plane ask for tens of gigabytes on its way to being
392  /// refused.
393  fn stage_input(&self, frame: &Frame) -> Result<frame::Audio, ResampleError> {
394    let samples = frame.nb_samples() as usize;
395    let channels = self.source.channels();
396
397    // What the *format* requires, not what the allocated frame reports
398    // — the frame does not exist yet.
399    let planes = if self.source.format.is_planar() {
400      channels.max(0) as usize
401    } else {
402      1
403    };
404    let found = frame.plane_count() as usize;
405    if planes > found {
406      return Err(ResampleError::PlaneCount {
407        expected: planes,
408        found,
409      });
410    }
411    let bytes = plane_bytes(self.source.format, samples, channels)
412      .ok_or(ResampleError::SampleCount { requested: samples })?;
413    for plane in frame.planes().iter().take(planes) {
414      let src = plane.data_ref().as_ref();
415      if src.len() < bytes {
416        return Err(ResampleError::PlaneCount {
417          expected: bytes,
418          found: src.len(),
419        });
420      }
421    }
422
423    // Only now, with every plane proved long enough for the sample
424    // count that sizes this allocation.
425    let mut input = new_audio_frame(
426      self.source.format,
427      samples,
428      self.source.rate,
429      self.staged_source_layout,
430    )?;
431    // What the allocation really produced. `data_mut` panics past its
432    // own plane count, and this crate does not put a panic on a path
433    // that reads foreign geometry.
434    let staged = input.planes();
435    if staged < planes {
436      return Err(ResampleError::PlaneCount {
437        expected: planes,
438        found: staged,
439      });
440    }
441    for (index, plane) in frame.planes().iter().take(planes).enumerate() {
442      let src = plane.data_ref().as_ref();
443      let dst = input.data_mut(index);
444      if dst.len() < bytes {
445        return Err(ResampleError::PlaneCount {
446          expected: bytes,
447          found: dst.len(),
448        });
449      }
450      dst[..bytes].copy_from_slice(&src[..bytes]);
451    }
452    Ok(input)
453  }
454
455  /// The most samples the next conversion could produce: the delay
456  /// line's contents plus `in_samples` of new input, rescaled to the
457  /// output rate and rounded up.
458  ///
459  /// Separate from the allocation because it is also the preflight the
460  /// output timeline is checked against — *before* `swr` consumes
461  /// anything, so a refusal leaves the session where a caller can retry
462  /// it.
463  fn output_capacity(&self, in_samples: i64) -> Result<usize, ResampleError> {
464    let delay_in = self.ctx.delay().map_or(0, |d| d.input.max(0));
465    let total = delay_in.saturating_add(in_samples).max(0) as i128;
466    let scaled = (total * i128::from(self.target.rate) + i128::from(self.source.rate) - 1)
467      / i128::from(self.source.rate).max(1);
468    // One extra sample of headroom: swr rounds its own accounting, and
469    // an output frame one short would silently push the remainder into
470    // the internal FIFO where the pts accounting cannot see it until
471    // the next call.
472    let samples = scaled + 1;
473    // `av_frame_get_buffer` takes the count as a `c_int`. A request
474    // past that is refused by name rather than clamped: a silently
475    // shortened output frame is a stream that loses samples.
476    if samples > i128::from(i32::MAX) {
477      return Err(ResampleError::SampleCount {
478        // Saturating only for a count past `usize` itself, which no
479        // machine could hold either way.
480        requested: usize::try_from(samples).unwrap_or(usize::MAX),
481      });
482    }
483    Ok(samples.max(1) as usize)
484  }
485
486  /// Refuses a conversion whose output could not be labelled: the
487  /// timeline plus everything this call might produce has to stay
488  /// inside `i64`.
489  ///
490  /// Asked before `swr` sees a sample, like everything else that can
491  /// fail. [`Self::finish_output`] performs the same addition against
492  /// the count actually produced, which cannot exceed the capacity
493  /// checked here — so once this passes, that one cannot fail.
494  fn check_timeline(&self, anchor: Option<i64>, capacity: usize) -> Result<(), ResampleError> {
495    let pts = self.next_pts.or(anchor).unwrap_or(0);
496    let samples = capacity as i64;
497    if pts.checked_add(samples).is_none() {
498      return Err(ResampleError::TimestampOverflow { pts, samples });
499    }
500    Ok(())
501  }
502
503  /// Allocates the output frame **and acquires every reference the
504  /// converted frame will need**, before `swr` is allowed to touch a
505  /// sample.
506  ///
507  /// This is the shape the whole seam is built around. Anything
508  /// fallible that runs *after* `swr_convert_frame` has consumed input
509  /// leaves a session no caller can act on: retrying feeds the same
510  /// samples twice, continuing loses them, and the delay line has moved
511  /// either way. The failure kept relocating — the timestamp addition,
512  /// the tail drain, then the output wrapping — so the fix is not
513  /// another check in another place but an ordering that leaves nothing
514  /// on the far side: the frame, one refcounted view per plane, a
515  /// placeholder for every unused slot, and the queue slot are all
516  /// taken here, where failing costs nothing but an error.
517  ///
518  /// The views are taken at the frame's **full capacity** and trimmed
519  /// afterwards to what `swr` produced ([`FfmpegBuffer::shrink_to`],
520  /// which only ever narrows), so the trimming needs no allocation and
521  /// cannot fail either.
522  fn prepare_output(&self, capacity: usize) -> Result<PreparedOutput, ResampleError> {
523    let frame = new_audio_frame(
524      self.target.format,
525      capacity,
526      self.target.rate,
527      self.staged_target_layout,
528    )?;
529    let channels = self.target.channels();
530    let plane_count = if self.target.format.is_planar() {
531      channels.max(0) as usize
532    } else {
533      1
534    };
535    let plane_len =
536      plane_bytes(self.target.format, capacity, channels).ok_or(ResampleError::SampleCount {
537        requested: capacity,
538      })?;
539    // Linear in the sample count, which is what lets the post-run
540    // trim be a multiplication rather than another fallible call.
541    let per_sample = plane_bytes(self.target.format, 1, channels)
542      .ok_or(ResampleError::SampleCount { requested: 1 })?;
543    if frame.planes() < plane_count {
544      return Err(ResampleError::PlaneCount {
545        expected: plane_count,
546        found: frame.planes(),
547      });
548    }
549
550    let mut buffers: [Option<FfmpegBuffer>; 8] = [const { None }; 8];
551    for (index, slot) in buffers.iter_mut().enumerate() {
552      *slot = Some(if index < plane_count {
553        // SAFETY: `frame` owns a live `AVFrame` this call just
554        // allocated; `data` is a public field and `plane_count` is
555        // within the eight slots `data` has.
556        let data_ptr = unsafe { (*frame.as_ptr()).data[index] };
557        if data_ptr.is_null() {
558          return Err(ResampleError::OutputBuffer { plane: index });
559        }
560        // SAFETY: the frame is live, and the helper only reads
561        // `buf[]`'s ranges to find the one containing `data_ptr`.
562        let buf =
563          unsafe { crate::convert::find_audio_backing_buffer(frame.as_ptr(), data_ptr, plane_len) }
564            .ok_or(ResampleError::OutputBuffer { plane: index })?;
565        // SAFETY: `buf` is non-null and live, and the helper proved the
566        // view lies inside it.
567        let offset = unsafe { (data_ptr as usize).wrapping_sub((*buf).data as usize) };
568        unsafe { FfmpegBuffer::from_ref_view(buf, offset, plane_len) }
569          .ok_or(ResampleError::OutputBuffer { plane: index })?
570      } else {
571        FfmpegBuffer::try_empty().ok_or(ResampleError::OutputBuffer { plane: index })?
572      });
573    }
574
575    Ok(PreparedOutput {
576      frame,
577      buffers,
578      plane_count,
579      plane_len,
580      per_sample,
581    })
582  }
583
584  /// Turns a converted frame into a `mediadecode` one. **Infallible**,
585  /// by construction: every allocation and every reference it needs was
586  /// taken in [`Self::prepare_output`], and everything left here is
587  /// arithmetic over values this type owns.
588  ///
589  /// `None` when the conversion produced nothing — the delay line
590  /// swallowed the input, which is ordinary and not a failure.
591  fn finish_output(&mut self, mut prepared: PreparedOutput) -> Option<Frame> {
592    let produced = prepared.frame.samples();
593    if produced == 0 {
594      return None;
595    }
596    let pts = self.next_pts.unwrap_or(0);
597    // `check_timeline` ran before `swr` did, against a capacity that is
598    // never smaller than what came out, so this addition cannot leave
599    // `i64`. It is stated rather than checked because a check here
600    // would be an error path on the wrong side of the conversion —
601    // exactly what this design exists to remove.
602    debug_assert!(
603      pts.checked_add(produced as i64).is_some(),
604      "the timeline was preflighted against a capacity >= produced",
605    );
606    self.next_pts = Some(pts.saturating_add(produced as i64));
607
608    let bytes = prepared
609      .per_sample
610      .saturating_mul(produced)
611      .min(prepared.plane_len);
612    let plane_count = prepared.plane_count;
613    let planes = std::array::from_fn(|index| {
614      let mut buffer = prepared.buffers[index]
615        .take()
616        .expect("prepare_output fills every slot");
617      if index < plane_count {
618        buffer.shrink_to(bytes);
619        Plane::new(buffer, bytes as u32)
620      } else {
621        Plane::new(buffer, 0)
622      }
623    });
624
625    Some(
626      AudioFrame::new(
627        self.target.rate,
628        produced as u32,
629        self.target.channels().clamp(0, 255) as u8,
630        self.target_format,
631        self.target_layout.clone(),
632        planes,
633        plane_count as u8,
634        AudioFrameExtra::default(),
635      )
636      .with_pts(Some(Timestamp::new(pts, self.target_timebase)))
637      .with_duration(Some(Timestamp::new(produced as i64, self.target_timebase))),
638    )
639  }
640}
641
642/// Everything a converted frame needs, acquired before the conversion
643/// runs. See [`FfmpegResampler::prepare_output`].
644struct PreparedOutput {
645  frame: frame::Audio,
646  /// One refcounted view per populated plane, a placeholder for every
647  /// other slot. Taken at full capacity; trimmed after the conversion.
648  buffers: [Option<FfmpegBuffer>; 8],
649  plane_count: usize,
650  /// Bytes one plane holds at full capacity — the ceiling every trim
651  /// stays under.
652  plane_len: usize,
653  /// Bytes one plane holds per sample.
654  per_sample: usize,
655}
656
657impl AudioResampler for FfmpegResampler {
658  type Adapter = Ffmpeg;
659  type Buffer = FfmpegBuffer;
660  type Error = ResampleError;
661
662  fn send_frame(&mut self, frame: &Frame) -> Result<(), ResampleError> {
663    if self.eof {
664      return Err(ResampleError::AfterEof);
665    }
666    self.check_source(frame)?;
667    // A frame carrying no samples is a header and nothing else. There
668    // is nothing to convert and nothing to stage: `av_frame_get_buffer`
669    // refuses a zero-sample allocation, so staging one would hand `swr`
670    // an unbacked `AVFrame` for no gain.
671    if frame.nb_samples() == 0 {
672      return Ok(());
673    }
674
675    // Nothing below touches the session's state until the conversion
676    // has succeeded. A refused frame must leave the timeline exactly
677    // where it was, or the next good frame inherits the rejected one's
678    // timestamp.
679    let anchor = self.anchor_of(frame)?;
680    let input = self.stage_input(frame)?;
681    let capacity = self.output_capacity(frame.nb_samples() as i64)?;
682    self.check_timeline(anchor, capacity)?;
683    let mut prepared = self.prepare_output(capacity)?;
684    // The last fallible thing before the conversion: room for the frame
685    // it will produce. `push_back` on a full queue allocates, and an
686    // allocation failure there aborts the process rather than
687    // unwinding — so the growth happens here, where it can be an error.
688    self
689      .ready
690      .try_reserve(1)
691      .map_err(|_| ResampleError::QueueAlloc)?;
692
693    // The only mutation. Everything above could fail and cost nothing;
694    // nothing below can fail at all.
695    self
696      .ctx
697      .run(&input, &mut prepared.frame)
698      .map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))?;
699
700    // The frame is inside the filter now, so the timeline may be
701    // anchored on it. Anchored on *input* rather than on the first
702    // output, because a call that produces nothing but fills the delay
703    // line still fixes where the stream starts.
704    if self.next_pts.is_none() {
705      self.next_pts = anchor;
706    }
707    if let Some(converted) = self.finish_output(prepared) {
708      self.ready.push_back(converted);
709    }
710    Ok(())
711  }
712
713  fn receive_frame(&mut self, dst: &mut Frame) -> Result<(), ResampleError> {
714    if let Some(frame) = self.ready.pop_front() {
715      *dst = frame;
716      return Ok(());
717    }
718    if !self.eof {
719      return Err(ResampleError::Again);
720    }
721    // EOF: drain the conversion tail. Without this every file loses the
722    // tens of milliseconds sitting inside the filter.
723    let remaining = self.delay();
724    if remaining <= 0 {
725      return Err(ResampleError::Again);
726    }
727    let capacity = remaining.min(i64::from(i32::MAX)) as usize;
728    // Same discipline as `send_frame`, and for the same reason: the
729    // tail is drained only once the timeline can hold it and every
730    // reference the converted frame needs is already in hand, so a
731    // failure leaves the delay line untouched instead of turning
732    // samples into an error.
733    self.check_timeline(None, capacity)?;
734    let mut prepared = self.prepare_output(capacity)?;
735    self
736      .ctx
737      .flush(&mut prepared.frame)
738      .map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))?;
739    match self.finish_output(prepared) {
740      Some(frame) => {
741        *dst = frame;
742        Ok(())
743      }
744      None => Err(ResampleError::Again),
745    }
746  }
747
748  fn send_eof(&mut self) -> Result<(), ResampleError> {
749    self.eof = true;
750    Ok(())
751  }
752
753  /// Resets the resampler for another stream on the same two specs.
754  ///
755  /// The `swr` context is **rebuilt**, not drained. `swresample` has no
756  /// reset call, and draining it dry cannot be verified from outside: a
757  /// `swr_convert_frame` that makes no progress reports no error, so a
758  /// drain loop that gives up and a drain loop that finished are
759  /// indistinguishable — and a flush that returned `Ok` with the old
760  /// delay line still inside would let one stream's tail contaminate
761  /// the next. A fresh context is the only reset whose success is a
762  /// fact.
763  ///
764  /// The new context is built before the old one is dropped, so a
765  /// failure leaves the resampler exactly as it was: this call either
766  /// resets everything or changes nothing.
767  fn flush(&mut self) -> Result<(), ResampleError> {
768    let ctx = open_context(
769      &self.source,
770      &self.target,
771      self.staged_source_layout,
772      self.staged_target_layout,
773    )?;
774    self.ctx = ctx;
775    self.ready.clear();
776    self.next_pts = None;
777    self.eof = false;
778    debug_assert_eq!(self.delay(), 0, "a fresh swr context holds nothing");
779    Ok(())
780  }
781}
782
783/// Errors from [`FfmpegResampler`].
784#[derive(thiserror::Error, Debug, Clone)]
785pub enum ResampleError {
786  /// No converted frame is ready yet — send more input, or
787  /// [`send_eof`](AudioResampler::send_eof) and drain the tail.
788  ///
789  /// This is the "needs more" signal, carried in the error type exactly
790  /// as
791  /// [`AudioStreamDecoder::receive_frame`](mediadecode::decoder::AudioStreamDecoder::receive_frame)
792  /// carries it.
793  #[error("no converted frame ready")]
794  Again,
795
796  /// A frame arrived whose shape is not the source spec this resampler
797  /// was built with — the mid-stream refusal.
798  ///
799  /// The face never silently reconfigures: doing so would resample the
800  /// two halves of a stream on different terms and hand back a single
801  /// unbroken timeline built out of them. Build a new resampler for the
802  /// new source spec.
803  #[error(
804    "source format changed mid-stream: expected {expected_rate} Hz {expected_format:?}, \
805     got {found_rate} Hz {found_format:?}"
806  )]
807  SourceChanged {
808    /// Rate the resampler was built for.
809    expected_rate: u32,
810    /// Sample format the resampler was built for.
811    expected_format: SampleFormat,
812    /// Rate the offending frame carried.
813    found_rate: u32,
814    /// Sample format the offending frame carried.
815    found_format: SampleFormat,
816  },
817
818  /// [`send_frame`](AudioResampler::send_frame) was called after
819  /// [`send_eof`](AudioResampler::send_eof). Call
820  /// [`flush`](AudioResampler::flush) first to reuse the resampler for
821  /// another stream.
822  #[error("send_frame after send_eof; flush() first to start another stream")]
823  AfterEof,
824
825  /// A frame's planes do not hold what its header claims — too few
826  /// planes for the format, or a plane shorter than its sample count
827  /// requires.
828  #[error("frame plane geometry mismatch: expected {expected}, found {found}")]
829  PlaneCount {
830    /// What the format and sample count require.
831    expected: usize,
832    /// What the frame carries.
833    found: usize,
834  },
835
836  /// A sample count no frame can hold: one whose byte size overflows,
837  /// or one past the `c_int` `av_frame_get_buffer` takes.
838  #[error("{requested} samples is not a frame size")]
839  SampleCount {
840    /// The count that was asked for.
841    requested: usize,
842  },
843
844  /// One end of the conversion declares a sample rate `swr` cannot be
845  /// driven with — zero, or past `c_int`.
846  #[error("the {end} rate {rate} is not a sample rate swr can use")]
847  UnsupportedRate {
848    /// Which end of the conversion.
849    end: SpecEnd,
850    /// The rate that was declared.
851    rate: u32,
852  },
853
854  /// One end of the conversion declares no sample format
855  /// (`AV_SAMPLE_FMT_NONE`) — the state a codec context is in before
856  /// its decoder opens.
857  #[error("the {end} spec names no sample format")]
858  UnsupportedFormat {
859    /// Which end of the conversion.
860    end: SpecEnd,
861  },
862
863  /// One end of the conversion declares a channel layout this backend
864  /// will not carry.
865  ///
866  /// Native and unspecified layouts are the two it does. A **custom**
867  /// or **ambisonic** `AVChannelLayout` owns a heap-allocated channel
868  /// map, and FFmpeg documents that such a layout must be copied with
869  /// `av_channel_layout_copy` rather than assigned — while
870  /// `ffmpeg_next::ChannelLayout` is a `Copy` wrapper with no
871  /// destructor. Every `AVFrame` this type stages or allocates receives
872  /// the layout by assignment, and `av_frame_free` runs
873  /// `av_channel_layout_uninit` on it: the first staged frame to be
874  /// dropped would free a map the spec, the decoder and every later
875  /// frame still point at. Refusing at construction is what keeps that
876  /// use-after-free unreachable; a resampler over those layouts is a
877  /// separate design, not a silent approximation.
878  #[error("the {end} channel layout is not supported: order {order}, {channels} channels")]
879  UnsupportedLayout {
880    /// Which end of the conversion.
881    end: SpecEnd,
882    /// `AVChannelOrder` as the raw integer it is on the wire.
883    order: i32,
884    /// The channel count the layout declares.
885    channels: i32,
886  },
887
888  /// A planar spec with more channels than a decoded frame has plane
889  /// slots.
890  ///
891  /// `mediadecode`'s `AudioFrame` carries a fixed eight planes
892  /// (`AV_NUM_DATA_POINTERS`); planar audio past that lives in
893  /// `AVFrame.extended_data[]`, which this crate does not plumb
894  /// through. As a **source** no valid frame could ever arrive; as a
895  /// **target** `swr` would produce one this crate cannot hand back —
896  /// and it would fail only after the input had been consumed, leaving
897  /// a session that cannot be retried. Both are refused at
898  /// construction, where nothing has happened yet.
899  #[error("the {end} spec is planar with {channels} channels; a frame carries {limit} planes")]
900  TooManyPlanes {
901    /// Which end of the conversion.
902    end: SpecEnd,
903    /// The channel count the layout declares.
904    channels: i32,
905    /// Plane slots a frame has.
906    limit: i32,
907  },
908
909  /// A frame's timestamp does not land on the output timeline: it does
910  /// not survive the rescale as an `i64`, or it is `AV_NOPTS_VALUE`,
911  /// which is a sentinel rather than a time.
912  ///
913  /// Raised before anything is staged, so a refused frame leaves the
914  /// resampler exactly as it was.
915  #[error("the frame timestamp {pts} does not land on the output timeline")]
916  TimestampOutOfRange {
917    /// The timestamp the frame carried, in its own timebase.
918    pts: i64,
919  },
920
921  /// The conversion between these two layouts would silently drop a
922  /// source channel: FFmpeg's own mixing matrix routes it to no output.
923  ///
924  /// `swr` mixes the channel positions its rematrix table knows and
925  /// processes the rest of the input as though it were absent — a log
926  /// line at most. Measured against FFmpeg 9, packed 22.2 → mono loses
927  /// fifteen of twenty-four channels and `cube` → stereo loses two of
928  /// eight, so this is not a matter of channel count. Installing an
929  /// explicit mix matrix is how such a conversion would be accepted
930  /// deliberately; until this crate has a seat for one, the pair is
931  /// refused.
932  #[error(
933    "converting {source_channels} channels to {target_channels} would drop source channel \
934     {channel}: FFmpeg's mixing matrix routes it to no output"
935  )]
936  ChannelDropped {
937    /// Channels the source layout declares.
938    source_channels: i32,
939    /// Channels the target layout declares.
940    target_channels: i32,
941    /// The first source channel that reaches no output channel.
942    channel: i32,
943  },
944
945  /// FFmpeg will not build a mixing matrix between these two layouts at
946  /// all.
947  #[error("FFmpeg builds no mixing matrix from {source_channels} channels to {target_channels}")]
948  RematrixUnsupported {
949    /// Channels the source layout declares.
950    source_channels: i32,
951    /// Channels the target layout declares.
952    target_channels: i32,
953  },
954
955  /// The output timeline would leave `i64`. Counted timestamps are
956  /// exact or they are nothing, so this is named rather than saturated.
957  #[error("the output timeline overflows: {pts} + {samples} samples")]
958  TimestampOverflow {
959    /// Where the timeline stood.
960    pts: i64,
961    /// How many samples were produced.
962    samples: i64,
963  },
964
965  /// The wrapped `swresample` call reported an error.
966  #[error(transparent)]
967  Resample(#[from] Error),
968
969  /// A reference to one of the output frame's planes could not be
970  /// taken.
971  ///
972  /// Raised while preparing the conversion, never after it: that is the
973  /// point of preparing.
974  #[error("the output frame's plane {plane} could not be referenced")]
975  OutputBuffer {
976    /// Which plane slot.
977    plane: usize,
978  },
979
980  /// The queue of converted frames could not be grown to hold one more.
981  #[error("out of memory reserving room for a converted frame")]
982  QueueAlloc,
983}
984
985impl ResampleError {
986  /// `true` for [`Self::Again`] — the "send more input" signal, which a
987  /// drain loop tests for rather than matching on.
988  #[inline]
989  pub const fn is_again(&self) -> bool {
990    matches!(self, Self::Again)
991  }
992}
993
994/// Which end of a conversion a refusal is about.
995#[derive(Copy, Clone, Debug, PartialEq, Eq)]
996pub enum SpecEnd {
997  /// The spec frames must arrive in.
998  Source,
999  /// The spec frames leave in.
1000  Target,
1001}
1002
1003impl core::fmt::Display for SpecEnd {
1004  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1005    f.write_str(match self {
1006      Self::Source => "source",
1007      Self::Target => "target",
1008    })
1009  }
1010}
1011
1012/// Plane slots a `mediadecode::frame::AudioFrame` has — the fixed array
1013/// matching `AV_NUM_DATA_POINTERS`. Planar audio past this many
1014/// channels lives in `AVFrame.extended_data[]` / `extended_buf[]`,
1015/// which this crate does not plumb through: `convert` refuses such a
1016/// frame and `AudioFrame::new` will not build one.
1017const MAX_AUDIO_PLANES: i32 = 8;
1018
1019/// Refuses a spec `swr` cannot be driven with, or whose channel layout
1020/// cannot be carried by value — see [`ResampleError::UnsupportedLayout`]
1021/// for that one, which is the whole reason this check exists at the
1022/// choke point rather than in the `const` constructor.
1023fn check_spec(spec: &ResampleSpec, end: SpecEnd) -> Result<(), ResampleError> {
1024  if spec.rate == 0 || spec.rate > i32::MAX as u32 {
1025    return Err(ResampleError::UnsupportedRate {
1026      end,
1027      rate: spec.rate,
1028    });
1029  }
1030  if spec.format == Sample::None {
1031    return Err(ResampleError::UnsupportedFormat { end });
1032  }
1033  let order = layout_order(&spec.layout);
1034  let channels = spec.layout.channels();
1035  let carried = order == AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32
1036    || order == AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32;
1037  if !carried || channels <= 0 {
1038    return Err(ResampleError::UnsupportedLayout {
1039      end,
1040      order,
1041      channels,
1042    });
1043  }
1044  // A planar spec with more channels than the frame model has plane
1045  // slots is a resampler that cannot work in either direction, and
1046  // saying so here is the difference between a refusal at construction
1047  // and a refusal on every frame — the target one arriving *after*
1048  // `swr` has already consumed the input, which is not a state a caller
1049  // can retry from.
1050  if spec.format.is_planar() && channels > MAX_AUDIO_PLANES {
1051    return Err(ResampleError::TooManyPlanes {
1052      end,
1053      channels,
1054      limit: MAX_AUDIO_PLANES,
1055    });
1056  }
1057  Ok(())
1058}
1059
1060/// A layout's `AVChannelOrder` as the integer it is on the wire.
1061///
1062/// Read raw rather than matched as an `AVChannelOrder`, the discipline
1063/// this crate keeps everywhere it touches a bindgen enum: a value
1064/// outside this build's discriminant set would be undefined behaviour
1065/// the moment it existed as one.
1066fn layout_order(layout: &ChannelLayout) -> i32 {
1067  // SAFETY: `layout` is a live `ChannelLayout` for the duration of this
1068  // call; `addr_of!` reaches its `order` field without forming a
1069  // reference to the enum.
1070  unsafe { read_unaligned(addr_of!(layout.0.order).cast::<i32>()) }
1071}
1072
1073/// FFmpeg's `SWR_CH_MAX`: the square its own matrix builder writes,
1074/// whatever the two layouts' channel counts are.
1075///
1076/// Not a convenience. `swr_build_matrix2` copies its internal
1077/// `[SWR_CH_MAX][SWR_CH_MAX]` block out at the caller's stride, so a
1078/// buffer sized to the actual channel counts is written far past its
1079/// end — measured, and the measurement is a killed process.
1080const SWR_CH_MAX: usize = 64;
1081
1082/// Refuses an **effective pair** whose rematrixing would silently drop
1083/// input channels.
1084///
1085/// Takes the layouts `swr` is configured with, not the ones the caller
1086/// declared. The two differ exactly where it matters: an unspecified
1087/// layout is resolved to FFmpeg's default for its channel count before
1088/// the context is opened, and twenty-four unspecified channels resolve
1089/// to 22.2 — so the declared pair says "unspecified, nothing to
1090/// rematrix" while the conversion that runs is the lossy one.
1091///
1092/// This is the second half of the crate's two-layout bookkeeping, and
1093/// the halves answer different questions. The **declared** layout is
1094/// what decoded frames carry (a WAV without a channel mask hands out
1095/// unspecified frames forever) and stays the yardstick for the
1096/// mid-stream refusal: *is this frame the stream I was built for?* The
1097/// **effective** layout is what `swr` and every staged `AVFrame` use,
1098/// and it is the one judged here: *what will `swr` actually do?*
1099///
1100/// Each end can be perfectly valid on its own and the conversion
1101/// between them still lose whole channels: `swr` mixes only the channel
1102/// positions its rematrix table knows, and quietly processes the rest
1103/// of the input as though it were not there. Measured against the
1104/// linked FFmpeg 9 with a tone isolated in each source channel: packed
1105/// 22.2 → mono drops fifteen of twenty-four (`swr` says as much in a
1106/// log line and converts anyway), `cube` → stereo drops two of *eight*
1107/// — so a channel-count threshold is both too strict and too loose to
1108/// be the rule.
1109///
1110/// The rule is asked of FFmpeg instead: build the mixing matrix its own
1111/// builder would use, and refuse when any input channel reaches no
1112/// output at all. `lfe_mix_level` is deliberately non-zero, so the
1113/// question is "can this channel reach the output" rather than "does
1114/// FFmpeg's default downmix policy include it" — the default leaves LFE
1115/// out of a downmix on purpose, and refusing an everyday 5.1 → stereo
1116/// over that would be absurd. The predicate matched the tone sweep
1117/// exactly on every pair measured.
1118///
1119/// A pair `swr` cannot matrix at all is refused too. Accepting these
1120/// deliberately is a *mix matrix* seat on the spec — a real design, not
1121/// something to mint in passing; until it exists, refusal is the honest
1122/// answer.
1123fn check_pair(source: &ChannelLayout, target: &ChannelLayout) -> Result<(), ResampleError> {
1124  let native = AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32;
1125  // A layout still unspecified *after* resolution — a channel count
1126  // FFmpeg has no default for — is mapped positionally by `swr` with no
1127  // rematrixing at all, and identical layouts need no matrix: neither
1128  // can drop a channel, and neither is what the builder describes.
1129  if layout_order(source) != native || layout_order(target) != native || source == target {
1130    return Ok(());
1131  }
1132  let source_channels = source.channels();
1133  let target_channels = target.channels();
1134
1135  let mut matrix = vec![0f64; SWR_CH_MAX * SWR_CH_MAX];
1136  // SAFETY: both layouts are live for the call; `matrix` is the full
1137  // `SWR_CH_MAX` square the builder writes, passed with the matching
1138  // stride; the encoding is a compile-time constant of this build; and
1139  // a null log context is documented as allowed.
1140  let rc = unsafe {
1141    swr_build_matrix2(
1142      &source.0,
1143      &target.0,
1144      core::f64::consts::FRAC_1_SQRT_2,
1145      core::f64::consts::FRAC_1_SQRT_2,
1146      1.0,
1147      1.0,
1148      1.0,
1149      matrix.as_mut_ptr(),
1150      SWR_CH_MAX as isize,
1151      AVMatrixEncoding::AV_MATRIX_ENCODING_NONE,
1152      core::ptr::null_mut(),
1153    )
1154  };
1155  if rc < 0 {
1156    return Err(ResampleError::RematrixUnsupported {
1157      source_channels,
1158      target_channels,
1159    });
1160  }
1161  for channel in 0..source_channels.min(SWR_CH_MAX as i32) {
1162    let index = channel as usize;
1163    if (0..target_channels.min(SWR_CH_MAX as i32) as usize)
1164      .all(|out| matrix[index + SWR_CH_MAX * out] == 0.0)
1165    {
1166      return Err(ResampleError::ChannelDropped {
1167        source_channels,
1168        target_channels,
1169        channel,
1170      });
1171    }
1172  }
1173  Ok(())
1174}
1175
1176/// Opens a `swr` context for the two specs. Shared by
1177/// [`FfmpegResampler::new`] and the rebuild
1178/// [`AudioResampler::flush`] performs.
1179fn open_context(
1180  source: &ResampleSpec,
1181  target: &ResampleSpec,
1182  staged_source_layout: ChannelLayout,
1183  staged_target_layout: ChannelLayout,
1184) -> Result<resampling::Context, ResampleError> {
1185  resampling::Context::get(
1186    source.format,
1187    staged_source_layout,
1188    source.rate,
1189    target.format,
1190    staged_target_layout,
1191    target.rate,
1192  )
1193  .map_err(|e| ResampleError::Resample(Error::Ffmpeg(e)))
1194}
1195
1196/// Allocates an audio `AVFrame`, checking every step the dependency's
1197/// own `frame::Audio::new` does not.
1198///
1199/// `ffmpeg_next`'s constructor dereferences `av_frame_alloc`'s result
1200/// without a null check and discards `av_frame_get_buffer`'s return
1201/// value, so an allocation failure there yields a frame whose planes
1202/// are not backed — which is then handed to FFI. Both are checked here;
1203/// on failure the caller gets a named error and no frame at all. The
1204/// null check is the crate's existing one
1205/// ([`crate::frame::alloc_av_audio_frame`], which the decoders already
1206/// allocate through), so there is one answer to `av_frame_alloc`
1207/// returning null rather than two.
1208fn new_audio_frame(
1209  format: Sample,
1210  samples: usize,
1211  rate: u32,
1212  layout: ChannelLayout,
1213) -> Result<frame::Audio, ResampleError> {
1214  if samples == 0 || samples > i32::MAX as usize {
1215    return Err(ResampleError::SampleCount { requested: samples });
1216  }
1217  let mut out = crate::frame::alloc_av_audio_frame()?;
1218  out.set_format(format);
1219  out.set_samples(samples);
1220  // The layout is assigned by value, which is sound only because
1221  // `check_spec` refused every layout that owns a heap channel map.
1222  out.set_channel_layout(layout);
1223  out.set_rate(rate);
1224  // SAFETY: `out` is a live `AVFrame` whose format, sample count and
1225  // layout were just set; `av_frame_get_buffer` allocates its planes
1226  // and reports failure in its return value, which is checked.
1227  let rc = unsafe { av_frame_get_buffer(out.as_mut_ptr(), 0) };
1228  if rc < 0 {
1229    return Err(ResampleError::Resample(Error::Ffmpeg(
1230      ffmpeg_next::Error::from(rc),
1231    )));
1232  }
1233  Ok(out)
1234}
1235
1236/// Bytes one plane holds for `samples` samples of `format`, or `None`
1237/// when that product does not fit a `usize`. Packed formats keep every
1238/// channel in the single plane; planar formats give each channel its
1239/// own.
1240fn plane_bytes(format: Sample, samples: usize, channels: i32) -> Option<usize> {
1241  let bytes = samples.checked_mul(format.bytes())?;
1242  if format.is_planar() {
1243    Some(bytes)
1244  } else {
1245    bytes.checked_mul(channels.max(1) as usize)
1246  }
1247}
1248
1249/// Builds a native-order [`ChannelLayout`] from a channel bitmask,
1250/// without ever forming an `AVChannelLayout` out of foreign memory:
1251/// the struct starts zeroed (`AV_CHANNEL_ORDER_UNSPEC` is `0`, a valid
1252/// discriminant) and FFmpeg fills it.
1253fn layout_from_mask(mask: u64) -> ChannelLayout {
1254  // SAFETY: a zeroed `AVChannelLayout` is a valid value — its `order`
1255  // field reads as `AV_CHANNEL_ORDER_UNSPEC`, the zero discriminant —
1256  // and `av_channel_layout_from_mask` overwrites it wholesale.
1257  unsafe {
1258    let mut layout = std::mem::zeroed();
1259    if av_channel_layout_from_mask(&mut layout, mask) < 0 {
1260      return ChannelLayout::default(mask.count_ones() as i32);
1261    }
1262    ChannelLayout(layout)
1263  }
1264}
1265
1266/// The layout `swr` will actually be configured with.
1267///
1268/// `swr_init` replaces an unspecified input or output layout with
1269/// FFmpeg's default for that channel count, and from then on compares
1270/// every frame handed to it against *that* layout — a staged frame
1271/// still carrying the unspecified one is refused with
1272/// `AVERROR_INPUT_CHANGED`. Applying the same rule here, once, keeps
1273/// the frames this type builds in step with the context it built.
1274///
1275/// The declared layout is kept separately and is what the mid-stream
1276/// check compares against, because it is what decoded frames really
1277/// carry: a WAV without a channel mask hands out unspecified frames
1278/// forever, whatever `swr` decided internally.
1279fn initialized_layout(layout: ChannelLayout) -> ChannelLayout {
1280  if layout.is_empty() {
1281    ChannelLayout::default(layout.channels())
1282  } else {
1283    layout
1284  }
1285}
1286
1287/// Reads an `AVChannelLayout` out of FFmpeg memory into a layout this
1288/// spec can own, or `None` for one it does not represent.
1289///
1290/// The `order` field is read as the integer it is on the wire: an
1291/// out-of-range value would be undefined behaviour the instant it
1292/// existed as an `AVChannelOrder`, which is the hazard this crate
1293/// keeps out everywhere it touches a bindgen enum.
1294///
1295/// A **custom** or **ambisonic** layout returns `None`. Both keep a
1296/// heap-allocated channel map inside the layout, and `ChannelLayout` is
1297/// a plain `Copy` wrapper with no destructor: owning one here would
1298/// either alias a map the decoder still frees or leak the copy. A
1299/// resampler over one of those layouts is a separate design, not a
1300/// silent approximation.
1301///
1302/// # Safety
1303///
1304/// `ptr` must be a live `*const AVChannelLayout` for the duration of
1305/// this call.
1306unsafe fn layout_from_raw(ptr: *const ffmpeg_next::ffi::AVChannelLayout) -> Option<ChannelLayout> {
1307  let order = unsafe { read_unaligned(addr_of!((*ptr).order).cast::<i32>()) };
1308  let channels = unsafe { (*ptr).nb_channels };
1309  if channels <= 0 {
1310    return None;
1311  }
1312  if order == AVChannelOrder::AV_CHANNEL_ORDER_NATIVE as i32 {
1313    // SAFETY: `u.mask` is the union's variant for NATIVE, and the
1314    // order was checked against our own constant before the read.
1315    let mask = unsafe { (*ptr).u.mask };
1316    if mask != 0 {
1317      return Some(layout_from_mask(mask));
1318    }
1319    // Native in name with no channels named: unspecified in substance.
1320    return Some(ResampleSpec::unspecified_layout(channels));
1321  }
1322  if order == AVChannelOrder::AV_CHANNEL_ORDER_UNSPEC as i32 {
1323    return Some(ResampleSpec::unspecified_layout(channels));
1324  }
1325  None
1326}
1327
1328/// Compile-time assurance that `SampleFormat`'s round trip through
1329/// FFmpeg's vocabulary is the identity on the closed set. Both
1330/// directions are hand-written tables, and a table that disagreed with
1331/// its inverse would silently mislabel every sample.
1332const _: () = {
1333  assert!(
1334    SampleFormat::from_raw(AVSampleFormat::AV_SAMPLE_FMT_NONE as i32)
1335      .to_ffmpeg()
1336      .is_none()
1337  );
1338};
1339
1340#[cfg(test)]
1341mod tests {
1342  use super::*;
1343
1344  use mediadecode::resampler::AudioResampler;
1345
1346  /// A 48 kHz packed-s16 stereo frame of silence, with the plane its
1347  /// header claims.
1348  fn stereo_frame(samples: u32) -> Frame {
1349    let plane = FfmpegBuffer::copy_from_slice(&vec![0u8; samples as usize * 2 * 2]).expect("plane");
1350    let planes = std::array::from_fn(|index| {
1351      Plane::new(
1352        if index == 0 {
1353          plane.clone()
1354        } else {
1355          FfmpegBuffer::empty()
1356        },
1357        0,
1358      )
1359    });
1360    AudioFrame::new(
1361      48_000,
1362      samples,
1363      2,
1364      SampleFormat::S16,
1365      crate::channel_layout::channel_layout_description_from_ffmpeg(&ChannelLayout::STEREO),
1366      planes,
1367      1,
1368      AudioFrameExtra::default(),
1369    )
1370    .with_pts(Some(Timestamp::new(
1371      0,
1372      Timebase::new(1, std::num::NonZeroI32::new(48_000).expect("a real rate")),
1373    )))
1374  }
1375
1376  fn stereo_to_mono() -> FfmpegResampler {
1377    FfmpegResampler::new(
1378      ResampleSpec::new(
1379        48_000,
1380        Sample::I16(ffmpeg_next::format::sample::Type::Packed),
1381        ChannelLayout::STEREO,
1382      ),
1383      ResampleSpec::new(
1384        16_000,
1385        Sample::I16(ffmpeg_next::format::sample::Type::Packed),
1386        ChannelLayout::MONO,
1387      ),
1388    )
1389    .expect("open resampler")
1390  }
1391
1392  #[test]
1393  fn an_allocation_fault_while_sending_leaves_the_session_untouched() {
1394    // The class this design exists to end: a failure on the far side of
1395    // `swr_convert_frame` leaves a session no caller can act on —
1396    // retrying feeds the same samples twice, continuing loses them, and
1397    // the delay line has moved either way. Every allocation the
1398    // conversion needs is taken before `swr` runs, so an allocator that
1399    // refuses everything can only produce an error that cost nothing.
1400    crate::fault_subprocess::in_subprocess(
1401      "resampler::tests::an_allocation_fault_while_sending_leaves_the_session_untouched",
1402      || {
1403        let mut resampler = stereo_to_mono();
1404        let frame = stereo_frame(4_800);
1405        let mut dst = crate::boundary::empty_audio_frame();
1406        resampler.send_frame(&frame).expect("a first frame");
1407        while resampler.receive_frame(&mut dst).is_ok() {}
1408        let delay = resampler.delay();
1409        assert!(delay > 0, "the filter has to be holding something");
1410
1411        crate::fault_subprocess::cap_ffmpeg_allocations(1);
1412        let refused = resampler.send_frame(&frame);
1413        crate::fault_subprocess::uncap_ffmpeg_allocations();
1414
1415        assert!(
1416          refused.is_err(),
1417          "an allocator that refuses everything must not look like success",
1418        );
1419        assert_eq!(
1420          resampler.delay(),
1421          delay,
1422          "the frame went into the filter anyway",
1423        );
1424        assert!(
1425          resampler.receive_frame(&mut dst).unwrap_err().is_again(),
1426          "a failed send left output ready",
1427        );
1428
1429        // And the session is still a session: the same frame converts.
1430        resampler
1431          .send_frame(&frame)
1432          .expect("the failure cost nothing");
1433        assert!(resampler.receive_frame(&mut dst).is_ok());
1434      },
1435    );
1436  }
1437
1438  #[test]
1439  fn an_allocation_fault_while_draining_keeps_the_tail() {
1440    // The same property one call along, where the samples at risk are
1441    // the ones already inside the filter: a drain that fails must leave
1442    // the tail where it was, not turn it into an error.
1443    crate::fault_subprocess::in_subprocess(
1444      "resampler::tests::an_allocation_fault_while_draining_keeps_the_tail",
1445      || {
1446        let mut resampler = stereo_to_mono();
1447        let frame = stereo_frame(4_800);
1448        let mut dst = crate::boundary::empty_audio_frame();
1449        for _ in 0..3 {
1450          resampler.send_frame(&frame).expect("send_frame");
1451          while resampler.receive_frame(&mut dst).is_ok() {}
1452        }
1453        resampler.send_eof().expect("eof");
1454        let tail = resampler.delay();
1455        assert!(tail > 0, "there has to be a tail to lose");
1456
1457        crate::fault_subprocess::cap_ffmpeg_allocations(1);
1458        let refused = resampler.receive_frame(&mut dst);
1459        crate::fault_subprocess::uncap_ffmpeg_allocations();
1460
1461        let refused = refused.expect_err("the drain cannot have succeeded");
1462        assert!(
1463          !refused.is_again(),
1464          "an allocation failure is not `send me more input`: {refused:?}",
1465        );
1466        assert_eq!(
1467          resampler.delay(),
1468          tail,
1469          "the tail was consumed by a drain that failed",
1470        );
1471
1472        // And it is still drainable, which is the whole point.
1473        resampler
1474          .receive_frame(&mut dst)
1475          .expect("the tail survived the failure");
1476      },
1477    );
1478  }
1479
1480  #[test]
1481  fn the_sample_format_table_round_trips() {
1482    for format in [
1483      SampleFormat::U8,
1484      SampleFormat::S16,
1485      SampleFormat::S32,
1486      SampleFormat::S64,
1487      SampleFormat::FLT,
1488      SampleFormat::DBL,
1489      SampleFormat::U8P,
1490      SampleFormat::S16P,
1491      SampleFormat::S32P,
1492      SampleFormat::S64P,
1493      SampleFormat::FLTP,
1494      SampleFormat::DBLP,
1495    ] {
1496      let ffmpeg = format.to_ffmpeg().expect("a named format");
1497      assert_eq!(
1498        SampleFormat::from_ffmpeg(ffmpeg),
1499        format,
1500        "{format:?} does not survive the round trip",
1501      );
1502      assert_eq!(ffmpeg.is_planar(), format.is_planar());
1503    }
1504    assert!(SampleFormat::NONE.to_ffmpeg().is_none());
1505    assert!(SampleFormat::from_raw(9999).to_ffmpeg().is_none());
1506  }
1507
1508  #[test]
1509  fn a_mask_rebuilds_the_layout_it_names() {
1510    let stereo = layout_from_mask(ChannelLayout::STEREO.bits());
1511    assert_eq!(stereo.channels(), 2);
1512    assert_eq!(stereo.bits(), ChannelLayout::STEREO.bits());
1513
1514    let five_one = layout_from_mask(ChannelLayout::_5POINT1.bits());
1515    assert_eq!(five_one.channels(), 6);
1516    assert_eq!(
1517      five_one.bits(),
1518      ChannelLayout::_5POINT1.bits(),
1519      "the side-vs-back distinction is exactly what a default layout would lose",
1520    );
1521  }
1522
1523  #[test]
1524  fn plane_geometry_follows_packed_versus_planar() {
1525    use ffmpeg_next::format::sample::Type;
1526    // Packed: one plane holding every channel.
1527    assert_eq!(
1528      plane_bytes(Sample::I16(Type::Packed), 1024, 2),
1529      Some(1024 * 2 * 2)
1530    );
1531    // Planar: one plane per channel, so the count does not multiply in.
1532    assert_eq!(
1533      plane_bytes(Sample::I16(Type::Planar), 1024, 2),
1534      Some(1024 * 2)
1535    );
1536    assert_eq!(
1537      plane_bytes(Sample::F32(Type::Planar), 1024, 6),
1538      Some(1024 * 4)
1539    );
1540    // A sample count whose byte size does not fit is not a size. This
1541    // is the arithmetic that used to run before the allocation it
1542    // feeds, and it wrapped.
1543    assert_eq!(
1544      plane_bytes(Sample::F32(Type::Packed), usize::MAX / 2, 8),
1545      None,
1546      "an overflowing plane size is refused, not wrapped",
1547    );
1548  }
1549
1550  #[test]
1551  fn the_target_timebase_is_one_tick_per_output_sample() {
1552    let spec = ResampleSpec::new(
1553      16_000,
1554      Sample::I16(ffmpeg_next::format::sample::Type::Packed),
1555      ChannelLayout::MONO,
1556    );
1557    let tb = spec.timebase();
1558    assert_eq!((tb.num(), tb.den().get()), (1, 16_000));
1559  }
1560}