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::{
21 Received, Sent, Timebase, decoder::AudioStreamDecoder, frame::AudioFrame, packet::AudioPacket,
22};
23use mediaframe::audio::ChannelLayoutDescription;
24
25use crate::{
26 DecoderLimits, Error, Ffmpeg, boundary,
27 convert::{self, ConvertError},
28 decoder::build_codec_context,
29 extras::{AudioFrameExtra, AudioPacketExtra},
30 frame::alloc_av_audio_frame,
31 sample_format::SampleFormat,
32};
33
34/// `mediadecode::AudioStreamDecoder` impl wrapping `ffmpeg::decoder::Audio`.
35pub struct CarrierAudioStreamDecoder<C: crate::FfmpegCarrier> {
36 decoder: ffmpeg_next::decoder::Audio,
37 scratch: frame::Audio,
38 time_base: Timebase,
39 limits: DecoderLimits,
40 /// Keeps the [`CallbackState`](crate::ffi::CallbackState) alive for as
41 /// long as the codec context that points at it.
42 ///
43 /// Declared **after** the decoder on purpose: struct fields drop in
44 /// declaration order, so the `AVCodecContext` is freed first and the
45 /// state it references outlives it.
46 _callback_state: Box<crate::ffi::CallbackState>,
47 /// The lane this decoder captures into. A marker: the carrier
48 /// appears in the frames it produces, not in its own state.
49 /// `true` when [`Self::scratch`] holds a decoded frame whose
50 /// conversion has **not committed**.
51 ///
52 /// `receive_frame` advances libavcodec: the frame it fills the
53 /// scratch with is out of the codec's queue and nothing re-offers it.
54 /// A conversion that then failed on an *allocation* used to leave
55 /// that frame in a scratch the next call overwrites — a decoded frame
56 /// lost to memory pressure, silently. So the receive and the
57 /// conversion are one transaction: while this is set, the next call
58 /// converts the scratch it already has instead of asking libavcodec
59 /// for another.
60 ///
61 /// The same seat the demux session keeps for a packet, and the same
62 /// discipline `flush` clears.
63 scratch_pending: bool,
64 /// `true` once [`Self::send_eof_impl`] has been accepted, until
65 /// [`Self::flush_impl`].
66 ///
67 /// **This road kept no end of its own before, and that was the gap.**
68 /// It leans on libavcodec to refuse a post-EOF submission — which
69 /// libavcodec does — but leaning is not knowing: asked "where is this
70 /// session", it could only answer `Streaming`, which is false the
71 /// moment a caller signals the end. A phase that can be wrong is the
72 /// thing [`SessionPhase`](crate::decoder::SessionPhase) exists to
73 /// abolish, so the latch is here to make the answer true rather than
74 /// to add a gate. The substrate is still the one that refuses.
75 eof: bool,
76 _carrier: core::marker::PhantomData<C>,
77}
78
79impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierAudioStreamDecoder<C> {
80 /// Opens an audio decoder for the given codec parameters.
81 ///
82 /// `limits` bounds what one decoded frame may cost and is taken here
83 /// rather than through a builder: half of it is written straight into
84 /// the `AVCodecContext` this call opens, and a context's ceiling
85 /// cannot be moved after `avcodec_open2`. See [`DecoderLimits`].
86 pub(crate) fn open_impl(
87 parameters: Parameters,
88 time_base: Timebase,
89 limits: DecoderLimits,
90 ) -> Result<Self, AudioDecodeError> {
91 // Use the checked codec-context builder — `Context::from_parameters`
92 // is OOM-UB-prone (see `crate::decoder::build_codec_context`).
93 let (ctx, callback_state) = build_codec_context(¶meters, limits, Some(time_base))
94 .map_err(AudioDecodeError::Decode)?;
95 // Opened without forming a bindgen enum from FFmpeg memory: the codec
96 // is resolved off a raw `codec_id`, and the medium is proved off a raw
97 // `codec_type`. See `crate::decoder::ensure_codec_type`.
98 let codec = crate::decoder::find_decoder(¶meters).map_err(AudioDecodeError::Decode)?;
99 let opened = ctx
100 .decoder()
101 .open_as(codec)
102 .map_err(|e| AudioDecodeError::Decode(Error::Ffmpeg(e)))?;
103 crate::decoder::ensure_codec_type(&opened, ffmpeg_next::ffi::AVMediaType::AVMEDIA_TYPE_AUDIO)
104 .map_err(AudioDecodeError::Decode)?;
105 let decoder = ffmpeg_next::decoder::Audio(opened);
106 let scratch = alloc_av_audio_frame().map_err(AudioDecodeError::Decode)?;
107 Ok(Self {
108 decoder,
109 scratch,
110 time_base,
111 limits,
112 _callback_state: callback_state,
113 scratch_pending: false,
114 eof: false,
115 _carrier: core::marker::PhantomData,
116 })
117 }
118
119 /// Returns the time base associated with the source stream.
120 #[cfg_attr(not(tarpaulin), inline(always))]
121 pub(crate) const fn time_base_impl(&self) -> Timebase {
122 self.time_base
123 }
124
125 /// The frame ceilings this decoder was opened with.
126 #[cfg_attr(not(tarpaulin), inline(always))]
127 pub(crate) const fn limits_impl(&self) -> DecoderLimits {
128 self.limits
129 }
130
131 /// Borrow the wrapped `ffmpeg::decoder::Audio` (e.g. to query
132 /// `channels()` / `rate()` / `format()`).
133 #[cfg_attr(not(tarpaulin), inline(always))]
134 pub(crate) const fn inner_impl(&self) -> &ffmpeg_next::decoder::Audio {
135 &self.decoder
136 }
137}
138
139impl<C: crate::FfmpegCarrier + crate::CarrierOps> CarrierAudioStreamDecoder<C> {
140 /// Where this session is. See
141 /// [`SessionPhase`](crate::decoder::SessionPhase) — one derivation,
142 /// no road reading the latch for itself. There is no probe on this
143 /// road, so only the committed pair is reachable.
144 const fn phase(&self) -> crate::decoder::SessionPhase {
145 if self.eof {
146 crate::decoder::SessionPhase::Draining
147 } else {
148 crate::decoder::SessionPhase::Streaming
149 }
150 }
151
152 /// **This road never refuses for a parked frame, and that asymmetry
153 /// is kept rather than papered over.** The video and subtitle
154 /// decoders answer [`Sent::MustDrain`] while their scratch holds an
155 /// undelivered frame; this one has a single scratch and cannot change
156 /// which is current, so a submission under a park loses nothing and
157 /// is simply taken. See
158 /// [`CarrierVideoStreamDecoder::scratch_pending`](crate::video::CarrierVideoStreamDecoder)
159 /// for the property that makes the refusal necessary over there.
160 ///
161 /// What it *does* answer `MustDrain` to is libavcodec's own back
162 /// pressure: `avcodec_send_packet` returning `EAGAIN` because its
163 /// output queue is full. That used to arrive as
164 /// `Decode(Ffmpeg(Other { errno: EAGAIN }))` — a fault-shaped value a
165 /// generic consumer could not recognise, which is what made the
166 /// two-offer rule necessary in the first place.
167 pub(crate) fn send_packet_impl(
168 &mut self,
169 packet: &AudioPacket<AudioPacketExtra, C::Buffer>,
170 ) -> Result<Sent, AudioDecodeError> {
171 // Scoped submission: the rebuilt `AVPacket` lives only inside this
172 // call, which is what lets the view lane hand libavcodec its own
173 // buffer instead of a copy. See `boundary::with_ffmpeg_audio_packet`.
174 let state: *const crate::ffi::CallbackState = &*self._callback_state;
175 let phase = self.phase();
176 let decoder = &mut self.decoder;
177 boundary::with_ffmpeg_audio_packet::<C, _>(
178 packet,
179 self.limits.packet_limits(),
180 // Nothing on this road records what it is sent, so the
181 // packet really does die inside the call and its body may
182 // be shared.
183 crate::carrier::BodyRoute::Submission,
184 |av_pkt| {
185 // Funnel, then gate — the same two steps the receive road
186 // takes. A frame the allocator judge refused surfaces named,
187 // not as the `EINVAL` a corrupt file also produces; whatever
188 // survives the funnel is read for back pressure.
189 match decoder.send_packet(av_pkt) {
190 Ok(()) => Ok(Sent::Accepted),
191 Err(e) => {
192 crate::decoder::software_send(state, e, phase).map_err(AudioDecodeError::Decode)
193 }
194 }
195 },
196 )
197 .map_err(|e| AudioDecodeError::Decode(Error::PacketBuild(e)))?
198 }
199
200 pub(crate) fn receive_frame_impl(
201 &mut self,
202 dst: &mut AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, C::Buffer>,
203 ) -> Result<Received, AudioDecodeError> {
204 let state: *const crate::ffi::CallbackState = &*self._callback_state;
205 let phase = self.phase();
206 // A frame whose conversion did not commit is converted again before
207 // another is asked for — see [`Self::scratch_pending`].
208 if !self.scratch_pending {
209 // Funnel first, classify second: the funnel is what turns a
210 // recorded budget refusal into its own name, and only what
211 // survives it is read as a drain state. `EAGAIN` and `Eof` are
212 // the protocol talking, so they leave through the `Ok` arm and
213 // the errno never reaches the caller.
214 if let Err(e) = self.decoder.receive_frame(&mut self.scratch) {
215 return crate::decoder::software_receive(state, e, phase).map_err(AudioDecodeError::Decode);
216 }
217 }
218 // SAFETY: the scratch holds a frame — either one `receive_frame`
219 // just filled it with, or one it was left holding by a conversion
220 // that did not commit. Convert takes what it needs out of it, so
221 // the scratch can be reused once this has committed.
222 let converted = unsafe {
223 convert::av_frame_to_audio_frame_as::<C>(
224 self.scratch.as_ptr(),
225 self.time_base,
226 self.limits.frame(),
227 )
228 };
229 match converted {
230 Ok(new_frame) => {
231 self.scratch_pending = false;
232 *dst = new_frame;
233 Ok(Received::Frame)
234 }
235 Err(e) => {
236 // Park only what another attempt could survive; a frame nothing
237 // can carry is let go, or every later receive answers with the
238 // same error.
239 self.scratch_pending = e.parks_in_decode();
240 Err(AudioDecodeError::Convert(e))
241 }
242 }
243 }
244
245 pub(crate) fn send_eof_impl(&mut self) -> Result<Sent, AudioDecodeError> {
246 let state: *const crate::ffi::CallbackState = &*self._callback_state;
247 let phase = self.phase();
248 match self.decoder.send_eof() {
249 Ok(()) => {
250 self.eof = true;
251 Ok(Sent::Accepted)
252 }
253 Err(e) => crate::decoder::software_send(state, e, phase).map_err(AudioDecodeError::Decode),
254 }
255 }
256
257 pub(crate) fn flush_impl(&mut self) -> Result<(), AudioDecodeError> {
258 // A parked frame belongs to the stream position being abandoned.
259 self.scratch_pending = false;
260 // And so does the end it was told about.
261 self.eof = false;
262 self.decoder.flush();
263 Ok(())
264 }
265}
266
267macro_rules! audio_lane_face {
268 ($($lane:ty),+ $(,)?) => { $(
269 impl CarrierAudioStreamDecoder<$lane> {
270 /// Opens an audio decoder for `parameters`.
271 pub fn open(
272 parameters: Parameters,
273 time_base: Timebase,
274 limits: DecoderLimits,
275 ) -> Result<Self, AudioDecodeError> {
276 Self::open_impl(parameters, time_base, limits)
277 }
278
279 /// The stream timebase every produced timestamp is stamped with.
280 pub const fn time_base(&self) -> Timebase {
281 self.time_base_impl()
282 }
283
284 /// The budgets this decoder was opened with.
285 pub const fn limits(&self) -> DecoderLimits {
286 self.limits_impl()
287 }
288
289 /// The wrapped decoder context.
290 pub const fn inner(&self) -> &ffmpeg_next::decoder::Audio {
291 self.inner_impl()
292 }
293 }
294
295 impl AudioStreamDecoder for CarrierAudioStreamDecoder<$lane> {
296 type Adapter = Ffmpeg;
297 type Buffer = <$lane as crate::FfmpegCarrier>::Buffer;
298 type Error = AudioDecodeError;
299
300 fn send_packet(
301 &mut self,
302 packet: &AudioPacket<AudioPacketExtra, Self::Buffer>,
303 ) -> Result<Sent, Self::Error> {
304 self.send_packet_impl(packet)
305 }
306
307 fn receive_frame(
308 &mut self,
309 dst: &mut AudioFrame<
310 SampleFormat,
311 ChannelLayoutDescription,
312 AudioFrameExtra,
313 Self::Buffer,
314 >,
315 ) -> Result<Received, Self::Error> {
316 self.receive_frame_impl(dst)
317 }
318
319 fn send_eof(&mut self) -> Result<Sent, Self::Error> {
320 self.send_eof_impl()
321 }
322
323 fn flush(&mut self) -> Result<(), Self::Error> {
324 self.flush_impl()
325 }
326 }
327 )+ };
328}
329
330audio_lane_face!(crate::View, crate::Owned);
331
332/// Errors from [`FfmpegAudioStreamDecoder`] — **faults only**.
333///
334/// Both arms are real failures. The drain's other two answers never
335/// reached this type even before they had names: they rode in as
336/// `Decode(Ffmpeg(Other { errno: EAGAIN }))` and
337/// `Decode(Ffmpeg(Eof))`, which a generic consumer had no way to
338/// recognise. They are [`Received`] states now.
339///
340/// **Open fault taxonomy, so it is `#[non_exhaustive]`.** New ways to
341/// fail are discovered — a backend, a ceiling, a corruption a codec
342/// learns to report — and a consumer that meets one it has never heard
343/// of should take its generic-fault path. That is exactly what the
344/// wildcard arm this attribute forces is for. The two status
345/// vocabularies opposite it,
346/// [`Sent`](mediadecode::Sent) and [`Received`](mediadecode::Received),
347/// are exhaustive for the mirror-image reason: their arms are the
348/// substrate's fixed state set, and there the wildcard would be dead
349/// weight hiding a state a consumer forgot.
350#[derive(thiserror::Error, Debug, IsVariant, Unwrap, TryUnwrap)]
351#[unwrap(ref, ref_mut)]
352#[try_unwrap(ref, ref_mut)]
353#[non_exhaustive]
354pub enum AudioDecodeError {
355 /// The wrapped `ffmpeg::decoder::Audio` reported an error.
356 #[error(transparent)]
357 Decode(#[from] Error),
358 /// Conversion from FFmpeg's `AVFrame` to mediadecode's `AudioFrame`
359 /// failed.
360 #[error(transparent)]
361 Convert(#[from] ConvertError),
362}