fdk-aac-rust 0.2.1

Third-party Rust port of the Fraunhofer FDK AAC Codec Library for Android
//! AAC-ELD low-overlap analysis filterbank.

use std::fmt;
use std::sync::OnceLock;

const AAC_ENCODER_ROM: &str = include_str!(concat!(
    env!("FDK_AAC_UPSTREAM_DIR"),
    "/libAACenc/src/aacEnc_rom.cpp"
));

static COEFFICIENTS_512: OnceLock<Vec<f32>> = OnceLock::new();
static COEFFICIENTS_480: OnceLock<Vec<f32>> = OnceLock::new();

fn analysis_coefficients(frame_length: usize) -> Result<&'static [f32], EldAnalysisError> {
    let (name, expected, storage) = match frame_length {
        512 => ("ELDAnalysis512", 1536, &COEFFICIENTS_512),
        480 => ("ELDAnalysis480", 1440, &COEFFICIENTS_480),
        other => return Err(EldAnalysisError::UnsupportedFrameLength(other)),
    };
    let coefficients = storage.get_or_init(|| parse_wtc_array(name).unwrap_or_default());
    if coefficients.len() != expected {
        return Err(EldAnalysisError::InvalidCoefficientCount {
            frame_length,
            expected,
            actual: coefficients.len(),
        });
    }
    Ok(coefficients)
}

fn parse_wtc_array(name: &str) -> Option<Vec<f32>> {
    let start = AAC_ENCODER_ROM.find(name)?;
    let body_start = AAC_ENCODER_ROM[start..].find('{')? + start + 1;
    let body_end = AAC_ENCODER_ROM[body_start..].find("};")? + body_start;
    let mut body = &AAC_ENCODER_ROM[body_start..body_end];
    let mut result = Vec::new();
    while let Some(start) = body.find("WTC") {
        body = &body[start + 3..];
        let digits = body.find("0x")? + 2;
        if body.len() < digits + 8 {
            return None;
        }
        let bits = u32::from_str_radix(&body[digits..digits + 8], 16).ok()?;
        result.push(bits as i32 as f32 / 2_147_483_648.0);
        body = &body[digits + 8..];
    }
    Some(result)
}

#[derive(Debug, Clone)]
pub struct EldAnalysisFilterbank {
    frame_length: usize,
    previous: Vec<f32>,
    overlap: Vec<f32>,
    dct_iv_kernel: Vec<f32>,
}

impl EldAnalysisFilterbank {
    pub fn new(frame_length: usize) -> Result<Self, EldAnalysisError> {
        analysis_coefficients(frame_length)?;
        let phase = std::f32::consts::PI / frame_length as f32;
        let normalization = (2.0 / frame_length as f32).sqrt();
        let mut dct_iv_kernel = Vec::with_capacity(frame_length * frame_length);
        for output in 0..frame_length {
            for input in 0..frame_length {
                dct_iv_kernel.push(
                    (phase * (input as f32 + 0.5) * (output as f32 + 0.5)).cos() * normalization,
                );
            }
        }
        Ok(Self {
            frame_length,
            previous: vec![0.0; frame_length],
            overlap: vec![0.0; 2 * frame_length],
            dct_iv_kernel,
        })
    }

    pub fn frame_length(&self) -> usize {
        self.frame_length
    }

    pub fn reset(&mut self) {
        self.previous.fill(0.0);
        self.overlap.fill(0.0);
    }

