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