aec3 0.1.8

An acoustic echo canceller written in rust based on the WebRTC aec3 project
Documentation
use std::env;
use std::fs::File;
use std::io::{self, Read};

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
enum DiagnosticLevel {
    Production = 0,
    Developer = 1,
    DeepDebug = 2,
}

#[derive(Debug, Serialize, Deserialize)]
enum DiagnosticRecord {
    Header { version: u32 },
    NewSet { set_index: u64 },
    RawF32 {
        level: DiagnosticLevel,
        set_index: u64,
        instance: usize,
        name: String,
        value: f32,
    },
    RawF32Slice {
        level: DiagnosticLevel,
        set_index: u64,
        instance: usize,
        name: String,
        values: Vec<f32>,
    },
    RawI32 {
        level: DiagnosticLevel,
        set_index: u64,
        instance: usize,
        name: String,
        value: i32,
    },
    RawI32Slice {
        level: DiagnosticLevel,
        set_index: u64,
        instance: usize,
        name: String,
        values: Vec<i32>,
    },
    RawUsize {
        level: DiagnosticLevel,
        set_index: u64,
        instance: usize,
        name: String,
        value: usize,
    },
    RawUsizeSlice {
        level: DiagnosticLevel,
        set_index: u64,
        instance: usize,
        name: String,
        values: Vec<usize>,
    },
    WavRef {
        level: DiagnosticLevel,
        set_index: u64,
        instance: usize,
        name: String,
        path: String,
        sample_rate_hz: usize,
        num_channels: usize,
    },
}

fn main() -> io::Result<()> {
    let mut args = env::args().skip(1);
    let log_path = args
        .next()
        .unwrap_or_else(|| "aec3_diagnostics/aec3_diagnostics.log".to_string());

    let mut file = File::open(&log_path)?;
    let mut data = Vec::new();
    file.read_to_end(&mut data)?;

    let mut offset = 0usize;
    let mut record_index = 0usize;
    while offset + 4 <= data.len() {
        let len = u32::from_le_bytes([
            data[offset],
            data[offset + 1],
            data[offset + 2],
            data[offset + 3],
        ]) as usize;
        offset += 4;
        if offset + len > data.len() {
            eprintln!("truncated record at index {record_index}");
            break;
        }
        let payload = &data[offset..offset + len];
        match postcard::from_bytes::<DiagnosticRecord>(payload) {
            Ok(record) => match record {
                DiagnosticRecord::RawF32Slice {
                    level,
                    set_index,
                    instance,
                    name,
                    values,
                } => {
                    println!(
                        "[{record_index}] RawF32Slice {{ level: {level:?}, set_index: {set_index}, instance: {instance}, name: \"{name}\", values: {} }}",
                        format_slice(&values, |&v| v == 0.0)
                    );
                }
                DiagnosticRecord::RawI32Slice {
                    level,
                    set_index,
                    instance,
                    name,
                    values,
                } => {
                    println!(
                        "[{record_index}] RawI32Slice {{ level: {level:?}, set_index: {set_index}, instance: {instance}, name: \"{name}\", values: {} }}",
                        format_slice(&values, |&v| v == 0)
                    );
                }
                DiagnosticRecord::RawUsizeSlice {
                    level,
                    set_index,
                    instance,
                    name,
                    values,
                } => {
                    println!(
                        "[{record_index}] RawUsizeSlice {{ level: {level:?}, set_index: {set_index}, instance: {instance}, name: \"{name}\", values: {} }}",
                        format_slice(&values, |&v| v == 0)
                    );
                }
                _ => println!("[{record_index}] {record:#?}"),
            },
            Err(err) => println!("[{record_index}] decode error: {err}"),
        }
        offset += len;
        record_index += 1;
    }

    Ok(())
}

fn format_slice<T: std::fmt::Display>(slice: &[T], is_zero: impl Fn(&T) -> bool) -> String {
    if slice.is_empty() {
        return "[]".to_string();
    }

    if slice.iter().all(is_zero) {
        return format!("[all zeros, len={}]", slice.len());
    }

    if slice.len() <= 8 {
        let items: Vec<_> = slice.iter().map(|v| v.to_string()).collect();
        return format!("[{}]", items.join(", "));
    }

    let first_four: Vec<_> = slice[..4].iter().map(|v| v.to_string()).collect();
    let last_four: Vec<_> = slice[slice.len() - 4..].iter().map(|v| v.to_string()).collect();

    format!(
        "[{}, ..., {}, len={}]",
        first_four.join(", "),
        last_four.join(", "),
        slice.len()
    )
}