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/// Codec factory for creating codec instances
88pub struct CodecFactory;
89
90impl CodecFactory {
91    /// Create a codec instance from configuration
92    ///
93    /// # Errors
94    ///
95    /// Returns an error when the configuration is invalid, the codec feature
96    /// is disabled, or codec construction fails.
97    // Preserve the public by-value constructor in a build where every codec
98    // branch is compiled out; feature-enabled branches transfer ownership to
99    // their concrete codec constructors.
100    #[cfg_attr(
101        not(any(feature = "g711", feature = "g729", feature = "opus")),
102        allow(clippy::needless_pass_by_value)
103    )]
104    pub fn create(config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
105        // Validate configuration first
106        config.validate()?;
107
108        match config.codec_type {
109            #[cfg(feature = "g711")]
110            CodecType::G711Pcmu => {
111                let codec = g711::G711Codec::new_pcmu(config)?;
112                Ok(Box::new(codec))
113            }
114
115            #[cfg(feature = "g711")]
116            CodecType::G711Pcma => {
117                let codec = g711::G711Codec::new_pcma(config)?;
118                Ok(Box::new(codec))
119            }
120
121            #[cfg(feature = "g729")]
122            CodecType::G729 | CodecType::G729A | CodecType::G729BA => {
123                let codec = g729::G729Codec::new(config)?;
124                Ok(Box::new(codec))
125            }
126
127            #[cfg(feature = "opus")]
128            CodecType::Opus => {
129                let codec = opus::OpusCodec::new(config)?;
130                Ok(Box::new(codec))
131            }
132
133            codec_type => Err(CodecError::feature_not_enabled(format!(
134                "Codec {codec_type} not enabled in build features"
135            ))),
136        }
137    }
138
139    /// Create a codec by name
140    ///
141    /// # Errors
142    ///
143    /// Returns an error when `name` is unknown, its feature is disabled, or
144    /// the configuration is invalid.
145    pub fn create_by_name(name: &str, config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
146        let codec_type = match normalize_codec_name(name).as_str() {
147            "PCMU" => CodecType::G711Pcmu,
148            "PCMA" => CodecType::G711Pcma,
149            "G729" => CodecType::G729,
150            "G729A" => CodecType::G729A,
151            "G729AB" | "G729BA" => CodecType::G729BA,
152            "OPUS" => CodecType::Opus,
153            _ => return Err(CodecError::unsupported_codec(name)),
154        };
155
156        let config = CodecConfig {
157            codec_type,
158            ..config
159        };
160
161        Self::create(config)
162    }
163
164    /// Create a codec by RTP payload type
165    ///
166    /// # Errors
167    ///
168    /// Returns an error when the payload type is unknown, its codec feature is
169    /// disabled, or the configuration is invalid.
170    pub fn create_by_payload_type(
171        payload_type: u8,
172        config: CodecConfig,
173    ) -> Result<Box<dyn AudioCodec>> {
174        let codec_type = match payload_type {
175            0 => CodecType::G711Pcmu,
176            8 => CodecType::G711Pcma,
177            18 => CodecType::G729,
178
179            _ => return Err(CodecError::unsupported_codec(format!("PT{payload_type}"))),
180        };
181
182        let config = CodecConfig {
183            codec_type,
184            ..config
185        };
186
187        Self::create(config)
188    }
189
190    /// Get all supported codec names
191    #[must_use]
192    pub fn supported_codecs() -> Vec<&'static str> {
193        vec![
194            #[cfg(feature = "g711")]
195            "PCMU",
196            #[cfg(feature = "g711")]
197            "PCMA",
198            #[cfg(feature = "g729")]
199            "G729",
200            #[cfg(feature = "g729")]
201            "G729A",
202            #[cfg(feature = "g729")]
203            "G729BA",
204            #[cfg(feature = "opus")]
205            "OPUS",
206        ]
207    }
208
209    /// Check if a codec is supported
210    #[must_use]
211    pub fn is_supported(name: &str) -> bool {
212        let normalized = normalize_codec_name(name);
213        match normalized.as_str() {
214            #[cfg(feature = "g711")]
215            "PCMU" | "PCMA" => true,
216            #[cfg(feature = "g729")]
217            "G729" | "G729A" | "G729AB" | "G729BA" => true,
218            #[cfg(feature = "opus")]
219            "OPUS" => true,
220            _ => false,
221        }
222    }
223}
224
225fn normalize_codec_name(name: &str) -> String {
226    name.to_ascii_uppercase().replace('.', "")
227}
228
229/// Codec registry for managing multiple codec instances
230pub struct CodecRegistry {
231    codecs: HashMap<String, Box<dyn AudioCodec>>,
232}
233
234impl CodecRegistry {
235    /// Create a new empty registry
236    #[must_use]
237    pub fn new() -> Self {
238        Self {
239            codecs: HashMap::new(),
240        }
241    }
242
243    /// Register a codec with a name
244    pub fn register(&mut self, name: String, codec: Box<dyn AudioCodec>) {
245        self.codecs.insert(name, codec);
246    }
247
248    /// Get a codec by name
249    #[must_use]
250    pub fn get(&self, name: &str) -> Option<&dyn AudioCodec> {
251        self.codecs.get(name).map(std::convert::AsRef::as_ref)
252    }
253
254    /// Get a mutable codec by name
255    pub fn get_mut(&mut self, name: &str) -> Option<&mut Box<dyn AudioCodec>> {
256        self.codecs.get_mut(name)
257    }
258
259    /// Remove a codec by name
260    pub fn remove(&mut self, name: &str) -> Option<Box<dyn AudioCodec>> {
261        self.codecs.remove(name)
262    }
263
264    /// List all registered codec names
265    #[must_use]
266    pub fn list_codecs(&self) -> Vec<&String> {
267        self.codecs.keys().collect()
268    }
269
270    /// Get the count of registered codecs
271    #[must_use]
272    pub fn len(&self) -> usize {
273        self.codecs.len()
274    }
275
276    /// Check if the registry is empty
277    #[must_use]
278    pub fn is_empty(&self) -> bool {
279        self.codecs.is_empty()
280    }
281
282    /// Clear all registered codecs
283    pub fn clear(&mut self) {
284        self.codecs.clear();
285    }
286}
287
288impl Default for CodecRegistry {
289    fn default() -> Self {
290        Self::new()
291    }
292}
293
294/// Codec capability information
295#[derive(Debug, Clone)]
296pub struct CodecCapabilities {
297    /// Available codec types
298    pub codec_types: Vec<CodecType>,
299    /// Codec information
300    pub codec_info: HashMap<CodecType, CodecInfo>,
301}
302
303impl CodecCapabilities {
304    /// Get capabilities for all supported codecs
305    #[must_use]
306    pub fn get_all() -> Self {
307        // Both values are populated by feature-gated blocks. In a deliberately
308        // codec-free build they remain empty and therefore need no mutation.
309        #[allow(unused_mut)]
310        let mut codec_types = Vec::new();
311        #[allow(unused_mut)]
312        let mut codec_info = HashMap::new();
313
314        #[cfg(feature = "g711")]
315        {
316            codec_types.push(CodecType::G711Pcmu);
317            codec_types.push(CodecType::G711Pcma);
318
319            codec_info.insert(
320                CodecType::G711Pcmu,
321                CodecInfo {
322                    name: "PCMU",
323                    sample_rate: 8000,
324                    channels: 1,
325                    bitrate: 64000,
326                    frame_size: 160,
327                    payload_type: Some(0),
328                },
329            );
330
331            codec_info.insert(
332                CodecType::G711Pcma,
333                CodecInfo {
334                    name: "PCMA",
335                    sample_rate: 8000,
336                    channels: 1,
337                    bitrate: 64000,
338                    frame_size: 160,
339                    payload_type: Some(8),
340                },
341            );
342        }
343
344        #[cfg(feature = "opus")]
345        {
346            codec_types.push(CodecType::Opus);
347            codec_info.insert(
348                CodecType::Opus,
349                CodecInfo {
350                    name: "opus",
351                    sample_rate: 48000,
352                    channels: 1,
353                    bitrate: 64000,
354                    frame_size: 960,
355                    payload_type: None,
356                },
357            );
358        }
359
360        #[cfg(feature = "g729")]
361        {
362            codec_types.push(CodecType::G729);
363            codec_types.push(CodecType::G729A);
364            codec_types.push(CodecType::G729BA);
365
366            codec_info.insert(
367                CodecType::G729,
368                CodecInfo {
369                    name: "G729",
370                    sample_rate: 8000,
371                    channels: 1,
372                    bitrate: 8000,
373                    frame_size: 80,
374                    payload_type: Some(18),
375                },
376            );
377            codec_info.insert(
378                CodecType::G729A,
379                CodecInfo {
380                    name: "G729A",
381                    sample_rate: 8000,
382                    channels: 1,
383                    bitrate: 8000,
384                    frame_size: 80,
385                    payload_type: Some(18),
386                },
387            );
388            codec_info.insert(
389                CodecType::G729BA,
390                CodecInfo {
391                    name: "G729BA",
392                    sample_rate: 8000,
393                    channels: 1,
394                    bitrate: 8000,
395                    frame_size: 80,
396                    payload_type: Some(18),
397                },
398            );
399        }
400
401        Self {
402            codec_types,
403            codec_info,
404        }
405    }
406
407    /// Check if a codec type is supported
408    #[must_use]
409    pub fn is_supported(&self, codec_type: CodecType) -> bool {
410        self.codec_types.contains(&codec_type)
411    }
412
413    /// Get information for a specific codec type
414    #[must_use]
415    pub fn get_info(&self, codec_type: CodecType) -> Option<&CodecInfo> {
416        self.codec_info.get(&codec_type)
417    }
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423
424    #[test]
425    fn test_codec_factory_supported_codecs() {
426        let supported = CodecFactory::supported_codecs();
427
428        #[cfg(any(feature = "g711", feature = "g729", feature = "opus"))]
429        assert!(!supported.is_empty());
430
431        #[cfg(not(any(feature = "g711", feature = "g729", feature = "opus")))]
432        assert!(supported.is_empty());
433
434        #[cfg(feature = "g711")]
435        {
436            assert!(supported.contains(&"PCMU"));
437            assert!(supported.contains(&"PCMA"));
438        }
439    }
440
441    #[test]
442    fn test_codec_factory_is_supported() {
443        #[cfg(feature = "g711")]
444        {
445            assert!(CodecFactory::is_supported("PCMU"));
446            assert!(CodecFactory::is_supported("pcmu"));
447            assert!(CodecFactory::is_supported("PCMA"));
448        }
449
450        assert!(!CodecFactory::is_supported("UNSUPPORTED"));
451        assert!(!CodecFactory::is_supported("G722"));
452
453        #[cfg(feature = "opus")]
454        for name in ["opus", "Opus", "OPUS"] {
455            assert!(CodecFactory::is_supported(name));
456        }
457        #[cfg(not(feature = "opus"))]
458        for name in ["opus", "Opus", "OPUS"] {
459            assert!(!CodecFactory::is_supported(name));
460        }
461    }
462
463    #[test]
464    fn test_codec_registry() {
465        let mut registry = CodecRegistry::new();
466        assert!(registry.is_empty());
467        assert_eq!(registry.len(), 0);
468
469        #[cfg(feature = "g711")]
470        {
471            let config = CodecConfig::g711_pcmu();
472            let codec = CodecFactory::create(config).unwrap();
473            registry.register("test_pcmu".to_string(), codec);
474
475            assert_eq!(registry.len(), 1);
476            assert!(!registry.is_empty());
477            assert!(registry.get("test_pcmu").is_some());
478        }
479
480        registry.clear();
481        assert!(registry.is_empty());
482    }
483
484    #[test]
485    fn test_codec_capabilities() {
486        let caps = CodecCapabilities::get_all();
487
488        #[cfg(any(feature = "g711", feature = "g729", feature = "opus"))]
489        {
490            assert!(!caps.codec_types.is_empty());
491            assert!(!caps.codec_info.is_empty());
492        }
493
494        #[cfg(not(any(feature = "g711", feature = "g729", feature = "opus")))]
495        {
496            assert!(caps.codec_types.is_empty());
497            assert!(caps.codec_info.is_empty());
498        }
499
500        #[cfg(feature = "g711")]
501        {
502            assert!(caps.is_supported(CodecType::G711Pcmu));
503            assert!(caps.get_info(CodecType::G711Pcmu).is_some());
504        }
505    }
506
507    #[test]
508    #[cfg(feature = "g711")]
509    fn test_codec_creation() {
510        let config = CodecConfig::g711_pcmu();
511        let codec = CodecFactory::create(config);
512        assert!(codec.is_ok());
513
514        let codec = codec.unwrap();
515        let info = codec.info();
516        assert_eq!(info.name, "PCMU");
517        assert_eq!(info.sample_rate, 8000);
518    }
519
520    #[test]
521    #[cfg(feature = "g711")]
522    fn test_codec_creation_by_name() {
523        let config = CodecConfig::new(CodecType::G711Pcmu);
524        let codec = CodecFactory::create_by_name("PCMU", config.clone());
525        assert!(codec.is_ok());
526
527        let codec = CodecFactory::create_by_name("UNKNOWN", config);
528        assert!(codec.is_err());
529    }
530
531    #[test]
532    #[cfg(feature = "g711")]
533    fn test_codec_creation_by_payload_type() {
534        let config = CodecConfig::new(CodecType::G711Pcmu);
535        let codec = CodecFactory::create_by_payload_type(0, config.clone());
536        assert!(codec.is_ok());
537
538        let codec = CodecFactory::create_by_payload_type(255, config);
539        assert!(codec.is_err());
540    }
541}