use crate::{VadParameter, error::*};
use aic_sdk_sys::*;
pub struct EnergyVadContext {
inner: *mut AicEnergyVadContext,
}
impl EnergyVadContext {
pub(crate) fn new(context_ptr: *mut AicEnergyVadContext) -> Self {
Self { inner: context_ptr }
}
fn as_ptr(&self) -> *const AicEnergyVadContext {
self.inner as *const AicEnergyVadContext
}
pub fn is_speech_detected(&self) -> bool {
let mut value: bool = false;
let error_code =
unsafe { aic_energy_vad_context_is_speech_detected(self.as_ptr(), &mut value) };
assert!(handle_error(error_code).is_ok());
value
}
pub fn set_parameter(&self, parameter: VadParameter, value: f32) -> Result<(), AicError> {
let error_code =
unsafe { aic_energy_vad_context_set_parameter(self.as_ptr(), parameter.into(), value) };
handle_error(error_code)
}
pub fn parameter(&self, parameter: VadParameter) -> f32 {
let mut value: f32 = 0.0;
let error_code = unsafe {
aic_energy_vad_context_get_parameter(self.as_ptr(), parameter.into(), &mut value)
};
assert_success(
error_code,
"`aic_energy_vad_context_get_parameter` failed. This is a bug, please open an issue on GitHub for further investigation.",
);
value
}
pub fn prediction_delay(&self) -> usize {
let mut delay: usize = 0;
let error_code =
unsafe { aic_energy_vad_context_get_prediction_delay(self.as_ptr(), &mut delay) };
assert_success(
error_code,
"`aic_energy_vad_context_get_prediction_delay` failed. This is a bug, please open an issue on GitHub for further investigation.",
);
delay
}
pub fn reset(&self) {
let error_code = unsafe { aic_energy_vad_context_reset(self.as_ptr()) };
assert_success(
error_code,
"`aic_energy_vad_context_reset` failed. This is a bug, please open an issue on GitHub for further investigation.",
);
}
}
impl Drop for EnergyVadContext {
fn drop(&mut self) {
if !self.inner.is_null() {
unsafe { aic_energy_vad_context_destroy(self.inner) };
}
}
}
unsafe impl Send for EnergyVadContext {}
unsafe impl Sync for EnergyVadContext {}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{license_key, test_model_path};
use crate::{Model, Processor, ProcessorConfig, ProcessorParameter};
fn model() -> Model<'static> {
Model::from_file(test_model_path("quail-l-16khz")).unwrap()
}
#[test]
fn shared_parameters_reset_and_context_lifetime() {
let model = model();
let config = ProcessorConfig::optimal(&model);
let mut processor = Processor::new(&model, &license_key())
.unwrap()
.with_config(&config)
.unwrap();
let vad = processor.energy_vad_context();
let other = processor.energy_vad_context();
assert!(!vad.is_speech_detected());
assert_eq!(vad.prediction_delay(), processor.context().audio_delay());
for (parameter, value) in [
(VadParameter::Sensitivity, 7.0),
(VadParameter::SpeechHoldDuration, 0.08),
(VadParameter::MinimumSpeechDuration, 0.02),
] {
vad.set_parameter(parameter, value).unwrap();
assert_eq!(other.parameter(parameter), value);
}
for value in [0.5, 16.0, f32::NAN] {
assert_eq!(
vad.set_parameter(VadParameter::Sensitivity, value),
Err(AicError::ParameterOutOfRange)
);
}
processor
.process(&mut vec![0.0; config.block_size])
.unwrap();
assert!(!vad.is_speech_detected());
drop(other);
drop(processor);
drop(model);
vad.reset();
assert!(!vad.is_speech_detected());
assert_eq!(vad.parameter(VadParameter::Sensitivity), 7.0);
}
#[test]
fn detects_speech_while_bypassed_and_supports_cross_thread_control() {
let model = model();
let audio = audio_file::read::<f32>(
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/data/test_signal.wav"),
audio_file::ReadConfig {
num_channels: Some(1),
..Default::default()
},
)
.unwrap();
let config = ProcessorConfig {
sample_rate: audio.sample_rate,
block_size: model.optimal_block_size(audio.sample_rate),
variable_block_size: false,
};
let mut processor = Processor::new(&model, &license_key())
.unwrap()
.with_config(&config)
.unwrap();
let vad = processor.energy_vad_context();
let control = processor.energy_vad_context();
processor
.context()
.set_parameter(ProcessorParameter::Bypass, 1.0)
.unwrap();
std::thread::scope(|scope| {
let worker = scope.spawn(move || {
let mut heard_speech = false;
let mut samples = audio.samples_interleaved;
for block in samples.chunks_exact_mut(config.block_size) {
processor.process(block).unwrap();
heard_speech |= vad.is_speech_detected();
}
assert!(heard_speech);
vad.reset();
assert!(!vad.is_speech_detected());
});
control
.set_parameter(VadParameter::Sensitivity, 6.0)
.unwrap();
let _ = control.is_speech_detected();
worker.join().unwrap();
});
}
#[cfg(feature = "async")]
#[tokio::test]
async fn async_processor_exposes_energy_vad_context() {
let model = model();
let config = ProcessorConfig::optimal(&model);
let processor = crate::ProcessorAsync::new(&model, &license_key())
.unwrap()
.with_config(&config)
.await
.unwrap();
let vad = processor.energy_vad_context().await;
vad.set_parameter(VadParameter::Sensitivity, 8.0).unwrap();
assert_eq!(vad.parameter(VadParameter::Sensitivity), 8.0);
vad.reset();
assert!(!vad.is_speech_detected());
}
}