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 zero-copy `FfmpegBuffer` plane views into the source
12//! `AVFrame`'s refcounted buffers; the consumer can hold the frame
13//! across decoder calls without copying.
14
15use derive_more::{IsVariant, TryUnwrap, Unwrap};
16use ffmpeg_next::{codec::Parameters, frame};
17use mediadecode::{Timebase, decoder::AudioStreamDecoder, frame::AudioFrame, packet::AudioPacket};
18use mediaframe::audio::ChannelLayoutDescription;
19
20use crate::{
21  Error, Ffmpeg, FfmpegBuffer, boundary,
22  convert::{self, ConvertError},
23  decoder::build_codec_context,
24  extras::{AudioFrameExtra, AudioPacketExtra},
25  frame::alloc_av_audio_frame,
26  sample_format::SampleFormat,
27};
28
29/// `mediadecode::AudioStreamDecoder` impl wrapping `ffmpeg::decoder::Audio`.
30pub struct FfmpegAudioStreamDecoder {
31  decoder: ffmpeg_next::decoder::Audio,
32  scratch: frame::Audio,
33  time_base: Timebase,
34}
35
36impl FfmpegAudioStreamDecoder {
37  /// Opens an audio decoder for the given codec parameters.
38  pub fn open(parameters: Parameters, time_base: Timebase) -> Result<Self, AudioDecodeError> {
39    // Use the checked codec-context builder — `Context::from_parameters`
40    // is OOM-UB-prone (see `crate::decoder::build_codec_context`).
41    let ctx = build_codec_context(&parameters).map_err(AudioDecodeError::Decode)?;
42    let decoder = ctx
43      .decoder()
44      .audio()
45      .map_err(|e| AudioDecodeError::Decode(Error::Ffmpeg(e)))?;
46    let scratch = alloc_av_audio_frame().map_err(AudioDecodeError::Decode)?;
47    Ok(Self {
48      decoder,
49      scratch,
50      time_base,
51    })
52  }
53
54  /// Returns the time base associated with the source stream.
55  #[cfg_attr(not(tarpaulin), inline(always))]
56  pub const fn time_base(&self) -> Timebase {
57    self.time_base
58  }
59
60  /// Borrow the wrapped `ffmpeg::decoder::Audio` (e.g. to query
61  /// `channels()` / `rate()` / `format()`).
62  #[cfg_attr(not(tarpaulin), inline(always))]
63  pub const fn inner(&self) -> &ffmpeg_next::decoder::Audio {
64    &self.decoder
65  }
66}
67
68impl AudioStreamDecoder for FfmpegAudioStreamDecoder {
69  type Adapter = Ffmpeg;
70  type Buffer = FfmpegBuffer;
71  type Error = AudioDecodeError;
72
73  fn send_packet(
74    &mut self,
75    packet: &AudioPacket<AudioPacketExtra, Self::Buffer>,
76  ) -> Result<(), Self::Error> {
77    let av_pkt = boundary::ffmpeg_packet_from_audio_packet(packet)
78      .map_err(|e| AudioDecodeError::Decode(Error::PacketBuild(e)))?;
79    self
80      .decoder
81      .send_packet(&av_pkt)
82      .map_err(|e| AudioDecodeError::Decode(Error::Ffmpeg(e)))
83  }
84
85  fn receive_frame(
86    &mut self,
87    dst: &mut AudioFrame<SampleFormat, ChannelLayoutDescription, AudioFrameExtra, Self::Buffer>,
88  ) -> Result<(), Self::Error> {
89    self
90      .decoder
91      .receive_frame(&mut self.scratch)
92      .map_err(|e| AudioDecodeError::Decode(Error::Ffmpeg(e)))?;
93    // SAFETY: scratch was just filled by receive_frame; convert
94    // refcounts each plane buffer it pulls into the produced
95    // AudioFrame so the scratch can be reused on the next call.
96    let new_frame =
97      unsafe { convert::av_frame_to_audio_frame(self.scratch.as_ptr(), self.time_base) }
98        .map_err(AudioDecodeError::Convert)?;
99    *dst = new_frame;
100    Ok(())
101  }
102
103  fn send_eof(&mut self) -> Result<(), Self::Error> {
104    self
105      .decoder
106      .send_eof()
107      .map_err(|e| AudioDecodeError::Decode(Error::Ffmpeg(e)))
108  }
109
110  fn flush(&mut self) -> Result<(), Self::Error> {
111    self.decoder.flush();
112    Ok(())
113  }
114}
115
116/// Errors from [`FfmpegAudioStreamDecoder`].
117#[derive(thiserror::Error, Debug, Clone, IsVariant, Unwrap, TryUnwrap)]
118#[unwrap(ref, ref_mut)]
119#[try_unwrap(ref, ref_mut)]
120pub enum AudioDecodeError {
121  /// The wrapped `ffmpeg::decoder::Audio` reported an error.
122  #[error(transparent)]
123  Decode(#[from] Error),
124  /// Conversion from FFmpeg's `AVFrame` to mediadecode's `AudioFrame`
125  /// failed.
126  #[error(transparent)]
127  Convert(#[from] ConvertError),
128}