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