use super::StandardsError;
use crate::quality::{PESQEvaluator, PolqaBandwidth, PolqaEvaluator};
use serde::{Deserialize, Serialize};
use voirs_sdk::AudioBuffer;
pub struct IsoUsacValidator {
sample_rate: u32,
bandwidth_mode: UsacBandwidthMode,
target_bitrate: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UsacBandwidthMode {
NarrowBand,
WideBand,
SuperWideBand,
FullBand,
}
impl UsacBandwidthMode {
pub fn sample_rate(&self) -> u32 {
match self {
Self::NarrowBand => 8000,
Self::WideBand => 16000,
Self::SuperWideBand => 24000,
Self::FullBand => 48000,
}
}
pub fn bandwidth_hz(&self) -> u32 {
match self {
Self::NarrowBand => 4000,
Self::WideBand => 8000,
Self::SuperWideBand => 12000,
Self::FullBand => 20000,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UsacCompliance {
pub is_compliant: bool,
pub compliance_level: super::ComplianceLevel,
pub bitrate_compliant: bool,
pub actual_bitrate: u32,
pub target_bitrate: u32,
pub bandwidth_compliant: bool,
pub delay_compliant: bool,
pub delay_ms: f32,
pub quality_score: f32,
pub artifacts_detected: Vec<String>,
pub validation_messages: Vec<String>,
}
impl IsoUsacValidator {
pub fn new(
bandwidth_mode: UsacBandwidthMode,
target_bitrate: u32,
) -> Result<Self, StandardsError> {
if target_bitrate < 8 || target_bitrate > 384 {
return Err(StandardsError::ValidationFailed {
message: format!(
"USAC bitrate must be between 8 and 384 kbps, got {} kbps",
target_bitrate
),
});
}
let sample_rate = bandwidth_mode.sample_rate();
Ok(Self {
sample_rate,
bandwidth_mode,
target_bitrate,
})
}
pub fn validate_compliance(
&self,
audio: &AudioBuffer,
reference: Option<&AudioBuffer>,
) -> Result<UsacCompliance, StandardsError> {
let mut validation_messages = Vec::new();
let mut artifacts_detected = Vec::new();
let actual_bitrate = self.estimate_bitrate(audio)?;
let bitrate_compliant = self.check_bitrate_compliance(actual_bitrate);
if !bitrate_compliant {
validation_messages.push(format!(
"Bitrate {} kbps outside acceptable range for target {} kbps",
actual_bitrate, self.target_bitrate
));
}
let bandwidth_compliant = self.check_bandwidth_compliance(audio)?;
if !bandwidth_compliant {
validation_messages.push(format!(
"Audio bandwidth does not match {:?} specification",
self.bandwidth_mode
));
}
let delay_ms = self.estimate_delay(audio)?;
let delay_compliant = self.check_delay_compliance(delay_ms);
if !delay_compliant {
validation_messages.push(format!(
"Delay {} ms exceeds maximum allowed for USAC",
delay_ms
));
}
let quality_score = if let Some(ref_audio) = reference {
self.estimate_quality_score(audio, ref_audio)?
} else {
self.estimate_nr_quality_score(audio)?
};
artifacts_detected.extend(self.detect_artifacts(audio)?);
let is_compliant = bitrate_compliant
&& bandwidth_compliant
&& delay_compliant
&& quality_score >= 3.5
&& artifacts_detected.is_empty();
let compliance_level = if is_compliant {
super::ComplianceLevel::FullyCompliant
} else if bitrate_compliant && bandwidth_compliant {
super::ComplianceLevel::PartiallyCompliant
} else {
super::ComplianceLevel::NotCompliant
};
Ok(UsacCompliance {
is_compliant,
compliance_level,
bitrate_compliant,
actual_bitrate,
target_bitrate: self.target_bitrate,
bandwidth_compliant,
delay_compliant,
delay_ms,
quality_score,
artifacts_detected,
validation_messages,
})
}
fn estimate_bitrate(&self, audio: &AudioBuffer) -> Result<u32, StandardsError> {
let samples = audio.samples();
let duration_sec = samples.len() as f32 / self.sample_rate as f32;
let mut entropy = 0.0f32;
for window in samples.windows(2) {
let diff = (window[1] - window[0]).abs();
entropy += diff;
}
entropy /= samples.len() as f32;
let estimated_bitrate = (entropy * 200.0 + 32.0).clamp(8.0, 384.0) as u32;
Ok(estimated_bitrate)
}
fn check_bitrate_compliance(&self, actual_bitrate: u32) -> bool {
let tolerance = (self.target_bitrate as f32 * 0.1) as u32;
let lower = self.target_bitrate.saturating_sub(tolerance);
let upper = self.target_bitrate + tolerance;
actual_bitrate >= lower && actual_bitrate <= upper
}
fn check_bandwidth_compliance(&self, audio: &AudioBuffer) -> Result<bool, StandardsError> {
use scirs2_fft::{RealFftPlanner, RealToComplex};
let samples = audio.samples();
if samples.is_empty() {
return Ok(false);
}
let fft_size = 2048;
let mut planner = RealFftPlanner::<f32>::new();
let fft = planner.plan_fft_forward(fft_size);
let mut buffer: Vec<f32> = samples.iter().take(fft_size).copied().collect();
buffer.resize(fft_size, 0.0);
let mut spectrum = vec![scirs2_core::Complex::new(0.0, 0.0); fft_size / 2 + 1];
fft.process(&mut buffer, &mut spectrum);
let max_freq = self.bandwidth_mode.bandwidth_hz();
let bin_resolution = self.sample_rate as f32 / fft_size as f32;
let max_bin = (max_freq as f32 / bin_resolution) as usize;
let in_band_energy: f32 = spectrum
.iter()
.take(max_bin.min(spectrum.len()))
.map(|c| c.norm_sqr())
.sum();
let total_energy: f32 = spectrum.iter().map(|c| c.norm_sqr()).sum();
let in_band_ratio = in_band_energy / total_energy.max(1e-10);
Ok(in_band_ratio >= 0.95)
}
fn estimate_delay(&self, _audio: &AudioBuffer) -> Result<f32, StandardsError> {
let frame_size_ms = match self.bandwidth_mode {
UsacBandwidthMode::NarrowBand => 20.0,
UsacBandwidthMode::WideBand => 20.0,
UsacBandwidthMode::SuperWideBand => 40.0,
UsacBandwidthMode::FullBand => 40.0,
};
let algorithmic_delay_ms = 20.0;
Ok(frame_size_ms + algorithmic_delay_ms)
}
fn check_delay_compliance(&self, delay_ms: f32) -> bool {
delay_ms < 100.0
}
fn estimate_quality_score(
&self,
degraded: &AudioBuffer,
reference: &AudioBuffer,
) -> Result<f32, StandardsError> {
let quality_score = match self.bandwidth_mode {
UsacBandwidthMode::NarrowBand | UsacBandwidthMode::WideBand => {
let pesq_evaluator = if self.bandwidth_mode == UsacBandwidthMode::NarrowBand {
PESQEvaluator::new_narrowband()
} else {
PESQEvaluator::new_wideband()
}
.map_err(|e| StandardsError::ValidationFailed {
message: format!("PESQ init failed: {}", e),
})?;
let pesq_score = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
pesq_evaluator
.calculate_pesq(reference, degraded)
.await
.map_err(|e| StandardsError::ValidationFailed {
message: format!("PESQ calculation failed: {}", e),
})
})
})?;
let mos = ((pesq_score + 0.5) / 5.0) * 4.0 + 1.0;
mos.clamp(1.0, 5.0)
}
UsacBandwidthMode::SuperWideBand | UsacBandwidthMode::FullBand => {
let polqa_bandwidth = if self.bandwidth_mode == UsacBandwidthMode::SuperWideBand {
PolqaBandwidth::SuperWideBand
} else {
PolqaBandwidth::FullBand
};
let polqa_evaluator = PolqaEvaluator::new(polqa_bandwidth).map_err(|e| {
StandardsError::ValidationFailed {
message: format!("POLQA init failed: {}", e),
}
})?;
let polqa_score = tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(async {
polqa_evaluator
.calculate_polqa(reference, degraded)
.await
.map_err(|e| StandardsError::ValidationFailed {
message: format!("POLQA calculation failed: {}", e),
})
})
})?;
polqa_score.clamp(1.0, 5.0)
}
};
Ok(quality_score)
}
fn estimate_nr_quality_score(&self, audio: &AudioBuffer) -> Result<f32, StandardsError> {
let samples = audio.samples();
let mut quality_score: f32 = 5.0;
let clipping_ratio =
samples.iter().filter(|&&s| s.abs() > 0.99).count() as f32 / samples.len() as f32;
if clipping_ratio > 0.01 {
quality_score -= 1.0;
}
let avg_level: f32 = samples.iter().map(|&s| s.abs()).sum::<f32>() / samples.len() as f32;
if avg_level < 0.01 {
quality_score -= 0.5;
}
let dc_offset: f32 = samples.iter().sum::<f32>() / samples.len() as f32;
if dc_offset.abs() > 0.1 {
quality_score -= 0.3;
}
Ok(quality_score.max(1.0))
}
fn detect_artifacts(&self, audio: &AudioBuffer) -> Result<Vec<String>, StandardsError> {
let mut artifacts = Vec::new();
let samples = audio.samples();
if self.detect_pre_echo(samples) {
artifacts.push("Pre-echo detected".to_string());
}
if self.detect_tonal_artifacts(samples)? {
artifacts.push("Tonal artifacts detected".to_string());
}
if self.detect_bandwidth_artifacts(samples)? {
artifacts.push("Bandwidth limitation artifacts detected".to_string());
}
Ok(artifacts)
}
fn detect_pre_echo(&self, samples: &[f32]) -> bool {
let frame_size = 256;
for i in 1..samples.len() / frame_size {
let prev_energy: f32 = samples[(i - 1) * frame_size..i * frame_size]
.iter()
.map(|&s| s * s)
.sum();
let curr_energy: f32 = samples[i * frame_size..(i + 1) * frame_size]
.iter()
.map(|&s| s * s)
.sum();
if curr_energy > prev_energy * 100.0 && prev_energy > 1e-6 {
return true;
}
}
false
}
fn detect_tonal_artifacts(&self, _samples: &[f32]) -> Result<bool, StandardsError> {
Ok(false)
}
fn detect_bandwidth_artifacts(&self, _samples: &[f32]) -> Result<bool, StandardsError> {
Ok(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_usac_validator_creation() {
let validator = IsoUsacValidator::new(UsacBandwidthMode::WideBand, 64);
assert!(validator.is_ok());
}
#[test]
fn test_invalid_bitrate() {
let validator = IsoUsacValidator::new(UsacBandwidthMode::WideBand, 500);
assert!(validator.is_err());
}
#[test]
fn test_bandwidth_modes() {
assert_eq!(UsacBandwidthMode::NarrowBand.sample_rate(), 8000);
assert_eq!(UsacBandwidthMode::WideBand.sample_rate(), 16000);
assert_eq!(UsacBandwidthMode::SuperWideBand.sample_rate(), 24000);
assert_eq!(UsacBandwidthMode::FullBand.sample_rate(), 48000);
}
#[test]
fn test_bitrate_compliance() {
let validator = IsoUsacValidator::new(UsacBandwidthMode::WideBand, 64).unwrap();
assert!(validator.check_bitrate_compliance(64)); assert!(validator.check_bitrate_compliance(60)); assert!(validator.check_bitrate_compliance(70)); assert!(!validator.check_bitrate_compliance(50)); assert!(!validator.check_bitrate_compliance(80)); }
#[test]
fn test_delay_compliance() {
let validator = IsoUsacValidator::new(UsacBandwidthMode::WideBand, 64).unwrap();
assert!(validator.check_delay_compliance(50.0));
assert!(validator.check_delay_compliance(99.0));
assert!(!validator.check_delay_compliance(100.0));
assert!(!validator.check_delay_compliance(150.0));
}
#[test]
fn test_compliance_validation() {
let validator = IsoUsacValidator::new(UsacBandwidthMode::WideBand, 64).unwrap();
let audio = AudioBuffer::new(vec![0.1; 16000], 16000, 1);
let result = validator.validate_compliance(&audio, None);
assert!(result.is_ok());
let compliance = result.unwrap();
assert!(compliance.quality_score >= 1.0 && compliance.quality_score <= 5.0);
}
}