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}