Skip to main content

codec_core/codecs/
mod.rs

1//! # Audio Codec Implementations
2//!
3//! This module contains G.711 audio codec implementation for `VoIP` applications.
4//!
5//! ## Available Codecs
6//!
7//! ### G.711 (PCMU/PCMA) - [`g711`]
8//! - **Standard**: ITU-T G.711
9//! - **Sample Rate**: 8 kHz
10//! - **Bitrate**: 64 kbps
11//! - **Quality**: ~37 dB SNR
12//! - **Use Case**: Standard telephony
13//! - **Variants**: μ-law (PCMU), A-law (PCMA)
14//!
15//! ## Testing
16//!
17//! G.711 is validated with real speech samples through WAV roundtrip tests:
18//! - Downloads reference audio samples
19//! - Round-trip encoding and decoding validation
20//! - Signal-to-Noise Ratio (SNR) measurement
21//!
22//! ## Usage Examples
23//!
24//! ### Using the Codec Factory
25//! ```rust
26//! # #[cfg(feature = "g711")]
27//! # {
28//! use codec_core::codecs::CodecFactory;
29//! use codec_core::types::{CodecConfig, CodecType, SampleRate};
30//!
31//! // Create any codec through the factory
32//! let config = CodecConfig::new(CodecType::G711Pcmu)
33//!     .with_sample_rate(SampleRate::Rate8000);
34//! let mut codec = CodecFactory::create(config)?;
35//!
36//! // Use unified interface
37//! let samples = vec![0i16; 160];
38//! let encoded = codec.encode(&samples)?;
39//! let decoded = codec.decode(&encoded)?;
40//! # }
41//! # Ok::<(), Box<dyn std::error::Error>>(())
42//! ```
43//!
44//! ### Direct Codec Access
45//! ```rust
46//! # #[cfg(feature = "g711")]
47//! # {
48//! use codec_core::codecs::g711::{G711Codec, G711Variant};
49//!
50//! // Direct instantiation
51//! let mut g711_ulaw = G711Codec::new(G711Variant::MuLaw);
52//! let mut g711_alaw = G711Codec::new(G711Variant::ALaw);
53//! # }
54//! # Ok::<(), Box<dyn std::error::Error>>(())
55//! ```
56//!
57//! ## Testing & Validation
58//!
59//! All codecs include comprehensive test suites:
60//! - ITU-T compliance validation
61//! - Real audio roundtrip tests
62//! - Performance benchmarks
63//! - Quality measurements (SNR)
64//!
65//! ```bash
66//! # Test all codecs
67//! cargo test
68//!
69//! # Test with real audio (downloads speech samples)
70//! cargo test wav_roundtrip_test -- --nocapture
71//! ```
72
73use crate::error::{CodecError, Result};
74use crate::types::{AudioCodec, CodecConfig, CodecInfo, CodecType};
75use std::collections::HashMap;
76
77// Codec implementations
78#[cfg(feature = "g711")]
79pub mod g711;
80
81#[cfg(feature = "g729")]
82pub mod g729;
83
84#[cfg(feature = "opus")]
85pub mod opus;
86
87#[cfg(any(feature = "amr-nb", feature = "amr-wb"))]
88pub mod amr;
89
90/// Codec factory for creating codec instances
91pub struct CodecFactory;
92
93impl CodecFactory {
94    /// Create a codec instance from configuration
95    ///
96    /// # Errors
97    ///
98    /// Returns an error when the configuration is invalid, the codec feature
99    /// is disabled, or codec construction fails.
100    // Preserve the public by-value constructor in a build where every codec
101    // branch is compiled out; feature-enabled branches transfer ownership to
102    // their concrete codec constructors.
103    #[cfg_attr(
104        not(any(feature = "g711", feature = "g729", feature = "opus")),
105        allow(clippy::needless_pass_by_value)
106    )]
107    pub fn create(config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
108        // Validate configuration first
109        config.validate()?;
110
111        match config.codec_type {
112            #[cfg(feature = "g711")]
113            CodecType::G711Pcmu => {
114                let codec = g711::G711Codec::new_pcmu(config)?;
115                Ok(Box::new(codec))
116            }
117
118            #[cfg(feature = "g711")]
119            CodecType::G711Pcma => {
120                let codec = g711::G711Codec::new_pcma(config)?;
121                Ok(Box::new(codec))
122            }
123
124            #[cfg(feature = "g729")]
125            CodecType::G729 | CodecType::G729A | CodecType::G729BA => {
126                let codec = g729::G729Codec::new(config)?;
127                Ok(Box::new(codec))
128            }
129
130            #[cfg(feature = "opus")]
131            CodecType::Opus => {
132                let codec = opus::OpusCodec::new(config)?;
133                Ok(Box::new(codec))
134            }
135
136            #[cfg(feature = "amr-nb")]
137            CodecType::AmrNb => {
138                let codec = amr::AmrCodec::new(&config)?;
139                Ok(Box::new(codec))
140            }
141
142            #[cfg(feature = "amr-wb")]
143            CodecType::AmrWb => {
144                let codec = amr::AmrCodec::new(&config)?;
145                Ok(Box::new(codec))
146            }
147
148            codec_type => Err(CodecError::feature_not_enabled(format!(
149                "Codec {codec_type} not enabled in build features"
150            ))),
151        }
152    }
153
154    /// Create a codec by name
155    ///
156    /// # Errors
157    ///
158    /// Returns an error when `name` is unknown, its feature is disabled, or
159    /// the configuration is invalid.
160    pub fn create_by_name(name: &str, config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
161        let codec_type = match normalize_codec_name(name).as_str() {
162            "PCMU" => CodecType::G711Pcmu,
163            "PCMA" => CodecType::G711Pcma,
164            "G729" => CodecType::G729,
165            "G729A" => CodecType::G729A,
166            "G729AB" | "G729BA" => CodecType::G729BA,
167            "OPUS" => CodecType::Opus,
168            "AMR" => CodecType::AmrNb,
169            "AMR-WB" | "AMRWB" => CodecType::AmrWb,
170            _ => return Err(CodecError::unsupported_codec(name)),
171        };
172
173        let config = CodecConfig {
174            codec_type,
175            ..config
176        };
177
178        Self::create(config)
179    }
180
181    /// Create a codec by RTP payload type
182    ///
183    /// # Errors
184    ///
185    /// Returns an error when the payload type is unknown, its codec feature is
186    /// disabled, or the configuration is invalid.
187    pub fn create_by_payload_type(
188        payload_type: u8,
189        config: CodecConfig,
190    ) -> Result<Box<dyn AudioCodec>> {
191        let codec_type = match payload_type {
192            0 => CodecType::G711Pcmu,
193            8 => CodecType::G711Pcma,
194            18 => CodecType::G729,
195
196            _ => return Err(CodecError::unsupported_codec(format!("PT{payload_type}"))),
197        };
198
199        let config = CodecConfig {
200            codec_type,
201            ..config
202        };
203
204        Self::create(config)
205    }
206
207    /// Get all supported codec names
208    #[must_use]
209    pub fn supported_codecs() -> Vec<&'static str> {
210        vec![
211            #[cfg(feature = "g711")]
212            "PCMU",
213            #[cfg(feature = "g711")]
214            "PCMA",
215            #[cfg(feature = "g729")]
216            "G729",
217            #[cfg(feature = "g729")]
218            "G729A",
219            #[cfg(feature = "g729")]
220            "G729BA",
221            #[cfg(feature = "opus")]
222            "OPUS",
223            #[cfg(feature = "amr-nb")]
224            "AMR",
225            #[cfg(feature = "amr-wb")]
226            "AMR-WB",
227        ]
228    }
229
230    /// Check if a codec is supported
231    #[must_use]
232    pub fn is_supported(name: &str) -> bool {
233        let normalized = normalize_codec_name(name);
234        match normalized.as_str() {
235            #[cfg(feature = "g711")]
236            "PCMU" | "PCMA" => true,
237            #[cfg(feature = "g729")]
238            "G729" | "G729A" | "G729AB" | "G729BA" => true,
239            #[cfg(feature = "opus")]
240            "OPUS" => true,
241            #[cfg(feature = "amr-nb")]
242            "AMR" => true,
243            #[cfg(feature = "amr-wb")]
244            "AMR-WB" | "AMRWB" => true,
245            _ => false,
246        }
247    }
248}
249
250fn normalize_codec_name(name: &str) -> String {
251    name.to_ascii_uppercase().replace('.', "")
252}
253
254/// Codec registry for managing multiple codec instances
255pub struct CodecRegistry {
256    codecs: HashMap<String, Box<dyn AudioCodec>>,
257}
258
259impl CodecRegistry {
260    /// Create a new empty registry
261    #[must_use]
262    pub fn new() -> Self {
263        Self {
264            codecs: HashMap::new(),
265        }
266    }
267
268    /// Register a codec with a name
269    pub fn register(&mut self, name: String, codec: Box<dyn AudioCodec>) {
270        self.codecs.insert(name, codec);
271    }
272
273    /// Get a codec by name
274    #[must_use]
275    pub fn get(&self, name: &str) -> Option<&dyn AudioCodec> {
276        self.codecs.get(name).map(std::convert::AsRef::as_ref)
277    }
278
279    /// Get a mutable codec by name
280    pub fn get_mut(&mut self, name: &str) -> Option<&mut Box<dyn AudioCodec>> {
281        self.codecs.get_mut(name)
282    }
283
284    /// Remove a codec by name
285    pub fn remove(&mut self, name: &str) -> Option<Box<dyn AudioCodec>> {
286        self.codecs.remove(name)
287    }
288
289    /// List all registered codec names
290    #[must_use]
291    pub fn list_codecs(&self) -> Vec<&String> {
292        self.codecs.keys().collect()
293    }
294
295    /// Get the count of registered codecs
296    #[must_use]
297    pub fn len(&self) -> usize {
298        self.codecs.len()
299    }
300
301    /// Check if the registry is empty
302    #[must_use]
303    pub fn is_empty(&self) -> bool {
304        self.codecs.is_empty()
305    }
306
307    /// Clear all registered codecs
308    pub fn clear(&mut self) {
309        self.codecs.clear();
310    }
311}
312
313impl Default for CodecRegistry {
314    fn default() -> Self {
315        Self::new()
316    }
317}
318
319/// Codec capability information
320#[derive(Debug, Clone)]
321pub struct CodecCapabilities {
322    /// Available codec types
323    pub codec_types: Vec<CodecType>,
324    /// Codec information
325    pub codec_info: HashMap<CodecType, CodecInfo>,
326}
327
328/// Register the AMR variants enabled by feature flags.
329///
330/// Split out of [`CodecCapabilities::get_all`] to keep that function within the
331/// workspace line limit as codecs accumulate.
332// Both parameters go unused in a build with neither AMR feature enabled.
333#[allow(unused_variables)]
334fn add_amr_capabilities(
335    codec_types: &mut Vec<CodecType>,
336    codec_info: &mut HashMap<CodecType, CodecInfo>,
337) {
338    #[cfg(feature = "amr-nb")]
339    {
340        codec_types.push(CodecType::AmrNb);
341        codec_info.insert(
342            CodecType::AmrNb,
343            CodecInfo {
344                name: "AMR",
345                sample_rate: 8000,
346                channels: 1,
347                bitrate: CodecType::AmrNb.default_bitrate(),
348                frame_size: 160,
349                payload_type: None,
350            },
351        );
352    }
353
354    #[cfg(feature = "amr-wb")]
355    {
356        codec_types.push(CodecType::AmrWb);
357        codec_info.insert(
358            CodecType::AmrWb,
359            CodecInfo {
360                name: "AMR-WB",
361                sample_rate: 16000,
362                channels: 1,
363                bitrate: CodecType::AmrWb.default_bitrate(),
364                frame_size: 320,
365                payload_type: None,
366            },
367        );
368    }
369}
370
371impl CodecCapabilities {
372    /// Get capabilities for all supported codecs
373    #[must_use]
374    pub fn get_all() -> Self {
375        // Both values are populated by feature-gated blocks. In a deliberately
376        // codec-free build they remain empty and therefore need no mutation.
377        #[allow(unused_mut)]
378        let mut codec_types = Vec::new();
379        #[allow(unused_mut)]
380        let mut codec_info = HashMap::new();
381
382        #[cfg(feature = "g711")]
383        {
384            codec_types.push(CodecType::G711Pcmu);
385            codec_types.push(CodecType::G711Pcma);
386
387            codec_info.insert(
388                CodecType::G711Pcmu,
389                CodecInfo {
390                    name: "PCMU",
391                    sample_rate: 8000,
392                    channels: 1,
393                    bitrate: 64000,
394                    frame_size: 160,
395                    payload_type: Some(0),
396                },
397            );
398
399            codec_info.insert(
400                CodecType::G711Pcma,
401                CodecInfo {
402                    name: "PCMA",
403                    sample_rate: 8000,
404                    channels: 1,
405                    bitrate: 64000,
406                    frame_size: 160,
407                    payload_type: Some(8),
408                },
409            );
410        }
411
412        #[cfg(feature = "opus")]
413        {
414            codec_types.push(CodecType::Opus);
415            codec_info.insert(
416                CodecType::Opus,
417                CodecInfo {
418                    name: "opus",
419                    sample_rate: 48000,
420                    channels: 1,
421                    bitrate: 64000,
422                    frame_size: 960,
423                    payload_type: None,
424                },
425            );
426        }
427
428        add_amr_capabilities(&mut codec_types, &mut codec_info);
429
430        #[cfg(feature = "g729")]
431        {
432            codec_types.push(CodecType::G729);
433            codec_types.push(CodecType::G729A);
434            codec_types.push(CodecType::G729BA);
435
436            codec_info.insert(
437                CodecType::G729,
438                CodecInfo {
439                    name: "G729",
440                    sample_rate: 8000,
441                    channels: 1,
442                    bitrate: 8000,
443                    frame_size: 80,
444                    payload_type: Some(18),
445                },
446            );
447            codec_info.insert(
448                CodecType::G729A,
449                CodecInfo {
450                    name: "G729A",
451                    sample_rate: 8000,
452                    channels: 1,
453                    bitrate: 8000,
454                    frame_size: 80,
455                    payload_type: Some(18),
456                },
457            );
458            codec_info.insert(
459                CodecType::G729BA,
460                CodecInfo {
461                    name: "G729BA",
462                    sample_rate: 8000,
463                    channels: 1,
464                    bitrate: 8000,
465                    frame_size: 80,
466                    payload_type: Some(18),
467                },
468            );
469        }
470
471        Self {
472            codec_types,
473            codec_info,
474        }
475    }
476
477    /// Check if a codec type is supported
478    #[must_use]
479    pub fn is_supported(&self, codec_type: CodecType) -> bool {
480        self.codec_types.contains(&codec_type)
481    }
482
483    /// Get information for a specific codec type
484    #[must_use]
485    pub fn get_info(&self, codec_type: CodecType) -> Option<&CodecInfo> {
486        self.codec_info.get(&codec_type)
487    }
488}
489
490#[cfg(test)]
491mod tests {
492    use super::*;
493
494    #[test]
495    fn test_codec_factory_supported_codecs() {
496        let supported = CodecFactory::supported_codecs();
497
498        #[cfg(any(feature = "g711", feature = "g729", feature = "opus"))]
499        assert!(!supported.is_empty());
500
501        #[cfg(not(any(feature = "g711", feature = "g729", feature = "opus")))]
502        assert!(supported.is_empty());
503
504        #[cfg(feature = "g711")]
505        {
506            assert!(supported.contains(&"PCMU"));
507            assert!(supported.contains(&"PCMA"));
508        }
509    }
510
511    #[test]
512    fn test_codec_factory_is_supported() {
513        #[cfg(feature = "g711")]
514        {
515            assert!(CodecFactory::is_supported("PCMU"));
516            assert!(CodecFactory::is_supported("pcmu"));
517            assert!(CodecFactory::is_supported("PCMA"));
518        }
519
520        assert!(!CodecFactory::is_supported("UNSUPPORTED"));
521        assert!(!CodecFactory::is_supported("G722"));
522
523        #[cfg(feature = "opus")]
524        for name in ["opus", "Opus", "OPUS"] {
525            assert!(CodecFactory::is_supported(name));
526        }
527        #[cfg(not(feature = "opus"))]
528        for name in ["opus", "Opus", "OPUS"] {
529            assert!(!CodecFactory::is_supported(name));
530        }
531    }
532
533    #[test]
534    fn test_codec_registry() {
535        let mut registry = CodecRegistry::new();
536        assert!(registry.is_empty());
537        assert_eq!(registry.len(), 0);
538
539        #[cfg(feature = "g711")]
540        {
541            let config = CodecConfig::g711_pcmu();
542            let codec = CodecFactory::create(config).unwrap();
543            registry.register("test_pcmu".to_string(), codec);
544
545            assert_eq!(registry.len(), 1);
546            assert!(!registry.is_empty());
547            assert!(registry.get("test_pcmu").is_some());
548        }
549
550        registry.clear();
551        assert!(registry.is_empty());
552    }
553
554    #[test]
555    fn test_codec_capabilities() {
556        let caps = CodecCapabilities::get_all();
557
558        #[cfg(any(feature = "g711", feature = "g729", feature = "opus"))]
559        {
560            assert!(!caps.codec_types.is_empty());
561            assert!(!caps.codec_info.is_empty());
562        }
563
564        #[cfg(not(any(feature = "g711", feature = "g729", feature = "opus")))]
565        {
566            assert!(caps.codec_types.is_empty());
567            assert!(caps.codec_info.is_empty());
568        }
569
570        #[cfg(feature = "g711")]
571        {
572            assert!(caps.is_supported(CodecType::G711Pcmu));
573            assert!(caps.get_info(CodecType::G711Pcmu).is_some());
574        }
575    }
576
577    #[test]
578    #[cfg(feature = "g711")]
579    fn test_codec_creation() {
580        let config = CodecConfig::g711_pcmu();
581        let codec = CodecFactory::create(config);
582        assert!(codec.is_ok());
583
584        let codec = codec.unwrap();
585        let info = codec.info();
586        assert_eq!(info.name, "PCMU");
587        assert_eq!(info.sample_rate, 8000);
588    }
589
590    #[test]
591    #[cfg(feature = "g711")]
592    fn test_codec_creation_by_name() {
593        let config = CodecConfig::new(CodecType::G711Pcmu);
594        let codec = CodecFactory::create_by_name("PCMU", config.clone());
595        assert!(codec.is_ok());
596
597        let codec = CodecFactory::create_by_name("UNKNOWN", config);
598        assert!(codec.is_err());
599    }
600
601    #[test]
602    #[cfg(feature = "g711")]
603    fn test_codec_creation_by_payload_type() {
604        let config = CodecConfig::new(CodecType::G711Pcmu);
605        let codec = CodecFactory::create_by_payload_type(0, config.clone());
606        assert!(codec.is_ok());
607
608        let codec = CodecFactory::create_by_payload_type(255, config);
609        assert!(codec.is_err());
610    }
611}