use opus_rs::range_coder::RangeCoder;
use opus_rs::silk::dec_api::SilkDecoder;
use opus_rs::silk::decode_frame::FLAG_DECODE_NORMAL;
use opus_rs::silk::resampler::SilkResampler;
mod hex {
pub fn encode(data: &[u8]) -> String {
let mut s = String::with_capacity(data.len() * 2);
for &b in data {
let _ = std::fmt::Write::write_fmt(&mut s, format_args!("{:02x}", b));
}
s
}
}
const C_ENCODED_8KHZ_20MS: &[u8] = &[
0x0b, 0x01, 0x84, 0xc1, 0xc1, 0xc7, 0xb6, 0x6f, 0x5e, 0x06, 0xa4, 0xb7, 0x28, 0xc8, 0x1c, 0x95,
0x61, 0x20, 0xe0, 0x78, 0x1c, 0x26, 0x4a, 0x17, 0x60,
];
const C_DECODED_OUTPUT: [i16; 160] = [
0, 0, 0, 0, 0, 96, 88, -40, -221, -254, -140, -137, -50, 143, 196, 308, 312, 170, -49, -142,
-98, -174, -159, -17, 188, 442, 705, 769, 816, 892, 940, 957, 931, 694, 468, 144, -285, -539,
-615, -548, -359, -71, 286, 669, 859, 615, 23, -133, -511, -579, -194, 398, 534, 9881, 17993,
24112, 27741, 27748, 22514, 14768, 6459, -4981, -14998, -22576, -27136, -28032, -28689, -25749,
-19147, -10515, -1472, 6676, 14070, 19202, 22220, 23035, 21366, 16971, 10466, 2930, -4784,
-11799, -19794, -24772, -28815, -29737, -26770, -20986, -12941, -2659, 7189, 16824, 24320,
28228, 28996, 26677, 21352, 13676, 4720, -5720, -15126, -21808, -25876, -26722, -24395, -19678,
-12851, -4686, 3415, 10846, 16881, 20841, 22387, 21458, 18307, 13037, 6373, -430, -6965,
-12590, -16299, -18131, -17694, -15420, -11394, -5332, 1854, 8371, 14311, 18235, 19646, 19002,
15403, 10174, 3355, -3381, -10082, -15317, -18443, -19567, -18499, -14524, -9203, -2225, 5588,
13002, 18315, 21218, 21622, 19489, 14464, 8000, 257, -7033, -14071, -19362, -21837, -21914,
-19643, -15421,
];
fn parse_toc(toc: u8) -> (i32, i32, i32) {
let bandwidth = (toc >> 5) & 0x03;
let config = (toc >> 3) & 0x07;
let frame_count_code = toc & 0x03;
let sample_rate_hz = match bandwidth {
0 => 8000, 1 => 12000, 2 => 16000, _ => 24000, };
(sample_rate_hz, config as i32, frame_count_code as i32)
}
#[test]
fn test_decode_c_encoded_silk_8khz() {
let toc = C_ENCODED_8KHZ_20MS[0];
let (sample_rate_hz, _config, frame_count_code) = parse_toc(toc);
println!("TOC: 0x{:02x}", toc);
println!("Sample rate: {} Hz", sample_rate_hz);
println!("Frame count code: {}", frame_count_code);
assert_eq!(sample_rate_hz, 8000, "Expected 8kHz sample rate");
let mut decoder = SilkDecoder::new();
let ret = decoder.init(sample_rate_hz, 1);
assert_eq!(ret, 0, "Failed to initialize decoder");
let frame_length = decoder.frame_length();
println!("Decoder frame length: {}", frame_length);
assert_eq!(frame_length, 160, "Expected 160 samples frame length");
let silk_payload = &C_ENCODED_8KHZ_20MS[1..];
println!("SILK payload: {} bytes", silk_payload.len());
println!("SILK hex: {}", hex::encode(silk_payload));
let mut range_coder = RangeCoder::new_decoder(&silk_payload);
let mut output = vec![0i16; 160];
let n_samples = decoder.decode(
&mut range_coder,
&mut output,
FLAG_DECODE_NORMAL,
true,
20,
8000,
);
println!("Decoded {} samples", n_samples);
println!();
println!("=== Comparison with C reference ===");
println!("Rust output [0..20]: {:?}", &output[..20]);
println!("C reference [0..20]: {:?}", &C_DECODED_OUTPUT[..20]);
println!();
let sum_sq: i64 = output[..n_samples as usize]
.iter()
.map(|&x| (x as i64) * (x as i64))
.sum();
let rust_rms = ((sum_sq as f64) / (n_samples as f64)).sqrt();
let c_sum_sq: i64 = C_DECODED_OUTPUT
.iter()
.map(|&x| (x as i64) * (x as i64))
.sum();
let c_rms = ((c_sum_sq as f64) / (C_DECODED_OUTPUT.len() as f64)).sqrt();
println!("Rust RMS energy: {:.2}", rust_rms);
println!("C reference RMS: {:.2}", c_rms);
println!("RMS ratio: {:.4}", rust_rms / c_rms);
println!();
let mut dot_product: i64 = 0;
let mut rust_norm: i64 = 0;
let mut c_norm: i64 = 0;
for i in 0..n_samples as usize {
dot_product += (output[i] as i64) * (C_DECODED_OUTPUT[i] as i64);
rust_norm += (output[i] as i64) * (output[i] as i64);
c_norm += (C_DECODED_OUTPUT[i] as i64) * (C_DECODED_OUTPUT[i] as i64);
}
let correlation = if rust_norm > 0 && c_norm > 0 {
(dot_product as f64) / ((rust_norm as f64).sqrt() * (c_norm as f64).sqrt())
} else {
0.0
};
println!("Correlation: {:.4}", correlation);
let non_zero_count = output[..n_samples as usize]
.iter()
.filter(|&&x| x != 0)
.count();
println!("Non-zero samples: {} / {}", non_zero_count, n_samples);
assert!(non_zero_count > 0, "Output is all zeros");
}
#[test]
fn test_decode_synth_16khz() {
let sample_rate = 16000;
let frame_size = 320;
let mut decoder = SilkDecoder::new();
let ret = decoder.init(sample_rate, 1);
assert_eq!(ret, 0, "Failed to initialize decoder");
assert_eq!(decoder.frame_length(), frame_size);
let toc: u8 = 0b10_001_0_00; println!("TOC for 16kHz 20ms: 0x{:02x}", toc);
println!("Decoder initialized successfully for 16kHz");
}
#[test]
fn test_decoder_reset() {
let mut decoder = SilkDecoder::new();
let ret = decoder.init(16000, 1);
assert_eq!(ret, 0, "Init 16kHz failed");
assert_eq!(decoder.sample_rate(), 16000);
decoder.reset();
let ret = decoder.init(8000, 1);
assert_eq!(ret, 0, "Init 8kHz failed");
assert_eq!(decoder.sample_rate(), 8000);
assert_eq!(decoder.channel_state[0].first_frame_after_reset, 1);
}
#[test]
fn test_decoder_invalid_sample_rate() {
let mut decoder = SilkDecoder::new();
let ret = decoder.init(48000, 1);
assert!(ret < 0, "Should reject 48kHz");
let ret = decoder.init(44100, 1);
assert!(ret < 0, "Should reject 44.1kHz");
let ret = decoder.init(24000, 1);
assert!(ret < 0, "Should reject 24kHz");
}
#[test]
fn test_resampler_8khz_to_48khz() {
let mut resampler = SilkResampler::default();
let ret = resampler.init(8000, 48000);
assert_eq!(ret, 0, "Resampler init should succeed for 8→48kHz");
let mut input = vec![0i16; 80];
for i in 0..80 {
let phase = 2.0f32 * std::f32::consts::PI * 440.0 * i as f32 / 8000.0;
input[i] = (phase.sin() * 16384.0) as i16;
}
let mut output = vec![0i16; 480];
let ret = resampler.process(&mut output, &input, 80);
assert_eq!(ret, 0, "Resampler process should succeed");
let nonzero = output.iter().filter(|&&x| x != 0).count();
assert!(nonzero > 0, "Resampler output should not be all-zero");
let in_energy: i64 = input.iter().map(|&x| (x as i64) * (x as i64)).sum();
let out_energy: i64 = output.iter().map(|&x| (x as i64) * (x as i64)).sum();
let ratio = out_energy as f64 / in_energy as f64;
println!("8kHz→48kHz resampler:");
println!(" Input energy: {}", in_energy);
println!(" Output energy: {}", out_energy);
println!(" Energy ratio (should be ~6): {:.2}", ratio);
println!(" Non-zero samples: {}/480", nonzero);
assert!(out_energy > 0, "Output energy must be non-zero");
}
#[test]
fn test_resampler_12khz_to_24khz() {
let mut resampler = SilkResampler::default();
let ret = resampler.init(12000, 24000);
assert_eq!(ret, 0, "Resampler init should succeed for 12→24kHz");
let mut input = vec![0i16; 120];
for i in 0..120 {
let phase = 2.0f32 * std::f32::consts::PI * 400.0 * i as f32 / 12000.0;
input[i] = (phase.sin() * 16384.0) as i16;
}
let mut output = vec![0i16; 240];
let ret = resampler.process(&mut output, &input, 120);
assert_eq!(ret, 0, "Resampler process should succeed");
let nonzero = output.iter().filter(|&&x| x != 0).count();
assert!(
nonzero > 0,
"12→24kHz resampler output should not be all-zero"
);
let in_energy: i64 = input.iter().map(|&x| (x as i64) * (x as i64)).sum();
let out_energy: i64 = output.iter().map(|&x| (x as i64) * (x as i64)).sum();
println!("12kHz→24kHz resampler (Up2HQ):");
println!(" Input energy: {}", in_energy);
println!(" Output energy: {}", out_energy);
println!(" Non-zero samples: {}/240", nonzero);
assert!(out_energy > 0, "Output energy must be non-zero");
}
#[test]
fn test_resampler_16khz_to_48khz() {
let mut resampler = SilkResampler::default();
let ret = resampler.init(16000, 48000);
assert_eq!(ret, 0, "Resampler init should succeed for 16→48kHz");
let mut input = vec![0i16; 160];
for i in 0..160 {
let phase = 2.0f32 * std::f32::consts::PI * 440.0 * i as f32 / 16000.0;
input[i] = (phase.sin() * 16384.0) as i16;
}
let mut output = vec![0i16; 480];
let ret = resampler.process(&mut output, &input, 160);
assert_eq!(ret, 0, "Resampler process should succeed");
let nonzero = output.iter().filter(|&&x| x != 0).count();
assert!(
nonzero > 0,
"16→48kHz resampler output should not be all-zero"
);
let out_energy: i64 = output.iter().map(|&x| (x as i64) * (x as i64)).sum();
println!("16kHz→48kHz resampler:");
println!(" Output energy: {}", out_energy);
println!(" Non-zero samples: {}/480", nonzero);
assert!(out_energy > 0, "Output energy must be non-zero");
}
#[test]
fn test_resampler_8khz_copy_mode() {
let mut resampler = SilkResampler::default();
let ret = resampler.init(8000, 8000);
assert_eq!(ret, 0, "Resampler init should succeed for 8→8kHz copy mode");
let mut input = vec![0i16; 80];
for i in 0..80 {
let phase = 2.0f32 * std::f32::consts::PI * 440.0 * i as f32 / 8000.0;
input[i] = (phase.sin() * 16384.0) as i16;
}
let mut output = vec![0i16; 80];
let ret = resampler.process(&mut output, &input, 80);
assert_eq!(ret, 0, "Copy mode process should succeed");
println!("8kHz→8kHz copy mode: output[0..5]={:?}", &output[..5]);
}
#[test]
fn test_resampler_delay_buffer_continuity() {
let mut resampler = SilkResampler::default();
let ret = resampler.init(8000, 48000);
assert_eq!(ret, 0, "Resampler init should succeed");
let frame_size_in = 80; let frame_size_out = 480;
let mut energies = Vec::new();
let mut prev_output_end = 0i16;
for frame_idx in 0..5 {
let mut input = vec![0i16; frame_size_in];
for i in 0..frame_size_in {
let t = (frame_idx * frame_size_in + i) as f32 / 8000.0;
let phase = 2.0f32 * std::f32::consts::PI * 440.0 * t;
input[i] = (phase.sin() * 16384.0) as i16;
}
let mut output = vec![0i16; frame_size_out];
let ret = resampler.process(&mut output, &input, frame_size_in as i32);
assert_eq!(ret, 0, "Frame {} process should succeed", frame_idx);
let energy: i64 = output.iter().map(|&x| (x as i64) * (x as i64)).sum();
energies.push(energy);
if frame_idx > 0 {
let jump = (output[0] as i32 - prev_output_end as i32).abs();
println!(
"Frame {}: energy={}, jump from previous frame={}",
frame_idx, energy, jump
);
} else {
println!("Frame {}: energy={}", frame_idx, energy);
}
prev_output_end = output[frame_size_out - 1];
}
for (i, &e) in energies.iter().enumerate().skip(1) {
assert!(e > 0, "Frame {} should have non-zero energy, got {}", i, e);
}
println!("✅ Delay buffer continuity test passed: {:?}", energies);
}
#[test]
fn test_resampler_8khz_to_16khz() {
let mut resampler = SilkResampler::default();
let ret = resampler.init(8000, 16000);
assert_eq!(ret, 0, "Resampler init should succeed for 8→16kHz");
let mut input = vec![0i16; 80];
for i in 0..80 {
let phase = 2.0f32 * std::f32::consts::PI * 440.0 * i as f32 / 8000.0;
input[i] = (phase.sin() * 16384.0) as i16;
}
let mut output = vec![0i16; 160];
let ret = resampler.process(&mut output, &input, 80);
assert_eq!(ret, 0, "Resampler process should succeed");
let nonzero = output.iter().filter(|&&x| x != 0).count();
let out_energy: i64 = output.iter().map(|&x| (x as i64) * (x as i64)).sum();
println!("8kHz→16kHz resampler:");
println!(" Output energy: {}", out_energy);
println!(" Non-zero samples: {}/160", nonzero);
assert!(
nonzero > 0,
"8→16kHz resampler output should not be all-zero"
);
assert!(out_energy > 0, "Output energy must be non-zero");
}