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(any(feature = "opus", feature = "opus-sim"))]
76pub mod opus;
77
78/// Codec factory for creating codec instances
79pub struct CodecFactory;
80
81impl CodecFactory {
82    /// Create a codec instance from configuration
83    pub fn create(config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
84        // Validate configuration first
85        config.validate()?;
86
87        match config.codec_type {
88            #[cfg(feature = "g711")]
89            CodecType::G711Pcmu => {
90                let codec = g711::G711Codec::new_pcmu(config)?;
91                Ok(Box::new(codec))
92            }
93
94            #[cfg(feature = "g711")]
95            CodecType::G711Pcma => {
96                let codec = g711::G711Codec::new_pcma(config)?;
97                Ok(Box::new(codec))
98            }
99
100            #[cfg(any(feature = "opus", feature = "opus-sim"))]
101            CodecType::Opus => {
102                let codec = opus::OpusCodec::new(config)?;
103                Ok(Box::new(codec))
104            }
105
106            codec_type => Err(CodecError::feature_not_enabled(format!(
107                "Codec {} not enabled in build features",
108                codec_type.name()
109            ))),
110        }
111    }
112
113    /// Create a codec by name
114    pub fn create_by_name(name: &str, config: CodecConfig) -> Result<Box<dyn AudioCodec>> {
115        let codec_type = match name.to_uppercase().as_str() {
116            "PCMU" => CodecType::G711Pcmu,
117            "PCMA" => CodecType::G711Pcma,
118
119            "OPUS" => CodecType::Opus,
120            _ => return Err(CodecError::unsupported_codec(name)),
121        };
122
123        let config = CodecConfig {
124            codec_type,
125            ..config
126        };
127
128        Self::create(config)
129    }
130
131    /// Create a codec by RTP payload type
132    pub fn create_by_payload_type(
133        payload_type: u8,
134        config: CodecConfig,
135    ) -> Result<Box<dyn AudioCodec>> {
136        let codec_type = match payload_type {
137            0 => CodecType::G711Pcmu,
138            8 => CodecType::G711Pcma,
139
140            _ => return Err(CodecError::unsupported_codec(format!("PT{}", payload_type))),
141        };
142
143        let config = CodecConfig {
144            codec_type,
145            ..config
146        };
147
148        Self::create(config)
149    }
150
151    /// Get all supported codec names
152    pub fn supported_codecs() -> Vec<&'static str> {
153        vec![
154            #[cfg(feature = "g711")]
155            "PCMU",
156            #[cfg(feature = "g711")]
157            "PCMA",
158            #[cfg(any(feature = "opus", feature = "opus-sim"))]
159            "OPUS",
160        ]
161    }
162
163    /// Check if a codec is supported
164    pub fn is_supported(name: &str) -> bool {
165        Self::supported_codecs().contains(&name.to_uppercase().as_str())
166    }
167}
168
169/// Codec registry for managing multiple codec instances
170pub struct CodecRegistry {
171    codecs: HashMap<String, Box<dyn AudioCodec>>,
172}
173
174impl CodecRegistry {
175    /// Create a new empty registry
176    pub fn new() -> Self {
177        Self {
178            codecs: HashMap::new(),
179        }
180    }
181
182    /// Register a codec with a name
183    pub fn register(&mut self, name: String, codec: Box<dyn AudioCodec>) {
184        self.codecs.insert(name, codec);
185    }
186
187    /// Get a codec by name
188    pub fn get(&self, name: &str) -> Option<&dyn AudioCodec> {
189        self.codecs.get(name).map(|codec| codec.as_ref())
190    }
191
192    /// Get a mutable codec by name
193    pub fn get_mut(&mut self, name: &str) -> Option<&mut Box<dyn AudioCodec>> {
194        self.codecs.get_mut(name)
195    }
196
197    /// Remove a codec by name
198    pub fn remove(&mut self, name: &str) -> Option<Box<dyn AudioCodec>> {
199        self.codecs.remove(name)
200    }
201
202    /// List all registered codec names
203    pub fn list_codecs(&self) -> Vec<&String> {
204        self.codecs.keys().collect()
205    }
206
207    /// Get the count of registered codecs
208    pub fn len(&self) -> usize {
209        self.codecs.len()
210    }
211
212    /// Check if the registry is empty
213    pub fn is_empty(&self) -> bool {
214        self.codecs.is_empty()
215    }
216
217    /// Clear all registered codecs
218    pub fn clear(&mut self) {
219        self.codecs.clear();
220    }
221}
222
223impl Default for CodecRegistry {
224    fn default() -> Self {
225        Self::new()
226    }
227}
228
229/// Codec capability information
230#[derive(Debug, Clone)]
231pub struct CodecCapabilities {
232    /// Available codec types
233    pub codec_types: Vec<CodecType>,
234    /// Codec information
235    pub codec_info: HashMap<CodecType, CodecInfo>,
236}
237
238impl CodecCapabilities {
239    /// Get capabilities for all supported codecs
240    pub fn get_all() -> Self {
241        let mut codec_types = Vec::new();
242        let mut codec_info = HashMap::new();
243
244        #[cfg(feature = "g711")]
245        {
246            codec_types.push(CodecType::G711Pcmu);
247            codec_types.push(CodecType::G711Pcma);
248
249            codec_info.insert(
250                CodecType::G711Pcmu,
251                CodecInfo {
252                    name: "PCMU",
253                    sample_rate: 8000,
254                    channels: 1,
255                    bitrate: 64000,
256                    frame_size: 160,
257                    payload_type: Some(0),
258                },
259            );
260
261            codec_info.insert(
262                CodecType::G711Pcma,
263                CodecInfo {
264                    name: "PCMA",
265                    sample_rate: 8000,
266                    channels: 1,
267                    bitrate: 64000,
268                    frame_size: 160,
269                    payload_type: Some(8),
270                },
271            );
272        }
273
274        #[cfg(any(feature = "opus", feature = "opus-sim"))]
275        {
276            codec_types.push(CodecType::Opus);
277            codec_info.insert(
278                CodecType::Opus,
279                CodecInfo {
280                    name: "opus",
281                    sample_rate: 48000,
282                    channels: 1,
283                    bitrate: 64000,
284                    frame_size: 960,
285                    payload_type: None,
286                },
287            );
288        }
289
290        Self {
291            codec_types,
292            codec_info,
293        }
294    }
295
296    /// Check if a codec type is supported
297    pub fn is_supported(&self, codec_type: CodecType) -> bool {
298        self.codec_types.contains(&codec_type)
299    }
300
301    /// Get information for a specific codec type
302    pub fn get_info(&self, codec_type: CodecType) -> Option<&CodecInfo> {
303        self.codec_info.get(&codec_type)
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    
311
312    #[test]
313    fn test_codec_factory_supported_codecs() {
314        let supported = CodecFactory::supported_codecs();
315        assert!(!supported.is_empty());
316
317        #[cfg(feature = "g711")]
318        {
319            assert!(supported.contains(&"PCMU"));
320            assert!(supported.contains(&"PCMA"));
321        }
322    }
323
324    #[test]
325    fn test_codec_factory_is_supported() {
326        #[cfg(feature = "g711")]
327        {
328            assert!(CodecFactory::is_supported("PCMU"));
329            assert!(CodecFactory::is_supported("pcmu"));
330            assert!(CodecFactory::is_supported("PCMA"));
331        }
332
333        assert!(!CodecFactory::is_supported("UNSUPPORTED"));
334    }
335
336    #[test]
337    fn test_codec_registry() {
338        let mut registry = CodecRegistry::new();
339        assert!(registry.is_empty());
340        assert_eq!(registry.len(), 0);
341
342        #[cfg(feature = "g711")]
343        {
344            let config = CodecConfig::g711_pcmu();
345            let codec = CodecFactory::create(config).unwrap();
346            registry.register("test_pcmu".to_string(), codec);
347
348            assert_eq!(registry.len(), 1);
349            assert!(!registry.is_empty());
350            assert!(registry.get("test_pcmu").is_some());
351        }
352
353        registry.clear();
354        assert!(registry.is_empty());
355    }
356
357    #[test]
358    fn test_codec_capabilities() {
359        let caps = CodecCapabilities::get_all();
360        assert!(!caps.codec_types.is_empty());
361        assert!(!caps.codec_info.is_empty());
362
363        #[cfg(feature = "g711")]
364        {
365            assert!(caps.is_supported(CodecType::G711Pcmu));
366            assert!(caps.get_info(CodecType::G711Pcmu).is_some());
367        }
368    }
369
370    #[test]
371    #[cfg(feature = "g711")]
372    fn test_codec_creation() {
373        let config = CodecConfig::g711_pcmu();
374        let codec = CodecFactory::create(config);
375        assert!(codec.is_ok());
376
377        let codec = codec.unwrap();
378        let info = codec.info();
379        assert_eq!(info.name, "PCMU");
380        assert_eq!(info.sample_rate, 8000);
381    }
382
383    #[test]
384    #[cfg(feature = "g711")]
385    fn test_codec_creation_by_name() {
386        let config = CodecConfig::new(CodecType::G711Pcmu);
387        let codec = CodecFactory::create_by_name("PCMU", config.clone());
388        assert!(codec.is_ok());
389
390        let codec = CodecFactory::create_by_name("UNKNOWN", config);
391        assert!(codec.is_err());
392    }
393
394    #[test]
395    #[cfg(feature = "g711")]
396    fn test_codec_creation_by_payload_type() {
397        let config = CodecConfig::new(CodecType::G711Pcmu);
398        let codec = CodecFactory::create_by_payload_type(0, config.clone());
399        assert!(codec.is_ok());
400
401        let codec = CodecFactory::create_by_payload_type(255, config);
402        assert!(codec.is_err());
403    }
404}