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//! use codec_core::codecs::CodecFactory;
27//! use codec_core::types::{CodecConfig, CodecType, SampleRate};
28//!
29//! // Create any codec through the factory
30//! let config = CodecConfig::new(CodecType::G711Pcmu)
31//!     .with_sample_rate(SampleRate::Rate8000);
32//! let mut codec = CodecFactory::create(config)?;
33//!
34//! // Use unified interface
35//! let samples = vec![0i16; 160];
36//! let encoded = codec.encode(&samples)?;
37//! let decoded = codec.decode(&encoded)?;
38//! # Ok::<(), Box<dyn std::error::Error>>(())
39//! ```
40//!
41//! ### Direct Codec Access
42//! ```rust
43//! use codec_core::codecs::g711::{G711Codec, G711Variant};
44//!
45//! // Direct instantiation
46//! let mut g711_ulaw = G711Codec::new(G711Variant::MuLaw);
47//! let mut g711_alaw = G711Codec::new(G711Variant::ALaw);
48//! # Ok::<(), Box<dyn std::error::Error>>(())
49//! ```
50//!
51//! ## Testing & Validation
52//!
53//! All codecs include comprehensive test suites:
54//! - ITU-T compliance validation
55//! - Real audio roundtrip tests
56//! - Performance benchmarks
57//! - Quality measurements (SNR)
58//!
59//! ```bash
60//! # Test all codecs
61//! cargo test
62//!
63//! # Test with real audio (downloads speech samples)
64//! cargo test wav_roundtrip_test -- --nocapture
65//! ```
66
67use crate::error::{CodecError, Result};
68use crate::types::{AudioCodec, CodecConfig, CodecInfo, CodecType};
69use std::collections::HashMap;
70
71// Codec implementations
72#[cfg(feature = "g711")]
73pub mod g711;
74
75#[cfg(feature = "g729")]
76pub mod g729;
77
78#[cfg(any(feature = "opus", feature = "opus-sim"))]
79pub mod opus;
80
81/// Codec factory for creating codec instances
82pub struct CodecFactory;
83
84impl CodecFactory {
85    /// Create a codec instance from configuration
86    pub fn create(config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
87        // Validate configuration first
88        config.validate()?;
89
90        match config.codec_type {
91            #[cfg(feature = "g711")]
92            CodecType::G711Pcmu => {
93                let codec = g711::G711Codec::new_pcmu(config)?;
94                Ok(Box::new(codec))
95            }
96
97            #[cfg(feature = "g711")]
98            CodecType::G711Pcma => {
99                let codec = g711::G711Codec::new_pcma(config)?;
100                Ok(Box::new(codec))
101            }
102
103            #[cfg(feature = "g729")]
104            CodecType::G729 | CodecType::G729A | CodecType::G729BA => {
105                let codec = g729::G729Codec::new(config)?;
106                Ok(Box::new(codec))
107            }
108
109            #[cfg(any(feature = "opus", feature = "opus-sim"))]
110            CodecType::Opus => {
111                let codec = opus::OpusCodec::new(config)?;
112                Ok(Box::new(codec))
113            }
114
115            codec_type => Err(CodecError::feature_not_enabled(format!(
116                "Codec {} not enabled in build features",
117                codec_type.name()
118            ))),
119        }
120    }
121
122    /// Create a codec by name
123    pub fn create_by_name(name: &str, config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
124        let codec_type = match normalize_codec_name(name).as_str() {
125            "PCMU" => CodecType::G711Pcmu,
126            "PCMA" => CodecType::G711Pcma,
127            "G729" => CodecType::G729,
128            "G729A" => CodecType::G729A,
129            "G729AB" | "G729BA" => CodecType::G729BA,
130            "OPUS" => CodecType::Opus,
131            _ => return Err(CodecError::unsupported_codec(name)),
132        };
133
134        let config = CodecConfig {
135            codec_type,
136            ..config
137        };
138
139        Self::create(config)
140    }
141
142    /// Create a codec by RTP payload type
143    pub fn create_by_payload_type(
144        payload_type: u8,
145        config: CodecConfig,
146    ) -> Result<Box<dyn AudioCodec>> {
147        let codec_type = match payload_type {
148            0 => CodecType::G711Pcmu,
149            8 => CodecType::G711Pcma,
150            18 => CodecType::G729,
151
152            _ => return Err(CodecError::unsupported_codec(format!("PT{}", payload_type))),
153        };
154
155        let config = CodecConfig {
156            codec_type,
157            ..config
158        };
159
160        Self::create(config)
161    }
162
163    /// Get all supported codec names
164    pub fn supported_codecs() -> Vec<&'static str> {
165        vec![
166            #[cfg(feature = "g711")]
167            "PCMU",
168            #[cfg(feature = "g711")]
169            "PCMA",
170            #[cfg(feature = "g729")]
171            "G729",
172            #[cfg(feature = "g729")]
173            "G729A",
174            #[cfg(feature = "g729")]
175            "G729BA",
176            #[cfg(any(feature = "opus", feature = "opus-sim"))]
177            "OPUS",
178        ]
179    }
180
181    /// Check if a codec is supported
182    pub fn is_supported(name: &str) -> bool {
183        let normalized = normalize_codec_name(name);
184        match normalized.as_str() {
185            #[cfg(feature = "g711")]
186            "PCMU" | "PCMA" => true,
187            #[cfg(feature = "g729")]
188            "G729" | "G729A" | "G729AB" | "G729BA" => true,
189            #[cfg(any(feature = "opus", feature = "opus-sim"))]
190            "OPUS" => true,
191            _ => false,
192        }
193    }
194}
195
196fn normalize_codec_name(name: &str) -> String {
197    name.to_ascii_uppercase().replace('.', "")
198}
199
200/// Codec registry for managing multiple codec instances
201pub struct CodecRegistry {
202    codecs: HashMap<String, Box<dyn AudioCodec>>,
203}
204
205impl CodecRegistry {
206    /// Create a new empty registry
207    pub fn new() -> Self {
208        Self {
209            codecs: HashMap::new(),
210        }
211    }
212
213    /// Register a codec with a name
214    pub fn register(&mut self, name: String, codec: Box<dyn AudioCodec>) {
215        self.codecs.insert(name, codec);
216    }
217
218    /// Get a codec by name
219    pub fn get(&self, name: &str) -> Option<&dyn AudioCodec> {
220        self.codecs.get(name).map(|codec| codec.as_ref())
221    }
222
223    /// Get a mutable codec by name
224    pub fn get_mut(&mut self, name: &str) -> Option<&mut Box<dyn AudioCodec>> {
225        self.codecs.get_mut(name)
226    }
227
228    /// Remove a codec by name
229    pub fn remove(&mut self, name: &str) -> Option<Box<dyn AudioCodec>> {
230        self.codecs.remove(name)
231    }
232
233    /// List all registered codec names
234    pub fn list_codecs(&self) -> Vec<&String> {
235        self.codecs.keys().collect()
236    }
237
238    /// Get the count of registered codecs
239    pub fn len(&self) -> usize {
240        self.codecs.len()
241    }
242
243    /// Check if the registry is empty
244    pub fn is_empty(&self) -> bool {
245        self.codecs.is_empty()
246    }
247
248    /// Clear all registered codecs
249    pub fn clear(&mut self) {
250        self.codecs.clear();
251    }
252}
253
254impl Default for CodecRegistry {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260/// Codec capability information
261#[derive(Debug, Clone)]
262pub struct CodecCapabilities {
263    /// Available codec types
264    pub codec_types: Vec<CodecType>,
265    /// Codec information
266    pub codec_info: HashMap<CodecType, CodecInfo>,
267}
268
269impl CodecCapabilities {
270    /// Get capabilities for all supported codecs
271    pub fn get_all() -> Self {
272        let mut codec_types = Vec::new();
273        let mut codec_info = HashMap::new();
274
275        #[cfg(feature = "g711")]
276        {
277            codec_types.push(CodecType::G711Pcmu);
278            codec_types.push(CodecType::G711Pcma);
279
280            codec_info.insert(
281                CodecType::G711Pcmu,
282                CodecInfo {
283                    name: "PCMU",
284                    sample_rate: 8000,
285                    channels: 1,
286                    bitrate: 64000,
287                    frame_size: 160,
288                    payload_type: Some(0),
289                },
290            );
291
292            codec_info.insert(
293                CodecType::G711Pcma,
294                CodecInfo {
295                    name: "PCMA",
296                    sample_rate: 8000,
297                    channels: 1,
298                    bitrate: 64000,
299                    frame_size: 160,
300                    payload_type: Some(8),
301                },
302            );
303        }
304
305        #[cfg(any(feature = "opus", feature = "opus-sim"))]
306        {
307            codec_types.push(CodecType::Opus);
308            codec_info.insert(
309                CodecType::Opus,
310                CodecInfo {
311                    name: "opus",
312                    sample_rate: 48000,
313                    channels: 1,
314                    bitrate: 64000,
315                    frame_size: 960,
316                    payload_type: None,
317                },
318            );
319        }
320
321        #[cfg(feature = "g729")]
322        {
323            codec_types.push(CodecType::G729);
324            codec_types.push(CodecType::G729A);
325            codec_types.push(CodecType::G729BA);
326
327            codec_info.insert(
328                CodecType::G729,
329                CodecInfo {
330                    name: "G729",
331                    sample_rate: 8000,
332                    channels: 1,
333                    bitrate: 8000,
334                    frame_size: 80,
335                    payload_type: Some(18),
336                },
337            );
338            codec_info.insert(
339                CodecType::G729A,
340                CodecInfo {
341                    name: "G729A",
342                    sample_rate: 8000,
343                    channels: 1,
344                    bitrate: 8000,
345                    frame_size: 80,
346                    payload_type: Some(18),
347                },
348            );
349            codec_info.insert(
350                CodecType::G729BA,
351                CodecInfo {
352                    name: "G729BA",
353                    sample_rate: 8000,
354                    channels: 1,
355                    bitrate: 8000,
356                    frame_size: 80,
357                    payload_type: Some(18),
358                },
359            );
360        }
361
362        Self {
363            codec_types,
364            codec_info,
365        }
366    }
367
368    /// Check if a codec type is supported
369    pub fn is_supported(&self, codec_type: CodecType) -> bool {
370        self.codec_types.contains(&codec_type)
371    }
372
373    /// Get information for a specific codec type
374    pub fn get_info(&self, codec_type: CodecType) -> Option<&CodecInfo> {
375        self.codec_info.get(&codec_type)
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_codec_factory_supported_codecs() {
385        let supported = CodecFactory::supported_codecs();
386        assert!(!supported.is_empty());
387
388        #[cfg(feature = "g711")]
389        {
390            assert!(supported.contains(&"PCMU"));
391            assert!(supported.contains(&"PCMA"));
392        }
393    }
394
395    #[test]
396    fn test_codec_factory_is_supported() {
397        #[cfg(feature = "g711")]
398        {
399            assert!(CodecFactory::is_supported("PCMU"));
400            assert!(CodecFactory::is_supported("pcmu"));
401            assert!(CodecFactory::is_supported("PCMA"));
402        }
403
404        assert!(!CodecFactory::is_supported("UNSUPPORTED"));
405    }
406
407    #[test]
408    fn test_codec_registry() {
409        let mut registry = CodecRegistry::new();
410        assert!(registry.is_empty());
411        assert_eq!(registry.len(), 0);
412
413        #[cfg(feature = "g711")]
414        {
415            let config = CodecConfig::g711_pcmu();
416            let codec = CodecFactory::create(config).unwrap();
417            registry.register("test_pcmu".to_string(), codec);
418
419            assert_eq!(registry.len(), 1);
420            assert!(!registry.is_empty());
421            assert!(registry.get("test_pcmu").is_some());
422        }
423
424        registry.clear();
425        assert!(registry.is_empty());
426    }
427
428    #[test]
429    fn test_codec_capabilities() {
430        let caps = CodecCapabilities::get_all();
431        assert!(!caps.codec_types.is_empty());
432        assert!(!caps.codec_info.is_empty());
433
434        #[cfg(feature = "g711")]
435        {
436            assert!(caps.is_supported(CodecType::G711Pcmu));
437            assert!(caps.get_info(CodecType::G711Pcmu).is_some());
438        }
439    }
440
441    #[test]
442    #[cfg(feature = "g711")]
443    fn test_codec_creation() {
444        let config = CodecConfig::g711_pcmu();
445        let codec = CodecFactory::create(config);
446        assert!(codec.is_ok());
447
448        let codec = codec.unwrap();
449        let info = codec.info();
450        assert_eq!(info.name, "PCMU");
451        assert_eq!(info.sample_rate, 8000);
452    }
453
454    #[test]
455    #[cfg(feature = "g711")]
456    fn test_codec_creation_by_name() {
457        let config = CodecConfig::new(CodecType::G711Pcmu);
458        let codec = CodecFactory::create_by_name("PCMU", config.clone());
459        assert!(codec.is_ok());
460
461        let codec = CodecFactory::create_by_name("UNKNOWN", config);
462        assert!(codec.is_err());
463    }
464
465    #[test]
466    #[cfg(feature = "g711")]
467    fn test_codec_creation_by_payload_type() {
468        let config = CodecConfig::new(CodecType::G711Pcmu);
469        let codec = CodecFactory::create_by_payload_type(0, config.clone());
470        assert!(codec.is_ok());
471
472        let codec = CodecFactory::create_by_payload_type(255, config);
473        assert!(codec.is_err());
474    }
475}