use crate::quality::mcd::MCDEvaluator;
use crate::quality::pesq::PESQEvaluator;
use crate::quality::stoi::STOIEvaluator;
use crate::EvaluationError;
use parking_lot::RwLock;
use scirs2_core::ndarray::Array1;
use scirs2_core::parallel_ops::*;
use scirs2_fft::{RealFftPlanner, RealToComplex};
use std::collections::HashMap;
use std::sync::Arc;
use voirs_sdk::AudioBuffer;
pub struct OptimizedQualityEvaluator {
pesq_cache: Arc<RwLock<HashMap<u32, Arc<PESQEvaluator>>>>,
stoi_cache: Arc<RwLock<HashMap<u32, Arc<STOIEvaluator>>>>,
mcd_cache: Arc<RwLock<HashMap<u32, Arc<MCDEvaluator>>>>,
fft_planner_pool: Arc<RwLock<RealFftPlanner<f32>>>,
frequency_mappings: Arc<RwLock<HashMap<u32, Vec<f32>>>>,
buffer_pool: Arc<RwLock<Vec<Vec<f32>>>>,
}
impl OptimizedQualityEvaluator {
pub fn new() -> Self {
Self {
pesq_cache: Arc::new(RwLock::new(HashMap::new())),
stoi_cache: Arc::new(RwLock::new(HashMap::new())),
mcd_cache: Arc::new(RwLock::new(HashMap::new())),
fft_planner_pool: Arc::new(RwLock::new(RealFftPlanner::<f32>::new())),
frequency_mappings: Arc::new(RwLock::new(HashMap::new())),
buffer_pool: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn calculate_pesq_optimized(
&self,
reference: &AudioBuffer,
degraded: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let sample_rate = reference.sample_rate();
let pesq_evaluator = {
let cache = self.pesq_cache.read();
if let Some(evaluator) = cache.get(&sample_rate) {
evaluator.clone()
} else {
drop(cache);
let mut cache = self.pesq_cache.write();
if let Some(evaluator) = cache.get(&sample_rate) {
evaluator.clone()
} else {
let evaluator = Arc::new(if sample_rate == 8000 {
PESQEvaluator::new_narrowband()?
} else {
PESQEvaluator::new_wideband()?
});
cache.insert(sample_rate, evaluator.clone());
evaluator
}
}
};
self.calculate_pesq_parallel(&pesq_evaluator, reference, degraded)
.await
}
async fn calculate_pesq_parallel(
&self,
evaluator: &PESQEvaluator,
reference: &AudioBuffer,
degraded: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let chunk_size = 16000; let ref_samples = reference.samples();
let deg_samples = degraded.samples();
if ref_samples.len() <= chunk_size {
return evaluator.calculate_pesq(reference, degraded).await;
}
let chunks: Vec<_> = (0..ref_samples.len()).step_by(chunk_size).collect();
let results: Result<Vec<f32>, _> = chunks
.par_iter()
.map(|&start| {
let end = (start + chunk_size).min(ref_samples.len());
let ref_chunk =
AudioBuffer::mono(ref_samples[start..end].to_vec(), reference.sample_rate());
let deg_chunk =
AudioBuffer::mono(deg_samples[start..end].to_vec(), degraded.sample_rate());
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current()
.block_on(evaluator.calculate_pesq(&ref_chunk, °_chunk))
})
})
.collect();
let scores = results?;
let total_samples = ref_samples.len() as f32;
let weighted_score = scores
.iter()
.enumerate()
.map(|(i, &score)| {
let start = i * chunk_size;
let end = ((i + 1) * chunk_size).min(ref_samples.len());
let weight = (end - start) as f32 / total_samples;
score * weight
})
.sum();
Ok(weighted_score)
}
pub async fn calculate_stoi_optimized(
&self,
reference: &AudioBuffer,
degraded: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let sample_rate = reference.sample_rate();
let stoi_evaluator = {
let cache = self.stoi_cache.read();
if let Some(evaluator) = cache.get(&sample_rate) {
evaluator.clone()
} else {
drop(cache);
let mut cache = self.stoi_cache.write();
if let Some(evaluator) = cache.get(&sample_rate) {
evaluator.clone()
} else {
let evaluator = Arc::new(STOIEvaluator::new(sample_rate)?);
cache.insert(sample_rate, evaluator.clone());
evaluator
}
}
};
self.calculate_stoi_simd(&stoi_evaluator, reference, degraded)
.await
}
async fn calculate_stoi_simd(
&self,
evaluator: &STOIEvaluator,
reference: &AudioBuffer,
degraded: &AudioBuffer,
) -> Result<f32, EvaluationError> {
evaluator.calculate_stoi(reference, degraded).await
}
pub async fn calculate_mcd_optimized(
&self,
reference: &AudioBuffer,
degraded: &AudioBuffer,
) -> Result<f32, EvaluationError> {
let sample_rate = reference.sample_rate();
let mcd_evaluator = {
let cache = self.mcd_cache.read();
if let Some(evaluator) = cache.get(&sample_rate) {
evaluator.clone()
} else {
drop(cache);
let mut cache = self.mcd_cache.write();
if let Some(evaluator) = cache.get(&sample_rate) {
evaluator.clone()
} else {
let evaluator = Arc::new(MCDEvaluator::new(sample_rate)?);
cache.insert(sample_rate, evaluator.clone());
evaluator
}
}
};
mcd_evaluator
.calculate_mcd_simple(reference, degraded)
.await
}
pub fn apply_filter_simd(&self, signal: &[f32], coeffs: &[f32]) -> Vec<f32> {
use crate::performance::simd;
if signal.len() > 1000 {
signal
.par_chunks(1000)
.map(|chunk| self.apply_filter_chunk(chunk, coeffs))
.flatten()
.collect()
} else {
self.apply_filter_chunk(signal, coeffs)
}
}
fn apply_filter_chunk(&self, signal: &[f32], coeffs: &[f32]) -> Vec<f32> {
let mut output = Vec::with_capacity(signal.len());
for i in 0..signal.len() {
let mut sum = 0.0;
for (j, &coeff) in coeffs.iter().enumerate() {
if i >= j {
sum += coeff * signal[i - j];
}
}
output.push(sum);
}
output
}
pub fn get_buffer(&self, size: usize) -> Vec<f32> {
let mut pool = self.buffer_pool.write();
for i in 0..pool.len() {
if pool[i].capacity() >= size {
let mut buffer = pool.swap_remove(i);
buffer.clear();
buffer.resize(size, 0.0);
return buffer;
}
}
vec![0.0; size]
}
pub fn return_buffer(&self, buffer: Vec<f32>) {
let mut pool = self.buffer_pool.write();
if pool.len() < 10 {
pool.push(buffer);
}
}
pub async fn evaluate_batch_optimized(
&self,
audio_pairs: &[(AudioBuffer, AudioBuffer)],
) -> Result<Vec<(f32, f32, f32)>, EvaluationError> {
let results: Result<Vec<_>, _> = audio_pairs
.par_iter()
.map(|(reference, degraded)| {
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
let pesq = self.calculate_pesq_optimized(reference, degraded).await?;
let stoi = self.calculate_stoi_optimized(reference, degraded).await?;
let mcd = self.calculate_mcd_optimized(reference, degraded).await?;
Ok::<(f32, f32, f32), EvaluationError>((pesq, stoi, mcd))
})
})
})
.collect();
results
}
pub fn clear_caches(&self) {
self.pesq_cache.write().clear();
self.stoi_cache.write().clear();
self.mcd_cache.write().clear();
self.frequency_mappings.write().clear();
self.buffer_pool.write().clear();
}
pub fn get_cache_stats(&self) -> CacheStats {
CacheStats {
pesq_cache_size: self.pesq_cache.read().len(),
stoi_cache_size: self.stoi_cache.read().len(),
mcd_cache_size: self.mcd_cache.read().len(),
buffer_pool_size: self.buffer_pool.read().len(),
}
}
}
impl Default for OptimizedQualityEvaluator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CacheStats {
pub pesq_cache_size: usize,
pub stoi_cache_size: usize,
pub mcd_cache_size: usize,
pub buffer_pool_size: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_optimized_evaluator_creation() {
let evaluator = OptimizedQualityEvaluator::new();
let stats = evaluator.get_cache_stats();
assert_eq!(stats.pesq_cache_size, 0);
assert_eq!(stats.stoi_cache_size, 0);
assert_eq!(stats.mcd_cache_size, 0);
}
#[tokio::test]
async fn test_buffer_pool() {
let evaluator = OptimizedQualityEvaluator::new();
let buffer1 = evaluator.get_buffer(1000);
assert_eq!(buffer1.len(), 1000);
evaluator.return_buffer(buffer1);
let buffer2 = evaluator.get_buffer(500);
assert_eq!(buffer2.len(), 500);
let stats = evaluator.get_cache_stats();
assert!(stats.buffer_pool_size <= 10);
}
#[test]
fn test_simd_filter() {
let evaluator = OptimizedQualityEvaluator::new();
let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let coeffs = vec![0.5, 0.3, 0.2];
let filtered = evaluator.apply_filter_simd(&signal, &coeffs);
assert_eq!(filtered.len(), signal.len());
assert!((filtered[0] - 0.5).abs() < 1e-6);
}
#[tokio::test]
async fn test_cache_reuse() {
let evaluator = OptimizedQualityEvaluator::new();
let audio1 = AudioBuffer::mono(vec![0.1; 16000], 16000);
let audio2 = AudioBuffer::mono(vec![0.2; 16000], 16000);
let _result1 = evaluator.calculate_pesq_optimized(&audio1, &audio2).await;
let stats1 = evaluator.get_cache_stats();
assert_eq!(stats1.pesq_cache_size, 1);
let _result2 = evaluator.calculate_pesq_optimized(&audio1, &audio2).await;
let stats2 = evaluator.get_cache_stats();
assert_eq!(stats2.pesq_cache_size, 1); }
}