use std::cell::UnsafeCell;
use std::ffi::c_char;
use std::marker::PhantomData;
use std::ptr;
use std::sync::Arc;
use audio_cpp_sys::*;
use crate::error::Error;
use crate::ffi;
use crate::model::Model;
use crate::types::{Device, LoaderInfo, ModelFamily, ModelInspection};
#[derive(Clone)]
pub(crate) struct RegistryGuard {
#[allow(dead_code)]
inner: Arc<RegistryInner>,
}
struct RegistryInner {
raw: *mut audiocpp_registry,
_no_sync: PhantomData<UnsafeCell<()>>,
}
unsafe impl Send for RegistryGuard {}
impl Drop for RegistryInner {
fn drop(&mut self) {
unsafe {
audiocpp_registry_free(self.raw);
}
}
}
pub struct Registry {
raw: *mut audiocpp_registry,
guard: RegistryGuard,
}
unsafe impl Send for Registry {}
impl Registry {
#[allow(clippy::arc_with_non_send_sync)]
pub fn new() -> Result<Self, Error> {
let raw = unsafe { audiocpp_registry_default() };
if raw.is_null() {
return Err(Error::NullHandle(ffi::last_error()));
}
let guard = RegistryGuard {
inner: Arc::new(RegistryInner {
raw,
_no_sync: PhantomData,
}),
};
Ok(Self { raw, guard })
}
pub fn families(&self) -> Result<Vec<String>, Error> {
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe { audiocpp_registry_families_json(self.raw, &mut out) })?;
let json = unsafe { ffi::take_string(out)? };
serde_json::from_str(&json).map_err(Error::from)
}
pub fn loaders(&self) -> Result<Vec<LoaderInfo>, Error> {
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe { audiocpp_registry_loaders_json(self.raw, &mut out) })?;
let json = unsafe { ffi::take_string(out)? };
let root: serde_json::Value = serde_json::from_str(&json)?;
let loaders = root.get("loaders").ok_or_else(|| {
Error::Other(format!(
"注册表 loaders JSON 缺少 `loaders` 顶层字段: {json}"
))
})?;
serde_json::from_value(loaders.clone()).map_err(Error::from)
}
pub fn devices() -> Result<Vec<Device>, Error> {
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe { audiocpp_registry_devices_json(&mut out) })?;
let json = unsafe { ffi::take_string(out)? };
serde_json::from_str(&json).map_err(Error::from)
}
pub fn supports_family(&self, family: &str) -> bool {
self.try_supports_family(family).unwrap_or(false)
}
pub fn try_supports_family(&self, family: &str) -> Result<bool, Error> {
let family_c = ffi::cstring(family)?;
let rc = unsafe {
audiocpp_registry_supports_family(self.raw, family_c.as_ptr() as *const c_char)
};
if rc < 0 {
return Err(Error::Ffi(ffi::last_error()));
}
Ok(rc != 0)
}
pub fn inspect(&self, model_path: &str) -> Result<ModelInspection, Error> {
let path_c = ffi::cstring(model_path)?;
let mut out: *mut c_char = ptr::null_mut();
ffi::check_rc(unsafe {
audiocpp_registry_inspect_json(self.raw, path_c.as_ptr() as *const c_char, &mut out)
})?;
let json = unsafe { ffi::take_string(out)? };
serde_json::from_str(&json).map_err(Error::from)
}
pub fn load(
&self,
model_path: &str,
family_hint: Option<ModelFamily>,
load_options: Option<&str>,
) -> Result<Model, Error> {
let path_c = ffi::cstring(model_path)?;
let hint_c = family_hint
.as_ref()
.map(|f| ffi::cstring(f.as_str()))
.transpose()?;
let options_c = load_options.map(ffi::cstring).transpose()?;
let raw = unsafe {
audiocpp_registry_load(
self.raw,
path_c.as_ptr() as *const c_char,
hint_c
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr() as *const c_char),
options_c
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr() as *const c_char),
)
};
if raw.is_null() {
return Err(Error::NullHandle(ffi::last_error()));
}
Ok(Model::from_raw(raw, self.guard.clone()))
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[test]
fn supports_family_known_and_unknown() {
let reg = Registry::new().unwrap();
assert!(reg.supports_family("silero_vad"));
assert!(!reg.supports_family("definitely_not_a_real_family_xyz"));
}
#[test]
fn model_outlives_registry() {
let weight = "../audio-cpp-sys/audio.cpp/assets/framework/models/silero_vad/silero_vad_16k.safetensors";
assert!(
std::path::Path::new(weight).exists(),
"内置权重缺失,submodule 未补齐?{weight}"
);
let model = {
let reg = Registry::new().unwrap();
reg.load(weight, None, None).unwrap()
};
let meta = model.metadata().unwrap();
assert_eq!(meta.family, "silero_vad");
}
}