#![forbid(unsafe_code)]
use std::f32::consts::PI;
fn read_pcm16(raw_data: &[u8], nchannels: usize) -> (Vec<f32>, usize) {
let mut samples = Vec::new();
for chunk in raw_data.chunks_exact(2) {
let chunk = chunk.try_into().unwrap();
let sample = i16::from_le_bytes(chunk) as i32;
let sample = (sample & 0xffff ^ 0x8000) - 0x8000;
samples.push(sample as f32);
}
let len = samples.len();
assert_eq!(len % nchannels, 0);
(samples, len / nchannels)
}
fn band_energy(
mut out: Option<&mut [f32]>,
ps: &mut [f32],
bands: &[usize],
input: &[f32],
channels: usize,
frames: usize,
window_size: usize,
step: usize,
downsample: usize,
) {
let mut window = vec![0f32; window_size];
let mut c = vec![0f32; window_size];
let mut s = vec![0f32; window_size];
let mut x = vec![0f32; channels * window_size];
let ps_sz = window_size / 2;
for xj in 0..window_size {
window[xj] = 0.5 - 0.5 * ((2.0 * PI / (window_size - 1) as f32) * xj as f32).cos();
}
for xj in 0..window_size {
c[xj] = (2.0 * PI / window_size as f32 * xj as f32).cos();
}
for xj in 0..window_size {
s[xj] = (2.0 * PI / window_size as f32 * xj as f32).sin();
}
for xi in 0..frames {
for ci in 0..channels {
for xk in 0..window_size {
x[ci * window_size + xk] = window[xk] * input[(xi * step + xk) * channels + ci]
}
}
let mut xj = 0;
for bi in 0..bands.len() - 1 {
let mut p: [f32; 2] = [0 as f32, 0.];
while xj < bands[bi + 1] {
for ci in 0..channels {
let mut ti = 0;
let mut im = 0f32;
let mut re = 0f32;
for xk in 0..window_size {
re += c[ti] * x[ci * window_size + xk];
im -= s[ti] * x[ci * window_size + xk];
ti += xj;
if ti >= window_size {
ti -= window_size;
}
}
re *= downsample as f32;
im *= downsample as f32;
ps[(xi * ps_sz + xj) * channels + ci] = re * re + im * im + 100000.0;
p[ci] += ps[(xi * ps_sz + xj) * channels + ci];
}
xj += 1;
}
if let Some(_out) = out.as_mut() {
_out[(xi * (bands.len() - 1) + bi) * channels] =
p[0] / (bands[bi + 1] - bands[bi]) as f32;
if channels == 2 {
_out[(xi * (bands.len() - 1) + bi) * channels + 1] =
p[1] / (bands[bi + 1] - bands[bi]) as f32;
}
}
}
}
}
const NBANDS: usize = 21;
const NFREQS: usize = 240;
static BANDS: [usize; NBANDS + 1] = [
0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 68, 80, 96, 120, 156, 200,
];
const TEST_WIN_SIZE: usize = 480;
const TEST_WIN_STEP: usize = 120;
#[derive(Debug, Copy, Clone)]
pub struct CompareParams {
pub sample_rate: usize,
pub stereo: bool,
}
impl Default for CompareParams {
fn default() -> Self {
Self {
sample_rate: 48000,
stereo: false,
}
}
}
pub struct CompareResult {
pub error: f64,
pub quality: f64,
}
pub fn opus_compare(params: CompareParams, true_file: &[u8], tested_file: &[u8]) -> CompareResult {
let nchannels = if params.stereo { 2 } else { 1 };
assert!(
matches!(params.sample_rate, 8000 | 12000 | 16000 | 24000 | 48000),
"Sampling rate must be 8000, 12000, 16000, 24000 or 48000"
);
let downsample = 48000 / params.sample_rate;
let ybands = match params.sample_rate {
8000 => 13,
12000 => 15,
16000 => 17,
24000 => 19,
_ => 21,
};
let yfreqs = NFREQS / downsample;
let (mut x, xlength) = read_pcm16(&true_file, 2);
if nchannels == 1 {
for xi in 0..xlength {
x[xi] = (x[2 * xi] + x[2 * xi + 1]) / 2.0;
}
}
let (y, ylength) = read_pcm16(&tested_file, nchannels);
assert_eq!(xlength, ylength * downsample, "Sample counts do not match");
assert!(
xlength >= TEST_WIN_SIZE,
"Insufficient sample data ({}<{})",
xlength,
TEST_WIN_SIZE
);
let nframes = (xlength - TEST_WIN_SIZE + TEST_WIN_STEP) / TEST_WIN_STEP;
let mut xb = vec![0f32; nframes * NBANDS * nchannels];
let mut x_bands = vec![0f32; nframes * NFREQS * nchannels];
let mut y_bands = vec![0f32; nframes * yfreqs * nchannels];
band_energy(
Some(&mut xb),
&mut x_bands,
&BANDS[..NBANDS + 1],
&x,
nchannels,
nframes,
TEST_WIN_SIZE,
TEST_WIN_STEP,
1,
);
drop(x);
band_energy(
None,
&mut y_bands,
&BANDS[..ybands + 1],
&y,
nchannels,
nframes,
TEST_WIN_SIZE / downsample,
TEST_WIN_STEP / downsample,
downsample,
);
drop(y);
for xi in 0..nframes {
for bi in 1..NBANDS {
for ci in 0..nchannels {
xb[(xi * NBANDS + bi) * nchannels + ci] +=
0.1f32 * xb[(xi * NBANDS + bi - 1) * nchannels + ci];
}
}
for bi in (0..NBANDS - 1).rev() {
for ci in 0..nchannels {
xb[(xi * NBANDS + bi) * nchannels + ci] +=
0.03f32 * xb[(xi * NBANDS + bi + 1) * nchannels + ci];
}
}
if xi > 0 {
for bi in 0..NBANDS {
for ci in 0..nchannels {
xb[(xi * NBANDS + bi) * nchannels + ci] +=
0.5f32 * xb[((xi - 1) * NBANDS + bi) * nchannels + ci]
}
}
}
if nchannels == 2 {
for bi in 0..NBANDS {
let l = xb[(xi * NBANDS + bi) * nchannels + 0];
let r = xb[(xi * NBANDS + bi) * nchannels + 1];
xb[(xi * NBANDS + bi) * nchannels + 0] += 0.01f32 * r;
xb[(xi * NBANDS + bi) * nchannels + 1] += 0.01f32 * l;
}
}
for bi in 0..ybands {
for xj in BANDS[bi]..BANDS[bi + 1] {
for ci in 0..nchannels {
x_bands[(xi * NFREQS + xj) * nchannels + ci] +=
0.1f32 * xb[(xi * NBANDS + bi) * nchannels + ci];
y_bands[(xi * yfreqs + xj) * nchannels + ci] +=
0.1f32 * xb[(xi * NBANDS + bi) * nchannels + ci];
}
}
}
}
for bi in 0..ybands {
for xj in BANDS[bi]..BANDS[bi + 1] {
for ci in 0..nchannels {
let mut xtmp = x_bands[xj * nchannels + ci];
let mut ytmp = y_bands[xj * nchannels + ci];
for xi in 1..nframes {
let xtmp2 = x_bands[(xi * NFREQS + xj) * nchannels + ci];
let ytmp2 = y_bands[(xi * yfreqs + xj) * nchannels + ci];
x_bands[(xi * NFREQS + xj) * nchannels + ci] += xtmp;
y_bands[(xi * yfreqs + xj) * nchannels + ci] += ytmp;
xtmp = xtmp2;
ytmp = ytmp2;
}
}
}
}
let max_compare = match params.sample_rate {
48000 => BANDS[NBANDS],
12000 => BANDS[ybands],
_ => BANDS[ybands] - 3,
};
let mut error = 0 as f64;
for xi in 0..nframes {
let mut err_frame = 0.0f64;
for bi in 0..ybands {
let mut err_band = 0.0f64;
for xj in BANDS[bi]..std::cmp::min(BANDS[bi + 1], max_compare) {
for ci in 0..nchannels {
let re = y_bands[(xi * yfreqs + xj) * nchannels + ci]
/ x_bands[(xi * NFREQS + xj) * nchannels + ci];
let mut im = re - re.ln() - 1.0;
if xj >= 79 && xj <= 81 {
im *= 0.1f32;
}
if xj == 80 {
im *= 0.1f32;
}
err_band += im as f64;
}
}
err_band /= ((BANDS[bi + 1] - BANDS[bi]) * nchannels) as f64;
err_frame += err_band * err_band;
}
err_frame /= NBANDS as f64;
err_frame *= err_frame;
error += err_frame * err_frame;
}
let error = (error / nframes as f64).powf(1.0 / 16.0);
let quality = 100.0 * (1.0 - 0.5 * (1.0 + error).ln() / 1.13f64.ln());
CompareResult { error, quality }
}