use crate::{error::*, frame::*, sys, types::*};
use std::ffi::{CStr, CString};
use std::ptr;
use std::sync::Mutex;
struct SendSyncPtr(*mut std::ffi::c_void);
unsafe impl Send for SendSyncPtr {}
unsafe impl Sync for SendSyncPtr {}
static GLOBAL_ERROR_CALLBACK: Mutex<Option<SendSyncPtr>> = Mutex::new(None);
fn optional_c_string(value: Option<&str>, parameter_name: &str) -> Result<Option<CString>> {
value
.map(|text| {
CString::new(text).map_err(|_| {
CcapError::InvalidParameter(format!("{} contains null byte", parameter_name))
})
})
.transpose()
}
pub struct Provider {
handle: *mut sys::CcapProvider,
is_opened: bool,
callback_ptr: Option<*mut std::ffi::c_void>,
}
unsafe impl Send for Provider {}
impl Provider {
pub fn new() -> Result<Self> {
let handle = unsafe { sys::ccap_provider_create() };
if handle.is_null() {
return Err(CcapError::DeviceOpenFailed);
}
Ok(Provider {
handle,
is_opened: false,
callback_ptr: None,
})
}
pub fn with_device(device_index: i32) -> Result<Self> {
Self::with_device_and_extra_info(device_index, None)
}
pub fn with_device_and_extra_info(device_index: i32, extra_info: Option<&str>) -> Result<Self> {
let extra_info = optional_c_string(extra_info, "extra info")?;
let handle = unsafe {
sys::ccap_provider_create_with_index(
device_index,
extra_info
.as_ref()
.map_or(ptr::null(), |value| value.as_ptr()),
)
};
if handle.is_null() {
return Err(CcapError::InvalidDevice(format!(
"device index {}",
device_index
)));
}
Ok(Provider {
handle,
is_opened: true,
callback_ptr: None,
})
}
pub fn with_device_name<S: AsRef<str>>(device_name: S) -> Result<Self> {
Self::with_device_name_and_extra_info(device_name, None)
}
pub fn with_device_name_and_extra_info<S: AsRef<str>>(
device_name: S,
extra_info: Option<&str>,
) -> Result<Self> {
let c_name = CString::new(device_name.as_ref()).map_err(|_| {
CcapError::InvalidParameter("device name contains null byte".to_string())
})?;
let extra_info = optional_c_string(extra_info, "extra info")?;
let handle = unsafe {
sys::ccap_provider_create_with_device(
c_name.as_ptr(),
extra_info
.as_ref()
.map_or(ptr::null(), |value| value.as_ptr()),
)
};
if handle.is_null() {
return Err(CcapError::InvalidDevice(device_name.as_ref().to_string()));
}
Ok(Provider {
handle,
is_opened: true,
callback_ptr: None,
})
}
pub fn get_devices() -> Result<Vec<DeviceInfo>> {
let provider = Self::new()?;
let mut device_names_list = sys::CcapDeviceNamesList::default();
let success = unsafe {
sys::ccap_provider_find_device_names_list(provider.handle, &mut device_names_list)
};
if !success {
return Ok(Vec::new());
}
let mut devices = Vec::new();
for i in 0..device_names_list.deviceCount {
let name_bytes = &device_names_list.deviceNames[i];
let name = unsafe {
let cstr = CStr::from_ptr(name_bytes.as_ptr());
cstr.to_string_lossy().to_string()
};
if let Ok(device_provider) = Self::with_device_name(&name) {
if let Ok(device_info) = device_provider.get_device_info_direct() {
devices.push(device_info);
} else {
devices.push(DeviceInfo {
name,
supported_pixel_formats: Vec::new(),
supported_resolutions: Vec::new(),
});
}
}
}
Ok(devices)
}
fn get_device_info_direct(&self) -> Result<DeviceInfo> {
let mut device_info = sys::CcapDeviceInfo::default();
let success = unsafe { sys::ccap_provider_get_device_info(self.handle, &mut device_info) };
if !success {
return Err(CcapError::DeviceOpenFailed);
}
let name = unsafe {
let cstr = CStr::from_ptr(device_info.deviceName.as_ptr());
cstr.to_string_lossy().to_string()
};
let mut formats = Vec::new();
for i in 0..device_info.pixelFormatCount {
if i < device_info.supportedPixelFormats.len() {
formats.push(PixelFormat::from(device_info.supportedPixelFormats[i]));
}
}
let mut resolutions = Vec::new();
for i in 0..device_info.resolutionCount {
if i < device_info.supportedResolutions.len() {
let res = &device_info.supportedResolutions[i];
resolutions.push(Resolution {
width: res.width,
height: res.height,
});
}
}
Ok(DeviceInfo {
name,
supported_pixel_formats: formats,
supported_resolutions: resolutions,
})
}
pub fn open(&mut self) -> Result<()> {
if self.is_opened {
return Ok(());
}
let result = unsafe { sys::ccap_provider_open_by_index(self.handle, -1, false) };
if !result {
return Err(CcapError::DeviceOpenFailed);
}
self.is_opened = true;
Ok(())
}
pub fn open_device(&mut self, device_name: Option<&str>, auto_start: bool) -> Result<()> {
self.open_device_with_extra_info(device_name, None, auto_start)
}
pub fn open_device_with_extra_info(
&mut self,
device_name: Option<&str>,
extra_info: Option<&str>,
auto_start: bool,
) -> Result<()> {
if let Some(name) = device_name {
let c_name = CString::new(name).map_err(|_| {
CcapError::InvalidParameter("device name contains null byte".to_string())
})?;
let extra_info = optional_c_string(extra_info, "extra info")?;
if !self.handle.is_null() {
let _ = self.stop_capture();
let _ = self.remove_new_frame_callback();
self.cleanup_callback();
unsafe {
sys::ccap_provider_destroy(self.handle);
}
self.handle = ptr::null_mut();
self.is_opened = false;
} else {
self.cleanup_callback();
}
self.handle = unsafe {
sys::ccap_provider_create_with_device(
c_name.as_ptr(),
extra_info
.as_ref()
.map_or(ptr::null(), |value| value.as_ptr()),
)
};
if self.handle.is_null() {
return Err(CcapError::InvalidDevice(name.to_string()));
}
self.is_opened = true;
if !auto_start {
self.stop_capture()?;
}
} else if extra_info.is_some() {
return self.open_with_index_and_extra_info(-1, extra_info, auto_start);
} else {
self.open()?;
}
if auto_start {
self.start_capture()?;
}
Ok(())
}
pub fn device_info(&self) -> Result<DeviceInfo> {
self.get_device_info_direct()
}
pub fn is_started(&self) -> bool {
unsafe { sys::ccap_provider_is_started(self.handle) }
}
pub fn start(&mut self) -> Result<()> {
self.start_capture()
}
pub fn stop(&mut self) -> Result<()> {
self.stop_capture()
}
pub fn is_opened(&self) -> bool {
self.is_opened
}
pub fn set_property(&mut self, property: PropertyName, value: f64) -> Result<()> {
let property_id: sys::CcapPropertyName = property.into();
let success = unsafe { sys::ccap_provider_set_property(self.handle, property_id, value) };
if !success {
return Err(CcapError::InvalidParameter(format!(
"property {:?}",
property
)));
}
Ok(())
}
pub fn get_property(&self, property: PropertyName) -> Result<f64> {
let property_id: sys::CcapPropertyName = property.into();
let value = unsafe { sys::ccap_provider_get_property(self.handle, property_id) };
Ok(value)
}
pub fn set_resolution(&mut self, width: u32, height: u32) -> Result<()> {
let (old_w, old_h) = self.resolution()?;
self.set_property(PropertyName::Width, width as f64)?;
if let Err(e) = self.set_property(PropertyName::Height, height as f64) {
let _ = self.set_property(PropertyName::Width, old_w as f64);
let _ = self.set_property(PropertyName::Height, old_h as f64);
return Err(e);
}
Ok(())
}
pub fn set_frame_rate(&mut self, fps: f64) -> Result<()> {
self.set_property(PropertyName::FrameRate, fps)
}
pub fn set_pixel_format(&mut self, format: PixelFormat) -> Result<()> {
self.set_property(PropertyName::PixelFormatOutput, format.to_c_enum() as f64)
}
pub fn grab_frame(&mut self, timeout_ms: u32) -> Result<Option<VideoFrame>> {
if !self.is_opened {
return Err(CcapError::DeviceNotOpened);
}
let frame = unsafe { sys::ccap_provider_grab(self.handle, timeout_ms) };
if frame.is_null() {
return Ok(None);
}
Ok(Some(VideoFrame::from_c_ptr(frame)))
}
pub fn start_capture(&mut self) -> Result<()> {
if !self.is_opened {
return Err(CcapError::DeviceNotOpened);
}
let result = unsafe { sys::ccap_provider_start(self.handle) };
if !result {
return Err(CcapError::CaptureStartFailed);
}
Ok(())
}
pub fn stop_capture(&mut self) -> Result<()> {
unsafe { sys::ccap_provider_stop(self.handle) };
Ok(())
}
pub fn version() -> Result<String> {
let version_ptr = unsafe { sys::ccap_get_version() };
if version_ptr.is_null() {
return Err(CcapError::Unknown { code: -1 });
}
let version_cstr = unsafe { CStr::from_ptr(version_ptr) };
version_cstr
.to_str()
.map(|s| s.to_string())
.map_err(|_| CcapError::Unknown { code: -2 })
}
pub fn list_devices(&self) -> Result<Vec<String>> {
let device_infos = Self::get_devices()?;
Ok(device_infos.into_iter().map(|info| info.name).collect())
}
pub fn find_device_names(&self) -> Result<Vec<String>> {
self.list_devices()
}
pub fn resolution(&self) -> Result<(u32, u32)> {
let width = self.get_property(PropertyName::Width)? as u32;
let height = self.get_property(PropertyName::Height)? as u32;
Ok((width, height))
}
pub fn pixel_format(&self) -> Result<PixelFormat> {
let format_val = self.get_property(PropertyName::PixelFormatOutput)? as u32;
Ok(PixelFormat::from_c_enum(format_val as sys::CcapPixelFormat))
}
pub fn frame_rate(&self) -> Result<f64> {
self.get_property(PropertyName::FrameRate)
}
pub fn set_error_callback<F>(callback: F)
where
F: Fn(i32, &str) + Send + Sync + 'static,
{
use std::os::raw::c_char;
type ErrorCallbackBox = Box<dyn Fn(i32, &str) + Send + Sync>;
unsafe extern "C" fn error_callback_wrapper(
error_code: sys::CcapErrorCode,
description: *const c_char,
user_data: *mut std::ffi::c_void,
) {
if user_data.is_null() || description.is_null() {
return;
}
let callback = &**(user_data as *const ErrorCallbackBox);
let desc_cstr = std::ffi::CStr::from_ptr(description);
if let Ok(desc_str) = desc_cstr.to_str() {
callback(error_code as i32, desc_str);
}
}
if let Ok(mut guard) = GLOBAL_ERROR_CALLBACK.lock() {
if let Some(SendSyncPtr(old_ptr)) = guard.take() {
unsafe {
let _ = Box::from_raw(old_ptr as *mut ErrorCallbackBox);
}
}
let callback_box: ErrorCallbackBox = Box::new(callback);
let callback_ptr = Box::into_raw(Box::new(callback_box));
unsafe {
sys::ccap_set_error_callback(
Some(error_callback_wrapper),
callback_ptr as *mut std::ffi::c_void,
);
}
*guard = Some(SendSyncPtr(callback_ptr as *mut std::ffi::c_void));
}
}
pub fn set_global_error_callback<F>(callback: F)
where
F: Fn(i32, &str) + Send + Sync + 'static,
{
Self::set_error_callback(callback)
}
pub fn clear_error_callback() {
type ErrorCallbackBox = Box<dyn Fn(i32, &str) + Send + Sync>;
if let Ok(mut guard) = GLOBAL_ERROR_CALLBACK.lock() {
unsafe {
sys::ccap_set_error_callback(None, ptr::null_mut());
}
if let Some(SendSyncPtr(old_ptr)) = guard.take() {
unsafe {
let _ = Box::from_raw(old_ptr as *mut ErrorCallbackBox);
}
}
}
}
pub fn clear_global_error_callback() {
Self::clear_error_callback()
}
pub fn open_with_index(&mut self, device_index: i32, auto_start: bool) -> Result<()> {
self.open_with_index_and_extra_info(device_index, None, auto_start)
}
pub fn open_with_index_and_extra_info(
&mut self,
device_index: i32,
extra_info: Option<&str>,
auto_start: bool,
) -> Result<()> {
let extra_info = optional_c_string(extra_info, "extra info")?;
if !self.handle.is_null() {
let _ = self.stop_capture();
let _ = self.remove_new_frame_callback();
self.cleanup_callback();
unsafe {
sys::ccap_provider_destroy(self.handle);
}
self.handle = ptr::null_mut();
self.is_opened = false;
} else {
self.cleanup_callback();
}
self.handle = unsafe {
sys::ccap_provider_create_with_index(
device_index,
extra_info
.as_ref()
.map_or(ptr::null(), |value| value.as_ptr()),
)
};
if self.handle.is_null() {
return Err(CcapError::InvalidDevice(format!(
"device index {}",
device_index
)));
}
self.is_opened = true;
if !auto_start {
self.stop_capture()?;
}
if auto_start {
self.start_capture()?;
}
Ok(())
}
pub fn set_new_frame_callback<F>(&mut self, callback: F) -> Result<()>
where
F: Fn(&VideoFrame) -> bool + Send + Sync + 'static,
{
use std::os::raw::c_void;
type CallbackBox = Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>;
self.cleanup_callback();
unsafe extern "C" fn new_frame_callback_wrapper(
frame: *const sys::CcapVideoFrame,
user_data: *mut c_void,
) -> bool {
if user_data.is_null() || frame.is_null() {
return false;
}
let callback = &**(user_data as *const CallbackBox);
let video_frame = VideoFrame::from_c_ptr_ref(frame as *mut sys::CcapVideoFrame);
callback(&video_frame)
}
let callback_box: CallbackBox = Box::new(callback);
let callback_ptr = Box::into_raw(Box::new(callback_box));
let success = unsafe {
sys::ccap_provider_set_new_frame_callback(
self.handle,
Some(new_frame_callback_wrapper),
callback_ptr as *mut c_void,
)
};
if success {
self.callback_ptr = Some(callback_ptr as *mut c_void);
Ok(())
} else {
unsafe {
let _ = Box::from_raw(callback_ptr);
}
Err(CcapError::InvalidParameter(
"Failed to set frame callback".to_string(),
))
}
}
pub fn remove_new_frame_callback(&mut self) -> Result<()> {
let success = unsafe {
sys::ccap_provider_set_new_frame_callback(self.handle, None, ptr::null_mut())
};
if success {
self.cleanup_callback();
Ok(())
} else {
Err(CcapError::CaptureStopFailed)
}
}
fn cleanup_callback(&mut self) {
type CallbackBox = Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>;
if let Some(callback_ptr) = self.callback_ptr.take() {
unsafe {
let _ = Box::from_raw(callback_ptr as *mut CallbackBox);
}
}
}
}
impl Drop for Provider {
fn drop(&mut self) {
self.cleanup_callback();
if !self.handle.is_null() {
unsafe {
sys::ccap_provider_destroy(self.handle);
}
self.handle = ptr::null_mut();
}
}
}