Skip to main content

oximedia_transcode/
codec_config.rs

1//! Codec-specific configuration and optimization.
2
3use crate::{QualityMode, QualityPreset, RateControlMode, TuneMode};
4use serde::{Deserialize, Serialize};
5
6/// Codec-specific configuration.
7#[derive(Debug, Clone)]
8pub struct CodecConfig {
9    /// Codec name.
10    pub codec: String,
11    /// Preset (speed/quality tradeoff).
12    pub preset: QualityPreset,
13    /// Tune for specific content.
14    pub tune: Option<TuneMode>,
15    /// Profile.
16    pub profile: Option<String>,
17    /// Level.
18    pub level: Option<String>,
19    /// Rate control mode.
20    pub rate_control: RateControlMode,
21    /// Additional options.
22    pub options: Vec<(String, String)>,
23}
24
25impl CodecConfig {
26    /// Creates a new codec configuration.
27    #[must_use]
28    pub fn new(codec: impl Into<String>) -> Self {
29        Self {
30            codec: codec.into(),
31            preset: QualityPreset::Medium,
32            tune: None,
33            profile: None,
34            level: None,
35            rate_control: RateControlMode::Crf(23),
36            options: Vec::new(),
37        }
38    }
39
40    /// Sets the preset.
41    #[must_use]
42    pub fn preset(mut self, preset: QualityPreset) -> Self {
43        self.preset = preset;
44        self
45    }
46
47    /// Sets the tune mode.
48    #[must_use]
49    pub fn tune(mut self, tune: TuneMode) -> Self {
50        self.tune = Some(tune);
51        self
52    }
53
54    /// Sets the profile.
55    #[must_use]
56    pub fn profile(mut self, profile: impl Into<String>) -> Self {
57        self.profile = Some(profile.into());
58        self
59    }
60
61    /// Sets the level.
62    #[must_use]
63    pub fn level(mut self, level: impl Into<String>) -> Self {
64        self.level = Some(level.into());
65        self
66    }
67
68    /// Sets the rate control mode.
69    #[must_use]
70    pub fn rate_control(mut self, mode: RateControlMode) -> Self {
71        self.rate_control = mode;
72        self
73    }
74
75    /// Adds a custom option.
76    #[must_use]
77    pub fn option(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
78        self.options.push((key.into(), value.into()));
79        self
80    }
81}
82
83/// H.264/AVC specific configuration.
84#[derive(Debug, Clone)]
85pub struct H264Config {
86    base: CodecConfig,
87}
88
89impl H264Config {
90    /// Creates a new H.264 configuration.
91    #[must_use]
92    pub fn new() -> Self {
93        Self {
94            base: CodecConfig::new("h264"),
95        }
96    }
97
98    /// Sets the profile (baseline, main, high, high10, high422, high444).
99    #[must_use]
100    pub fn profile(mut self, profile: H264Profile) -> Self {
101        self.base.profile = Some(profile.as_str().to_string());
102        self
103    }
104
105    /// Sets the level (e.g., "3.0", "4.0", "5.1").
106    #[must_use]
107    pub fn level(mut self, level: impl Into<String>) -> Self {
108        self.base.level = Some(level.into());
109        self
110    }
111
112    /// Enables cabac entropy coding.
113    #[must_use]
114    pub fn cabac(mut self, enable: bool) -> Self {
115        self.base.options.push((
116            "cabac".to_string(),
117            if enable { "1" } else { "0" }.to_string(),
118        ));
119        self
120    }
121
122    /// Sets the number of reference frames.
123    #[must_use]
124    pub fn refs(mut self, refs: u8) -> Self {
125        self.base
126            .options
127            .push(("refs".to_string(), refs.to_string()));
128        self
129    }
130
131    /// Sets the number of B-frames.
132    #[must_use]
133    pub fn bframes(mut self, bframes: u8) -> Self {
134        self.base
135            .options
136            .push(("bframes".to_string(), bframes.to_string()));
137        self
138    }
139
140    /// Enables 8x8 DCT.
141    #[must_use]
142    pub fn dct8x8(mut self, enable: bool) -> Self {
143        self.base.options.push((
144            "8x8dct".to_string(),
145            if enable { "1" } else { "0" }.to_string(),
146        ));
147        self
148    }
149
150    /// Sets the deblocking filter parameters.
151    #[must_use]
152    pub fn deblock(mut self, alpha: i8, beta: i8) -> Self {
153        self.base
154            .options
155            .push(("deblock".to_string(), format!("{alpha}:{beta}")));
156        self
157    }
158
159    /// Converts to base codec config.
160    #[must_use]
161    pub fn build(self) -> CodecConfig {
162        self.base
163    }
164}
165
166impl Default for H264Config {
167    fn default() -> Self {
168        Self::new()
169    }
170}
171
172/// H.264 profiles.
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub enum H264Profile {
175    /// Baseline profile.
176    Baseline,
177    /// Main profile.
178    Main,
179    /// High profile.
180    High,
181    /// High 10 profile (10-bit).
182    High10,
183    /// High 4:2:2 profile.
184    High422,
185    /// High 4:4:4 profile.
186    High444,
187}
188
189impl H264Profile {
190    #[must_use]
191    fn as_str(self) -> &'static str {
192        match self {
193            Self::Baseline => "baseline",
194            Self::Main => "main",
195            Self::High => "high",
196            Self::High10 => "high10",
197            Self::High422 => "high422",
198            Self::High444 => "high444",
199        }
200    }
201}
202
203/// VP9 specific configuration.
204#[derive(Debug, Clone)]
205pub struct Vp9Config {
206    base: CodecConfig,
207}
208
209impl Vp9Config {
210    /// Creates a new VP9 configuration.
211    #[must_use]
212    pub fn new() -> Self {
213        Self {
214            base: CodecConfig::new("vp9"),
215        }
216    }
217
218    /// Sets the CPU used (0-8, lower = slower/better).
219    #[must_use]
220    pub fn cpu_used(mut self, cpu_used: u8) -> Self {
221        self.base
222            .options
223            .push(("cpu-used".to_string(), cpu_used.to_string()));
224        self
225    }
226
227    /// Sets the tile columns (for parallel encoding).
228    #[must_use]
229    pub fn tile_columns(mut self, columns: u8) -> Self {
230        self.base
231            .options
232            .push(("tile-columns".to_string(), columns.to_string()));
233        self
234    }
235
236    /// Sets the tile rows (for parallel encoding).
237    #[must_use]
238    pub fn tile_rows(mut self, rows: u8) -> Self {
239        self.base
240            .options
241            .push(("tile-rows".to_string(), rows.to_string()));
242        self
243    }
244
245    /// Sets the frame parallel encoding.
246    #[must_use]
247    pub fn frame_parallel(mut self, enable: bool) -> Self {
248        self.base.options.push((
249            "frame-parallel".to_string(),
250            if enable { "1" } else { "0" }.to_string(),
251        ));
252        self
253    }
254
255    /// Sets the auto alt reference frames.
256    #[must_use]
257    pub fn auto_alt_ref(mut self, frames: u8) -> Self {
258        self.base
259            .options
260            .push(("auto-alt-ref".to_string(), frames.to_string()));
261        self
262    }
263
264    /// Sets the lag in frames.
265    #[must_use]
266    pub fn lag_in_frames(mut self, lag: u32) -> Self {
267        self.base
268            .options
269            .push(("lag-in-frames".to_string(), lag.to_string()));
270        self
271    }
272
273    /// Enables row-based multi-threading.
274    #[must_use]
275    pub fn row_mt(mut self, enable: bool) -> Self {
276        self.base.options.push((
277            "row-mt".to_string(),
278            if enable { "1" } else { "0" }.to_string(),
279        ));
280        self
281    }
282
283    /// Converts to base codec config.
284    #[must_use]
285    pub fn build(self) -> CodecConfig {
286        self.base
287    }
288}
289
290impl Default for Vp9Config {
291    fn default() -> Self {
292        Self::new()
293    }
294}
295
296/// AV1 specific configuration.
297#[derive(Debug, Clone)]
298pub struct Av1Config {
299    base: CodecConfig,
300}
301
302impl Av1Config {
303    /// Creates a new AV1 configuration.
304    #[must_use]
305    pub fn new() -> Self {
306        Self {
307            base: CodecConfig::new("av1"),
308        }
309    }
310
311    /// Sets the CPU used (0-8, lower = slower/better).
312    #[must_use]
313    pub fn cpu_used(mut self, cpu_used: u8) -> Self {
314        self.base
315            .options
316            .push(("cpu-used".to_string(), cpu_used.to_string()));
317        self
318    }
319
320    /// Sets the tile columns.
321    #[must_use]
322    pub fn tiles(mut self, columns: u8, rows: u8) -> Self {
323        self.base
324            .options
325            .push(("tiles".to_string(), format!("{columns}x{rows}")));
326        self
327    }
328
329    /// Enables row-based multi-threading.
330    #[must_use]
331    pub fn row_mt(mut self, enable: bool) -> Self {
332        self.base.options.push((
333            "row-mt".to_string(),
334            if enable { "1" } else { "0" }.to_string(),
335        ));
336        self
337    }
338
339    /// Sets the usage mode (good, realtime).
340    #[must_use]
341    pub fn usage(mut self, usage: Av1Usage) -> Self {
342        self.base
343            .options
344            .push(("usage".to_string(), usage.as_str().to_string()));
345        self
346    }
347
348    /// Enables film grain synthesis.
349    #[must_use]
350    pub fn enable_film_grain(mut self, enable: bool) -> Self {
351        self.base.options.push((
352            "enable-film-grain".to_string(),
353            if enable { "1" } else { "0" }.to_string(),
354        ));
355        self
356    }
357
358    /// Converts to base codec config.
359    #[must_use]
360    pub fn build(self) -> CodecConfig {
361        self.base
362    }
363}
364
365impl Default for Av1Config {
366    fn default() -> Self {
367        Self::new()
368    }
369}
370
371/// AV1 usage modes.
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373pub enum Av1Usage {
374    /// Good quality mode.
375    Good,
376    /// Real-time mode.
377    Realtime,
378}
379
380impl Av1Usage {
381    #[must_use]
382    fn as_str(self) -> &'static str {
383        match self {
384            Self::Good => "good",
385            Self::Realtime => "realtime",
386        }
387    }
388}
389
390/// Opus audio codec configuration.
391#[derive(Debug, Clone)]
392pub struct OpusConfig {
393    base: CodecConfig,
394}
395
396impl OpusConfig {
397    /// Creates a new Opus configuration.
398    #[must_use]
399    pub fn new() -> Self {
400        Self {
401            base: CodecConfig::new("opus"),
402        }
403    }
404
405    /// Sets the application type.
406    #[must_use]
407    pub fn application(mut self, app: OpusApplication) -> Self {
408        self.base
409            .options
410            .push(("application".to_string(), app.as_str().to_string()));
411        self
412    }
413
414    /// Sets the complexity (0-10).
415    #[must_use]
416    pub fn complexity(mut self, complexity: u8) -> Self {
417        self.base
418            .options
419            .push(("complexity".to_string(), complexity.to_string()));
420        self
421    }
422
423    /// Sets the frame duration in milliseconds.
424    #[must_use]
425    pub fn frame_duration(mut self, duration_ms: f32) -> Self {
426        self.base
427            .options
428            .push(("frame_duration".to_string(), duration_ms.to_string()));
429        self
430    }
431
432    /// Enables variable bitrate.
433    #[must_use]
434    pub fn vbr(mut self, enable: bool) -> Self {
435        self.base.options.push((
436            "vbr".to_string(),
437            if enable { "on" } else { "off" }.to_string(),
438        ));
439        self
440    }
441
442    /// Converts to base codec config.
443    #[must_use]
444    pub fn build(self) -> CodecConfig {
445        self.base
446    }
447}
448
449impl Default for OpusConfig {
450    fn default() -> Self {
451        Self::new()
452    }
453}
454
455/// Opus application types.
456#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
457pub enum OpusApplication {
458    /// Voice over IP.
459    Voip,
460    /// Audio streaming.
461    Audio,
462    /// Low delay.
463    LowDelay,
464}
465
466impl OpusApplication {
467    #[must_use]
468    fn as_str(self) -> &'static str {
469        match self {
470            Self::Voip => "voip",
471            Self::Audio => "audio",
472            Self::LowDelay => "lowdelay",
473        }
474    }
475}
476
477/// Creates codec configuration from quality mode.
478#[must_use]
479pub fn codec_config_from_quality(codec: &str, quality: QualityMode) -> CodecConfig {
480    let preset = quality.to_preset();
481    let crf = quality.to_crf();
482
483    match codec {
484        "h264" => H264Config::new()
485            .profile(H264Profile::High)
486            .refs(3)
487            .bframes(3)
488            .build()
489            .preset(preset)
490            .rate_control(RateControlMode::Crf(crf)),
491        "vp9" => Vp9Config::new()
492            .cpu_used(preset.cpu_used())
493            .row_mt(true)
494            .build()
495            .preset(preset)
496            .rate_control(RateControlMode::Crf(crf)),
497        "av1" => Av1Config::new()
498            .cpu_used(preset.cpu_used())
499            .row_mt(true)
500            .usage(Av1Usage::Good)
501            .build()
502            .preset(preset)
503            .rate_control(RateControlMode::Crf(crf)),
504        "opus" => OpusConfig::new()
505            .application(OpusApplication::Audio)
506            .complexity(10)
507            .vbr(true)
508            .build()
509            .preset(preset),
510        _ => CodecConfig::new(codec)
511            .preset(preset)
512            .rate_control(RateControlMode::Crf(crf)),
513    }
514}
515
516#[cfg(test)]
517mod tests {
518    use super::*;
519
520    #[test]
521    fn test_codec_config_new() {
522        let config = CodecConfig::new("h264");
523        assert_eq!(config.codec, "h264");
524        assert_eq!(config.preset, QualityPreset::Medium);
525    }
526
527    #[test]
528    fn test_h264_config() {
529        let config = H264Config::new()
530            .profile(H264Profile::High)
531            .level("4.0")
532            .refs(3)
533            .bframes(3)
534            .cabac(true)
535            .dct8x8(true)
536            .build();
537
538        assert_eq!(config.codec, "h264");
539        assert_eq!(config.profile, Some("high".to_string()));
540        assert_eq!(config.level, Some("4.0".to_string()));
541        assert!(config.options.len() > 0);
542    }
543
544    #[test]
545    fn test_vp9_config() {
546        let config = Vp9Config::new()
547            .cpu_used(4)
548            .tile_columns(2)
549            .tile_rows(1)
550            .row_mt(true)
551            .build();
552
553        assert_eq!(config.codec, "vp9");
554        assert!(config
555            .options
556            .iter()
557            .any(|(k, v)| k == "cpu-used" && v == "4"));
558        assert!(config
559            .options
560            .iter()
561            .any(|(k, v)| k == "row-mt" && v == "1"));
562    }
563
564    #[test]
565    fn test_av1_config() {
566        let config = Av1Config::new()
567            .cpu_used(6)
568            .tiles(4, 2)
569            .usage(Av1Usage::Good)
570            .row_mt(true)
571            .build();
572
573        assert_eq!(config.codec, "av1");
574        assert!(config
575            .options
576            .iter()
577            .any(|(k, v)| k == "cpu-used" && v == "6"));
578        assert!(config
579            .options
580            .iter()
581            .any(|(k, v)| k == "tiles" && v == "4x2"));
582    }
583
584    #[test]
585    fn test_opus_config() {
586        let config = OpusConfig::new()
587            .application(OpusApplication::Audio)
588            .complexity(10)
589            .vbr(true)
590            .build();
591
592        assert_eq!(config.codec, "opus");
593        assert!(config
594            .options
595            .iter()
596            .any(|(k, v)| k == "application" && v == "audio"));
597        assert!(config
598            .options
599            .iter()
600            .any(|(k, v)| k == "complexity" && v == "10"));
601    }
602
603    #[test]
604    fn test_codec_config_from_quality() {
605        let config = codec_config_from_quality("h264", QualityMode::High);
606        assert_eq!(config.codec, "h264");
607        assert_eq!(config.preset, QualityPreset::Slow);
608        assert_eq!(config.rate_control, RateControlMode::Crf(20));
609    }
610
611    #[test]
612    fn test_h264_profiles() {
613        assert_eq!(H264Profile::Baseline.as_str(), "baseline");
614        assert_eq!(H264Profile::Main.as_str(), "main");
615        assert_eq!(H264Profile::High.as_str(), "high");
616    }
617
618    #[test]
619    fn test_av1_usage() {
620        assert_eq!(Av1Usage::Good.as_str(), "good");
621        assert_eq!(Av1Usage::Realtime.as_str(), "realtime");
622    }
623
624    #[test]
625    fn test_opus_application() {
626        assert_eq!(OpusApplication::Voip.as_str(), "voip");
627        assert_eq!(OpusApplication::Audio.as_str(), "audio");
628        assert_eq!(OpusApplication::LowDelay.as_str(), "lowdelay");
629    }
630}