Skip to main content

ff_encode/audio/
codec_options.rs

1//! Per-codec encoding options for [`AudioEncoderBuilder`](super::builder::AudioEncoderBuilder).
2//!
3//! Pass an [`AudioCodecOptions`] value to
4//! `AudioEncoderBuilder::codec_options()` to control codec-specific behaviour.
5//! Options are applied via `av_opt_set` / direct field assignment **before**
6//! `avcodec_open2`.  Any option that the chosen encoder does not support is
7//! logged as a warning and skipped — it never causes `build()` to return an
8//! error.
9
10/// Per-codec encoding options for audio.
11///
12/// The variant must match the codec passed to
13/// `AudioEncoderBuilder::audio_codec()`.  A mismatch is silently ignored
14/// (the options are not applied).
15#[derive(Debug, Clone)]
16pub enum AudioCodecOptions {
17    /// Opus (libopus) encoding options.
18    Opus(OpusOptions),
19    /// AAC encoding options.
20    Aac(AacOptions),
21    /// MP3 (libmp3lame) encoding options.
22    Mp3(Mp3Options),
23    /// FLAC encoding options.
24    Flac(FlacOptions),
25}
26
27// ── Opus ──────────────────────────────────────────────────────────────────────
28
29/// Opus (libopus) per-codec options.
30#[derive(Debug, Clone)]
31pub struct OpusOptions {
32    /// Encoder application mode, optimised for the content type.
33    pub application: OpusApplication,
34    /// Frame duration in milliseconds.
35    ///
36    /// Must be one of `2`, `5`, `10`, `20`, `40`, or `60`.
37    /// `None` uses the libopus default (20 ms).
38    /// `build()` returns [`EncodeError::InvalidOption`](crate::EncodeError::InvalidOption)
39    /// if the value is not in the allowed set.
40    pub frame_duration_ms: Option<u32>,
41}
42
43impl Default for OpusOptions {
44    fn default() -> Self {
45        Self {
46            application: OpusApplication::Audio,
47            frame_duration_ms: None,
48        }
49    }
50}
51
52/// Opus encoder application mode.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
54pub enum OpusApplication {
55    /// Optimised for general audio (music, speech mix). Default.
56    #[default]
57    Audio,
58    /// Optimised for VoIP / speech clarity at low bitrates.
59    Voip,
60    /// Minimum latency mode — disables lookahead.
61    LowDelay,
62}
63
64impl OpusApplication {
65    pub(super) fn as_str(self) -> &'static str {
66        match self {
67            Self::Audio => "audio",
68            Self::Voip => "voip",
69            Self::LowDelay => "lowdelay",
70        }
71    }
72}
73
74// ── AAC ───────────────────────────────────────────────────────────────────────
75
76/// AAC per-codec options.
77#[derive(Debug, Clone)]
78pub struct AacOptions {
79    /// AAC encoding profile.
80    ///
81    /// `He` and `Hev2` typically require `libfdk_aac` (non-free). The built-in
82    /// FFmpeg `aac` encoder may not support them — the failure is logged as a
83    /// warning and encoding continues with the encoder's default profile.
84    pub profile: AacProfile,
85    /// VBR quality mode (1–5). `Some(q)` enables VBR; `None` uses CBR.
86    ///
87    /// Only supported by `libfdk_aac`. The built-in `aac` encoder ignores this
88    /// option (logged as a warning). `build()` returns
89    /// [`EncodeError::InvalidOption`](crate::EncodeError::InvalidOption) if the
90    /// value is outside 1–5.
91    pub vbr_quality: Option<u8>,
92}
93
94impl Default for AacOptions {
95    fn default() -> Self {
96        Self {
97            profile: AacProfile::Lc,
98            vbr_quality: None,
99        }
100    }
101}
102
103/// AAC encoding profile.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
105pub enum AacProfile {
106    /// AAC-LC (Low Complexity) — widest compatibility. Default.
107    #[default]
108    Lc,
109    /// HE-AAC v1 — with Spectral Band Replication (SBR). Typically requires `libfdk_aac`.
110    He,
111    /// HE-AAC v2 — with SBR + Parametric Stereo (PS). Typically requires `libfdk_aac`.
112    Hev2,
113}
114
115impl AacProfile {
116    pub(super) fn as_str(self) -> &'static str {
117        match self {
118            Self::Lc => "aac_low",
119            Self::He => "aac_he",
120            Self::Hev2 => "aac_he_v2",
121        }
122    }
123}
124
125// ── MP3 ───────────────────────────────────────────────────────────────────────
126
127/// MP3 quality mode: VBR quality scale or CBR fixed bitrate.
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum Mp3Quality {
130    /// Variable bitrate — libmp3lame `q` scale (0 = best / V0, 9 = worst / V9).
131    ///
132    /// `build()` returns [`EncodeError::InvalidOption`](crate::EncodeError::InvalidOption)
133    /// if the value exceeds 9.
134    Vbr(u8),
135    /// Constant bitrate in bits/sec (e.g. `128_000` for 128 kbps).
136    Cbr(u32),
137}
138
139/// MP3 (libmp3lame) per-codec options.
140#[derive(Debug, Clone)]
141pub struct Mp3Options {
142    /// VBR quality or CBR bitrate selection.
143    pub quality: Mp3Quality,
144}
145
146impl Default for Mp3Options {
147    fn default() -> Self {
148        Self {
149            quality: Mp3Quality::Vbr(4),
150        }
151    }
152}
153
154// ── FLAC ──────────────────────────────────────────────────────────────────────
155
156/// FLAC per-codec options.
157#[derive(Debug, Clone)]
158pub struct FlacOptions {
159    /// Compression level (0–12). `0` = fastest / largest, `12` = slowest / smallest.
160    pub compression_level: u8,
161}
162
163impl Default for FlacOptions {
164    fn default() -> Self {
165        Self {
166            compression_level: 5,
167        }
168    }
169}
170
171// ── Tests ─────────────────────────────────────────────────────────────────────
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn opus_application_should_return_correct_str() {
179        assert_eq!(OpusApplication::Audio.as_str(), "audio");
180        assert_eq!(OpusApplication::Voip.as_str(), "voip");
181        assert_eq!(OpusApplication::LowDelay.as_str(), "lowdelay");
182    }
183
184    #[test]
185    fn opus_options_default_should_have_audio_application_and_no_frame_duration() {
186        let opts = OpusOptions::default();
187        assert_eq!(opts.application, OpusApplication::Audio);
188        assert!(opts.frame_duration_ms.is_none());
189    }
190
191    #[test]
192    fn aac_profile_should_return_correct_str() {
193        assert_eq!(AacProfile::Lc.as_str(), "aac_low");
194        assert_eq!(AacProfile::He.as_str(), "aac_he");
195        assert_eq!(AacProfile::Hev2.as_str(), "aac_he_v2");
196    }
197
198    #[test]
199    fn aac_options_default_should_have_lc_profile_and_no_vbr() {
200        let opts = AacOptions::default();
201        assert_eq!(opts.profile, AacProfile::Lc);
202        assert!(opts.vbr_quality.is_none());
203    }
204
205    #[test]
206    fn mp3_options_default_should_have_vbr_quality_4() {
207        let opts = Mp3Options::default();
208        assert_eq!(opts.quality, Mp3Quality::Vbr(4));
209    }
210
211    #[test]
212    fn mp3_quality_enum_variants_are_accessible() {
213        let _vbr = Mp3Quality::Vbr(0);
214        let _cbr = Mp3Quality::Cbr(192_000);
215    }
216
217    #[test]
218    fn flac_options_default_should_have_compression_level_5() {
219        let opts = FlacOptions::default();
220        assert_eq!(opts.compression_level, 5);
221    }
222
223    #[test]
224    fn audio_codec_options_enum_variants_are_accessible() {
225        let _opus = AudioCodecOptions::Opus(OpusOptions::default());
226        let _aac = AudioCodecOptions::Aac(AacOptions::default());
227        let _mp3 = AudioCodecOptions::Mp3(Mp3Options::default());
228        let _flac = AudioCodecOptions::Flac(FlacOptions::default());
229    }
230}