use std::ffi::{c_void, CStr, CString};
use std::ptr::NonNull;
use edgefirst_tflite_sys::hal_ffi::{HalCameraAdaptorFormatInfo, HalCameraAdaptorFunctions};
use edgefirst_tflite_sys::vx_ffi::VxCameraAdaptorFunctions;
use edgefirst_tflite_sys::TfLiteDelegate;
use crate::error::{self, Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormatInfo {
pub input_channels: i32,
pub output_channels: i32,
pub fourcc: String,
}
#[derive(Debug)]
pub struct CameraAdaptor<'a> {
delegate: NonNull<TfLiteDelegate>,
hal_handle: Option<*mut c_void>,
hal_fns: Option<&'a HalCameraAdaptorFunctions>,
vx_fns: Option<&'a VxCameraAdaptorFunctions>,
}
impl<'a> CameraAdaptor<'a> {
pub(crate) fn new(
delegate: NonNull<TfLiteDelegate>,
hal_handle: Option<*mut c_void>,
hal_fns: Option<&'a HalCameraAdaptorFunctions>,
vx_fns: Option<&'a VxCameraAdaptorFunctions>,
) -> Self {
Self {
delegate,
hal_handle,
hal_fns,
vx_fns,
}
}
fn hal_delegate_ptr(&self) -> *mut c_void {
self.hal_handle
.unwrap_or_else(|| self.delegate.as_ptr().cast::<c_void>())
}
#[must_use]
pub fn is_format_supported(&self, format: &str) -> bool {
let Ok(c_format) = CString::new(format) else {
return false;
};
if let Some(hal) = self.hal_fns {
unsafe { (hal.is_supported)(self.hal_delegate_ptr(), c_format.as_ptr()) == 1 }
} else if let Some(vx) = self.vx_fns {
unsafe { (vx.is_supported)(c_format.as_ptr()) }
} else {
false
}
}
pub fn format_info(&self, format: &str) -> Result<FormatInfo> {
let c_format = CString::new(format)
.map_err(|_| Error::invalid_argument("format contains interior NUL byte"))?;
if let Some(hal) = self.hal_fns {
let mut info = HalCameraAdaptorFormatInfo::default();
let ret = unsafe {
(hal.get_format_info)(
self.hal_delegate_ptr(),
c_format.as_ptr(),
&mut info,
std::mem::size_of::<HalCameraAdaptorFormatInfo>(),
)
};
error::hal_to_result(ret, "hal_camera_adaptor_get_format_info")?;
let fourcc_len = info
.fourcc
.iter()
.position(|&b| b == 0)
.unwrap_or(info.fourcc.len());
let fourcc = String::from_utf8_lossy(&info.fourcc[..fourcc_len]).into_owned();
Ok(FormatInfo {
input_channels: info.input_channels,
output_channels: info.output_channels,
fourcc,
})
} else if let Some(vx) = self.vx_fns {
let input_channels = unsafe { (vx.get_input_channels)(c_format.as_ptr()) };
let output_channels = unsafe { (vx.get_output_channels)(c_format.as_ptr()) };
let fourcc_ptr = unsafe { (vx.get_fourcc)(c_format.as_ptr()) };
let fourcc = if fourcc_ptr.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(fourcc_ptr) }
.to_str()
.unwrap_or("")
.to_owned()
};
Ok(FormatInfo {
input_channels,
output_channels,
fourcc,
})
} else {
Err(Error::invalid_argument(
"no CameraAdaptor backend available",
))
}
}
fn vx(&self) -> Result<&VxCameraAdaptorFunctions> {
self.vx_fns.ok_or_else(|| {
Error::invalid_argument(
"this method requires the `VxDelegate` CameraAdaptor API, which is not available",
)
})
}
#[deprecated(note = "`VxDelegate`-specific, will be removed in a future release")]
pub fn set_format(&self, tensor_index: i32, format: &str) -> Result<()> {
let vx = self.vx()?;
let c_format = CString::new(format)
.map_err(|_| Error::invalid_argument("format contains interior NUL byte"))?;
let status =
unsafe { (vx.set_format)(self.delegate.as_ptr(), tensor_index, c_format.as_ptr()) };
error::status_to_result(status)
}
#[deprecated(note = "`VxDelegate`-specific, will be removed in a future release")]
#[allow(clippy::too_many_arguments)]
pub fn set_format_ex(
&self,
tensor_index: i32,
format: &str,
width: u32,
height: u32,
letterbox: bool,
letterbox_color: u32,
) -> Result<()> {
let vx = self.vx()?;
let c_format = CString::new(format)
.map_err(|_| Error::invalid_argument("format contains interior NUL byte"))?;
let status = unsafe {
(vx.set_format_ex)(
self.delegate.as_ptr(),
tensor_index,
c_format.as_ptr(),
width,
height,
letterbox,
letterbox_color,
)
};
error::status_to_result(status)
}
#[deprecated(note = "`VxDelegate`-specific, will be removed in a future release")]
pub fn set_formats(
&self,
tensor_index: i32,
camera_format: &str,
model_format: &str,
) -> Result<()> {
let vx = self.vx()?;
let c_camera = CString::new(camera_format)
.map_err(|_| Error::invalid_argument("camera_format contains interior NUL byte"))?;
let c_model = CString::new(model_format)
.map_err(|_| Error::invalid_argument("model_format contains interior NUL byte"))?;
let status = unsafe {
(vx.set_formats)(
self.delegate.as_ptr(),
tensor_index,
c_camera.as_ptr(),
c_model.as_ptr(),
)
};
error::status_to_result(status)
}
#[deprecated(note = "`VxDelegate`-specific, will be removed in a future release")]
pub fn set_fourcc(&self, tensor_index: i32, fourcc: u32) -> Result<()> {
let vx = self.vx()?;
let status = unsafe { (vx.set_fourcc)(self.delegate.as_ptr(), tensor_index, fourcc) };
error::status_to_result(status)
}
#[deprecated(note = "`VxDelegate`-specific, will be removed in a future release")]
#[must_use]
pub fn format(&self, tensor_index: i32) -> Option<String> {
let vx = self.vx_fns?;
let ptr = unsafe { (vx.get_format)(self.delegate.as_ptr(), tensor_index) };
if ptr.is_null() {
return None;
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().ok().map(String::from)
}
#[deprecated(
note = "`VxDelegate`-specific, use `is_format_supported()` instead; will be removed in a future release"
)]
#[must_use]
pub fn is_supported(&self, format: &str) -> bool {
let Ok(c_format) = CString::new(format) else {
return false;
};
if let Some(vx) = self.vx_fns {
unsafe { (vx.is_supported)(c_format.as_ptr()) }
} else {
false
}
}
#[deprecated(
note = "`VxDelegate`-specific, use `format_info()` instead; will be removed in a future release"
)]
#[must_use]
pub fn input_channels(&self, format: &str) -> i32 {
let Ok(c_format) = CString::new(format) else {
return 0;
};
self.vx_fns.map_or(0, |vx| {
unsafe { (vx.get_input_channels)(c_format.as_ptr()) }
})
}
#[deprecated(
note = "`VxDelegate`-specific, use `format_info()` instead; will be removed in a future release"
)]
#[must_use]
pub fn output_channels(&self, format: &str) -> i32 {
let Ok(c_format) = CString::new(format) else {
return 0;
};
self.vx_fns.map_or(0, |vx| {
unsafe { (vx.get_output_channels)(c_format.as_ptr()) }
})
}
#[deprecated(
note = "`VxDelegate`-specific, use `format_info()` instead; will be removed in a future release"
)]
#[must_use]
pub fn fourcc(&self, format: &str) -> Option<String> {
let vx = self.vx_fns?;
let c_format = CString::new(format).ok()?;
let ptr = unsafe { (vx.get_fourcc)(c_format.as_ptr()) };
if ptr.is_null() {
return None;
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().ok().map(String::from)
}
#[deprecated(note = "`VxDelegate`-specific, will be removed in a future release")]
#[must_use]
pub fn from_fourcc(&self, fourcc: &str) -> Option<String> {
let vx = self.vx_fns?;
let c_fourcc = CString::new(fourcc).ok()?;
let ptr = unsafe { (vx.from_fourcc)(c_fourcc.as_ptr()) };
if ptr.is_null() {
return None;
}
let cstr = unsafe { CStr::from_ptr(ptr) };
cstr.to_str().ok().map(String::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_info_debug() {
let info = FormatInfo {
input_channels: 4,
output_channels: 3,
fourcc: "RGBA".to_owned(),
};
let debug = format!("{info:?}");
assert!(debug.contains("FormatInfo"));
assert!(debug.contains("input_channels"));
assert!(debug.contains("RGBA"));
}
#[test]
fn format_info_clone_eq() {
let info = FormatInfo {
input_channels: 4,
output_channels: 3,
fourcc: "RGBA".to_owned(),
};
let cloned = info.clone();
assert_eq!(info, cloned);
}
}