use crate::{energy_vad::EnergyVadContext, error::*, model::Model};
use aic_sdk_sys::{AicProcessorParameter::*, *};
use std::{ffi::CString, marker::PhantomData, ptr};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProcessorConfig {
pub sample_rate: u32,
pub block_size: usize,
pub variable_block_size: bool,
}
impl ProcessorConfig {
pub fn optimal(model: &Model) -> Self {
let sample_rate = model.optimal_sample_rate();
let block_size = model.optimal_block_size(sample_rate);
ProcessorConfig {
sample_rate,
block_size,
variable_block_size: false,
}
}
pub fn with_variable_block_size(mut self, variable_block_size: bool) -> Self {
self.variable_block_size = variable_block_size;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProcessorParameter {
Bypass,
EnhancementLevel,
}
impl From<ProcessorParameter> for AicProcessorParameter::Type {
fn from(parameter: ProcessorParameter) -> Self {
match parameter {
ProcessorParameter::Bypass => AIC_PROCESSOR_PARAMETER_BYPASS,
ProcessorParameter::EnhancementLevel => AIC_PROCESSOR_PARAMETER_ENHANCEMENT_LEVEL,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OtelConfig {
pub enable: bool,
pub session_id: Option<String>,
pub export_interval_ms: u32,
}
impl OtelConfig {
pub fn disabled() -> Self {
Self {
enable: false,
session_id: None,
export_interval_ms: 0,
}
}
pub fn enabled() -> Self {
Self {
enable: true,
session_id: None,
export_interval_ms: 0,
}
}
pub fn with_session_id(session_id: impl Into<String>) -> Self {
Self {
enable: true,
session_id: Some(session_id.into()),
export_interval_ms: 0,
}
}
}
pub struct ProcessorContext {
inner: *mut AicProcessorContext,
}
impl ProcessorContext {
pub(crate) fn new(ctx_ptr: *mut AicProcessorContext) -> Self {
Self { inner: ctx_ptr }
}
fn as_ptr(&self) -> *const AicProcessorContext {
self.inner as *const AicProcessorContext
}
pub fn set_parameter(&self, parameter: ProcessorParameter, value: f32) -> Result<(), AicError> {
let error_code =
unsafe { aic_processor_context_set_parameter(self.as_ptr(), parameter.into(), value) };
handle_error(error_code)
}
pub fn parameter(&self, parameter: ProcessorParameter) -> f32 {
let mut value: f32 = 0.0;
let error_code = unsafe {
aic_processor_context_get_parameter(self.as_ptr(), parameter.into(), &mut value)
};
assert_success(
error_code,
"`aic_processor_context_get_parameter` failed. This is a bug, please open an issue on GitHub for further investigation.",
);
value
}
pub fn audio_delay(&self) -> usize {
let mut delay: usize = 0;
let error_code =
unsafe { aic_processor_context_get_audio_delay(self.as_ptr(), &mut delay) };
assert_success(
error_code,
"`aic_processor_context_get_audio_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_processor_context_reset(self.as_ptr()) };
assert_success(
error_code,
"`aic_processor_context_reset` failed. This is a bug, please open an issue on GitHub for further investigation.",
);
}
pub fn update_bearer_token(&self, token: &str) -> Result<(), AicError> {
let c_token = CString::new(token).map_err(|_| AicError::LicenseFormatInvalid)?;
let error_code =
unsafe { aic_processor_context_update_bearer_token(self.as_ptr(), c_token.as_ptr()) };
handle_error(error_code)
}
}
impl Drop for ProcessorContext {
fn drop(&mut self) {
if !self.inner.is_null() {
unsafe { aic_processor_context_destroy(self.inner) };
}
}
}
unsafe impl Send for ProcessorContext {}
unsafe impl Sync for ProcessorContext {}
pub struct Processor<'a> {
inner: *mut AicProcessor,
initialized: bool,
marker: PhantomData<&'a [u8]>,
}
impl<'a> Processor<'a> {
pub fn new(model: &Model<'a>, license_key: &str) -> Result<Self, AicError> {
Self::create(model, license_key, None)
}
pub fn with_otel_config(
model: &Model<'a>,
license_key: &str,
otel_config: &OtelConfig,
) -> Result<Self, AicError> {
Self::create(model, license_key, Some(otel_config))
}
fn create(
model: &Model<'a>,
license_key: &str,
otel_config: Option<&OtelConfig>,
) -> Result<Self, AicError> {
unsafe { crate::set_sdk_id(2) };
let c_session_id = otel_config
.and_then(|o| o.session_id.as_deref())
.map(CString::new)
.transpose()
.map_err(|_| AicError::Internal)?;
let c_otel = otel_config.map(|o| AicOtelConfig {
enable: o.enable,
session_id: c_session_id.as_ref().map_or(ptr::null(), |s| s.as_ptr()),
export_interval_ms: o.export_interval_ms,
});
let c_otel_ptr = c_otel
.as_ref()
.map_or(ptr::null(), |o| o as *const AicOtelConfig);
let mut processor_ptr: *mut AicProcessor = ptr::null_mut();
let c_license_key =
CString::new(license_key).map_err(|_| AicError::LicenseFormatInvalid)?;
let error_code = unsafe {
aic_processor_create(
&mut processor_ptr,
model.as_ptr(),
c_license_key.as_ptr(),
c_otel_ptr,
)
};
handle_error(error_code)?;
assert!(
!processor_ptr.is_null(),
"C library returned success but null pointer"
);
Ok(Self {
inner: processor_ptr,
initialized: false,
marker: PhantomData,
})
}
pub fn with_config(mut self, config: &ProcessorConfig) -> Result<Self, AicError> {
self.initialize(config)?;
Ok(self)
}
pub fn context(&self) -> ProcessorContext {
let mut processor_context: *mut AicProcessorContext = ptr::null_mut();
let error_code =
unsafe { aic_processor_context_create(&mut processor_context, self.as_ptr()) };
assert!(handle_error(error_code).is_ok());
assert!(
!processor_context.is_null(),
"C library returned success but null pointer"
);
ProcessorContext::new(processor_context)
}
pub fn energy_vad_context(&mut self) -> EnergyVadContext {
let mut context_ptr: *mut AicEnergyVadContext = ptr::null_mut();
let error_code = unsafe { aic_energy_vad_context_create(&mut context_ptr, self.as_ptr()) };
assert!(handle_error(error_code).is_ok());
assert!(
!context_ptr.is_null(),
"C library returned success but null pointer"
);
EnergyVadContext::new(context_ptr)
}
pub fn initialize(&mut self, config: &ProcessorConfig) -> Result<(), AicError> {
let error_code = unsafe {
aic_processor_initialize(
self.inner,
config.sample_rate,
config.block_size,
config.variable_block_size,
)
};
handle_error(error_code)?;
self.initialized = true;
Ok(())
}
pub fn process(&mut self, audio: &mut [f32]) -> Result<(), AicError> {
if !self.initialized {
return Err(AicError::NotInitialized);
}
let audio_len = audio.len();
let error_code =
unsafe { aic_processor_process(self.inner, audio.as_mut_ptr(), audio_len) };
handle_error(error_code)
}
pub fn terminate_session(&mut self) {
let error_code = unsafe { aic_processor_terminate_session(self.inner) };
assert_success(
error_code,
"`aic_processor_terminate_session` failed. This is a bug, please open an issue on GitHub for further investigation.",
);
}
fn as_ptr(&self) -> *const AicProcessor {
self.inner as *const AicProcessor
}
}
impl<'a> Drop for Processor<'a> {
fn drop(&mut self) {
if !self.inner.is_null() {
unsafe { aic_processor_destroy(self.inner) };
}
}
}
unsafe impl<'a> Send for Processor<'a> {}
unsafe impl<'a> Sync for Processor<'a> {}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{license_key, test_model_path};
const TEST_MODEL_ID: &str = "rook-s-48khz";
fn load_test_model() -> Result<(Model<'static>, String), AicError> {
let model = Model::from_file(test_model_path(TEST_MODEL_ID))?;
Ok((model, license_key()))
}
#[test]
fn model_creation_and_basic_operations() {
dbg!(crate::get_sdk_version());
dbg!(crate::get_compatible_model_version());
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let mut audio = vec![0.0f32; config.block_size];
processor.process(&mut audio).unwrap();
}
#[test]
fn process_fixed_block_size() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let mut audio = vec![0.0f32; config.block_size];
processor.process(&mut audio).unwrap();
}
#[test]
fn process_variable_block_size() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model).with_variable_block_size(true);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let mut audio = vec![0.0f32; config.block_size];
processor.process(&mut audio).unwrap();
let mut audio = vec![0.0f32; 20];
processor.process(&mut audio).unwrap();
}
#[test]
fn process_variable_block_size_fails_when_disabled() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let mut audio = vec![0.0f32; config.block_size];
processor.process(&mut audio).unwrap();
let mut audio = vec![0.0f32; 20];
let result = processor.process(&mut audio);
assert_eq!(result, Err(AicError::AudioConfigMismatch));
}
#[test]
fn model_can_be_dropped_after_creating_processor() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
drop(model);
let mut audio = vec![0.0f32; config.block_size];
processor.process(&mut audio).unwrap();
}
#[test]
fn processor_is_send_and_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Send>() {}
assert_send::<Processor>();
assert_sync::<Processor>();
}
struct MyModel {
_model: Model<'static>,
_processor: Processor<'static>,
}
impl MyModel {
pub fn new() -> Self {
let (model, license_key) = load_test_model().unwrap();
let processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&ProcessorConfig::optimal(&model))
.unwrap();
MyModel {
_model: model,
_processor: processor,
}
}
}
#[test]
fn can_create_self_referential_structs_with_statics() {
let _model = MyModel::new();
}
}
#[doc(hidden)]
mod _compile_fail_tests {
}