use std::collections::HashMap;
use std::ffi::CString;
use std::sync::{Mutex, OnceLock};
use orbbec_sys::{ob_filter, ob_frame};
use crate::error::{check_error, Error};
use crate::pipeline::{Frame, Frameset};
type FilterCallbackMap = HashMap<usize, Box<dyn FnMut(Frame) + Send>>;
static FILTER_CALLBACKS: OnceLock<Mutex<FilterCallbackMap>> = OnceLock::new();
fn filter_callbacks() -> &'static Mutex<FilterCallbackMap> {
FILTER_CALLBACKS.get_or_init(|| Mutex::new(HashMap::new()))
}
extern "C" fn filter_c_callback(frame: *mut ob_frame, user_data: *mut std::os::raw::c_void) {
let key = user_data as usize;
let Ok(mut map) = filter_callbacks().lock() else {
return;
};
if let (Some(cb), Some(frame)) = (map.get_mut(&key), unsafe { Frame::from_raw(frame) }) {
cb(frame);
}
}
pub struct Filter {
raw: *mut ob_filter,
}
unsafe impl Send for Filter {}
impl Filter {
pub fn new(name: &str) -> Result<Self, Error> {
let name = CString::new(name).map_err(|_| Error {
status: orbbec_sys::OBStatus_OB_STATUS_ERROR,
message: "filter name contains a NUL byte".to_string(),
})?;
let raw = unsafe { check_error(|e| orbbec_sys::ob_create_filter(name.as_ptr(), e))? };
Ok(Self { raw })
}
pub fn as_raw(&self) -> *mut ob_filter {
self.raw
}
pub fn process(&self, frame: &Frame) -> Result<Option<Frame>, Error> {
self.process_raw(frame.as_raw())
}
pub fn process_frameset(&self, frameset: &Frameset) -> Result<Option<Frame>, Error> {
self.process_raw(frameset.as_raw())
}
fn process_raw(&self, raw: *const ob_frame) -> Result<Option<Frame>, Error> {
let out = unsafe { check_error(|e| orbbec_sys::ob_filter_process(self.raw, raw, e))? };
Ok(unsafe { Frame::from_raw(out) })
}
pub fn push_frame(&self, frame: &Frame) -> Result<(), Error> {
unsafe { check_error(|e| orbbec_sys::ob_filter_push_frame(self.raw, frame.as_raw(), e)) }
}
pub fn set_callback<F>(&self, callback: F) -> Result<(), Error>
where
F: FnMut(Frame) + Send + 'static,
{
let key = self.raw as usize;
filter_callbacks()
.lock()
.expect("filter callback registry poisoned")
.insert(key, Box::new(callback));
let user_data = key as *mut std::os::raw::c_void;
unsafe {
check_error(|e| {
orbbec_sys::ob_filter_set_callback(self.raw, Some(filter_c_callback), user_data, e)
})
}
}
pub fn set_config_value(&self, name: &str, value: f64) -> Result<(), Error> {
let name = CString::new(name).map_err(|_| Error {
status: orbbec_sys::OBStatus_OB_STATUS_ERROR,
message: "config name contains a NUL byte".to_string(),
})?;
unsafe {
check_error(|e| {
orbbec_sys::ob_filter_set_config_value(self.raw, name.as_ptr(), value, e)
})
}
}
pub fn get_config_value(&self, name: &str) -> Result<f64, Error> {
let name = CString::new(name).map_err(|_| Error {
status: orbbec_sys::OBStatus_OB_STATUS_ERROR,
message: "config name contains a NUL byte".to_string(),
})?;
unsafe {
check_error(|e| {
orbbec_sys::ob_filter_get_config_value(self.raw, name.as_ptr(), e)
})
}
}
}
impl Drop for Filter {
fn drop(&mut self) {
filter_callbacks()
.lock()
.expect("filter callback registry poisoned")
.remove(&(self.raw as usize));
let _ = unsafe { check_error(|e| orbbec_sys::ob_delete_filter(self.raw, e)) };
}
}