use crate::{error::*, model::Model};
use aic_sdk_sys::{AicProcessorParameter::*, *};
use std::{ffi::CString, marker::PhantomData, ptr, sync::Once};
static SET_WRAPPER_ID: Once = Once::new();
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ProcessorConfig {
pub sample_rate: u32,
pub num_channels: u16,
pub num_frames: usize,
pub allow_variable_frames: bool,
}
impl ProcessorConfig {
pub fn optimal(model: &Model) -> Self {
let sample_rate = model.optimal_sample_rate();
let num_frames = model.optimal_num_frames(sample_rate);
ProcessorConfig {
sample_rate,
num_channels: 1,
num_frames,
allow_variable_frames: false,
}
}
pub fn with_num_channels(mut self, num_channels: u16) -> Self {
self.num_channels = num_channels;
self
}
pub fn with_allow_variable_frames(mut self, allow_variable_frames: bool) -> Self {
self.allow_variable_frames = allow_variable_frames;
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_const_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_const_ptr(), parameter.into(), value)
};
handle_error(error_code)
}
pub fn parameter(&self, parameter: ProcessorParameter) -> Result<f32, AicError> {
let mut value: f32 = 0.0;
let error_code = unsafe {
aic_processor_context_get_parameter(self.as_const_ptr(), parameter.into(), &mut value)
};
handle_error(error_code)?;
Ok(value)
}
pub fn output_delay(&self) -> usize {
let mut delay: usize = 0;
let error_code =
unsafe { aic_processor_context_get_output_delay(self.as_const_ptr(), &mut delay) };
assert_success(
error_code,
"`aic_get_output_delay` failed. This is a bug, please open an issue on GitHub for further investigation.",
);
delay
}
pub fn reset(&self) -> Result<(), AicError> {
let error_code = unsafe { aic_processor_context_reset(self.as_const_ptr()) };
handle_error(error_code)
}
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_const_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,
num_channels: Option<u16>,
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> {
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);
SET_WRAPPER_ID.call_once(|| unsafe {
aic_set_sdk_wrapper_id(2);
});
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_const_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,
num_channels: None,
marker: PhantomData,
})
}
pub fn with_config(mut self, config: &ProcessorConfig) -> Result<Self, AicError> {
self.initialize(config)?;
Ok(self)
}
pub fn processor_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_const_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 vad_context(&self) -> crate::VadContext {
let mut vad_ptr: *mut AicVadContext = ptr::null_mut();
let error_code = unsafe { aic_vad_context_create(&mut vad_ptr, self.as_const_ptr()) };
assert!(handle_error(error_code).is_ok());
assert!(
!vad_ptr.is_null(),
"C library returned success but null pointer"
);
crate::vad::VadContext::new(vad_ptr)
}
pub fn initialize(&mut self, config: &ProcessorConfig) -> Result<(), AicError> {
let error_code = unsafe {
aic_processor_initialize(
self.inner,
config.sample_rate,
config.num_channels,
config.num_frames,
config.allow_variable_frames,
)
};
handle_error(error_code)?;
self.num_channels = Some(config.num_channels);
Ok(())
}
#[allow(clippy::doc_overindented_list_items)]
pub fn process_planar<V: AsMut<[f32]>>(&mut self, audio: &mut [V]) -> Result<(), AicError> {
const MAX_CHANNELS: u16 = 16;
let Some(num_channels) = self.num_channels else {
return Err(AicError::ProcessorNotInitialized);
};
if audio.len() != num_channels as usize {
return Err(AicError::AudioConfigMismatch);
}
if num_channels > MAX_CHANNELS {
return Err(AicError::AudioConfigUnsupported);
}
let num_frames = if audio.is_empty() {
0
} else {
audio[0].as_mut().len()
};
let mut audio_ptrs = [std::ptr::null_mut::<f32>(); MAX_CHANNELS as usize];
for (i, channel) in audio.iter_mut().enumerate() {
if channel.as_mut().len() != num_frames {
return Err(AicError::AudioConfigMismatch);
}
audio_ptrs[i] = channel.as_mut().as_mut_ptr();
}
let error_code = unsafe {
aic_processor_process_planar(self.inner, audio_ptrs.as_ptr(), num_channels, num_frames)
};
handle_error(error_code)
}
#[allow(clippy::doc_overindented_list_items)]
pub fn process_interleaved(&mut self, audio: &mut [f32]) -> Result<(), AicError> {
let Some(num_channels) = self.num_channels else {
return Err(AicError::ProcessorNotInitialized);
};
if !audio.len().is_multiple_of(num_channels as usize) {
return Err(AicError::AudioConfigMismatch);
}
let num_frames = audio.len() / num_channels as usize;
let error_code = unsafe {
aic_processor_process_interleaved(
self.inner,
audio.as_mut_ptr(),
num_channels,
num_frames,
)
};
handle_error(error_code)
}
#[allow(clippy::doc_overindented_list_items)]
pub fn process_sequential(&mut self, audio: &mut [f32]) -> Result<(), AicError> {
let Some(num_channels) = self.num_channels else {
return Err(AicError::ProcessorNotInitialized);
};
if !audio.len().is_multiple_of(num_channels as usize) {
return Err(AicError::AudioConfigMismatch);
}
let num_frames = audio.len() / num_channels as usize;
let error_code = unsafe {
aic_processor_process_sequential(
self.inner,
audio.as_mut_ptr(),
num_channels,
num_frames,
)
};
handle_error(error_code)
}
fn as_const_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 std::{
fs,
path::{Path, PathBuf},
sync::{Mutex, OnceLock},
};
fn download_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
fn find_existing_model(target_dir: &Path) -> Option<PathBuf> {
let entries = fs::read_dir(target_dir).ok()?;
for entry in entries.flatten() {
let path = entry.path();
if path
.file_name()
.and_then(|n| n.to_str())
.map(|name| name.contains("rook_s_48khz") && name.ends_with(".aicmodel"))
.unwrap_or(false)
&& path.is_file()
{
return Some(path);
}
}
None
}
fn get_rook_s_48khz() -> Result<PathBuf, AicError> {
let target_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("target");
if let Some(existing) = find_existing_model(&target_dir) {
return Ok(existing);
}
let _guard = download_lock().lock().unwrap();
if let Some(existing) = find_existing_model(&target_dir) {
return Ok(existing);
}
if cfg!(feature = "download-model") {
Model::download("rook-s-48khz", target_dir)
} else {
panic!(
"Model `rook-s-48khz` not found in {} and `download-model` feature is disabled",
target_dir.display()
);
}
}
fn load_test_model() -> Result<(Model<'static>, String), AicError> {
let license_key = std::env::var("AIC_SDK_LICENSE")
.expect("AIC_SDK_LICENSE environment variable must be set for tests");
let model_path = get_rook_s_48khz()?;
let model = Model::from_file(&model_path)?;
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).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let num_channels = config.num_channels as usize;
let mut audio = vec![vec![0.0f32; config.num_frames]; num_channels];
let mut audio_refs: Vec<&mut [f32]> =
audio.iter_mut().map(|ch| ch.as_mut_slice()).collect();
processor.process_planar(&mut audio_refs).unwrap();
}
#[test]
fn process_interleaved_fixed_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let num_channels = config.num_channels as usize;
let mut audio = vec![0.0f32; num_channels * config.num_frames];
processor.process_interleaved(&mut audio).unwrap();
}
#[test]
fn process_planar_fixed_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let mut left = vec![0.0f32; config.num_frames];
let mut right = vec![0.0f32; config.num_frames];
let mut audio = [left.as_mut_slice(), right.as_mut_slice()];
processor.process_planar(&mut audio).unwrap();
}
#[test]
fn process_sequential_fixed_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let num_channels = config.num_channels as usize;
let mut audio = vec![0.0f32; num_channels * config.num_frames];
processor.process_sequential(&mut audio).unwrap();
}
#[test]
fn process_interleaved_variable_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model)
.with_num_channels(2)
.with_allow_variable_frames(true);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let num_channels = config.num_channels as usize;
let mut audio = vec![0.0f32; num_channels * config.num_frames];
processor.process_interleaved(&mut audio).unwrap();
let mut audio = vec![0.0f32; num_channels * 20];
processor.process_interleaved(&mut audio).unwrap();
}
#[test]
fn process_planar_variable_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model)
.with_num_channels(2)
.with_allow_variable_frames(true);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let mut left = vec![0.0f32; config.num_frames];
let mut right = vec![0.0f32; config.num_frames];
let mut audio = [left.as_mut_slice(), right.as_mut_slice()];
processor.process_planar(&mut audio).unwrap();
let mut left = vec![0.0f32; 20];
let mut right = vec![0.0f32; 20];
let mut audio = [left.as_mut_slice(), right.as_mut_slice()];
processor.process_planar(&mut audio).unwrap();
}
#[test]
fn process_sequential_variable_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model)
.with_num_channels(2)
.with_allow_variable_frames(true);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let num_channels = config.num_channels as usize;
let mut audio = vec![0.0f32; num_channels * config.num_frames];
processor.process_sequential(&mut audio).unwrap();
let mut audio = vec![0.0f32; num_channels * 20];
processor.process_sequential(&mut audio).unwrap();
}
#[test]
fn process_interleaved_variable_frames_fails_without_allow_variable_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let num_channels = config.num_channels as usize;
let mut audio = vec![0.0f32; num_channels * config.num_frames];
processor.process_interleaved(&mut audio).unwrap();
let mut audio = vec![0.0f32; num_channels * 20];
let result = processor.process_interleaved(&mut audio);
assert_eq!(result, Err(AicError::AudioConfigMismatch));
}
#[test]
fn process_planar_variable_frames_fails_without_allow_variable_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let mut left = vec![0.0f32; config.num_frames];
let mut right = vec![0.0f32; config.num_frames];
let mut audio = [left.as_mut_slice(), right.as_mut_slice()];
processor.process_planar(&mut audio).unwrap();
let mut left = vec![0.0f32; 20];
let mut right = vec![0.0f32; 20];
let mut audio = [left.as_mut_slice(), right.as_mut_slice()];
let result = processor.process_planar(&mut audio);
assert_eq!(result, Err(AicError::AudioConfigMismatch));
}
#[test]
fn process_sequential_variable_frames_fails_without_allow_variable_frames() {
let (model, license_key) = load_test_model().unwrap();
let config = ProcessorConfig::optimal(&model).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
let num_channels = config.num_channels as usize;
let mut audio = vec![0.0f32; num_channels * config.num_frames];
processor.process_sequential(&mut audio).unwrap();
let mut audio = vec![0.0f32; num_channels * 20];
let result = processor.process_sequential(&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).with_num_channels(2);
let mut processor = Processor::new(&model, &license_key)
.unwrap()
.with_config(&config)
.unwrap();
drop(model);
let num_channels = config.num_channels as usize;
let mut audio = vec![vec![0.0f32; config.num_frames]; num_channels];
let mut audio_refs: Vec<&mut [f32]> =
audio.iter_mut().map(|ch| ch.as_mut_slice()).collect();
processor.process_planar(&mut audio_refs).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 {
}