opus-rs 0.1.26

pure Rust implementation of Opus codec
Documentation
//! Differential tests: compare opus-rs decoder output against C libopus 1.6.1.
//!
//! Requires reference files generated by `tests/c_ref_gen` (linked against
//! `opus-1.6.1/libopus.a`). Files live in /tmp/opus_ref/. Tests are skipped
//! if the reference files are not present.
//!
//! ## Known results
//!
//! | Test case | SNR | Status |
//! |-----------|-----|--------|
//! | SILK mono 16kHz (frames 0–17, before mode switch) | ∞ (bit-exact) | ✅ |
//! | CELT mono 48kHz | 66 dB | ✅ |
//! | CELT stereo 48kHz | 131 dB | ✅ |
//! | Hybrid mono 48kHz | 14.6 dB | ⚠️ #8 gap |
//! | CELT at 16kHz (pure or after SILK→CELT transition) | < 0 dB | ❌ pre-existing |
//!
//! The 16kHz CELT issue is pre-existing (CELT decoder uses a fixed 48kHz mode;
//! when `OpusDecoder` is created at 16kHz and receives CELT packets, the frame
//! size mismatch causes garbage output). This is not caused by the #7/#8/#9
//! changes.

use opus_rs::OpusDecoder;
use std::fs;

struct RefData {
    packets: Vec<Vec<u8>>,
    ref_pcm: Vec<f32>,
    frame_size: usize,
    sampling_rate: i32,
    channels: usize,
}

fn load_ref(path_prefix: &str, sampling_rate: i32, channels: usize) -> Option<RefData> {
    let pkt_path = format!("/tmp/opus_ref/{}.pkts", path_prefix);
    let pcm_path = format!("/tmp/opus_ref/{}.pcm", path_prefix);
    let pkt_raw = fs::read(&pkt_path).ok()?;
    let pcm_raw = fs::read(&pcm_path).ok()?;

    let frame_size = (sampling_rate / 50) as usize; // 20ms
    let mut packets = Vec::new();
    let mut pos = 0;
    while pos + 2 <= pkt_raw.len() {
        let n = u16::from_le_bytes([pkt_raw[pos], pkt_raw[pos + 1]]) as usize;
        pos += 2;
        if pos + n > pkt_raw.len() {
            break;
        }
        packets.push(pkt_raw[pos..pos + n].to_vec());
        pos += n;
    }

    let ref_pcm: Vec<f32> = pcm_raw
        .chunks_exact(4)
        .map(|c| f32::from_le_bytes(c.try_into().unwrap()))
        .collect();

    Some(RefData {
        packets,
        ref_pcm,
        frame_size,
        sampling_rate,
        channels,
    })
}

