test(codec): unit tests for Opus encode/decode + PLC sizing

Add #[cfg(test)] coverage for src/codec/opus_impl.rs: encode→decode
round-trip shape + signal-energy survival, decoded duration follows the
packet, and the key regression guard — decode(None)/decode(Some(&[]))
conceals exactly frame_samples per channel (960 mono / 1920 stereo),
pinning the previously-fixed 120ms-burst PLC sizing bug. Tests-only; no
production behavior change.

Implemented by Gemini (junior implementer), reviewed and verified by senior
(cargo build + clippy --all-targets + cargo test all green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 05:23:07 -04:00
co-authored by Claude Opus 4.8
parent 131d61e61a
commit 87fb1595c9
+77
View File
@@ -80,3 +80,80 @@ impl AudioDecoder for OpusDecoder {
Ok(pcm)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_trip() {
let mut encoder = OpusEncoder::new(48000, Channels::Mono, Application::Voip).unwrap();
let mut decoder = OpusDecoder::new(48000, Channels::Mono, 960).unwrap();
// 1. Round-trip shape: generate a 960-sample mono PCM frame (440Hz sine wave)
let mut pcm = vec![0i16; 960];
for (i, sample) in pcm.iter_mut().enumerate() {
let t = (i as f32) / 48000.0;
let val = (t * 440.0 * 2.0 * std::f32::consts::PI).sin();
*sample = (val * 10000.0) as i16;
}
// encode it
let compressed = encoder.encode(&pcm).unwrap();
assert!(!compressed.is_empty(), "Compressed buffer should not be empty");
assert!(
compressed.len() < pcm.len() * std::mem::size_of::<i16>(),
"Compressed size ({}) should be smaller than raw PCM size ({})",
compressed.len(),
pcm.len() * std::mem::size_of::<i16>()
);
// decode it
let decoded = decoder.decode(Some(&compressed)).unwrap();
assert_eq!(decoded.len(), 960, "Decoded sample count should be exactly 960");
// 2. Round-trip carries signal energy (not silence)
let sum_sq: f64 = decoded.iter().map(|&x| (x as f64).powi(2)).sum();
let rms = (sum_sq / decoded.len() as f64).sqrt();
// Since input had amplitude ~10000, let's verify RMS is significantly above 0 (e.g. > 100.0)
assert!(rms > 100.0, "Decoded signal should carry energy (RMS was {})", rms);
}
#[test]
fn test_plc_sizing() {
let mut decoder = OpusDecoder::new(48000, Channels::Mono, 960).unwrap();
// decode(None) returns exactly frame_samples (960) samples
let plc_none = decoder.decode(None).unwrap();
assert_eq!(plc_none.len(), 960, "decode(None) should yield exactly 960 samples");
// decode(Some(&[])) (empty slice) does the same
let plc_empty = decoder.decode(Some(&[])).unwrap();
assert_eq!(plc_empty.len(), 960, "decode(Some(&[])) should yield exactly 960 samples");
}
#[test]
fn test_decoded_duration_follows_packet() {
let mut encoder = OpusEncoder::new(48000, Channels::Mono, Application::Voip).unwrap();
let mut decoder = OpusDecoder::new(48000, Channels::Mono, 960).unwrap();
let pcm = vec![0i16; 960];
let compressed = encoder.encode(&pcm).unwrap();
let decoded = decoder.decode(Some(&compressed)).unwrap();
assert_eq!(decoded.len(), 960, "Decoded sample count should match packet duration");
}
#[test]
fn test_stereo_plc_sizing() {
let mut decoder = OpusDecoder::new(48000, Channels::Stereo, 960).unwrap();
// decode(None) returns exactly frame_samples * 2 (1920) samples
let plc_none = decoder.decode(None).unwrap();
assert_eq!(plc_none.len(), 960 * 2, "Stereo decode(None) should yield exactly 1920 samples");
// decode(Some(&[])) (empty slice) does the same
let plc_empty = decoder.decode(Some(&[])).unwrap();
assert_eq!(plc_empty.len(), 960 * 2, "Stereo decode(Some(&[])) should yield exactly 1920 samples");
}
}