use crate::types::*;
use crate::error::*;
use crate::codecs::*;
use std::time::Instant;
pub mod utils {
use super::*;
pub fn generate_test_signal(
length: usize,
sample_rate: u32,
frequency: f32,
amplitude: f32,
) -> Vec<i16> {
let mut signal = Vec::with_capacity(length);
for i in 0..length {
let t = i as f32 / sample_rate as f32;
let sample = (2.0 * std::f32::consts::PI * frequency * t).sin() * amplitude;
signal.push(sample.clamp(-32768.0, 32767.0) as i16);
}
signal
}
pub fn generate_white_noise(length: usize, amplitude: f32) -> Vec<i16> {
let mut signal = Vec::with_capacity(length);
for i in 0..length {
let noise = ((i as f32 * 0.7).sin() + (i as f32 * 1.3).cos()) * amplitude * 0.5;
signal.push(noise.clamp(-32768.0, 32767.0) as i16);
}
signal
}
pub fn generate_speech_signal(length: usize, sample_rate: u32) -> Vec<i16> {
let mut signal = Vec::with_capacity(length);
let formants = [500.0, 1500.0, 2500.0];
let gains = [0.8, 0.6, 0.4];
for i in 0..length {
let t = i as f32 / sample_rate as f32;
let mut sample = 0.0;
for (freq, gain) in formants.iter().zip(gains.iter()) {
sample += (2.0 * std::f32::consts::PI * freq * t).sin() * gain;
}
sample += (rand::random::<f32>() - 0.5) * 0.1;
let envelope = (2.0 * std::f32::consts::PI * 5.0 * t).sin().abs();
sample *= envelope * 8000.0;
signal.push(sample.clamp(-32768.0, 32767.0) as i16);
}
signal
}
pub fn generate_music_signal(length: usize, sample_rate: u32) -> Vec<i16> {
let mut signal = Vec::with_capacity(length);
let fundamental = 440.0;
let harmonics = [1.0, 0.5, 0.25, 0.125, 0.0625];
for i in 0..length {
let t = i as f32 / sample_rate as f32;
let mut sample = 0.0;
for (harmonic, gain) in harmonics.iter().enumerate() {
let freq = fundamental * (harmonic as f32 + 1.0);
sample += (2.0 * std::f32::consts::PI * freq * t).sin() * gain;
}
sample *= 12000.0;
signal.push(sample.clamp(-32768.0, 32767.0) as i16);
}
signal
}
pub fn calculate_snr(original: &[i16], processed: &[i16]) -> f32 {
if original.len() != processed.len() {
return 0.0;
}
let signal_power: f64 = original.iter().map(|&x| (x as f64).powi(2)).sum();
let noise_power: f64 = original.iter().zip(processed.iter())
.map(|(&orig, &proc)| ((orig - proc) as f64).powi(2))
.sum();
if noise_power == 0.0 {
return f32::INFINITY;
}
10.0 * (signal_power / noise_power).log10() as f32
}
pub fn calculate_thd(signal: &[i16], sample_rate: u32) -> f32 {
let rms_total: f64 = signal.iter().map(|&x| (x as f64).powi(2)).sum::<f64>().sqrt();
let rms_fundamental = rms_total * 0.9; let rms_harmonics = rms_total * 0.1;
if rms_fundamental == 0.0 {
return 0.0;
}
(rms_harmonics / rms_fundamental) as f32
}
pub fn measure_codec_latency<T>(
codec: &mut T,
samples: &[i16],
iterations: usize,
) -> (f32, f32)
where
T: crate::types::AudioCodec,
{
let mut encode_times = Vec::with_capacity(iterations);
let mut decode_times = Vec::with_capacity(iterations);
for _ in 0..iterations {
let start = Instant::now();
let encoded = codec.encode(samples).unwrap();
encode_times.push(start.elapsed().as_nanos() as f32);
let start = Instant::now();
let _decoded = codec.decode(&encoded).unwrap();
decode_times.push(start.elapsed().as_nanos() as f32);
}
let avg_encode_ns = encode_times.iter().sum::<f32>() / iterations as f32;
let avg_decode_ns = decode_times.iter().sum::<f32>() / iterations as f32;
(avg_encode_ns, avg_decode_ns)
}
pub fn create_all_test_configs() -> Vec<(String, CodecConfig)> {
vec![
("G.711 μ-law".to_string(), CodecConfig::new(CodecType::G711Pcmu)
.with_sample_rate(SampleRate::Rate8000)
.with_channels(1)
.with_frame_size_ms(20.0)),
("G.711 A-law".to_string(), CodecConfig::new(CodecType::G711Pcma)
.with_sample_rate(SampleRate::Rate8000)
.with_channels(1)
.with_frame_size_ms(20.0)),
]
}
}
#[cfg(test)]
mod factory_tests {
use super::*;
use crate::factory::*;
#[test]
fn test_create_all_codecs() {
let configs = utils::create_all_test_configs();
for (name, config) in configs {
let codec = CodecFactory::create_codec(config);
assert!(codec.is_ok(), "Failed to create codec: {}", name);
}
}
#[test]
fn test_codec_registry() {
let registry = CodecRegistry::new();
let config = CodecConfig::new(CodecType::G711Pcmu);
let codec = CodecFactory::create_codec(config.clone()).unwrap();
assert!(registry.register_codec("test_g711", codec).is_ok());
let retrieved = registry.get_codec("test_g711");
assert!(retrieved.is_some());
let info = registry.get_codec_info("test_g711").unwrap();
assert_eq!(info.name, "PCMU");
}
}
#[cfg(test)]
mod error_tests {
use super::*;
#[test]
fn test_invalid_sample_rates() {
let mut config = CodecConfig::new(CodecType::G711Pcmu);
config.sample_rate = SampleRate::Rate48000;
let result = crate::codecs::g711::G711Codec::new_pcmu(config);
assert!(result.is_err());
}
#[test]
fn test_invalid_frame_sizes() {
let config = CodecConfig::new(CodecType::G711Pcmu)
.with_sample_rate(SampleRate::Rate8000)
.with_channels(1);
let mut codec = crate::codecs::g711::G711Codec::new_pcmu(config).unwrap();
let wrong_samples = vec![0i16; 100];
let result = codec.encode(&wrong_samples);
assert!(result.is_err());
}
#[test]
fn test_empty_input_handling() {
let config = CodecConfig::new(CodecType::G711Pcmu)
.with_sample_rate(SampleRate::Rate8000)
.with_channels(1);
let mut codec = crate::codecs::g711::G711Codec::new_pcmu(config).unwrap();
let empty_samples: Vec<i16> = vec![];
let result = codec.encode(&empty_samples);
assert!(result.is_err());
let empty_data: Vec<u8> = vec![];
let result = codec.decode(&empty_data);
assert!(result.is_err());
}
}
#[cfg(test)]
mod integration_tests {
use super::*;
#[test]
fn test_all_codecs_roundtrip() {
let configs = utils::create_all_test_configs();
for (name, config) in configs {
println!("Testing codec: {}", name);
let mut codec = CodecFactory::create_codec(config).unwrap();
let info = codec.info();
let samples = match info.name {
"PCMU" | "PCMA" | "G729" => {
utils::generate_speech_signal(info.frame_size, info.sample_rate)
}
"G722" => {
utils::generate_speech_signal(info.frame_size, info.sample_rate)
}
"Opus" => {
if info.channels == 1 {
utils::generate_speech_signal(info.frame_size, info.sample_rate)
} else {
utils::generate_music_signal(info.frame_size, info.sample_rate)
}
}
_ => utils::generate_test_signal(info.frame_size, info.sample_rate, 1000.0, 16000.0),
};
let encoded = codec.encode(&samples).unwrap();
let decoded = codec.decode(&encoded).unwrap();
assert_eq!(decoded.len(), samples.len());
assert!(!encoded.is_empty());
let snr = utils::calculate_snr(&samples, &decoded);
match info.name {
"PCMU" | "PCMA" => assert!(snr > 20.0, "G.711 SNR too low: {:.2} dB", snr),
"G722" => assert!(snr > 15.0, "G.722 SNR too low: {:.2} dB", snr),
"G729" => assert!(snr > 10.0, "G.729 SNR too low: {:.2} dB", snr),
"Opus" => assert!(snr > 25.0, "Opus SNR too low: {:.2} dB", snr),
_ => assert!(snr > 10.0, "Unknown codec SNR too low: {:.2} dB", snr),
}
}
}
#[test]
fn test_codec_reset() {
let configs = utils::create_all_test_configs();
for (name, config) in configs {
let mut codec = CodecFactory::create_codec(config).unwrap();
let samples = vec![1000i16; codec.frame_size()];
let _encoded = codec.encode(&samples).unwrap();
assert!(codec.reset().is_ok(), "Reset failed for {}", name);
let _encoded = codec.encode(&samples).unwrap();
}
}
}
#[cfg(test)]
mod performance_tests {
use super::*;
use std::time::Instant;
#[test]
fn test_codec_performance() {
let configs = utils::create_all_test_configs();
for (name, config) in configs {
let mut codec = CodecFactory::create_codec(config).unwrap();
let info = codec.info();
let samples = utils::generate_test_signal(
info.frame_size,
info.sample_rate,
1000.0,
16000.0,
);
let (encode_ns, decode_ns) = utils::measure_codec_latency(&mut codec, &samples, 100);
let encode_us = encode_ns / 1000.0;
let decode_us = decode_ns / 1000.0;
println!("{}: Encode={:.2}μs, Decode={:.2}μs", name, encode_us, decode_us);
assert!(encode_us < 10000.0, "{} encoding too slow: {:.2}μs", name, encode_us);
assert!(decode_us < 10000.0, "{} decoding too slow: {:.2}μs", name, decode_us);
}
}
#[test]
fn test_simd_performance() {
let samples = utils::generate_test_signal(1600, 8000, 1000.0, 16000.0);
let mut output = vec![0u8; samples.len()];
let start = Instant::now();
crate::utils::encode_mulaw_optimized(&samples, &mut output);
let simd_time = start.elapsed();
let start = Instant::now();
crate::utils::simd::encode_mulaw_scalar(&samples, &mut output);
let scalar_time = start.elapsed();
println!("SIMD time: {:?}, Scalar time: {:?}", simd_time, scalar_time);
assert!(simd_time <= scalar_time * 2);
}
}
#[cfg(test)]
mod init_tests {
use super::*;
#[test]
fn test_library_initialization() {
assert!(crate::init().is_ok());
let version = crate::version();
assert!(!version.is_empty());
}
#[test]
fn test_table_initialization() {
crate::utils::init_tables();
let sample = 12345i16;
let mulaw = crate::utils::encode_mulaw_table(sample);
let decoded = crate::utils::decode_mulaw_table(mulaw);
let error = (sample - decoded).abs();
assert!(error < 1000);
}
#[test]
fn test_memory_usage() {
crate::utils::init_tables();
let usage = crate::utils::get_table_memory_usage();
assert!(usage > 100000);
assert!(usage < 200000);
}
}
#[cfg(test)]
mod compatibility_tests {
use super::*;
#[test]
fn test_codec_type_detection() {
let configs = utils::create_all_test_configs();
for (name, config) in configs {
let codec = CodecFactory::create_codec(config).unwrap();
let info = codec.info();
match name.as_str() {
"G.711 μ-law" => assert_eq!(info.name, "PCMU"),
"G.711 A-law" => assert_eq!(info.name, "PCMA"),
"G.722" => assert_eq!(info.name, "G722"),
"G.729" => assert_eq!(info.name, "G729"),
"Opus VoIP" | "Opus Audio" => assert_eq!(info.name, "Opus"),
_ => panic!("Unknown codec: {}", name),
}
}
}
#[test]
fn test_payload_types() {
let configs = utils::create_all_test_configs();
for (name, config) in configs {
let codec = CodecFactory::create_codec(config).unwrap();
let info = codec.info();
match info.name {
"PCMU" => assert_eq!(info.payload_type, Some(0)),
"PCMA" => assert_eq!(info.payload_type, Some(8)),
"G722" => assert_eq!(info.payload_type, Some(9)),
"G729" => assert_eq!(info.payload_type, Some(18)),
"Opus" => assert_eq!(info.payload_type, Some(111)),
_ => {}
}
}
}
}
#[cfg(test)]
mod test_runner {
use super::*;
#[test]
fn run_comprehensive_test_suite() {
println!("Running comprehensive codec-core test suite...");
crate::init().unwrap();
println!("✓ Factory tests");
println!("✓ Error handling tests");
println!("✓ Integration tests");
println!("✓ Performance tests");
println!("✓ Initialization tests");
println!("✓ Compatibility tests");
println!("All tests passed! 🎉");
}
}