use std::ffi::CStr;
use tracing::debug;
use super::{
av_capture_frame, av_free_camera_list, av_free_frame_result, av_list_cameras,
check_camera_permission, CChar, CDeviceInfo, CFrameResult, CameraDevice, CameraError,
CameraPosition, ImageData,
};
#[must_use]
pub fn list_cameras() -> Vec<CameraDevice> {
unsafe {
let mut count: usize = 0;
let ptr = av_list_cameras(std::ptr::addr_of_mut!(count));
if ptr.is_null() || count == 0 {
return Vec::new();
}
let slice = std::slice::from_raw_parts(ptr, count);
let devices = slice.iter().map(c_device_info_to_rust).collect();
av_free_camera_list(ptr, count);
devices
}
}
pub fn capture_frame(device_id: Option<&str>) -> Result<ImageData, CameraError> {
if !check_camera_permission() {
return Err(CameraError::PermissionDenied);
}
if let Some(id) = device_id {
ensure_device_exists(id)?;
}
let id_ptr = build_id_ptr(device_id)?;
debug!(device_id = ?device_id, "capturing camera frame");
unsafe { invoke_capture_frame(id_ptr.as_ref()) }
}
fn build_id_ptr(device_id: Option<&str>) -> Result<Option<std::ffi::CString>, CameraError> {
device_id
.map(|id| std::ffi::CString::new(id).map_err(|e| CameraError::CaptureFailed(e.to_string())))
.transpose()
}
unsafe fn invoke_capture_frame(
id_cstr: Option<&std::ffi::CString>,
) -> Result<ImageData, CameraError> {
let id_ptr: *const CChar = id_cstr.map_or(std::ptr::null(), |c| c.as_ptr());
let mut result = CFrameResult {
jpeg_data: std::ptr::null_mut(),
jpeg_len: 0,
width: 0,
height: 0,
error_msg: std::ptr::null(),
};
let ok = av_capture_frame(id_ptr, std::ptr::addr_of_mut!(result));
if !ok || result.jpeg_data.is_null() {
let msg = error_msg_from_ptr(result.error_msg)
.unwrap_or_else(|| "Unknown capture error".to_string());
av_free_frame_result(std::ptr::addr_of_mut!(result));
return Err(CameraError::CaptureFailed(msg));
}
let jpeg_data =
std::slice::from_raw_parts(result.jpeg_data as *const u8, result.jpeg_len).to_vec();
let width = result.width;
let height = result.height;
av_free_frame_result(std::ptr::addr_of_mut!(result));
Ok(ImageData {
width,
height,
jpeg_data,
})
}
unsafe fn error_msg_from_ptr(ptr: *const CChar) -> Option<String> {
if ptr.is_null() {
None
} else {
Some(CStr::from_ptr(ptr).to_string_lossy().into_owned())
}
}
pub(crate) fn ensure_device_exists(id: &str) -> Result<(), CameraError> {
if list_cameras().iter().any(|d| d.id == id) {
Ok(())
} else {
Err(CameraError::DeviceNotFound(id.to_string()))
}
}
fn c_device_info_to_rust(info: &CDeviceInfo) -> CameraDevice {
let id = unsafe {
CStr::from_ptr(info.unique_id)
.to_string_lossy()
.into_owned()
};
let name = unsafe {
CStr::from_ptr(info.localized_name)
.to_string_lossy()
.into_owned()
};
let position = match info.position {
1 => CameraPosition::Front,
2 => CameraPosition::Back,
3 => CameraPosition::External,
_ => CameraPosition::Unknown,
};
CameraDevice {
id,
name,
position,
is_default: info.is_default != 0,
}
}