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}