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