Skip to main content

mediadecode_ffmpeg/
audio.rs

1//! `mediadecode::AudioStreamDecoder` impl backed by
2//! `ffmpeg::decoder::Audio`.
3//!
4//! Mirrors the shape of [`crate::FfmpegVideoStreamDecoder`] without
5//! the HW-fallback wrinkle — audio decoders never go through a
6//! hardware backend in the FFmpeg world, so there's no probe, no
7//! state machine, just `send_packet` / `receive_frame` over the
8//! software decoder.
9//!
10//! Frames produced via [`crate::convert::av_frame_to_audio_frame`]
11//! carry `FfmpegBytes` planes copied out of the source `AVFrame` — the
12//! [D-seat amputation contract][law]. The consumer can hold the frame
13//! across decoder calls, send it to another thread, and outlive the
14//! decoder that made it.
15//!
16//! [law]: mediadecode::adapter#the-d-seat-amputation-contract
17
18use derive_more::{IsVariant, TryUnwrap, Unwrap};
19use ffmpeg_next::{codec::Parameters, frame};
20use mediadecode::{Timebase, decoder::AudioStreamDecoder, frame::AudioFrame, packet::AudioPacket};
21use mediaframe::audio::ChannelLayoutDescription;
22
23use crate::{
24  DecoderLimits, Error, Ffmpeg, boundary,
25  convert::{self, ConvertError},
26  decoder::build_codec_context,
27  extras::{AudioFrameExtra, AudioPacketExtra},
28  frame::alloc_av_audio_frame,
29  sample_format::SampleFormat,
30};
31
32/// `mediadecode::AudioStreamDecoder` impl wrapping `ffmpeg::decoder::Audio`.
33pub struct CarrierAudioStreamDecoder<C: crate::FfmpegCarrier> {
34  decoder: ffmpeg_next::decoder::Audio,
35  scratch: frame::Audio,
36  time_base: Timebase,
37  limits: DecoderLimits,
38  /// Keeps the [`CallbackState`](crate::ffi::CallbackState) alive for as
39  /// long as the codec context that points at it.
40  ///
41  /// Declared **after** the decoder on purpose: struct fields drop in
42  /// declaration order, so the `AVCodecContext` is freed first and the
43  /// state it references outlives it.
44  _callback_state: Box<crate::ffi::CallbackState>,
45  /// The lane this decoder captures into. A marker: the carrier
46  /// appears in the frames it produces, not in its own state.
47  /// `true` when [`Self::scratch`] holds a decoded frame whose
48  /// conversion has **not committed**.
49  ///
50  /// `receive_frame` advances libavcodec: the frame it fills the
51  /// scratch with is out of the codec's queue and nothing re-offers it.
52  /// A conversion that then failed on an *allocation* used to leave
53  /// that frame in a scratch the next call overwrites — a decoded frame
54  /// lost to memory pressure, silently. So the receive and the
55  /// conversion are one transaction: while this is set, the next call
56  /// converts the scratch it already has instead of asking libavcodec
57  /// for another.
58  ///
59  /// The same seat the demux session keeps for a packet, and the same
60  /// discipline `flush` clears.
61  scratch_pending: bool,
62  _carrier: core::marker::PhantomData<C>,
63}
64
65impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierAudioStreamDecoder<C> {
66  /// Opens an audio decoder for the given codec parameters.
67  ///
68  /// `limits` bounds what one decoded frame may cost and is taken here
69  /// rather than through a builder: half of it is written straight into
70  /// the `AVCodecContext` this call opens, and a context's ceiling
71  /// cannot be moved after `avcodec_open2`. See [`DecoderLimits`].
72  pub(crate) fn open_impl(
73    parameters: Parameters,
74    time_base: Timebase,
75    limits: DecoderLimits,
76  ) -> Result<Self, AudioDecodeError> {
77    // Use the checked codec-context builder — `Context::from_parameters`
78    // is OOM-UB-prone (see `crate::decoder::build_codec_context`).
79    let (ctx, callback_state) =
80      build_codec_context(&parameters, limits).map_err(AudioDecodeError::Decode)?;
81    // Opened without forming a bindgen enum from FFmpeg memory: the codec
82    // is resolved off a raw `codec_id`, and the medium is proved off a raw
83    // `codec_type`. See `crate::decoder::ensure_codec_type`.
84    let codec = crate::decoder::find_decoder(&parameters).map_err(AudioDecodeError::Decode)?;
85    let opened = ctx
86      .decoder()
87      .open_as(codec)
88      .map_err(|e| AudioDecodeError::Decode(Error::Ffmpeg(e)))?;
89    crate::decoder::ensure_codec_type(&opened, ffmpeg_next::ffi::AVMediaType::AVMEDIA_TYPE_AUDIO)
90      .map_err(AudioDecodeError::Decode)?;
91    let decoder = ffmpeg_next::decoder::Audio(opened);
92    let scratch = alloc_av_audio_frame().map_err(AudioDecodeError::Decode)?;
93    Ok(Self {
94      decoder,
95      scratch,
96      time_base,
97      limits,
98      _callback_state: callback_state,
99      scratch_pending: false,
100      _carrier: core::marker::PhantomData,
101    })
102  }
103
104  /// Returns the time base associated with the source stream.
105  #[cfg_attr(not(tarpaulin), inline(always))]
106  pub(crate) const fn time_base_impl(&self) -> Timebase {
107    self.time_base
108  }
109
110  /// The frame ceilings this decoder was opened with.
111  #[cfg_attr(not(tarpaulin), inline(always))]
112  pub(crate) const fn limits_impl(&self) -> DecoderLimits {
113    self.limits
114  }
115
116  /// Borrow the wrapped `ffmpeg::decoder::Audio` (e.g. to query
117  /// `channels()` / `rate()` / `format()`).
118  #[cfg_attr(not(tarpaulin), inline(always))]
119  pub(crate) const fn inner_impl(&self) -> &ffmpeg_next::decoder::Audio {
120    &self.decoder
121  }
122}
123
124impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierAudioStreamDecoder<C> {
125  pub(crate) fn send_packet_impl(
126    &mut self,
127    packet: &AudioPacket<AudioPacketExtra, C::Buffer>,
128  ) -> Result<(), AudioDecodeError> {
129    // Scoped submission: the rebuilt `AVPacket` lives only inside this
130    // call, which is what lets the view lane hand libavcodec its own
131    // buffer instead of a copy. See `boundary::with_ffmpeg_audio_packet`.
132    let state: *const crate::ffi::CallbackState = &*self._callback_state;
133    let decoder = &mut self.decoder;
134    boundary::with_ffmpeg_audio_packet::<C, _>(
135      packet,
136      self.limits.packet_limits(),
137      // Nothing on this road records what it is sent, so the
138      // packet really does die inside the call and its body may
139      // be shared.
140      crate::carrier::BodyRoute::Submission,
141      |av_pkt| {
142        // Through the software funnel: a frame the allocator judge
143        // refused surfaces named, not as the `EINVAL` a corrupt file
144        // also produces.
145        decoder
146          .send_packet(av_pkt)
147          .map_err(|e| AudioDecodeError::Decode(crate::decoder::software_exit(state, e)))
148      },
149    )
150    .map_err(|e| AudioDecodeError::Decode(Error::PacketBuild(e)))?
151  }
152
153  pub(crate) fn receive_frame_impl(
154    &mut self,
155    dst: &mut AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, C::Buffer>,
156  ) -> Result<(), AudioDecodeError> {
157    let state: *const crate::ffi::CallbackState = &*self._callback_state;
158    // A frame whose conversion did not commit is converted again before
159    // another is asked for — see [`Self::scratch_pending`].
160    if !self.scratch_pending {
161      self
162        .decoder
163        .receive_frame(&mut self.scratch)
164        .map_err(|e| AudioDecodeError::Decode(crate::decoder::software_exit(state, e)))?;
165    }
166    // SAFETY: the scratch holds a frame — either one `receive_frame`
167    // just filled it with, or one it was left holding by a conversion
168    // that did not commit. Convert takes what it needs out of it, so
169    // the scratch can be reused once this has committed.
170    let converted = unsafe {
171      convert::av_frame_to_audio_frame_as::<C>(
172        self.scratch.as_ptr(),
173        self.time_base,
174        self.limits.frame(),
175      )
176    };
177    match converted {
178      Ok(new_frame) => {
179        self.scratch_pending = false;
180        *dst = new_frame;
181        Ok(())
182      }
183      Err(e) => {
184        // Park only what another attempt could survive; a frame nothing
185        // can carry is let go, or every later receive answers with the
186        // same error.
187        self.scratch_pending = e.parks_in_decode();
188        Err(AudioDecodeError::Convert(e))
189      }
190    }
191  }
192
193  pub(crate) fn send_eof_impl(&mut self) -> Result<(), AudioDecodeError> {
194    let state: *const crate::ffi::CallbackState = &*self._callback_state;
195    self
196      .decoder
197      .send_eof()
198      .map_err(|e| AudioDecodeError::Decode(crate::decoder::software_exit(state, e)))
199  }
200
201  pub(crate) fn flush_impl(&mut self) -> Result<(), AudioDecodeError> {
202    // A parked frame belongs to the stream position being abandoned.
203    self.scratch_pending = false;
204    self.decoder.flush();
205    Ok(())
206  }
207}
208
209macro_rules! audio_lane_face {
210  ($($lane:ty),+ $(,)?) => { $(
211    impl CarrierAudioStreamDecoder<$lane> {
212      /// Opens an audio decoder for `parameters`.
213      pub fn open(
214        parameters: Parameters,
215        time_base: Timebase,
216        limits: DecoderLimits,
217      ) -> Result<Self, AudioDecodeError> {
218        Self::open_impl(parameters, time_base, limits)
219      }
220
221      /// The stream timebase every produced timestamp is stamped with.
222      pub const fn time_base(&self) -> Timebase {
223        self.time_base_impl()
224      }
225
226      /// The budgets this decoder was opened with.
227      pub const fn limits(&self) -> DecoderLimits {
228        self.limits_impl()
229      }
230
231      /// The wrapped decoder context.
232      pub const fn inner(&self) -> &ffmpeg_next::decoder::Audio {
233        self.inner_impl()
234      }
235    }
236
237    impl AudioStreamDecoder for CarrierAudioStreamDecoder<$lane> {
238      type Adapter = Ffmpeg;
239      type Buffer = <$lane as crate::FfmpegCarrier>::Buffer;
240      type Error = AudioDecodeError;
241
242      fn send_packet(
243        &mut self,
244        packet: &AudioPacket<AudioPacketExtra, Self::Buffer>,
245      ) -> Result<(), Self::Error> {
246        self.send_packet_impl(packet)
247      }
248
249      fn receive_frame(
250        &mut self,
251        dst: &mut AudioFrame<
252          SampleFormat,
253          ChannelLayoutDescription,
254          AudioFrameExtra,
255          Self::Buffer,
256        >,
257      ) -> Result<(), Self::Error> {
258        self.receive_frame_impl(dst)
259      }
260
261      fn send_eof(&mut self) -> Result<(), Self::Error> {
262        self.send_eof_impl()
263      }
264
265      fn flush(&mut self) -> Result<(), Self::Error> {
266        self.flush_impl()
267      }
268    }
269  )+ };
270}
271
272audio_lane_face!(crate::View, crate::Owned);
273
274/// Errors from [`FfmpegAudioStreamDecoder`].
275#[derive(thiserror::Error, Debug, Clone, IsVariant, Unwrap, TryUnwrap)]
276#[unwrap(ref, ref_mut)]
277#[try_unwrap(ref, ref_mut)]
278pub enum AudioDecodeError {
279  /// The wrapped `ffmpeg::decoder::Audio` reported an error.
280  #[error(transparent)]
281  Decode(#[from] Error),
282  /// Conversion from FFmpeg's `AVFrame` to mediadecode's `AudioFrame`
283  /// failed.
284  #[error(transparent)]
285  Convert(#[from] ConvertError),
286}