use talk_rs::audio::encoder::{AudioEncoder, OpusEncoder};
use talk_rs::config::AudioConfig;
fn test_audio_config() -> AudioConfig {
AudioConfig {
sample_rate: 16000,
channels: 1,
bitrate: 32000,
}
}
fn generate_sine_wave(num_samples: usize, frequency: f32, sample_rate: u32) -> Vec<i16> {
let mut samples = Vec::with_capacity(num_samples);
let phase_increment = 2.0 * std::f32::consts::PI * frequency / sample_rate as f32;
for i in 0..num_samples {
let phase = phase_increment * i as f32;
let sample = (phase.sin() * i16::MAX as f32) as i16;
samples.push(sample);
}
samples
}
#[test]
fn test_opus_encode_real_audio() {
let config = test_audio_config();
let mut encoder = OpusEncoder::new(config).expect("encoder creation should succeed");
let pcm = generate_sine_wave(16000, 440.0, 16000);
let mut all_encoded = Vec::new();
for chunk in pcm.chunks(320) {
let encoded = encoder.encode(chunk).expect("encode should succeed");
all_encoded.extend_from_slice(&encoded);
}
let flushed = encoder.flush().expect("flush should succeed");
all_encoded.extend_from_slice(&flushed);
assert!(
!all_encoded.is_empty(),
"Encoded output should not be empty"
);
assert!(
all_encoded.len() < pcm.len() * 2,
"Opus should compress PCM data (PCM: {} bytes, Opus: {} bytes)",
pcm.len() * 2,
all_encoded.len()
);
}
#[test]
fn test_opus_encode_decode_roundtrip() {
let config = test_audio_config();
let mut encoder = OpusEncoder::new(config.clone()).expect("encoder creation should succeed");
let original_pcm = generate_sine_wave(16000, 440.0, 16000);
let mut encoded_frames = Vec::new();
for chunk in original_pcm.chunks(320) {
let encoded = encoder.encode(chunk).expect("encode should succeed");
if !encoded.is_empty() {
encoded_frames.push(encoded);
}
}
let flushed = encoder.flush().expect("flush should succeed");
if !flushed.is_empty() {
encoded_frames.push(flushed);
}
let mut decoder = opus::Decoder::new(config.sample_rate, opus::Channels::Mono)
.expect("decoder creation should succeed");
let mut all_decoded = Vec::new();
for frame in encoded_frames {
let mut decoded_chunk = vec![0i16; 320];
let decoded_len = decoder
.decode(&frame, &mut decoded_chunk, false)
.expect("decode should succeed");
decoded_chunk.truncate(decoded_len);
all_decoded.extend_from_slice(&decoded_chunk);
}
assert!(
all_decoded.len() >= 15000,
"Decoded length should be close to original (got {})",
all_decoded.len()
);
let non_zero_count = all_decoded.iter().filter(|&&s| s != 0).count();
assert!(
non_zero_count > all_decoded.len() / 2,
"Most decoded samples should be non-zero (got {} non-zero out of {})",
non_zero_count,
all_decoded.len()
);
let mut correlation_sum = 0i64;
for (orig, decoded) in original_pcm.iter().zip(all_decoded.iter()) {
correlation_sum += (*orig as i64) * (*decoded as i64);
}
assert!(
correlation_sum > 0,
"Decoded signal should have positive correlation with original"
);
}
#[test]
fn test_opus_encoder_flush_produces_output() {
let config = test_audio_config();
let mut encoder = OpusEncoder::new(config).expect("encoder creation should succeed");
let partial_pcm = generate_sine_wave(100, 440.0, 16000);
let encoded = encoder.encode(&partial_pcm).expect("encode should succeed");
assert!(
encoded.is_empty(),
"Partial frame should not produce output immediately"
);
let flushed = encoder.flush().expect("flush should succeed");
assert!(
!flushed.is_empty(),
"Flush should produce output for buffered partial frame"
);
assert!(
!flushed.is_empty(),
"Flushed data should contain encoded frame"
);
}
#[test]
fn test_opus_encoder_different_bitrates() {
let bitrates = vec![16000, 32000, 64000];
for bitrate in bitrates {
let config = AudioConfig {
sample_rate: 16000,
channels: 1,
bitrate,
};
let mut encoder = OpusEncoder::new(config).expect("encoder creation should succeed");
let pcm = generate_sine_wave(16000, 440.0, 16000);
let mut all_encoded = Vec::new();
for chunk in pcm.chunks(320) {
let encoded = encoder.encode(chunk).expect("encode should succeed");
all_encoded.extend_from_slice(&encoded);
}
let flushed = encoder.flush().expect("flush should succeed");
all_encoded.extend_from_slice(&flushed);
assert!(
!all_encoded.is_empty(),
"Encoding at {} bps should produce output",
bitrate
);
assert!(
!all_encoded.is_empty(),
"Encoded data at {} bps should be non-empty",
bitrate
);
}
}