opus-rs 0.1.1

Rust implementation of Opus codec
Documentation
use opus_rs::range_coder::RangeCoder;
/// Test SILK decoder against C reference encoded data
/// This test uses bitstreams generated by the C opus library (opus-1.6)
use opus_rs::silk::dec_api::SilkDecoder;
use opus_rs::silk::decode_frame::FLAG_DECODE_NORMAL;

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
    }
}

/// C reference encoded bitstream for 440Hz sine wave at 8kHz, 20ms frame
/// Generated by: opus_encode_float() with 10kbps CBR, complexity 0
/// Input: sin(2*pi*440*i/8000) for i in 0..160
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,
];

/// C reference decoded output (160 samples at 8kHz = 20ms)
/// Generated by opus_decode() from the above encoded data
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,
];

/// Decode TOC byte
fn parse_toc(toc: u8) -> (i32, i32, i32) {
    // SILK-only TOC format: [bw:2][config:3][s:1][c:2]
    let bandwidth = (toc >> 5) & 0x03;
    let config = (toc >> 3) & 0x07;
    let frame_count_code = toc & 0x03;

    // Map bandwidth to sample rate
    let sample_rate_hz = match bandwidth {
        0 => 8000,  // NB
        1 => 12000, // MB
        2 => 16000, // WB
        _ => 24000, // SWB/FB (shouldn't happen for SILK)
    };

    (sample_rate_hz, config as i32, frame_count_code as i32)
}

/// Test decoding C-encoded SILK bitstream
#[test]
fn test_decode_c_encoded_silk_8khz() {
    // Parse TOC byte
    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");

    // Initialize decoder
    let mut decoder = SilkDecoder::new();
    let ret = decoder.init(sample_rate_hz, 1);
    assert_eq!(ret, 0, "Failed to initialize decoder");

    // Get frame length
    let frame_length = decoder.frame_length();
    println!("Decoder frame length: {}", frame_length);
    assert_eq!(frame_length, 160, "Expected 160 samples frame length");

    // The SILK payload starts after TOC byte
    let silk_payload = &C_ENCODED_8KHZ_20MS[1..];
    println!("SILK payload: {} bytes", silk_payload.len());
    println!("SILK hex: {}", hex::encode(silk_payload));

    // Create range decoder
    let mut range_coder = RangeCoder::new_decoder(silk_payload.to_vec());

    // Decode
    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!();

    // Print comparison
    println!("=== Comparison with C reference ===");
    println!("Rust output [0..20]: {:?}", &output[..20]);
    println!("C reference [0..20]: {:?}", &C_DECODED_OUTPUT[..20]);
    println!();

    // Calculate and compare RMS energy
    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!();

    // Calculate correlation
    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);

    // Check for non-zero output
    let non_zero_count = output[..n_samples as usize]
        .iter()
        .filter(|&&x| x != 0)
        .count();
    println!("Non-zero samples: {} / {}", non_zero_count, n_samples);

    // Basic sanity check
    assert!(non_zero_count > 0, "Output is all zeros");
}

/// Test decoder with synthetic 16kHz data
#[test]
fn test_decode_synth_16khz() {
    let sample_rate = 16000;
    let frame_size = 320; // 20ms at 16kHz

    // Initialize decoder
    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);

    // Create a minimal valid SILK bitstream
    // TOC byte for 16kHz, 20ms: bandwidth=2 (WB), config=0 (10ms frames x2), stereo=0, code=0
    // Actually, for 20ms frames: config maps to frame durations
    // config 0 = 10ms, 1 = 20ms, 2 = 40ms, 3 = 60ms (for SILK)
    let toc: u8 = 0b10_001_0_00; // WB, 20ms frame, mono, 1 frame
    println!("TOC for 16kHz 20ms: 0x{:02x}", toc);

    // We need actual encoded data to decode, which we don't have
    // So this test just verifies decoder initialization works
    println!("Decoder initialized successfully for 16kHz");
}

/// Test decoder reset functionality
#[test]
fn test_decoder_reset() {
    let mut decoder = SilkDecoder::new();

    // Initialize at 16kHz
    let ret = decoder.init(16000, 1);
    assert_eq!(ret, 0, "Init 16kHz failed");
    assert_eq!(decoder.sample_rate(), 16000);

    // Reset and reinit at 8kHz
    decoder.reset();
    let ret = decoder.init(8000, 1);
    assert_eq!(ret, 0, "Init 8kHz failed");
    assert_eq!(decoder.sample_rate(), 8000);

    // Verify state is reset
    assert_eq!(decoder.channel_state[0].first_frame_after_reset, 1);
}

/// Test that decoder handles invalid sample rates correctly
#[test]
fn test_decoder_invalid_sample_rate() {
    let mut decoder = SilkDecoder::new();

    // SILK only supports 8, 12, 16 kHz
    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");
}