    pub fn analyze(&mut self, input: &[f32]) -> Result<Vec<f32>, EldAnalysisError> {
        if input.len() != self.frame_length {
            return Err(EldAnalysisError::InputLengthMismatch {
                expected: self.frame_length,
                actual: input.len(),
            });
        }
        if input.iter().any(|sample| !sample.is_finite()) {
            return Err(EldAnalysisError::NonFiniteInput);
        }
        let n = self.frame_length;
        let mut time = Vec::with_capacity(2 * n);
        time.extend_from_slice(&self.previous);
        time.extend_from_slice(input);
        let window = analysis_coefficients(n)?;
        let mut folded = vec![0.0f32; n];
        for i in 0..n / 4 {
            let upper_left = time[n + 3 * n / 4 - 1 - i];
            let upper_right = time[n + 3 * n / 4 + i];
            let z0 = upper_left * window[n / 2 - 1 - i] + upper_right * window[n / 2 + i];
            let out = 0.5
                * (upper_left * window[n + n / 2 - 1 - i] + upper_right * window[n + n / 2 + i])
                + 0.25 * self.overlap[n / 2 + i] * window[2 * n + i];
            self.overlap[n / 2 + i] = self.overlap[i];
            self.overlap[i] = z0;
            folded[i] = self.overlap[n / 2 + i]
                + 0.25 * self.overlap[n + n / 2 - 1 - i] * window[2 * n + n / 2 + i];
            folded[n - 1 - i] = out;
            self.overlap[n + n / 2 - 1 - i] = out;
        }
        for i in n / 4..n / 2 {
            let upper_left = time[n + 3 * n / 4 - 1 - i];
            let z0 = upper_left * window[n / 2 - 1 - i];
            let out = 0.5 * upper_left * window[n + n / 2 - 1 - i]
                + 0.25 * self.overlap[n / 2 + i] * window[2 * n + i];
            self.overlap[n / 2 + i] = self.overlap[i] + time[n - n / 4 + i] * window[n / 2 + i];
            self.overlap[i] = z0;
            folded[i] = self.overlap[n / 2 + i]
                + 0.25 * self.overlap[n + n / 2 - 1 - i] * window[2 * n + n / 2 + i];
            folded[n - 1 - i] = out;
            self.overlap[n + n / 2 - 1 - i] = out;
        }
        let mut spectrum = vec![0.0f32; n];
        for (output, value) in spectrum.iter_mut().enumerate() {
            let row = &self.dct_iv_kernel[output * n..(output + 1) * n];
            *value = folded
                .iter()
                .zip(row)
                .map(|(&sample, &coefficient)| sample * coefficient)
                .sum();
        }
        self.previous.copy_from_slice(input);
        Ok(spectrum)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EldAnalysisError {
    UnsupportedFrameLength(usize),
    InvalidCoefficientCount {
        frame_length: usize,
        expected: usize,
        actual: usize,
    },
    InputLengthMismatch {
        expected: usize,
        actual: usize,
    },
    NonFiniteInput,
}

impl fmt::Display for EldAnalysisError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::UnsupportedFrameLength(length) => {
                write!(f, "unsupported AAC-ELD analysis length {length}")
            }
            Self::InvalidCoefficientCount {
                frame_length,
                expected,
                actual,
            } => write!(
                f,
                "AAC-ELD {frame_length} analysis window has {actual} coefficients, expected {expected}"
            ),
            Self::InputLengthMismatch { expected, actual } => {
                write!(f, "AAC-ELD analysis expected {expected} samples, got {actual}")
            }
            Self::NonFiniteInput => write!(f, "AAC-ELD analysis input contains NaN or infinity"),
        }
    }
}

impl std::error::Error for EldAnalysisError {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn loads_eld_analysis_rom_and_processes_both_frame_lengths() {
        for length in [480, 512] {
            assert_eq!(analysis_coefficients(length).unwrap().len(), 3 * length);
            let mut filterbank = EldAnalysisFilterbank::new(length).unwrap();
            assert!(filterbank
                .analyze(&vec![0.0; length])
                .unwrap()
                .iter()
                .all(|sample| *sample == 0.0));
            let mut impulse = vec![0.0; length];
            impulse[length / 2] = 1.0;
            assert!(filterbank
                .analyze(&impulse)
                .unwrap()
                .iter()
                .any(|sample| *sample != 0.0));
        }
    }
}