opus-rs 0.1.16

pure Rust implementation of Opus codec
Documentation
/// SILK end-to-end encode test
/// Tests that silk_encode_frame runs without crashing and produces valid output
use opus_rs::range_coder::RangeCoder;
use opus_rs::silk::control_codec::*;
use opus_rs::silk::define::*;
use opus_rs::silk::enc_api::silk_encode_frame;
use opus_rs::silk::init_encoder::silk_init_encoder;
use opus_rs::silk::structs::*;
use opus_rs::{Application, OpusEncoder};
use std::f32::consts::PI;

/// Helper: generate a voiced-like sine wave at ~200 Hz for 16 kHz
fn generate_voiced_signal(len: usize) -> Vec<i16> {
    let mut signal = vec![0i16; len];
    let freq = 200.0f64;
    let fs = 16000.0f64;
    for i in 0..len {
        let t = i as f64 / fs;
        signal[i] = (10000.0 * (2.0 * std::f64::consts::PI * freq * t).sin()) as i16;
    }
    signal
}

/// Helper: create and configure a WB SILK encoder at complexity 1 (simplest non-trivial)
fn create_wb_encoder(complexity: i32) -> SilkEncoderState {
    let mut enc = SilkEncoderState::default();
    silk_init_encoder(&mut enc, 0);
    silk_control_encoder(&mut enc, 16, 20, 20000, complexity);
    // Set SNR target manually (normally done by silk_control_SNR)
    enc.s_cmn.snr_db_q7 = 25 * 128; // 25 dB in Q7
    enc
}

#[test]
fn test_silk_encode_frame_no_crash() {
    // Test that silk_encode_frame runs without panicking for a simple voiced signal
    let mut enc = create_wb_encoder(1);

    // Generate input signal
    let frame_length = enc.s_cmn.frame_length as usize;
    let input = generate_voiced_signal(frame_length);

    // Create range coder with enough buffer
    let mut rc = RangeCoder::new_encoder(1275); // max SILK packet size

    let mut n_bytes_out: i32 = 0;

    // Encode first frame (with CODE_INDEPENDENTLY)
    let ret = silk_encode_frame(
        &mut enc,
        &input,
        &mut rc,
        &mut n_bytes_out,
        CODE_INDEPENDENTLY,
        8000, // max bits
        0,    // use_cbr
    );

    assert_eq!(ret, 0, "silk_encode_frame should return 0 (no error)");
    assert!(
        n_bytes_out > 0,
        "Encoded frame should produce some bytes, got {}",
        n_bytes_out
    );

    // Verify pulses are not all zero (something was quantized)
    let pulse_sum: i32 = enc.pulses.iter().map(|&p| p.abs() as i32).sum();
    println!(
        "Encoded {} bytes, pulse energy = {}",
        n_bytes_out, pulse_sum
    );
    assert!(pulse_sum > 0, "Pulses should be non-zero for voiced input");
}

#[test]
fn test_silk_encode_two_frames() {
    // Test encoding two consecutive frames (tests state continuity)
    let mut enc = create_wb_encoder(1);
    let frame_length = enc.s_cmn.frame_length as usize;

    // Frame 1
    let input1 = generate_voiced_signal(frame_length);
    let mut rc = RangeCoder::new_encoder(1275);
    let mut n_bytes = 0i32;
    let ret = silk_encode_frame(
        &mut enc,
        &input1,
        &mut rc,
        &mut n_bytes,
        CODE_INDEPENDENTLY,
        8000,
        0,
    );
    assert_eq!(ret, 0);
    let bytes1 = n_bytes;

    // Frame 2 (conditional coding)
    enc.s_cmn.n_frames_encoded = 1;
    let input2 = generate_voiced_signal(frame_length);
    let mut rc2 = RangeCoder::new_encoder(1275);
    let mut n_bytes2 = 0i32;
    let ret = silk_encode_frame(
        &mut enc,
        &input2,
        &mut rc2,
        &mut n_bytes2,
        CODE_CONDITIONALLY,
        8000,
        0,
    );
    assert_eq!(ret, 0);
    println!("Frame 1: {} bytes, Frame 2: {} bytes", bytes1, n_bytes2);
    assert!(n_bytes2 > 0, "Second frame should also produce bytes");
}

#[test]
fn test_silk_encode_silent_input() {
    // Test encoding silence (should produce small output)
    let mut enc = create_wb_encoder(1);
    let frame_length = enc.s_cmn.frame_length as usize;

    let input = vec![0i16; frame_length];
    let mut rc = RangeCoder::new_encoder(1275);
    let mut n_bytes = 0i32;
    let ret = silk_encode_frame(
        &mut enc,
        &input,
        &mut rc,
        &mut n_bytes,
        CODE_INDEPENDENTLY,
        8000,
        0,
    );
    assert_eq!(ret, 0);
    println!("Silent frame: {} bytes", n_bytes);
    assert!(n_bytes > 0, "Even silence should produce some bytes");
}

