ccap/
provider.rs

1//! Camera provider for synchronous camera capture operations
2
3use crate::{error::*, frame::*, sys, types::*};
4use std::ffi::{CStr, CString};
5use std::ptr;
6use std::sync::Mutex;
7
8/// A wrapper around a raw pointer that can be safely shared between threads.
9/// This is used for storing callback pointers that we know are safe to share
10/// because the callback itself is `Send + Sync`.
11struct SendSyncPtr(*mut std::ffi::c_void);
12
13// SAFETY: The pointer stored here always points to a `Box<ErrorCallbackBox>`
14// where `ErrorCallbackBox = Box<dyn Fn(i32, &str) + Send + Sync>`.
15// The underlying callback is `Send + Sync`, so the pointer is safe to share.
16unsafe impl Send for SendSyncPtr {}
17unsafe impl Sync for SendSyncPtr {}
18
19// Global error callback storage - must be at module level to be shared between functions
20static GLOBAL_ERROR_CALLBACK: Mutex<Option<SendSyncPtr>> = Mutex::new(None);
21
22/// Type alias for the global error callback
23///
24/// # Thread Safety
25///
26/// `Provider` implements `Send` to allow moving the provider between threads.
27/// However, the underlying C++ implementation is **NOT thread-safe**.
28///
29/// **Important**: You must ensure that:
30/// - Only one thread accesses the `Provider` at a time
31/// - Use `Arc<Mutex<Provider>>` or similar synchronization if sharing between threads
32/// - If you need to integrate with an async runtime, wrap the `Provider` yourself (e.g. with a mutex and a dedicated worker thread)
33///
34/// # Example (Safe Multi-threaded Usage)
35///
36/// ```ignore
37/// use std::sync::{Arc, Mutex};
38/// use ccap::Provider;
39///
40/// let provider = Arc::new(Mutex::new(Provider::new()?));
41/// let provider_clone = Arc::clone(&provider);
42///
43/// std::thread::spawn(move || {
44///     let mut guard = provider_clone.lock().unwrap();
45///     // Safe: mutex ensures exclusive access
46///     guard.grab_frame(1000).ok();
47/// });
48/// ```
49pub struct Provider {
50    handle: *mut sys::CcapProvider,
51    is_opened: bool,
52    callback_ptr: Option<*mut std::ffi::c_void>,
53}
54
55// SAFETY: Provider is Send because:
56// 1. The handle is a raw pointer to C++ Provider, which can be safely moved between threads
57// 2. The callback_ptr ownership is properly tracked and cleaned up
58// 3. We document that users MUST synchronize access externally
59//
60// WARNING: The underlying C++ Provider is NOT thread-safe. Moving the Provider
61// to another thread is safe, but concurrent access from multiple threads is NOT.
62// Users must use external synchronization (e.g., Mutex) for multi-threaded access.
63unsafe impl Send for Provider {}
64
65impl Provider {
66    /// Create a new camera provider
67    pub fn new() -> Result<Self> {
68        let handle = unsafe { sys::ccap_provider_create() };
69        if handle.is_null() {
70            return Err(CcapError::DeviceOpenFailed);
71        }
72
73        Ok(Provider {
74            handle,
75            is_opened: false,
76            callback_ptr: None,
77        })
78    }
79
80    /// Create a provider with a specific device index
81    pub fn with_device(device_index: i32) -> Result<Self> {
82        let handle = unsafe { sys::ccap_provider_create_with_index(device_index, ptr::null()) };
83        if handle.is_null() {
84            return Err(CcapError::InvalidDevice(format!(
85                "device index {}",
86                device_index
87            )));
88        }
89
90        Ok(Provider {
91            handle,
92            // ccap C API contract: create_with_index opens the device.
93            // See `include/ccap_c.h`: "Create a camera provider and open device by index".
94            is_opened: true,
95            callback_ptr: None,
96        })
97    }
98
99    /// Create a provider with a specific device name
100    pub fn with_device_name<S: AsRef<str>>(device_name: S) -> Result<Self> {
101        let c_name = CString::new(device_name.as_ref()).map_err(|_| {
102            CcapError::InvalidParameter("device name contains null byte".to_string())
103        })?;
104
105        let handle = unsafe { sys::ccap_provider_create_with_device(c_name.as_ptr(), ptr::null()) };
106        if handle.is_null() {
107            return Err(CcapError::InvalidDevice(device_name.as_ref().to_string()));
108        }
109
110        Ok(Provider {
111            handle,
112            // ccap C API contract: create_with_device opens the device.
113            // See `include/ccap_c.h`: "Create a camera provider and open specified device".
114            is_opened: true,
115            callback_ptr: None,
116        })
117    }
118
119    /// Get available camera devices
120    pub fn get_devices() -> Result<Vec<DeviceInfo>> {
121        // Create a temporary provider to query devices
122        let provider = Self::new()?;
123        let mut device_names_list = sys::CcapDeviceNamesList::default();
124
125        let success = unsafe {
126            sys::ccap_provider_find_device_names_list(provider.handle, &mut device_names_list)
127        };
128
129        if !success {
130            return Ok(Vec::new());
131        }
132
133        let mut devices = Vec::new();
134        for i in 0..device_names_list.deviceCount {
135            let name_bytes = &device_names_list.deviceNames[i];
136            let name = unsafe {
137                let cstr = CStr::from_ptr(name_bytes.as_ptr());
138                cstr.to_string_lossy().to_string()
139            };
140
141            // Try to get device info by creating provider with this device
142            if let Ok(device_provider) = Self::with_device_name(&name) {
143                if let Ok(device_info) = device_provider.get_device_info_direct() {
144                    devices.push(device_info);
145                } else {
146                    // Fallback: create minimal device info from just the name
147                    devices.push(DeviceInfo {
148                        name,
149                        supported_pixel_formats: Vec::new(),
150                        supported_resolutions: Vec::new(),
151                    });
152                }
153            }
154        }
155
156        Ok(devices)
157    }
158
159    /// Get device info directly from current provider
160    fn get_device_info_direct(&self) -> Result<DeviceInfo> {
161        let mut device_info = sys::CcapDeviceInfo::default();
162
163        let success = unsafe { sys::ccap_provider_get_device_info(self.handle, &mut device_info) };
164
165        if !success {
166            return Err(CcapError::DeviceOpenFailed);
167        }
168
169        let name = unsafe {
170            let cstr = CStr::from_ptr(device_info.deviceName.as_ptr());
171            cstr.to_string_lossy().to_string()
172        };
173
174        let mut formats = Vec::new();
175        for i in 0..device_info.pixelFormatCount {
176            if i < device_info.supportedPixelFormats.len() {
177                formats.push(PixelFormat::from(device_info.supportedPixelFormats[i]));
178            }
179        }
180
181        let mut resolutions = Vec::new();
182        for i in 0..device_info.resolutionCount {
183            if i < device_info.supportedResolutions.len() {
184                let res = &device_info.supportedResolutions[i];
185                resolutions.push(Resolution {
186                    width: res.width,
187                    height: res.height,
188                });
189            }
190        }
191
192        Ok(DeviceInfo {
193            name,
194            supported_pixel_formats: formats,
195            supported_resolutions: resolutions,
196        })
197    }
198
199    /// Open the camera device
200    pub fn open(&mut self) -> Result<()> {
201        if self.is_opened {
202            return Ok(());
203        }
204
205        let result = unsafe { sys::ccap_provider_open_by_index(self.handle, -1, false) };
206        if !result {
207            return Err(CcapError::DeviceOpenFailed);
208        }
209
210        self.is_opened = true;
211        Ok(())
212    }
213
214    /// Open device with optional device name and auto start
215    pub fn open_device(&mut self, device_name: Option<&str>, auto_start: bool) -> Result<()> {
216        if let Some(name) = device_name {
217            // Recreate provider with specific device
218            if !self.handle.is_null() {
219                // If the previous provider was running, stop it and detach callbacks
220                // before destroying the underlying handle.
221                let _ = self.stop_capture();
222                let _ = self.remove_new_frame_callback();
223                self.cleanup_callback();
224                unsafe {
225                    sys::ccap_provider_destroy(self.handle);
226                }
227            }
228            let c_name = CString::new(name).map_err(|_| {
229                CcapError::InvalidParameter("device name contains null byte".to_string())
230            })?;
231            self.handle =
232                unsafe { sys::ccap_provider_create_with_device(c_name.as_ptr(), ptr::null()) };
233            if self.handle.is_null() {
234                return Err(CcapError::InvalidDevice(name.to_string()));
235            }
236            self.is_opened = true;
237        } else {
238            self.open()?;
239        }
240        if auto_start {
241            self.start_capture()?;
242        }
243        Ok(())
244    }
245
246    /// Get device info for the current provider
247    pub fn device_info(&self) -> Result<DeviceInfo> {
248        self.get_device_info_direct()
249    }
250
251    /// Check if capture is started
252    pub fn is_started(&self) -> bool {
253        unsafe { sys::ccap_provider_is_started(self.handle) }
254    }
255
256    /// Start capture (alias for start_capture)
257    pub fn start(&mut self) -> Result<()> {
258        self.start_capture()
259    }
260
261    /// Stop capture (alias for stop_capture)  
262    pub fn stop(&mut self) -> Result<()> {
263        self.stop_capture()
264    }
265
266    /// Check if the camera is opened
267    pub fn is_opened(&self) -> bool {
268        self.is_opened
269    }
270
271    /// Set camera property
272    pub fn set_property(&mut self, property: PropertyName, value: f64) -> Result<()> {
273        let property_id: sys::CcapPropertyName = property.into();
274        let success = unsafe { sys::ccap_provider_set_property(self.handle, property_id, value) };
275
276        if !success {
277            return Err(CcapError::InvalidParameter(format!(
278                "property {:?}",
279                property
280            )));
281        }
282
283        Ok(())
284    }
285
286    /// Get camera property
287    pub fn get_property(&self, property: PropertyName) -> Result<f64> {
288        let property_id: sys::CcapPropertyName = property.into();
289        let value = unsafe { sys::ccap_provider_get_property(self.handle, property_id) };
290
291        Ok(value)
292    }
293
294    /// Set camera resolution
295    pub fn set_resolution(&mut self, width: u32, height: u32) -> Result<()> {
296        // Avoid leaving the device in a partially-updated state if only one property update
297        // succeeds (e.g. width succeeds but height fails).
298        let (old_w, old_h) = self.resolution()?;
299
300        self.set_property(PropertyName::Width, width as f64)?;
301        if let Err(e) = self.set_property(PropertyName::Height, height as f64) {
302            // Best-effort rollback.
303            let _ = self.set_property(PropertyName::Width, old_w as f64);
304            let _ = self.set_property(PropertyName::Height, old_h as f64);
305            return Err(e);
306        }
307
308        Ok(())
309    }
310
311    /// Set camera frame rate
312    pub fn set_frame_rate(&mut self, fps: f64) -> Result<()> {
313        self.set_property(PropertyName::FrameRate, fps)
314    }
315
316    /// Set pixel format
317    pub fn set_pixel_format(&mut self, format: PixelFormat) -> Result<()> {
318        self.set_property(PropertyName::PixelFormatOutput, format.to_c_enum() as f64)
319    }
320
321    /// Grab a single frame with timeout
322    pub fn grab_frame(&mut self, timeout_ms: u32) -> Result<Option<VideoFrame>> {
323        if !self.is_opened {
324            return Err(CcapError::DeviceNotOpened);
325        }
326
327        let frame = unsafe { sys::ccap_provider_grab(self.handle, timeout_ms) };
328        if frame.is_null() {
329            return Ok(None);
330        }
331
332        Ok(Some(VideoFrame::from_c_ptr(frame)))
333    }
334
335    /// Start continuous capture
336    pub fn start_capture(&mut self) -> Result<()> {
337        if !self.is_opened {
338            return Err(CcapError::DeviceNotOpened);
339        }
340
341        let result = unsafe { sys::ccap_provider_start(self.handle) };
342        if !result {
343            return Err(CcapError::CaptureStartFailed);
344        }
345
346        Ok(())
347    }
348
349    /// Stop continuous capture
350    pub fn stop_capture(&mut self) -> Result<()> {
351        unsafe { sys::ccap_provider_stop(self.handle) };
352        Ok(())
353    }
354
355    /// Get library version
356    pub fn version() -> Result<String> {
357        let version_ptr = unsafe { sys::ccap_get_version() };
358        if version_ptr.is_null() {
359            return Err(CcapError::Unknown { code: -1 });
360        }
361
362        let version_cstr = unsafe { CStr::from_ptr(version_ptr) };
363        version_cstr
364            .to_str()
365            .map(|s| s.to_string())
366            .map_err(|_| CcapError::Unknown { code: -2 })
367    }
368
369    /// List device names (simple string list)
370    pub fn list_devices(&self) -> Result<Vec<String>> {
371        let device_infos = Self::get_devices()?;
372        Ok(device_infos.into_iter().map(|info| info.name).collect())
373    }
374
375    /// Find device names (alias for list_devices)
376    pub fn find_device_names(&self) -> Result<Vec<String>> {
377        self.list_devices()
378    }
379
380    /// Get current resolution (convenience getter)
381    pub fn resolution(&self) -> Result<(u32, u32)> {
382        let width = self.get_property(PropertyName::Width)? as u32;
383        let height = self.get_property(PropertyName::Height)? as u32;
384        Ok((width, height))
385    }
386
387    /// Get current pixel format (convenience getter)
388    pub fn pixel_format(&self) -> Result<PixelFormat> {
389        let format_val = self.get_property(PropertyName::PixelFormatOutput)? as u32;
390        Ok(PixelFormat::from_c_enum(format_val as sys::CcapPixelFormat))
391    }
392
393    /// Get current frame rate (convenience getter)
394    pub fn frame_rate(&self) -> Result<f64> {
395        self.get_property(PropertyName::FrameRate)
396    }
397
398    /// Set error callback for camera errors
399    ///
400    /// # Memory Safety
401    ///
402    /// This is a **global** callback that persists until replaced or cleared.
403    /// Calling this function multiple times will properly clean up the previous callback.
404    ///
405    /// **Important**: this callback is process-global (shared by all `Provider` instances).
406    /// The last one set wins.
407    ///
408    /// # Thread Safety
409    ///
410    /// The callback will be invoked from the camera capture thread. Ensure your
411    /// callback is thread-safe (`Send + Sync`).
412    ///
413    /// # Example
414    ///
415    /// ```ignore
416    /// Provider::set_error_callback(|code, desc| {
417    ///     eprintln!("Camera error {}: {}", code, desc);
418    /// });
419    /// ```
420    pub fn set_error_callback<F>(callback: F)
421    where
422        F: Fn(i32, &str) + Send + Sync + 'static,
423    {
424        use std::os::raw::c_char;
425
426        type ErrorCallbackBox = Box<dyn Fn(i32, &str) + Send + Sync>;
427
428        unsafe extern "C" fn error_callback_wrapper(
429            error_code: sys::CcapErrorCode,
430            description: *const c_char,
431            user_data: *mut std::ffi::c_void,
432        ) {
433            if user_data.is_null() || description.is_null() {
434                return;
435            }
436
437            // SAFETY: user_data points to Box<ErrorCallbackBox> created below
438            let callback = &**(user_data as *const ErrorCallbackBox);
439            let desc_cstr = std::ffi::CStr::from_ptr(description);
440            if let Ok(desc_str) = desc_cstr.to_str() {
441                callback(error_code as i32, desc_str);
442            }
443        }
444
445        // Clean up old callback if exists (use module-level GLOBAL_ERROR_CALLBACK)
446        if let Ok(mut guard) = GLOBAL_ERROR_CALLBACK.lock() {
447            if let Some(SendSyncPtr(old_ptr)) = guard.take() {
448                unsafe {
449                    let _ = Box::from_raw(old_ptr as *mut ErrorCallbackBox);
450                }
451            }
452
453            // Store new callback - double box for stable pointer
454            let callback_box: ErrorCallbackBox = Box::new(callback);
455            let callback_ptr = Box::into_raw(Box::new(callback_box));
456
457            unsafe {
458                sys::ccap_set_error_callback(
459                    Some(error_callback_wrapper),
460                    callback_ptr as *mut std::ffi::c_void,
461                );
462            }
463
464            *guard = Some(SendSyncPtr(callback_ptr as *mut std::ffi::c_void));
465        }
466    }
467
468    /// Set the **global** error callback.
469    ///
470    /// This is an alias for [`Provider::set_error_callback`] to make the global scope explicit.
471    pub fn set_global_error_callback<F>(callback: F)
472    where
473        F: Fn(i32, &str) + Send + Sync + 'static,
474    {
475        Self::set_error_callback(callback)
476    }
477
478    /// Clear the global error callback
479    ///
480    /// This removes the error callback and frees associated memory.
481    pub fn clear_error_callback() {
482        type ErrorCallbackBox = Box<dyn Fn(i32, &str) + Send + Sync>;
483
484        // Use module-level GLOBAL_ERROR_CALLBACK (same as set_error_callback)
485        if let Ok(mut guard) = GLOBAL_ERROR_CALLBACK.lock() {
486            // Always clear the C-side callback even if we don't have a stored Rust callback.
487            unsafe {
488                sys::ccap_set_error_callback(None, ptr::null_mut());
489            }
490            if let Some(SendSyncPtr(old_ptr)) = guard.take() {
491                unsafe {
492                    let _ = Box::from_raw(old_ptr as *mut ErrorCallbackBox);
493                }
494            }
495        }
496    }
497
498    /// Clear the **global** error callback.
499    ///
500    /// This is an alias for [`Provider::clear_error_callback`] to make the global scope explicit.
501    pub fn clear_global_error_callback() {
502        Self::clear_error_callback()
503    }
504
505    /// Open device with index and auto start
506    pub fn open_with_index(&mut self, device_index: i32, auto_start: bool) -> Result<()> {
507        // If the previous provider was running, stop it and detach callbacks
508        // before destroying the underlying handle.
509        if !self.handle.is_null() {
510            let _ = self.stop_capture();
511            let _ = self.remove_new_frame_callback();
512            self.cleanup_callback();
513            unsafe {
514                sys::ccap_provider_destroy(self.handle);
515            }
516        } else {
517            // Clean up any stale callback allocation even if handle is null.
518            self.cleanup_callback();
519        }
520
521        // Create a new provider with the specified device index
522        self.handle = unsafe { sys::ccap_provider_create_with_index(device_index, ptr::null()) };
523
524        if self.handle.is_null() {
525            return Err(CcapError::InvalidDevice(format!(
526                "device index {}",
527                device_index
528            )));
529        }
530
531        // ccap C API contract: create_with_index opens the device.
532        self.is_opened = true;
533        if auto_start {
534            self.start_capture()?;
535        }
536        Ok(())
537    }
538
539    /// Set a callback for new frame notifications
540    ///
541    /// The callback receives a reference to the captured frame and returns `true`
542    /// to continue capturing or `false` to stop.
543    ///
544    /// # Thread Safety
545    ///
546    /// The callback will be invoked from the camera capture thread. Ensure your
547    /// callback is thread-safe (`Send + Sync`).
548    ///
549    /// # Example
550    ///
551    /// ```ignore
552    /// provider.set_new_frame_callback(|frame| {
553    ///     println!("Got frame: {}x{}", frame.width(), frame.height());
554    ///     true // continue capturing
555    /// })?;
556    /// ```
557    pub fn set_new_frame_callback<F>(&mut self, callback: F) -> Result<()>
558    where
559        F: Fn(&VideoFrame) -> bool + Send + Sync + 'static,
560    {
561        use std::os::raw::c_void;
562
563        // Type alias for the boxed callback to ensure consistency
564        type CallbackBox = Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>;
565
566        // Clean up old callback if exists
567        self.cleanup_callback();
568
569        unsafe extern "C" fn new_frame_callback_wrapper(
570            frame: *const sys::CcapVideoFrame,
571            user_data: *mut c_void,
572        ) -> bool {
573            if user_data.is_null() || frame.is_null() {
574                return false;
575            }
576
577            // SAFETY: user_data points to a Box<CallbackBox> that we created below
578            let callback = &**(user_data as *const CallbackBox);
579
580            // Create a temporary VideoFrame wrapper that doesn't own the frame
581            let video_frame = VideoFrame::from_c_ptr_ref(frame as *mut sys::CcapVideoFrame);
582            callback(&video_frame)
583        }
584
585        // Box the callback as a trait object, then box again to get a thin pointer
586        // This ensures we can safely convert to/from *mut c_void
587        let callback_box: CallbackBox = Box::new(callback);
588        let callback_ptr = Box::into_raw(Box::new(callback_box));
589
590        let success = unsafe {
591            sys::ccap_provider_set_new_frame_callback(
592                self.handle,
593                Some(new_frame_callback_wrapper),
594                callback_ptr as *mut c_void,
595            )
596        };
597
598        if success {
599            self.callback_ptr = Some(callback_ptr as *mut c_void);
600            Ok(())
601        } else {
602            // Clean up on failure
603            unsafe {
604                let _ = Box::from_raw(callback_ptr);
605            }
606            Err(CcapError::InvalidParameter(
607                "Failed to set frame callback".to_string(),
608            ))
609        }
610    }
611
612    /// Remove frame callback
613    pub fn remove_new_frame_callback(&mut self) -> Result<()> {
614        let success = unsafe {
615            sys::ccap_provider_set_new_frame_callback(self.handle, None, ptr::null_mut())
616        };
617
618        if success {
619            self.cleanup_callback();
620            Ok(())
621        } else {
622            Err(CcapError::CaptureStopFailed)
623        }
624    }
625
626    /// Clean up callback pointer
627    fn cleanup_callback(&mut self) {
628        // Type alias must match what we used in set_new_frame_callback
629        type CallbackBox = Box<dyn Fn(&VideoFrame) -> bool + Send + Sync>;
630
631        if let Some(callback_ptr) = self.callback_ptr.take() {
632            unsafe {
633                // SAFETY: callback_ptr was created with Box::into_raw(Box::new(callback_box))
634                // where callback_box is a CallbackBox
635                let _ = Box::from_raw(callback_ptr as *mut CallbackBox);
636            }
637        }
638    }
639}
640
641impl Drop for Provider {
642    fn drop(&mut self) {
643        // Clean up callback first
644        self.cleanup_callback();
645
646        if !self.handle.is_null() {
647            unsafe {
648                sys::ccap_provider_destroy(self.handle);
649            }
650            self.handle = ptr::null_mut();
651        }
652    }
653}