Skip to main content

ff_encode/audio/
encoder.rs

1//! Audio encoder public API.
2//!
3//! This module provides the public interface for audio encoding operations.
4
5use crate::{EncodeError, EncoderBuilder};
6use ff_format::AudioFrame;
7use std::time::Instant;
8
9use super::encoder_inner::{AudioEncoderConfig, AudioEncoderInner};
10
11/// Audio encoder.
12///
13/// Encodes audio frames to a file using FFmpeg.
14///
15/// # Examples
16///
17/// ```no_run
18/// use ff_encode::{AudioEncoder, AudioCodec};
19/// use ff_format::{AudioFrame, SampleFormat};
20///
21/// let mut encoder = AudioEncoder::create("output.m4a")
22///     .expect("Failed to create encoder")
23///     .audio(48000, 2)
24///     .audio_codec(AudioCodec::Aac)
25///     .build_audio()
26///     .expect("Failed to build encoder");
27///
28/// // Create and push audio frames
29/// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32).unwrap();
30/// encoder.push(&frame).expect("Failed to push frame");
31///
32/// encoder.finish().expect("Failed to finish encoding");
33/// ```
34pub struct AudioEncoder {
35    inner: Option<AudioEncoderInner>,
36    _config: AudioEncoderConfig,
37    _start_time: Instant,
38}
39
40impl AudioEncoder {
41    /// Create a new encoder builder with the given output path.
42    ///
43    /// # Arguments
44    ///
45    /// * `path` - Output file path
46    ///
47    /// # Returns
48    ///
49    /// Returns an [`EncoderBuilder`] for configuring the encoder.
50    ///
51    /// # Examples
52    ///
53    /// ```ignore
54    /// use ff_encode::AudioEncoder;
55    ///
56    /// let builder = AudioEncoder::create("output.m4a")?;
57    /// ```
58    pub fn create<P: AsRef<std::path::Path>>(path: P) -> Result<EncoderBuilder, EncodeError> {
59        EncoderBuilder::new(path.as_ref().to_path_buf())
60    }
61
62    /// Create an encoder from a builder.
63    ///
64    /// This is called by [`EncoderBuilder::build()`] and should not be called directly.
65    pub(crate) fn from_builder(builder: EncoderBuilder) -> Result<Self, EncodeError> {
66        let config = AudioEncoderConfig {
67            path: builder.path.clone(),
68            sample_rate: builder
69                .audio_sample_rate
70                .ok_or_else(|| EncodeError::InvalidConfig {
71                    reason: "Audio sample rate not configured".to_string(),
72                })?,
73            channels: builder
74                .audio_channels
75                .ok_or_else(|| EncodeError::InvalidConfig {
76                    reason: "Audio channels not configured".to_string(),
77                })?,
78            codec: builder.audio_codec,
79            bitrate: builder.audio_bitrate,
80            _progress_callback: builder.progress_callback.is_some(),
81        };
82
83        let inner = Some(AudioEncoderInner::new(&config)?);
84
85        Ok(Self {
86            inner,
87            _config: config,
88            _start_time: Instant::now(),
89        })
90    }
91
92    /// Get the actual audio codec being used.
93    ///
94    /// Returns the name of the FFmpeg encoder (e.g., "aac", "libopus").
95    #[must_use]
96    pub fn actual_codec(&self) -> &str {
97        self.inner
98            .as_ref()
99            .map_or("", |inner| inner.actual_codec.as_str())
100    }
101
102    /// Push an audio frame for encoding.
103    ///
104    /// # Arguments
105    ///
106    /// * `frame` - The audio frame to encode
107    ///
108    /// # Errors
109    ///
110    /// Returns an error if encoding fails or the encoder is not initialized.
111    ///
112    /// # Examples
113    ///
114    /// ```ignore
115    /// use ff_encode::{AudioEncoder, AudioCodec};
116    /// use ff_format::{AudioFrame, SampleFormat};
117    ///
118    /// let mut encoder = AudioEncoder::create("output.m4a")?
119    ///     .audio(48000, 2)
120    ///     .audio_codec(AudioCodec::Aac)
121    ///     .build()?;
122    ///
123    /// let frame = AudioFrame::empty(1024, 2, 48000, SampleFormat::F32)?;
124    /// encoder.push(&frame)?;
125    /// ```
126    pub fn push(&mut self, frame: &AudioFrame) -> Result<(), EncodeError> {
127        let inner = self
128            .inner
129            .as_mut()
130            .ok_or_else(|| EncodeError::InvalidConfig {
131                reason: "Audio encoder not initialized".to_string(),
132            })?;
133
134        // SAFETY: inner is properly initialized and we have exclusive access
135        unsafe { inner.push_frame(frame)? };
136        Ok(())
137    }
138
139    /// Finish encoding and write the file trailer.
140    ///
141    /// This method must be called to properly finalize the output file.
142    /// It flushes any remaining encoded frames and writes the file trailer.
143    ///
144    /// # Errors
145    ///
146    /// Returns an error if finalizing fails.
147    pub fn finish(mut self) -> Result<(), EncodeError> {
148        if let Some(mut inner) = self.inner.take() {
149            // SAFETY: inner is properly initialized and we have exclusive access
150            unsafe { inner.finish()? };
151        }
152        Ok(())
153    }
154}
155
156impl Drop for AudioEncoder {
157    fn drop(&mut self) {
158        // AudioEncoderInner will handle cleanup in its Drop implementation
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_create_encoder_builder() {
168        let builder = AudioEncoder::create("output.m4a");
169        assert!(builder.is_ok());
170    }
171}