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, ContainerInfo, 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 result in &mut decoder {
207    ///     let frame = result?;
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, container_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            container_info,
240            fused: false,
241        })
242    }
243}
244
245/// An audio decoder for extracting audio frames from media files.
246///
247/// The decoder provides frame-by-frame access to audio content with support
248/// for resampling and format conversion.
249///
250/// # Construction
251///
252/// Use [`AudioDecoder::open()`] to create a builder, then call [`AudioDecoderBuilder::build()`]:
253///
254/// ```ignore
255/// use ff_decode::AudioDecoder;
256/// use ff_format::SampleFormat;
257///
258/// let decoder = AudioDecoder::open("audio.mp3")?
259///     .output_format(SampleFormat::F32)
260///     .output_sample_rate(48000)
261///     .build()?;
262/// ```
263///
264/// # Frame Decoding
265///
266/// Frames can be decoded one at a time or using an iterator:
267///
268/// ```ignore
269/// // Decode one frame
270/// if let Some(frame) = decoder.decode_one()? {
271///     println!("Frame with {} samples", frame.samples());
272/// }
273///
274/// // Iterator form — AudioDecoder implements Iterator directly
275/// for result in &mut decoder {
276///     let frame = result?;
277///     // Process frame...
278/// }
279/// ```
280///
281/// # Seeking
282///
283/// The decoder supports seeking to specific positions:
284///
285/// ```ignore
286/// use std::time::Duration;
287///
288/// // Seek to 30 seconds
289/// decoder.seek(Duration::from_secs(30))?;
290/// ```
291pub struct AudioDecoder {
292    /// Path to the media file
293    path: PathBuf,
294    /// Decoder configuration
295    #[allow(dead_code)]
296    config: AudioDecoderConfig,
297    /// Internal decoder state
298    inner: AudioDecoderInner,
299    /// Audio stream information
300    stream_info: AudioStreamInfo,
301    /// Container-level metadata
302    container_info: ContainerInfo,
303    /// Set to `true` after a decoding error; causes [`Iterator::next`] to return `None`.
304    fused: bool,
305}
306
307impl AudioDecoder {
308    /// Opens a media file and returns a builder for configuring the decoder.
309    ///
310    /// This is the entry point for creating a decoder. The returned builder
311    /// allows setting options before the decoder is fully initialized.
312    ///
313    /// # Arguments
314    ///
315    /// * `path` - Path to the media file to decode.
316    ///
317    /// # Examples
318    ///
319    /// ```ignore
320    /// use ff_decode::AudioDecoder;
321    ///
322    /// // Simple usage
323    /// let decoder = AudioDecoder::open("audio.mp3")?
324    ///     .build()?;
325    ///
326    /// // With options
327    /// let decoder = AudioDecoder::open("audio.mp3")?
328    ///     .output_format(SampleFormat::F32)
329    ///     .output_sample_rate(48000)
330    ///     .build()?;
331    /// ```
332    ///
333    /// # Note
334    ///
335    /// This method does not validate that the file exists or is a valid
336    /// media file. Validation occurs when [`AudioDecoderBuilder::build()`] is called.
337    pub fn open(path: impl AsRef<Path>) -> AudioDecoderBuilder {
338        AudioDecoderBuilder::new(path.as_ref().to_path_buf())
339    }
340
341    // =========================================================================
342    // Information Methods
343    // =========================================================================
344
345    /// Returns the audio stream information.
346    ///
347    /// This contains metadata about the audio stream including sample rate,
348    /// channel count, codec, and format characteristics.
349    #[must_use]
350    pub fn stream_info(&self) -> &AudioStreamInfo {
351        &self.stream_info
352    }
353
354    /// Returns the sample rate in Hz.
355    #[must_use]
356    pub fn sample_rate(&self) -> u32 {
357        self.stream_info.sample_rate()
358    }
359
360    /// Returns the number of audio channels.
361    ///
362    /// The type is `u32` to match `FFmpeg` and professional audio APIs. When
363    /// integrating with `rodio` or `cpal` (which require `u16`), cast with
364    /// `decoder.channels() as u16` — channel counts never exceed `u16::MAX`
365    /// in practice.
366    #[must_use]
367    pub fn channels(&self) -> u32 {
368        self.stream_info.channels()
369    }
370
371    /// Returns the total duration of the audio.
372    ///
373    /// Returns [`Duration::ZERO`] if duration is unknown.
374    #[must_use]
375    pub fn duration(&self) -> Duration {
376        self.stream_info.duration().unwrap_or(Duration::ZERO)
377    }
378
379    /// Returns the total duration of the audio, or `None` for live streams
380    /// or formats that do not carry duration information.
381    #[must_use]
382    pub fn duration_opt(&self) -> Option<Duration> {
383        self.stream_info.duration()
384    }
385
386    /// Returns container-level metadata (format name, bitrate, stream count).
387    #[must_use]
388    pub fn container_info(&self) -> &ContainerInfo {
389        &self.container_info
390    }
391
392    /// Returns the current playback position.
393    #[must_use]
394    pub fn position(&self) -> Duration {
395        self.inner.position()
396    }
397
398    /// Returns `true` if the end of stream has been reached.
399    #[must_use]
400    pub fn is_eof(&self) -> bool {
401        self.inner.is_eof()
402    }
403
404    /// Returns the file path being decoded.
405    #[must_use]
406    pub fn path(&self) -> &Path {
407        &self.path
408    }
409
410    // =========================================================================
411    // Decoding Methods
412    // =========================================================================
413
414    /// Decodes the next audio frame.
415    ///
416    /// This method reads and decodes a single frame from the audio stream.
417    ///
418    /// # Returns
419    ///
420    /// - `Ok(Some(frame))` - A frame was successfully decoded
421    /// - `Ok(None)` - End of stream reached, no more frames
422    /// - `Err(_)` - An error occurred during decoding
423    ///
424    /// # Errors
425    ///
426    /// Returns [`DecodeError`] if:
427    /// - Reading from the file fails
428    /// - Decoding the frame fails
429    /// - Sample format conversion fails
430    ///
431    /// # Examples
432    ///
433    /// ```ignore
434    /// use ff_decode::AudioDecoder;
435    ///
436    /// let mut decoder = AudioDecoder::open("audio.mp3")?.build()?;
437    ///
438    /// while let Some(frame) = decoder.decode_one()? {
439    ///     println!("Frame with {} samples", frame.samples());
440    ///     // Process frame...
441    /// }
442    /// ```
443    pub fn decode_one(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
444        self.inner.decode_one()
445    }
446
447    /// Decodes all frames and returns their raw PCM data.
448    ///
449    /// This method decodes the entire audio file and returns all samples
450    /// as a contiguous byte buffer.
451    ///
452    /// # Performance
453    ///
454    /// - Memory scales with audio duration and quality
455    /// - For 10 minutes of stereo 48kHz F32 audio: ~110 MB
456    ///
457    /// # Returns
458    ///
459    /// A byte vector containing all audio samples in the configured output format.
460    ///
461    /// # Errors
462    ///
463    /// Returns [`DecodeError`] if:
464    /// - Decoding any frame fails
465    /// - The file cannot be read
466    ///
467    /// # Examples
468    ///
469    /// ```ignore
470    /// use ff_decode::AudioDecoder;
471    /// use ff_format::SampleFormat;
472    ///
473    /// let mut decoder = AudioDecoder::open("audio.mp3")?
474    ///     .output_format(SampleFormat::F32)
475    ///     .build()?;
476    ///
477    /// let samples = decoder.decode_all()?;
478    /// println!("Decoded {} bytes", samples.len());
479    /// ```
480    ///
481    /// # Memory Usage
482    ///
483    /// Stereo 48kHz F32 audio:
484    /// - 1 minute: ~11 MB
485    /// - 5 minutes: ~55 MB
486    /// - 10 minutes: ~110 MB
487    pub fn decode_all(&mut self) -> Result<Vec<u8>, DecodeError> {
488        let mut buffer = Vec::new();
489
490        while let Some(frame) = self.decode_one()? {
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        while let Some(frame) = self.decode_one()? {
555            let frame_time = frame.timestamp().as_duration();
556
557            // Stop if we've passed the end of the range
558            if frame_time >= end {
559                break;
560            }
561
562            // Only collect frames within the range
563            if frame_time >= start {
564                for plane in frame.planes() {
565                    buffer.extend_from_slice(plane);
566                }
567            }
568        }
569
570        Ok(buffer)
571    }
572
573    // =========================================================================
574    // Seeking Methods
575    // =========================================================================
576
577    /// Seeks to a specified position in the audio stream.
578    ///
579    /// This method performs efficient seeking without reopening the file.
580    ///
581    /// # Arguments
582    ///
583    /// * `position` - Target position to seek to.
584    /// * `mode` - Seek mode (Keyframe, Exact, or Backward).
585    ///
586    /// # Errors
587    ///
588    /// Returns [`DecodeError::SeekFailed`] if:
589    /// - The target position is beyond the audio duration
590    /// - The file format doesn't support seeking
591    /// - The seek operation fails internally
592    ///
593    /// # Examples
594    ///
595    /// ```ignore
596    /// use ff_decode::{AudioDecoder, SeekMode};
597    /// use std::time::Duration;
598    ///
599    /// let mut decoder = AudioDecoder::open("audio.mp3")?.build()?;
600    ///
601    /// // Seek to 30 seconds with keyframe mode (fast)
602    /// decoder.seek(Duration::from_secs(30), SeekMode::Keyframe)?;
603    ///
604    /// // Seek to exact position (slower but precise)
605    /// decoder.seek(Duration::from_secs(45), SeekMode::Exact)?;
606    ///
607    /// // Decode next frame
608    /// if let Some(frame) = decoder.decode_one()? {
609    ///     println!("Frame at {:?}", frame.timestamp().as_duration());
610    /// }
611    /// ```
612    pub fn seek(&mut self, position: Duration, mode: crate::SeekMode) -> Result<(), DecodeError> {
613        self.inner.seek(position, mode)
614    }
615
616    /// Flushes the decoder's internal buffers.
617    ///
618    /// This method clears any cached frames and resets the decoder state.
619    /// The decoder is ready to receive new packets after flushing.
620    pub fn flush(&mut self) {
621        self.inner.flush();
622    }
623}
624
625impl Iterator for AudioDecoder {
626    type Item = Result<AudioFrame, DecodeError>;
627
628    fn next(&mut self) -> Option<Self::Item> {
629        if self.fused {
630            return None;
631        }
632        match self.decode_one() {
633            Ok(Some(frame)) => Some(Ok(frame)),
634            Ok(None) => None,
635            Err(e) => {
636                self.fused = true;
637                Some(Err(e))
638            }
639        }
640    }
641}
642
643impl std::iter::FusedIterator for AudioDecoder {}
644
645#[cfg(test)]
646mod tests {
647    use super::*;
648    use std::path::PathBuf;
649
650    #[test]
651    fn test_builder_default_values() {
652        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3"));
653
654        assert_eq!(builder.path(), Path::new("test.mp3"));
655        assert!(builder.get_output_format().is_none());
656        assert!(builder.get_output_sample_rate().is_none());
657        assert!(builder.get_output_channels().is_none());
658    }
659
660    #[test]
661    fn test_builder_output_format() {
662        let builder =
663            AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_format(SampleFormat::F32);
664
665        assert_eq!(builder.get_output_format(), Some(SampleFormat::F32));
666    }
667
668    #[test]
669    fn test_builder_output_sample_rate() {
670        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_sample_rate(48000);
671
672        assert_eq!(builder.get_output_sample_rate(), Some(48000));
673    }
674
675    #[test]
676    fn test_builder_output_channels() {
677        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3")).output_channels(2);
678
679        assert_eq!(builder.get_output_channels(), Some(2));
680    }
681
682    #[test]
683    fn test_builder_chaining() {
684        let builder = AudioDecoderBuilder::new(PathBuf::from("test.mp3"))
685            .output_format(SampleFormat::F32)
686            .output_sample_rate(48000)
687            .output_channels(2);
688
689        assert_eq!(builder.get_output_format(), Some(SampleFormat::F32));
690        assert_eq!(builder.get_output_sample_rate(), Some(48000));
691        assert_eq!(builder.get_output_channels(), Some(2));
692    }
693
694    #[test]
695    fn test_decoder_open() {
696        let builder = AudioDecoder::open("audio.mp3");
697        assert_eq!(builder.path(), Path::new("audio.mp3"));
698    }
699
700    #[test]
701    fn test_build_file_not_found() {
702        let result = AudioDecoder::open("nonexistent_file_12345.mp3").build();
703
704        assert!(result.is_err());
705        match result {
706            Err(DecodeError::FileNotFound { path }) => {
707                assert!(
708                    path.to_string_lossy()
709                        .contains("nonexistent_file_12345.mp3")
710                );
711            }
712            Err(e) => panic!("Expected FileNotFound error, got: {e:?}"),
713            Ok(_) => panic!("Expected error, got Ok"),
714        }
715    }
716
717    #[test]
718    fn test_decoder_config_default() {
719        let config = AudioDecoderConfig::default();
720
721        assert!(config.output_format.is_none());
722        assert!(config.output_sample_rate.is_none());
723        assert!(config.output_channels.is_none());
724    }
725}