fn run_diff(label: &str, data: &RefData) {
    let mut dec = OpusDecoder::new(data.sampling_rate, data.channels).unwrap();
    let fs_ch = data.frame_size * data.channels;
    let mut rust_pcm: Vec<f32> = Vec::new();

    for pkt in &data.packets {
        let mut buf = vec![0.0f32; fs_ch * 2];
        match dec.decode(pkt, data.frame_size, &mut buf) {
            Ok(n) => rust_pcm.extend_from_slice(&buf[..n * data.channels]),
            Err(e) => {
                panic!("{}: decode error on packet (toc={:02x}): {}", label, pkt[0], e);
            }
        }
    }

    // Per-frame SNR analysis: find best lag per frame, classify as exact/good/bad.
    let n_frames = data.packets.len();
    let fs_ch = data.frame_size * data.channels;
    let mut n_exact = 0usize;
    let mut n_good = 0usize;
    let mut n_bad = 0usize;
    let mut worst_snr = f64::MAX;
    let mut worst_frame = 0usize;

    for f in 0..n_frames {
        let r_start = f * fs_ch;
        let ref_start = f * fs_ch;
        if r_start + fs_ch > rust_pcm.len() || ref_start + fs_ch > data.ref_pcm.len() {
            break;
        }
        let r_frame = &rust_pcm[r_start..r_start + fs_ch];
        let f_frame = &data.ref_pcm[ref_start..ref_start + fs_ch];

        let mut best_snr = f64::MIN;
        for lag in -30i32..=30i32 {
            let lag = lag as isize;
            let (rs, fs): (&[f32], &[f32]) = if lag >= 0 {
                let l = lag as usize;
                if l >= fs_ch { continue; }
                (&r_frame[l..], &f_frame[..fs_ch - l])
            } else {
                let l = (-lag) as usize;
                if l >= fs_ch { continue; }
                (&r_frame[..fs_ch - l], &f_frame[l..])
            };
            let m = rs.len().min(fs.len());
            if m < 10 { continue; }
            let mut ef = 0.0f64; let mut err = 0.0f64;
            for i in 0..m {
                let r = rs[i] as f64; let f = fs[i] as f64;
                ef += f * f; err += (r - f) * (r - f);
            }
            if ef > 0.0 {
                let snr = if err > 1e-30 { 10.0 * (ef / err).log10() } else { 999.0 };
                if snr > best_snr { best_snr = snr; }
            }
        }

        if best_snr > 100.0 {
            n_exact += 1;
        } else if best_snr > 40.0 {
            n_good += 1;
        } else {
            n_bad += 1;
            if best_snr < worst_snr {
                worst_snr = best_snr;
                worst_frame = f;
            }
        }
    }

    println!(
        "{}: {} frames — exact(>100dB):{}, good(>40dB):{}, bad(<40dB):{} | worst: frame {} @ {:.1}dB",
        label, n_frames, n_exact, n_good, n_bad, worst_frame, worst_snr
    );
}

#[test]
fn diff_silk_mono() {
    if let Some(data) = load_ref("silk_m", 16000, 1) {
        run_diff("SILK mono 16kHz", &data);
    } else {
        eprintln!("skipping diff_silk_mono (no ref files)");
    }
}

#[test]
fn diff_silk_stereo() {
    if let Some(data) = load_ref("silk_s", 16000, 2) {
        run_diff("SILK stereo 16kHz", &data);
        // Verify L != R (M/S decode working)
        let mut dec = OpusDecoder::new(16000, 2).unwrap();
        if let Some(pkt) = data.packets.first() {
            let mut buf = vec![0.0f32; 320 * 2];
            if let Ok(n) = dec.decode(pkt, 320, &mut buf) {
                let mut max_diff = 0.0f32;
                for i in 0..n {
                    max_diff = max_diff.max((buf[i * 2] - buf[i * 2 + 1]).abs());
                }
                println!("SILK stereo max |L-R| = {:.6} (should be > 0 for M/S)", max_diff);
                assert!(max_diff > 0.001, "L and R must differ for stereo M/S decode");
            }
        }
    } else {
        eprintln!("skipping diff_silk_stereo (no ref files)");
    }
}

#[test]
fn diff_celt_mono() {
    if let Some(data) = load_ref("celt_m", 48000, 1) {
        run_diff("CELT mono 48kHz", &data);
    } else {
        eprintln!("skipping diff_celt_mono (no ref files)");
    }
}

#[test]
fn diff_celt_stereo() {
    if let Some(data) = load_ref("celt_s", 48000, 2) {
        run_diff("CELT stereo 48kHz", &data);
    } else {
        eprintln!("skipping diff_celt_stereo (no ref files)");
    }
}

#[test]
fn diff_celt_16k() {
    if let Some(data) = load_ref("celt16k", 16000, 1) {
        run_diff("CELT mono 16kHz (downsample=3)", &data);
    } else {
        eprintln!("skipping diff_celt_16k (no ref files)");
    }
}

#[test]
fn diff_hybrid_mono() {
    if let Some(data) = load_ref("hybrid_m", 48000, 1) {
        run_diff("Hybrid mono 48kHz", &data);
    } else {
        eprintln!("skipping diff_hybrid_mono (no ref files)");
    }
}