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) -> impl Iterator<Item = Result<AudioFrame, DecodeError>> + '_ {
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(crate) 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}