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}