Skip to main content

ff_decode/audio/
builder.rs

1//! Audio decoder builder for constructing audio decoders with custom configuration.
2//!
3//! This module provides the [`AudioDecoderBuilder`] type which enables fluent
4//! configuration of audio decoders. Use [`AudioDecoder::open()`] to start building.
5
6use std::path::{Path, PathBuf};
7use std::time::Duration;
8
9use ff_format::{AudioFrame, AudioStreamInfo, SampleFormat};
10
11use crate::audio::decoder_inner::AudioDecoderInner;
12use crate::error::DecodeError;
13
14/// Internal configuration for the audio decoder.
15#[derive(Debug, Clone, Default)]
16#[allow(clippy::struct_field_names)]
17#[allow(dead_code)] // Fields will be used when SwResample is fully implemented
18pub(crate) struct AudioDecoderConfig {
19    /// Output sample format (None = use source format)
20    pub output_format: Option<SampleFormat>,
21    /// Output sample rate (None = use source sample rate)
22    pub output_sample_rate: Option<u32>,
23    /// Output channel count (None = use source channel count)
24    pub output_channels: Option<u32>,
25}
26
27/// Builder for configuring and constructing an [`AudioDecoder`].
28///
29/// This struct provides a fluent interface for setting up decoder options
30/// before opening an audio file. It is created by calling [`AudioDecoder::open()`].
31///
32/// # Examples
33///
34/// ## Basic Usage
35///
36/// ```ignore
37/// use ff_decode::AudioDecoder;
38///
39/// let decoder = AudioDecoder::open("audio.mp3")?
40///     .build()?;
41/// ```
42///
43/// ## With Custom Format and Sample Rate
44///
45/// ```ignore
46/// use ff_decode::AudioDecoder;
47/// use ff_format::SampleFormat;
48///
49/// let decoder = AudioDecoder::open("audio.mp3")?
50///     .output_format(SampleFormat::F32)
51///     .output_sample_rate(48000)
52///     .build()?;
53/// ```
54#[derive(Debug)]
55pub struct AudioDecoderBuilder {
56    /// Path to the media file
57    path: PathBuf,
58    /// Output sample format (None = use source format)
59    output_format: Option<SampleFormat>,
60    /// Output sample rate (None = use source sample rate)
61    output_sample_rate: Option<u32>,
62    /// Output channel count (None = use source channel count)
63    output_channels: Option<u32>,
64}
65
66impl AudioDecoderBuilder {
67    /// Creates a new builder for the specified file path.
68    ///
69    /// This is an internal constructor; use [`AudioDecoder::open()`] instead.
70    pub(crate) fn new(path: PathBuf) -> Self {
71        Self {
72            path,
73            output_format: None,
74            output_sample_rate: None,
75            output_channels: None,
76        }
77    }
78
79    /// Sets the output sample format for decoded frames.
80    ///
81    /// If not set, frames are returned in the source format. Setting an
82    /// output format enables automatic conversion during decoding.
83    ///
84    /// # Common Formats
85    ///
86    /// - [`SampleFormat::F32`] - 32-bit float, most common for editing
87    /// - [`SampleFormat::I16`] - 16-bit integer, CD quality
88    /// - [`SampleFormat::F32p`] - Planar 32-bit float, efficient for processing
89    ///
90    /// # Examples
91    ///
92    /// ```ignore
93    /// use ff_decode::AudioDecoder;
94    /// use ff_format::SampleFormat;
95    ///
96    /// let decoder = AudioDecoder::open("audio.mp3")?
97    ///     .output_format(SampleFormat::F32)
98    ///     .build()?;
99    /// ```
100    #[must_use]
101    pub fn output_format(mut self, format: SampleFormat) -> Self {
102        self.output_format = Some(format);
103        self
104    }
105
106    /// Sets the output sample rate in Hz.
107    ///
108    /// If not set, frames are returned at the source sample rate. Setting an
109    /// output sample rate enables automatic resampling during decoding.
110    ///
111    /// # Common Sample Rates
112    ///
113    /// - 44100 Hz - CD quality audio
114    /// - 48000 Hz - Professional audio, most common in video
115    /// - 96000 Hz - High-resolution audio
116    ///
117    /// # Examples
118    ///
119    /// ```ignore
120    /// use ff_decode::AudioDecoder;
121    ///
122    /// // Resample to 48kHz
123    /// let decoder = AudioDecoder::open("audio.mp3")?
124    ///     .output_sample_rate(48000)
125    ///     .build()?;
126    /// ```
127    #[must_use]
128    pub fn output_sample_rate(mut self, sample_rate: u32) -> Self {
129        self.output_sample_rate = Some(sample_rate);
130        self
131    }
132
133    /// Sets the output channel count.
134    ///
135    /// If not set, frames are returned with the source channel count. Setting an
136    /// output channel count enables automatic channel remixing during decoding.
137    ///
138    /// # Common Channel Counts
139    ///
140    /// - 1 - Mono
141    /// - 2 - Stereo
142    /// - 6 - 5.1 surround sound
143    ///
144    /// # Examples
145    ///
146    /// ```ignore
147    /// use ff_decode::AudioDecoder;
148    ///
149    /// // Convert to stereo
150    /// let decoder = AudioDecoder::open("audio.mp3")?
151    ///     .output_channels(2)
152    ///     .build()?;
153    /// ```
154    #[must_use]
155    pub fn output_channels(mut self, channels: u32) -> Self {
156        self.output_channels = Some(channels);
157        self
158    }
159
160    /// Returns the configured file path.
161    #[must_use]
162    pub fn path(&self) -> &Path {
163        &self.path
164    }
165
166    /// Returns the configured output format, if any.
167    #[must_use]
168    pub fn get_output_format(&self) -> Option<SampleFormat> {
169        self.output_format
170    }
171
172    /// Returns the configured output sample rate, if any.
173    #[must_use]
174    pub fn get_output_sample_rate(&self) -> Option<u32> {
175        self.output_sample_rate
176    }
177
178    /// Returns the configured output channel count, if any.
179    #[must_use]
180    pub fn get_output_channels(&self) -> Option<u32> {
181        self.output_channels
182    }
183
184    /// Builds the audio decoder with the configured options.
185    ///
186    /// This method opens the media file, initializes the decoder context,
187    /// and prepares for frame decoding.
188    ///
189    /// # Errors
190    ///
191    /// Returns an error if:
192    /// - The file cannot be found ([`DecodeError::FileNotFound`])
193    /// - The file contains no audio stream ([`DecodeError::NoAudioStream`])
194    /// - The codec is not supported ([`DecodeError::UnsupportedCodec`])
195    /// - Other `FFmpeg` errors occur ([`DecodeError::Ffmpeg`])
196    ///
197    /// # Examples
198    ///
199    /// ```ignore
200    /// use ff_decode::AudioDecoder;
201    ///
202    /// let decoder = AudioDecoder::open("audio.mp3")?
203    ///     .build()?;
204    ///
205    /// // Start decoding
206    /// for frame in decoder.frames().take(100) {
207    ///     let frame = frame?;
208    ///     // Process frame...
209    /// }
210    /// ```
211    pub fn build(self) -> Result<AudioDecoder, DecodeError> {
212        // Verify the file exists
213        if !self.path.exists() {
214            return Err(DecodeError::FileNotFound {
215                path: self.path.clone(),
216            });
217        }
218
219        // Build the internal configuration
220        let config = AudioDecoderConfig {
221            output_format: self.output_format,
222            output_sample_rate: self.output_sample_rate,
223            output_channels: self.output_channels,
224        };
225
226        // Create the decoder inner
227        let (inner, stream_info) = AudioDecoderInner::new(
228            &self.path,
229            self.output_format,
230            self.output_sample_rate,
231            self.output_channels,
232        )?;
233
234        Ok(AudioDecoder {
235            path: self.path,
236            config,
237            inner,
238            stream_info,
239        })
240    }
241}
242
243/// An audio decoder for extracting audio frames from media files.
244///
245/// The decoder provides frame-by-frame access to audio content with support
246/// for resampling and format conversion.
247///
248/// # Construction
249///
250/// Use [`AudioDecoder::open()`] to create a builder, then call [`AudioDecoderBuilder::build()`]:
251///
252/// ```ignore
253/// use ff_decode::AudioDecoder;
254/// use ff_format::SampleFormat;
255///
256/// let decoder = AudioDecoder::open("audio.mp3")?
257///     .output_format(SampleFormat::F32)
258///     .output_sample_rate(48000)
259///     .build()?;
260/// ```
261///
262/// # Frame Decoding
263///
264/// Frames can be decoded one at a time or using an iterator:
265///
266/// ```ignore
267/// // Decode one frame
268/// if let Some(frame) = decoder.decode_one()? {
269///     println!("Frame with {} samples", frame.samples());
270/// }
271///
272/// // Use iterator
273/// for frame in decoder.frames().take(100) {
274///     let frame = frame?;
275///     // Process frame...
276/// }
277/// ```
278///
279/// # Seeking
280///
281/// The decoder supports seeking to specific positions:
282///
283/// ```ignore
284/// use std::time::Duration;
285///
286/// // Seek to 30 seconds
287/// decoder.seek(Duration::from_secs(30))?;
288/// ```
289pub struct AudioDecoder {
290    /// Path to the media file
291    path: PathBuf,
292    /// Decoder configuration
293    #[allow(dead_code)]
294    config: AudioDecoderConfig,
295    /// Internal decoder state
296    inner: AudioDecoderInner,
297    /// Audio stream information
298    stream_info: AudioStreamInfo,
299}
300
301impl AudioDecoder {
302    /// Opens a media file and returns a builder for configuring the decoder.
303    ///
304    /// This is the entry point for creating a decoder. The returned builder
305    /// allows setting options before the decoder is fully initialized.
306    ///
307    /// # Arguments
308    ///
309    /// * `path` - Path to the media file to decode.
310    ///
311    /// # Examples
312    ///
313    /// ```ignore
314    /// use ff_decode::AudioDecoder;
315    ///
316    /// // Simple usage
317    /// let decoder = AudioDecoder::open("audio.mp3")?
318    ///     .build()?;
319    ///
320    /// // With options
321    /// let decoder = AudioDecoder::open("audio.mp3")?
322    ///     .output_format(SampleFormat::F32)
323    ///     .output_sample_rate(48000)
324    ///     .build()?;
325    /// ```
326    ///
327    /// # Note
328    ///
329    /// This method does not validate that the file exists or is a valid
330    /// media file. Validation occurs when [`AudioDecoderBuilder::build()`] is called.
331    pub fn open(path: impl AsRef<Path>) -> AudioDecoderBuilder {
332        AudioDecoderBuilder::new(path.as_ref().to_path_buf())
333    }
334
335    // =========================================================================
336    // Information Methods
337    // =========================================================================
338
339    /// Returns the audio stream information.
340    ///
341    /// This contains metadata about the audio stream including sample rate,
342    /// channel count, codec, and format characteristics.
343    #[must_use]
344    pub fn stream_info(&self) -> &AudioStreamInfo {
345        &self.stream_info
346    }
347
348    /// Returns the sample rate in Hz.
349    #[must_use]
350    pub fn sample_rate(&self) -> u32 {
351        self.stream_info.sample_rate()
352    }
353
354    /// Returns the number of audio channels.
355    #[must_use]
356    pub fn channels(&self) -> u32 {
357        self.stream_info.channels()
358    }
359
360    /// Returns the total duration of the audio.
361    ///
362    /// Returns [`Duration::ZERO`] if duration is unknown.
363    #[must_use]
364    pub fn duration(&self) -> Duration {
365        self.stream_info.duration().unwrap_or(Duration::ZERO)
366    }
367
368    /// Returns the current playback position.
369    #[must_use]
370    pub fn position(&self) -> Duration {
371        self.inner.position()
372    }
373
374    /// Returns `true` if the end of stream has been reached.
375    #[must_use]
376    pub fn is_eof(&self) -> bool {
377        self.inner.is_eof()
378    }
379
380    /// Returns the file path being decoded.
381    #[must_use]
382    pub fn path(&self) -> &Path {
383        &self.path
384    }
385
386    // =========================================================================
387    // Decoding Methods
388    // =========================================================================
389
390    /// Decodes the next audio frame.
391    ///
392    /// This method reads and decodes a single frame from the audio stream.
393    ///
394    /// # Returns
395    ///
396    /// - `Ok(Some(frame))` - A frame was successfully decoded
397    /// - `Ok(None)` - End of stream reached, no more frames
398    /// - `Err(_)` - An error occurred during decoding
399    ///
400    /// # Errors
401    ///
402    /// Returns [`DecodeError`] if:
403    /// - Reading from the file fails
404    /// - Decoding the frame fails
405    /// - Sample format conversion fails
406    ///
407    /// # Examples
408    ///
409    /// ```ignore
410    /// use ff_decode::AudioDecoder;
411    ///
412    /// let mut decoder = AudioDecoder::open("audio.mp3")?.build()?;
413    ///
414    /// while let Some(frame) = decoder.decode_one()? {
415    ///     println!("Frame with {} samples", frame.samples());
416    ///     // Process frame...
417    /// }
418    /// ```
419    pub fn decode_one(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
420        self.inner.decode_one()
421    }
422
423    /// Returns an iterator over decoded frames.
424    ///
425    /// This provides a convenient way to iterate over all frames in the audio.
426    /// The iterator will continue until end of stream or an error occurs.
427    ///
428    /// # Examples
429    ///
430    /// ```ignore
431    /// use ff_decode::AudioDecoder;
432    ///
433    /// let mut decoder = AudioDecoder::open("audio.mp3")?.build()?;
434    ///
435    /// // Process first 100 frames
436    /// for frame in decoder.frames().take(100) {
437    ///     let frame = frame?;
438    ///     // Process frame...
439    /// }
440    /// ```
441    pub fn frames(&mut self) -> AudioFrameIterator<'_> {
442        AudioFrameIterator { decoder: self }
443    }
444
445    /// Decodes all frames and returns their raw PCM data.
446    ///
447    /// This method decodes the entire audio file and returns all samples
448    /// as a contiguous byte buffer.
449    ///
450    /// # Performance
451    ///
452    /// - Memory scales with audio duration and quality
453    /// - For 10 minutes of stereo 48kHz F32 audio: ~110 MB
454    ///
455    /// # Returns
456    ///
457    /// A byte vector containing all audio samples in the configured output format.
458    ///
459    /// # Errors
460    ///
461    /// Returns [`DecodeError`] if:
462    /// - Decoding any frame fails
463    /// - The file cannot be read
464    ///
465    /// # Examples
466    ///
467    /// ```ignore
468    /// use ff_decode::AudioDecoder;
469    /// use ff_format::SampleFormat;
470    ///
471    /// let mut decoder = AudioDecoder::open("audio.mp3")?
472    ///     .output_format(SampleFormat::F32)
473    ///     .build()?;
474    ///
475    /// let samples = decoder.decode_all()?;
476    /// println!("Decoded {} bytes", samples.len());
477    /// ```
478    ///
479    /// # Memory Usage
480    ///
481    /// Stereo 48kHz F32 audio:
482    /// - 1 minute: ~11 MB
483    /// - 5 minutes: ~55 MB
484    /// - 10 minutes: ~110 MB
485    pub fn decode_all(&mut self) -> Result<Vec<u8>, DecodeError> {
486        let mut buffer = Vec::new();
487
488        for frame_result in self.frames() {
489            let frame = frame_result?;
490
491            // Collect samples from all planes
492            for plane in frame.planes() {
493                buffer.extend_from_slice(plane);
494            }
495        }
496
497        Ok(buffer)
498    }
499
500    /// Decodes all frames within a specified time range.
501    ///
502    /// This method seeks to the start position and decodes all frames until
503    /// the end position is reached. Frames outside the range are skipped.
504    ///
505    /// # Arguments
506    ///
507    /// * `start` - Start of the time range (inclusive).
508    /// * `end` - End of the time range (exclusive).
509    ///
510    /// # Returns
511    ///
512    /// A byte vector containing audio samples within `[start, end)`.
513    ///
514    /// # Errors
515    ///
516    /// Returns [`DecodeError`] if:
517    /// - Seeking to the start position fails
518    /// - Decoding frames fails
519    /// - The time range is invalid (start >= end)
520    ///
521    /// # Examples
522    ///
523    /// ```ignore
524    /// use ff_decode::AudioDecoder;
525    /// use std::time::Duration;
526    ///
527    /// let mut decoder = AudioDecoder::open("audio.mp3")?.build()?;
528    ///
529    /// // Decode audio from 5s to 10s
530    /// let samples = decoder.decode_range(
531    ///     Duration::from_secs(5),
532    ///     Duration::from_secs(10),
533    /// )?;
534    ///
535    /// println!("Decoded {} bytes", samples.len());
536    /// ```
537    pub fn decode_range(&mut self, start: Duration, end: Duration) -> Result<Vec<u8>, DecodeError> {
538        // Validate range
539        if start >= end {
540            return Err(DecodeError::DecodingFailed {
541                timestamp: Some(start),
542                reason: format!(
543                    "Invalid time range: start ({start:?}) must be before end ({end:?})"
544                ),
545            });
546        }
547
548        // Seek to start position (keyframe mode for efficiency)
549        self.seek(start, crate::SeekMode::Keyframe)?;
550
551        // Collect frames in the range
552        let mut buffer = Vec::new();
553
554        for frame_result in self.frames() {
555            let frame = frame_result?;
556            let frame_time = frame.timestamp().as_duration();
557
558            // Stop if we've passed the end of the range
559            if frame_time >= end {
560                break;
561            }
562
563            // Only collect frames within the range
564            if frame_time >= start {
565                for plane in frame.planes() {
566                    buffer.extend_from_slice(plane);
567                }
568            }
569        }
570
571        Ok(buffer)
572    }
573
574    // =========================================================================
575    // Seeking Methods
576    // =========================================================================
577
578    /// Seeks to a specified position in the audio stream.
579    ///
580    /// This method performs efficient seeking without reopening the file.
581    ///
582    /// # Arguments
583    ///
584    /// * `position` - Target position to seek to.
585    /// * `mode` - Seek mode (Keyframe, Exact, or Backward).
586    ///
587    /// # Errors
588    ///
589    /// Returns [`DecodeError::SeekFailed`] if:
590    /// - The target position is beyond the audio duration
591    /// - The file format doesn't support seeking
592    /// - The seek operation fails internally
593    ///
594    /// # Examples
595    ///
596    /// ```ignore
597    /// use ff_decode::{AudioDecoder, SeekMode};
598    /// use std::time::Duration;
599    ///
600    /// let mut decoder = AudioDecoder::open("audio.mp3")?.build()?;
601    ///
602    /// // Seek to 30 seconds with keyframe mode (fast)
603    /// decoder.seek(Duration::from_secs(30), SeekMode::Keyframe)?;
604    ///
605    /// // Seek to exact position (slower but precise)
606    /// decoder.seek(Duration::from_secs(45), SeekMode::Exact)?;
607    ///
608    /// // Decode next frame
609    /// if let Some(frame) = decoder.decode_one()? {
610    ///     println!("Frame at {:?}", frame.timestamp().as_duration());
611    /// }
612    /// ```
613    pub fn seek(&mut self, position: Duration, mode: crate::SeekMode) -> Result<(), DecodeError> {
614        self.inner.seek(position, mode)
615    }
616
617    /// Flushes the decoder's internal buffers.
618    ///
619    /// This method clears any cached frames and resets the decoder state.
620    /// The decoder is ready to receive new packets after flushing.
621    pub fn flush(&mut self) {
622        self.inner.flush();
623    }
624}
625
626/// Iterator over decoded audio frames.
627///
628/// Created by calling [`AudioDecoder::frames()`]. Yields frames until the end
629/// of the stream is reached or an error occurs.
630pub struct AudioFrameIterator<'a> {
631    decoder: &'a mut AudioDecoder,
632}
633
634impl Iterator for AudioFrameIterator<'_> {
635    type Item = Result<AudioFrame, DecodeError>;
636
637    fn next(&mut self) -> Option<Self::Item> {
638        match self.decoder.decode_one() {
639            Ok(Some(frame)) => Some(Ok(frame)),
640            Ok(None) => None, // EOF
641            Err(e) => Some(Err(e)),
642        }
643    }
644}
645
646#[cfg(test)]
647mod tests {
648    use super::*;
649    use std::path::PathBuf;
650
651    #[test]
652    fn test_builder_default_values() {
653        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3"));
654
655        assert_eq!(builder.path(), Path::new("test.mp3"));
656        assert!(builder.get_output_format().is_none());
657        assert!(builder.get_output_sample_rate().is_none());
658        assert!(builder.get_output_channels().is_none());
659    }
660
661    #[test]
662    fn test_builder_output_format() {
663        let builder =
664            AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_format(SampleFormat::F32);
665
666        assert_eq!(builder.get_output_format(), Some(SampleFormat::F32));
667    }
668
669    #[test]
670    fn test_builder_output_sample_rate() {
671        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_sample_rate(48000);
672
673        assert_eq!(builder.get_output_sample_rate(), Some(48000));
674    }
675
676    #[test]
677    fn test_builder_output_channels() {
678        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_channels(2);
679
680        assert_eq!(builder.get_output_channels(), Some(2));
681    }
682
683    #[test]
684    fn test_builder_chaining() {
685        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3"))
686            .output_format(SampleFormat::F32)
687            .output_sample_rate(48000)
688            .output_channels(2);
689
690        assert_eq!(builder.get_output_format(), Some(SampleFormat::F32));
691        assert_eq!(builder.get_output_sample_rate(), Some(48000));
692        assert_eq!(builder.get_output_channels(), Some(2));
693    }
694
695    #[test]
696    fn test_decoder_open() {
697        let builder = AudioDecoder::open("audio.mp3");
698        assert_eq!(builder.path(), Path::new("audio.mp3"));
699    }
700
701    #[test]
702    fn test_build_file_not_found() {
703        let result = AudioDecoder::open("nonexistent_file_12345.mp3").build();
704
705        assert!(result.is_err());
706        match result {
707            Err(DecodeError::FileNotFound { path }) => {
708                assert!(
709                    path.to_string_lossy()
710                        .contains("nonexistent_file_12345.mp3")
711                );
712            }
713            Err(e) => panic!("Expected FileNotFound error, got: {e:?}"),
714            Ok(_) => panic!("Expected error, got Ok"),
715        }
716    }
717
718    #[test]
719    fn test_decoder_config_default() {
720        let config = AudioDecoderConfig::default();
721
722        assert!(config.output_format.is_none());
723        assert!(config.output_sample_rate.is_none());
724        assert!(config.output_channels.is_none());
725    }
726}