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