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