Skip to main content

aic_sdk_sys/
lib.rs

1//! Raw FFI bindings to the AIC library.
2//!
3//! This module contains automatically generated bindings from the C header file.
4//! The bindings are generated using bindgen and may contain naming conventions
5//! that don't match Rust standards, which is expected for FFI code.
6//!
7//! # Runtime linking
8//!
9//! Enable the `runtime-linking` feature to load the AIC dynamic library at runtime instead of
10//! linking to `libaic` at build time. The library is loaded automatically the first time any
11//! `aic_*` function is called, using the platform's default name (`libaic.so`, `libaic.dylib`,
12//! or `aic.dll`) resolved through the normal dynamic loader search path (`LD_LIBRARY_PATH`,
13//! rpath, system directories, …).
14//!
15//! To load a specific file instead, call [`load_library`] with an explicit path before the first
16//! SDK call:
17//!
18//! ```no_run
19//! # #[cfg(feature = "runtime-linking")]
20//! # unsafe fn example() -> Result<(), aic_sdk_sys::DynamicLoadingError> {
21//! unsafe { aic_sdk_sys::load_library("/path/to/libaic.so")? };
22//! # Ok(())
23//! # }
24//! ```
25
26#![allow(non_upper_case_globals)]
27#![allow(non_camel_case_types)]
28#![allow(non_snake_case)]
29#![allow(dead_code)]
30#![allow(clippy::all)]
31
32include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
33
34#[cfg(not(feature = "runtime-linking"))]
35unsafe extern "C" {
36    /// Sets the SDK wrapper ID.
37    ///
38    /// This function is not included in the C SDK's header file, but it is part of the library.
39    pub fn aic_set_sdk_wrapper_id(id: u32);
40}
41
42#[cfg(feature = "runtime-linking")]
43mod runtime_linking {
44    use super::*;
45    use libloading::Library;
46    use std::{
47        ffi::{c_char, c_float, c_uint},
48        fmt,
49        path::{Path, PathBuf},
50        sync::OnceLock,
51    };
52
53    static AIC_LIBRARY: OnceLock<LoadedLibrary> = OnceLock::new();
54
55    /// Error returned when loading the AIC dynamic library at runtime fails.
56    #[derive(Debug)]
57    pub enum DynamicLoadingError {
58        /// A dynamic library was already loaded. The active library cannot be replaced safely.
59        AlreadyLoaded,
60        /// Opening the dynamic library failed.
61        OpenLibrary {
62            /// Path that was passed to [`load_library`].
63            path: PathBuf,
64            /// Error reported by the platform dynamic loader.
65            source: libloading::Error,
66        },
67        /// A required AIC symbol could not be found in the dynamic library.
68        LoadSymbol {
69            /// Name of the missing symbol.
70            symbol: &'static str,
71            /// Error reported by the platform dynamic loader.
72            source: libloading::Error,
73        },
74    }
75
76    impl fmt::Display for DynamicLoadingError {
77        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78            match self {
79                Self::AlreadyLoaded => write!(f, "the AIC dynamic library is already loaded"),
80                Self::OpenLibrary { path, source } => write!(
81                    f,
82                    "failed to load AIC dynamic library '{}': {source}",
83                    path.display()
84                ),
85                Self::LoadSymbol { symbol, source } => {
86                    write!(f, "failed to load AIC symbol '{symbol}': {source}")
87                }
88            }
89        }
90    }
91
92    impl std::error::Error for DynamicLoadingError {
93        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
94            match self {
95                Self::AlreadyLoaded => None,
96                Self::OpenLibrary { source, .. } | Self::LoadSymbol { source, .. } => Some(source),
97            }
98        }
99    }
100
101    struct LoadedLibrary {
102        _library: Library,
103        symbols: Symbols,
104    }
105
106    impl LoadedLibrary {
107        unsafe fn load(path: &Path) -> Result<Self, DynamicLoadingError> {
108            let library = unsafe { Library::new(path) }.map_err(|source| {
109                DynamicLoadingError::OpenLibrary {
110                    path: path.to_path_buf(),
111                    source,
112                }
113            })?;
114            let symbols = unsafe { Symbols::load(&library)? };
115
116            Ok(Self {
117                _library: library,
118                symbols,
119            })
120        }
121    }
122
123    fn symbols() -> &'static Symbols {
124        &AIC_LIBRARY
125            .get_or_init(|| {
126                // No explicit `load_library` happened, so load the platform's default `aic`
127                // library by name and let the OS dynamic loader resolve it.
128                let name = libloading::library_filename("aic");
129                // SAFETY: opening the platform `aic` library; the operator is responsible for
130                // making an ABI-compatible build discoverable on the loader search path.
131                unsafe { LoadedLibrary::load(Path::new(&name)) }.unwrap_or_else(|err| {
132                    panic!(
133                        "{err}. Make it discoverable on the dynamic loader search path (e.g. \
134                         LD_LIBRARY_PATH, rpath, or a system install), or call \
135                         `aic_sdk_sys::load_library` with an explicit path before using the SDK."
136                    )
137                })
138            })
139            .symbols
140    }
141
142    macro_rules! aic_symbols {
143        ($(
144            fn $name:ident($($arg:ident: $arg_ty:ty),* $(,)?) $(-> $ret:ty)?;
145        )+) => {
146            struct Symbols {
147                $(
148                    $name: unsafe extern "C" fn($($arg_ty),*) $(-> $ret)?,
149                )+
150            }
151
152            impl Symbols {
153                unsafe fn load(library: &Library) -> Result<Self, DynamicLoadingError> {
154                    $(
155                        let $name = *unsafe {
156                            library.get(concat!(stringify!($name), "\0").as_bytes())
157                        }
158                        .map_err(|source| DynamicLoadingError::LoadSymbol {
159                            symbol: stringify!($name),
160                            source,
161                        })?;
162                    )+
163
164                    Ok(Self { $($name,)+ })
165                }
166            }
167
168            $(
169                pub unsafe fn $name($($arg: $arg_ty),*) $(-> $ret)? {
170                    unsafe { (symbols().$name)($($arg),*) }
171                }
172            )+
173        };
174    }
175
176    // This table mirrors the function declarations in `include/aic.h` (plus the headerless
177    // `aic_set_sdk_wrapper_id`). It is maintained by hand because bindgen's generated `extern`
178    // declarations are blocklisted in the `runtime-linking` mode. Whenever `aic.h` changes, update the signatures
179    // below to match — a wrong signature compiles but is undefined behavior at call time. The
180    // `check-header` CI job fails when `aic.h` drifts from the SDK release, and the `linking` CI
181    // job runs the `basic_usage` example against a real `libaic` to exercise these symbols.
182    aic_symbols! {
183        fn aic_get_sdk_version() -> *const c_char;
184        fn aic_get_compatible_model_version() -> c_uint;
185        fn aic_model_create_from_file(model: *mut *mut AicModel, file_path: *const c_char) -> AicErrorCode::Type;
186        fn aic_model_create_from_buffer(model: *mut *mut AicModel, buffer: *const u8, buffer_len: usize) -> AicErrorCode::Type;
187        fn aic_model_destroy(model: *mut AicModel);
188        fn aic_model_get_id(model: *const AicModel) -> *const c_char;
189        fn aic_model_get_optimal_sample_rate(model: *const AicModel, sample_rate: *mut c_uint) -> AicErrorCode::Type;
190        fn aic_model_get_optimal_num_frames(model: *const AicModel, sample_rate: c_uint, num_frames: *mut usize) -> AicErrorCode::Type;
191        fn aic_processor_create(processor: *mut *mut AicProcessor, model: *const AicModel, license_key: *const c_char, otel_config: *const AicOtelConfig) -> AicErrorCode::Type;
192        fn aic_processor_destroy(processor: *mut AicProcessor);
193        fn aic_processor_initialize(processor: *mut AicProcessor, sample_rate: c_uint, num_channels: u16, num_frames: usize, allow_variable_frames: bool) -> AicErrorCode::Type;
194        fn aic_processor_process_planar(processor: *mut AicProcessor, audio: *const *mut c_float, num_channels: u16, num_frames: usize) -> AicErrorCode::Type;
195        fn aic_processor_process_interleaved(processor: *mut AicProcessor, audio: *mut c_float, num_channels: u16, num_frames: usize) -> AicErrorCode::Type;
196        fn aic_processor_process_sequential(processor: *mut AicProcessor, audio: *mut c_float, num_channels: u16, num_frames: usize) -> AicErrorCode::Type;
197        fn aic_processor_context_create(context: *mut *mut AicProcessorContext, processor: *const AicProcessor) -> AicErrorCode::Type;
198        fn aic_processor_context_destroy(context: *mut AicProcessorContext);
199        fn aic_processor_context_reset(context: *const AicProcessorContext) -> AicErrorCode::Type;
200        fn aic_processor_context_set_parameter(context: *const AicProcessorContext, parameter: AicProcessorParameter::Type, value: c_float) -> AicErrorCode::Type;
201        fn aic_processor_context_get_parameter(context: *const AicProcessorContext, parameter: AicProcessorParameter::Type, value: *mut c_float) -> AicErrorCode::Type;
202        fn aic_processor_context_get_output_delay(context: *const AicProcessorContext, delay: *mut usize) -> AicErrorCode::Type;
203        fn aic_processor_context_update_bearer_token(context: *const AicProcessorContext, token: *const c_char) -> AicErrorCode::Type;
204        fn aic_vad_context_create(context: *mut *mut AicVadContext, processor: *const AicProcessor) -> AicErrorCode::Type;
205        fn aic_vad_context_destroy(context: *mut AicVadContext);
206        fn aic_vad_context_is_speech_detected(context: *const AicVadContext, value: *mut bool) -> AicErrorCode::Type;
207        fn aic_vad_context_set_parameter(context: *const AicVadContext, parameter: AicVadParameter::Type, value: c_float) -> AicErrorCode::Type;
208        fn aic_vad_context_get_parameter(context: *const AicVadContext, parameter: AicVadParameter::Type, value: *mut c_float) -> AicErrorCode::Type;
209        fn aic_set_sdk_wrapper_id(id: c_uint);
210    }
211
212    /// Loads the AIC dynamic library from an explicit `path`.
213    ///
214    /// This is **optional**: if it is never called, the library is loaded automatically on first
215    /// use from the platform's default name (`libaic.so` / `libaic.dylib` / `aic.dll`) via the OS
216    /// loader search path. Call this when you need to choose the exact file instead.
217    ///
218    /// It must run before the first SDK call; once the library is loaded (explicitly or
219    /// automatically) it is kept for the rest of the process so function pointers and SDK-owned
220    /// objects stay valid, and a later call returns [`DynamicLoadingError::AlreadyLoaded`].
221    ///
222    /// # Safety
223    ///
224    /// `path` must point to a dynamic library that is ABI-compatible with the bundled `aic.h`
225    /// header used to build these bindings. Loading an incompatible library can cause undefined
226    /// behavior when its functions are called.
227    pub unsafe fn load_library<P: AsRef<Path>>(path: P) -> Result<(), DynamicLoadingError> {
228        let loaded = unsafe { LoadedLibrary::load(path.as_ref())? };
229        AIC_LIBRARY
230            .set(loaded)
231            .map_err(|_| DynamicLoadingError::AlreadyLoaded)
232    }
233
234    /// Returns whether an AIC dynamic library has already been loaded.
235    pub fn is_library_loaded() -> bool {
236        AIC_LIBRARY.get().is_some()
237    }
238}
239
240#[cfg(feature = "runtime-linking")]
241pub use runtime_linking::*;