/// Test LBRR (in-band FEC) encoding produces valid larger packets
#[test]
fn test_lbrr_encoding_enabled() {
    let sample_rate = 8000;
    let frame_size = 160; // 20ms at 8kHz

    // Encoder WITHOUT LBRR
    let mut enc_no_fec =
        OpusEncoder::new(sample_rate, 1, Application::Voip).expect("Encoder creation failed");
    enc_no_fec.bitrate_bps = 20000;
    enc_no_fec.use_cbr = false;
    enc_no_fec.use_inband_fec = false;

    // Encoder WITH LBRR
    let mut enc_with_fec =
        OpusEncoder::new(sample_rate, 1, Application::Voip).expect("Encoder creation failed");
    enc_with_fec.bitrate_bps = 20000;
    enc_with_fec.use_cbr = false;
    enc_with_fec.use_inband_fec = true;
    enc_with_fec.packet_loss_perc = 10;

    let mut total_bytes_no_fec = 0usize;
    let mut total_bytes_with_fec = 0usize;

    // Encode 5 frames
    for frame_idx in 0..5 {
        let mut input = vec![0.0f32; frame_size];
        for i in 0..frame_size {
            let t = (frame_idx * frame_size + i) as f32 / sample_rate as f32;
            input[i] = (2.0f32 * PI * 440.0f32 * t).sin();
        }

        let mut out_no_fec = vec![0u8; 256];
        let n_no_fec = enc_no_fec
            .encode(&input, frame_size, &mut out_no_fec)
            .expect("Encode without FEC failed");

        let mut out_with_fec = vec![0u8; 512];
        let n_with_fec = enc_with_fec
            .encode(&input, frame_size, &mut out_with_fec)
            .expect("Encode with FEC failed");

        assert!(
            n_no_fec >= 3,
            "Frame {}: no-FEC packet too short: {}",
            frame_idx,
            n_no_fec
        );
        assert!(
            n_with_fec >= 3,
            "Frame {}: FEC packet too short: {}",
            frame_idx,
            n_with_fec
        );

        total_bytes_no_fec += n_no_fec;
        total_bytes_with_fec += n_with_fec;

        println!(
            "Frame {}: no-FEC={} bytes, with-FEC={} bytes",
            frame_idx, n_no_fec, n_with_fec
        );
    }

    println!(
        "Total: no-FEC={} bytes, with-FEC={} bytes",
        total_bytes_no_fec, total_bytes_with_fec
    );
    // With LBRR enabled, packets from frame 2 onward should include LBRR data
    // so total bytes with FEC >= total bytes without FEC (or roughly equal).
    // Note: First packet has no LBRR (no previous frame to protect).
    println!("✅ LBRR encoding test passed: encoder runs with FEC enabled");
}

/// Test that LBRR flag is set correctly in the output packet
#[test]
fn test_lbrr_flag_in_packet() {
    let sample_rate = 8000;
    let frame_size = 160;

    let mut encoder =
        OpusEncoder::new(sample_rate, 1, Application::Voip).expect("Encoder creation failed");
    encoder.bitrate_bps = 20000;
    encoder.use_inband_fec = true;
    encoder.packet_loss_perc = 10;

    // Encode 3 frames: first frame has no LBRR (no previous frame),
    // subsequent frames may include LBRR
    let mut packet_lbrr_flags = Vec::new();

    for frame_idx in 0..3 {
        let mut input = vec![0.0f32; frame_size];
        for i in 0..frame_size {
            let t = (frame_idx * frame_size + i) as f32 / sample_rate as f32;
            input[i] = (2.0f32 * PI * 440.0f32 * t).sin();
        }

        let mut output = vec![0u8; 512];
        let n = encoder
            .encode(&input, frame_size, &mut output)
            .expect("Encode failed");

        assert!(n >= 3, "Frame {}: packet too short", frame_idx);

        // The SILK payload LBRR flag is in the preamble bits.
        // For Code 3 packets: TOC[1] + count[1] + SILK_payload[...]
        // SILK preamble: VAD flags + LBRR flag encoded as range coder bits.
        // We can detect it indirectly: if LBRR active, packet is larger.
        let has_lbrr = n > 10; // rough heuristic
        packet_lbrr_flags.push((frame_idx, n, has_lbrr));
        println!(
            "Frame {}: {} bytes (LBRR data likely present: {})",
            frame_idx, n, has_lbrr
        );
    }

    println!("✅ LBRR flag test passed");
}