libcamera/
camera.rs

1use std::{
2    collections::HashMap,
3    ffi::CStr,
4    io,
5    marker::PhantomData,
6    ops::{Deref, DerefMut},
7    ptr::NonNull,
8    sync::Mutex,
9};
10
11use libcamera_sys::*;
12
13use crate::{
14    camera_manager::CameraTracker,
15    control::{ControlInfoMap, ControlList, PropertyList},
16    geometry::{Rectangle, Size},
17    request::Request,
18    stream::{Stream, StreamConfigurationRef, StreamRole},
19    utils::Immutable,
20};
21
22/// Status of [CameraConfiguration]
23#[derive(Debug, Clone, Copy)]
24pub enum CameraConfigurationStatus {
25    /// Camera configuration was validated without issues.
26    Valid,
27    /// Camera configuration is valid, but some of the fields were adjusted by libcamera.
28    Adjusted,
29    /// Camera configuration is invalid.
30    Invalid,
31}
32
33impl CameraConfigurationStatus {
34    pub fn is_valid(&self) -> bool {
35        matches!(self, Self::Valid)
36    }
37
38    pub fn is_adjusted(&self) -> bool {
39        matches!(self, Self::Adjusted)
40    }
41
42    pub fn is_invalid(&self) -> bool {
43        matches!(self, Self::Invalid)
44    }
45}
46
47impl TryFrom<libcamera_camera_configuration_status_t> for CameraConfigurationStatus {
48    type Error = ();
49
50    fn try_from(value: libcamera_camera_configuration_status_t) -> Result<Self, Self::Error> {
51        match value {
52            libcamera_camera_configuration_status::LIBCAMERA_CAMERA_CONFIGURATION_STATUS_VALID => Ok(Self::Valid),
53            libcamera_camera_configuration_status::LIBCAMERA_CAMERA_CONFIGURATION_STATUS_ADJUSTED => Ok(Self::Adjusted),
54            libcamera_camera_configuration_status::LIBCAMERA_CAMERA_CONFIGURATION_STATUS_INVALID => Ok(Self::Invalid),
55            _ => Err(()),
56        }
57    }
58}
59
60/// Desired orientation of the captured image.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum Orientation {
63    Rotate0,
64    Rotate0Mirror,
65    Rotate180,
66    Rotate180Mirror,
67    Rotate90Mirror,
68    Rotate270,
69    Rotate270Mirror,
70    Rotate90,
71}
72
73impl TryFrom<libcamera_orientation_t> for Orientation {
74    type Error = ();
75
76    fn try_from(value: libcamera_orientation_t) -> Result<Self, Self::Error> {
77        match value {
78            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_0 => Ok(Self::Rotate0),
79            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_0_MIRROR => Ok(Self::Rotate0Mirror),
80            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_180 => Ok(Self::Rotate180),
81            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_180_MIRROR => Ok(Self::Rotate180Mirror),
82            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_90_MIRROR => Ok(Self::Rotate90Mirror),
83            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_270 => Ok(Self::Rotate270),
84            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_270_MIRROR => Ok(Self::Rotate270Mirror),
85            libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_90 => Ok(Self::Rotate90),
86            _ => Err(()),
87        }
88    }
89}
90
91impl From<Orientation> for libcamera_orientation_t {
92    fn from(value: Orientation) -> Self {
93        match value {
94            Orientation::Rotate0 => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_0,
95            Orientation::Rotate0Mirror => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_0_MIRROR,
96            Orientation::Rotate180 => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_180,
97            Orientation::Rotate180Mirror => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_180_MIRROR,
98            Orientation::Rotate90Mirror => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_90_MIRROR,
99            Orientation::Rotate270 => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_270,
100            Orientation::Rotate270Mirror => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_270_MIRROR,
101            Orientation::Rotate90 => libcamera_orientation::LIBCAMERA_ORIENTATION_ROTATE_90,
102        }
103    }
104}
105
106pub struct SensorConfiguration {
107    item: NonNull<libcamera_sensor_configuration_t>,
108}
109
110impl SensorConfiguration {
111    pub fn new() -> Self {
112        let ptr = NonNull::new(unsafe { libcamera_sensor_configuration_create() }).unwrap();
113        Self { item: ptr }
114    }
115
116    pub fn from_ptr(ptr: NonNull<libcamera_sensor_configuration_t>) -> Self {
117        Self { item: ptr }
118    }
119
120    pub fn set_bit_depth(&mut self, depth: u32) {
121        unsafe { libcamera_sensor_configuration_set_bit_depth(self.item.as_ptr(), depth) }
122    }
123
124    pub fn set_output_size(&mut self, width: u32, height: u32) {
125        unsafe { libcamera_sensor_configuration_set_output_size(self.item.as_ptr(), width, height) }
126    }
127
128    pub fn set_analog_crop(&mut self, crop: Rectangle) {
129        let rect: libcamera_rectangle_t = crop.into();
130        unsafe { libcamera_sensor_configuration_set_analog_crop(self.item.as_ptr(), &rect) }
131    }
132
133    pub fn set_binning(&mut self, x: u32, y: u32) {
134        unsafe { libcamera_sensor_configuration_set_binning(self.item.as_ptr(), x, y) }
135    }
136
137    pub fn set_skipping(&mut self, x_odd: u32, x_even: u32, y_odd: u32, y_even: u32) {
138        unsafe { libcamera_sensor_configuration_set_skipping(self.item.as_ptr(), x_odd, x_even, y_odd, y_even) }
139    }
140
141    pub fn is_valid(&self) -> bool {
142        unsafe { libcamera_sensor_configuration_is_valid(self.item.as_ptr()) }
143    }
144
145    pub fn bit_depth(&self) -> u32 {
146        unsafe { libcamera_sensor_configuration_get_bit_depth(self.item.as_ptr()) }
147    }
148
149    pub fn output_size(&self) -> Size {
150        let size = unsafe { libcamera_sensor_configuration_get_output_size(self.item.as_ptr()) };
151        size.into()
152    }
153
154    pub fn analog_crop(&self) -> Rectangle {
155        unsafe { libcamera_sensor_configuration_get_analog_crop(self.item.as_ptr()).into() }
156    }
157
158    pub fn binning(&self) -> (u32, u32) {
159        let mut x = 0;
160        let mut y = 0;
161        unsafe { libcamera_sensor_configuration_get_binning(self.item.as_ptr(), &mut x, &mut y) };
162        (x, y)
163    }
164
165    pub fn skipping(&self) -> (u32, u32, u32, u32) {
166        let (mut x_odd, mut x_even, mut y_odd, mut y_even) = (0, 0, 0, 0);
167        unsafe {
168            libcamera_sensor_configuration_get_skipping(
169                self.item.as_ptr(),
170                &mut x_odd,
171                &mut x_even,
172                &mut y_odd,
173                &mut y_even,
174            )
175        };
176        (x_odd, x_even, y_odd, y_even)
177    }
178}
179
180impl Default for SensorConfiguration {
181    fn default() -> Self {
182        Self::new()
183    }
184}
185
186impl Drop for SensorConfiguration {
187    fn drop(&mut self) {
188        unsafe { libcamera_sensor_configuration_destroy(self.item.as_ptr()) }
189    }
190}
191
192/// Camera configuration.
193///
194/// Contains [StreamConfigurationRef] for each stream used by the camera.
195pub struct CameraConfiguration {
196    ptr: NonNull<libcamera_camera_configuration_t>,
197}
198
199impl CameraConfiguration {
200    pub(crate) unsafe fn from_ptr(ptr: NonNull<libcamera_camera_configuration_t>) -> Self {
201        Self { ptr }
202    }
203
204    /// Returns immutable [StreamConfigurationRef] for the camera stream.
205    ///
206    /// # Parameters
207    ///
208    /// * `index` - Camera stream index.
209    pub fn get(&self, index: usize) -> Option<Immutable<StreamConfigurationRef<'_>>> {
210        let ptr = unsafe { libcamera_camera_configuration_at(self.ptr.as_ptr(), index as _) };
211        NonNull::new(ptr).map(|p| Immutable(unsafe { StreamConfigurationRef::from_ptr(p) }))
212    }
213
214    /// Returns mutable [StreamConfigurationRef] for the camera stream.
215    ///
216    /// # Parameters
217    ///
218    /// * `index` - Camera stream index.
219    pub fn get_mut(&mut self, index: usize) -> Option<StreamConfigurationRef<'_>> {
220        let ptr = unsafe { libcamera_camera_configuration_at(self.ptr.as_ptr(), index as _) };
221        NonNull::new(ptr).map(|p| unsafe { StreamConfigurationRef::from_ptr(p) })
222    }
223
224    /// Append a new stream configuration for a given role.
225    pub fn add_configuration(&mut self) -> Option<StreamConfigurationRef<'_>> {
226        let ptr = unsafe { libcamera_camera_configuration_add_configuration(self.ptr.as_ptr()) };
227        NonNull::new(ptr).map(|p| unsafe { StreamConfigurationRef::from_ptr(p) })
228    }
229
230    /// Append a new stream configuration by cloning an existing configuration.
231    ///
232    /// This mirrors libcamera's `addConfiguration(const StreamConfiguration&)` and produces
233    /// a valid entry instead of an empty placeholder, allowing multi-stream configurations
234    /// to validate successfully.
235    pub fn add_configuration_like(
236        &mut self,
237        template: &StreamConfigurationRef<'_>,
238    ) -> Option<StreamConfigurationRef<'_>> {
239        let ptr =
240            unsafe { libcamera_camera_configuration_add_configuration_from(self.ptr.as_ptr(), template.as_ptr()) };
241        NonNull::new(ptr).map(|p| unsafe { StreamConfigurationRef::from_ptr(p) })
242    }
243
244    /// Append multiple stream configurations by cloning existing templates.
245    ///
246    /// Returns the newly appended configurations (in order) for further adjustment.
247    pub fn add_configurations_like<'a>(
248        &mut self,
249        templates: &[&StreamConfigurationRef<'a>],
250    ) -> Vec<StreamConfigurationRef<'_>> {
251        let mut appended = Vec::with_capacity(templates.len());
252        for tmpl in templates {
253            let ptr =
254                unsafe { libcamera_camera_configuration_add_configuration_from(self.ptr.as_ptr(), tmpl.as_ptr()) };
255            if let Some(cfg) = NonNull::new(ptr).map(|p| unsafe { StreamConfigurationRef::from_ptr(p) }) {
256                appended.push(cfg);
257            }
258        }
259        appended
260    }
261
262    pub fn set_sensor_configuration(&mut self, mode: SensorConfiguration) {
263        unsafe { libcamera_camera_set_sensor_configuration(self.ptr.as_ptr(), mode.item.as_ptr()) }
264    }
265
266    /// Returns number of streams within camera configuration.
267    pub fn len(&self) -> usize {
268        unsafe { libcamera_camera_configuration_size(self.ptr.as_ptr()) }
269    }
270
271    /// Returns `true` if camera configuration has no streams.
272    pub fn is_empty(&self) -> bool {
273        self.len() == 0
274    }
275
276    /// Validates camera configuration.
277    pub fn validate(&mut self) -> CameraConfigurationStatus {
278        unsafe { libcamera_camera_configuration_validate(self.ptr.as_ptr()) }
279            .try_into()
280            .unwrap()
281    }
282
283    /// Returns the desired orientation of the captured image.
284    pub fn orientation(&self) -> Orientation {
285        unsafe { libcamera_camera_configuration_get_orientation(self.ptr.as_ptr()) }
286            .try_into()
287            .unwrap()
288    }
289
290    /// Sets the desired orientation of the captured image.
291    pub fn set_orientation(&mut self, orientation: Orientation) {
292        unsafe { libcamera_camera_configuration_set_orientation(self.ptr.as_ptr(), orientation.into()) }
293    }
294
295    /// Returns the sensor configuration if one is set by the application or pipeline.
296    pub fn sensor_configuration(&self) -> Option<SensorConfiguration> {
297        let ptr = unsafe { libcamera_camera_configuration_get_sensor_configuration(self.ptr.as_ptr()) };
298        NonNull::new(ptr).map(SensorConfiguration::from_ptr)
299    }
300
301    /// Re-validate and print stride/frame_size adjustments for each stream (helper for debugging).
302    pub fn validate_and_log(&mut self) -> CameraConfigurationStatus {
303        let status = self.validate();
304        for i in 0..self.len() {
305            if let Some(cfg) = self.get(i) {
306                eprintln!(
307                    "Stream {} after validate(): stride={}, frame_size={}",
308                    i,
309                    cfg.get_stride(),
310                    cfg.get_frame_size()
311                );
312            }
313        }
314        status
315    }
316
317    /// Return the libcamera textual representation of this configuration.
318    pub fn to_string_repr(&self) -> String {
319        unsafe {
320            let ptr = libcamera_camera_configuration_to_string(self.ptr.as_ptr());
321            if ptr.is_null() {
322                return String::new();
323            }
324            let s = std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned();
325            libc::free(ptr.cast());
326            s
327        }
328    }
329}
330
331impl core::fmt::Debug for CameraConfiguration {
332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        let mut list = f.debug_list();
334        for i in 0..self.len() {
335            list.entry(&self.get(i).unwrap().0);
336        }
337        list.finish()
338    }
339}
340
341impl Drop for CameraConfiguration {
342    fn drop(&mut self) {
343        unsafe { libcamera_camera_configuration_destroy(self.ptr.as_ptr()) }
344    }
345}
346
347/// A read-only instance of a camera.
348///
349/// Can be used to obtain camera parameters or supported stream configurations.
350/// In order to be used for capturing, it must be turned into an [ActiveCamera] by [Camera::acquire()].
351pub struct Camera<'d> {
352    pub(crate) ptr: NonNull<libcamera_camera_t>,
353    _phantom: PhantomData<&'d ()>,
354    pub(crate) _token: Option<std::sync::Arc<()>>,
355}
356
357impl<'d> Camera<'d> {
358    pub(crate) unsafe fn from_ptr(ptr: NonNull<libcamera_camera_t>) -> Self {
359        Self {
360            ptr,
361            _phantom: Default::default(),
362            _token: None,
363        }
364    }
365
366    pub(crate) unsafe fn from_ptr_tracked(
367        ptr: NonNull<libcamera_camera_t>,
368        tracker: Option<std::sync::Arc<CameraTracker>>,
369    ) -> Self {
370        let token = tracker.map(|t| t.track());
371        Self {
372            ptr,
373            _phantom: Default::default(),
374            _token: token,
375        }
376    }
377
378    /// ID of the camera.
379    ///
380    /// This usually contains hardware path within the system and is not human-friendly.
381    /// Use [properties::Model](crate::properties::Model) from [Camera::properties()] to obtain a human readable
382    /// identification instead.
383    pub fn id(&self) -> &str {
384        unsafe { CStr::from_ptr(libcamera_camera_id(self.ptr.as_ptr())) }
385            .to_str()
386            .unwrap()
387    }
388
389    /// Returns a list of available camera controls and their limit.
390    pub fn controls(&self) -> &ControlInfoMap {
391        unsafe {
392            ControlInfoMap::from_ptr(NonNull::new(libcamera_camera_controls(self.ptr.as_ptr()).cast_mut()).unwrap())
393        }
394    }
395
396    /// Returns a list of camera properties.
397    ///
398    /// See [properties](crate::properties) for available items.
399    pub fn properties(&self) -> &PropertyList {
400        unsafe {
401            PropertyList::from_ptr(NonNull::new(libcamera_camera_properties(self.ptr.as_ptr()).cast_mut()).unwrap())
402        }
403    }
404
405    /// Returns the set of active streams for this camera.
406    pub fn streams(&self) -> Vec<Stream> {
407        let set = unsafe { libcamera_camera_streams(self.ptr.as_ptr()) };
408        if set.is_null() {
409            return Vec::new();
410        }
411        let count = unsafe { libcamera_stream_set_size(set) };
412        let streams = (0..count)
413            .filter_map(|i| unsafe { NonNull::new(libcamera_stream_set_get(set, i)).map(|p| Stream::from_ptr(p)) })
414            .collect();
415        unsafe { libcamera_stream_set_destroy(set as *mut _) };
416        streams
417    }
418
419    /// Generates default camera configuration for the given [StreamRole]s.
420    ///
421    /// The resulting [CameraConfiguration] contains stream configurations for each of the requested roles.
422    ///
423    /// Generated configuration can be adjusted as needed and then passed onto [ActiveCamera::configure()] to apply.
424    pub fn generate_configuration(&self, roles: &[StreamRole]) -> Option<CameraConfiguration> {
425        let roles: Vec<libcamera_stream_role::Type> = roles.iter().map(|r| (*r).into()).collect();
426        let cfg =
427            unsafe { libcamera_camera_generate_configuration(self.ptr.as_ptr(), roles.as_ptr(), roles.len() as _) };
428        NonNull::new(cfg).map(|p| unsafe { CameraConfiguration::from_ptr(p) })
429    }
430
431    /// Try roles in order and return the first generated configuration that succeeds.
432    pub fn generate_first_supported_configuration(
433        &self,
434        roles: &[StreamRole],
435    ) -> Option<(CameraConfiguration, StreamRole)> {
436        roles
437            .iter()
438            .find_map(|role| self.generate_configuration(&[*role]).map(|cfg| (cfg, *role)))
439    }
440
441    /// Acquires exclusive rights to the camera, which allows changing configuration and capturing.
442    pub fn acquire(&self) -> io::Result<ActiveCamera<'d>> {
443        let ret = unsafe { libcamera_camera_acquire(self.ptr.as_ptr()) };
444        if ret < 0 {
445            Err(io::Error::from_raw_os_error(-ret))
446        } else {
447            Ok(unsafe {
448                ActiveCamera::from_ptr_with_token(
449                    NonNull::new(libcamera_camera_copy(self.ptr.as_ptr())).unwrap(),
450                    self._token.clone(),
451                )
452            })
453        }
454    }
455}
456
457impl Drop for Camera<'_> {
458    fn drop(&mut self) {
459        unsafe { libcamera_camera_destroy(self.ptr.as_ptr()) }
460    }
461}
462
463extern "C" fn camera_request_completed_cb(ptr: *mut core::ffi::c_void, req: *mut libcamera_request_t) {
464    let mut state = unsafe { &*(ptr as *const Mutex<ActiveCameraState<'_>>) }
465        .lock()
466        .unwrap();
467    let req = state.requests.remove(&req).unwrap();
468
469    if let Some(cb) = &mut state.request_completed_cb {
470        cb(req);
471    }
472}
473
474extern "C" fn camera_buffer_completed_cb(
475    ptr: *mut core::ffi::c_void,
476    req: *mut libcamera_request_t,
477    fb: *mut libcamera_framebuffer_t,
478) {
479    let mut state = unsafe { &*(ptr as *const Mutex<ActiveCameraState<'_>>) }
480        .lock()
481        .unwrap();
482
483    let (req_ptr, stream) = match state
484        .requests
485        .get_mut(&req)
486        .and_then(|r| r.stream_for_buffer_ptr(fb).map(|s| (r as *mut Request, s)))
487    {
488        Some(v) => v,
489        None => return,
490    };
491
492    if let Some(cb) = state.buffer_completed_cb.as_mut() {
493        // Safety: req_ptr is valid while held in the map; we only borrow it temporarily.
494        unsafe {
495            cb(&mut *req_ptr, stream);
496        }
497    }
498}
499
500extern "C" fn camera_disconnected_cb(ptr: *mut core::ffi::c_void) {
501    let mut state = unsafe { &*(ptr as *const Mutex<ActiveCameraState<'_>>) }
502        .lock()
503        .unwrap();
504    if let Some(cb) = state.disconnected_cb.as_mut() {
505        cb();
506    }
507}
508
509#[derive(Default)]
510struct ActiveCameraState<'d> {
511    /// List of queued requests that are yet to be executed.
512    /// Used to temporarily store [Request] before returning it back to the user.
513    requests: HashMap<*mut libcamera_request_t, Request>,
514    /// Callback for libcamera `requestCompleted` signal.
515    request_completed_cb: Option<Box<dyn FnMut(Request) + Send + 'd>>,
516    /// Callback for libcamera `bufferCompleted` signal.
517    buffer_completed_cb: Option<BufferCompletedCb<'d>>,
518    /// Callback for libcamera `disconnected` signal.
519    disconnected_cb: Option<Box<dyn FnMut() + Send + 'd>>,
520}
521
522type BufferCompletedCb<'d> = Box<dyn FnMut(&mut Request, Stream) + Send + 'd>;
523
524/// An active instance of a camera.
525///
526/// This gives exclusive access to the camera and allows capturing and modifying configuration.
527///
528/// Obtained by [Camera::acquire()].
529pub struct ActiveCamera<'d> {
530    cam: Camera<'d>,
531    /// Handle to disconnect `requestCompleted` signal.
532    request_completed_handle: *mut libcamera_callback_handle_t,
533    /// Handle to disconnect `bufferCompleted` signal.
534    buffer_completed_handle: *mut libcamera_callback_handle_t,
535    /// Handle to disconnect `disconnected` signal.
536    disconnected_handle: *mut libcamera_callback_handle_t,
537    /// Internal state that is shared with callback handlers.
538    state: Box<Mutex<ActiveCameraState<'d>>>,
539    _token: Option<std::sync::Arc<()>>,
540}
541
542/// Lightweight buffer completion event for channel delivery.
543#[derive(Debug, Clone, Copy)]
544pub struct BufferCompletedEvent {
545    pub stream: Stream,
546    pub request_cookie: u64,
547    pub request_sequence: u32,
548    pub buffer_ptr: usize,
549}
550
551impl<'d> ActiveCamera<'d> {
552    pub(crate) unsafe fn from_ptr_with_token(
553        ptr: NonNull<libcamera_camera_t>,
554        token: Option<std::sync::Arc<()>>,
555    ) -> Self {
556        let mut state = Box::new(Mutex::new(ActiveCameraState::default()));
557
558        let request_completed_handle = unsafe {
559            libcamera_camera_request_completed_connect(
560                ptr.as_ptr(),
561                Some(camera_request_completed_cb),
562                // state is valid for the lifetime of `ActiveCamera` and this callback will be disconnected on drop.
563                state.as_mut() as *mut Mutex<ActiveCameraState<'_>> as *mut _,
564            )
565        };
566
567        Self {
568            cam: Camera::from_ptr(ptr),
569            request_completed_handle,
570            buffer_completed_handle: core::ptr::null_mut(),
571            disconnected_handle: core::ptr::null_mut(),
572            state,
573            _token: token,
574        }
575    }
576
577    /// Sets a callback for completed camera requests.
578    ///
579    /// Callback is executed in the libcamera thread context so it is best to setup a channel to send all requests for
580    /// processing elsewhere.
581    ///
582    /// Only one callback can be set at a time. If there was a previously set callback, it will be discarded when
583    /// setting a new one.
584    pub fn on_request_completed(&mut self, cb: impl FnMut(Request) + Send + 'd) {
585        let mut state = self.state.lock().unwrap();
586        state.request_completed_cb = Some(Box::new(cb));
587    }
588
589    /// Sets a callback for per-buffer completion events.
590    ///
591    /// This fires for every buffer as libcamera emits `bufferCompleted`; the corresponding `Request` remains queued
592    /// until the `requestCompleted` signal fires.
593    pub fn on_buffer_completed(&mut self, cb: impl FnMut(&mut Request, Stream) + Send + 'd) {
594        {
595            let mut state = self.state.lock().unwrap();
596            state.buffer_completed_cb = Some(Box::new(cb));
597        }
598        if self.buffer_completed_handle.is_null() {
599            let data = self.state.as_mut() as *mut Mutex<ActiveCameraState<'_>> as *mut _;
600            self.buffer_completed_handle = unsafe {
601                libcamera_camera_buffer_completed_connect(self.ptr.as_ptr(), Some(camera_buffer_completed_cb), data)
602            };
603        }
604    }
605
606    /// Subscribe to request completed events via a channel (async-friendly).
607    ///
608    /// The returned receiver yields owned `Request`s as libcamera completes them.
609    pub fn subscribe_request_completed(&mut self) -> std::sync::mpsc::Receiver<Request> {
610        let (tx, rx) = std::sync::mpsc::channel();
611        self.on_request_completed(move |req| {
612            let _ = tx.send(req);
613        });
614        rx
615    }
616
617    /// Subscribe to per-buffer completion events via a channel (async-friendly).
618    ///
619    /// The receiver yields lightweight `BufferCompletedEvent` snapshots; the underlying Request
620    /// remains owned by libcamera until requestCompleted fires.
621    pub fn subscribe_buffer_completed(&mut self) -> std::sync::mpsc::Receiver<BufferCompletedEvent> {
622        let (tx, rx) = std::sync::mpsc::channel();
623        self.on_buffer_completed(move |req, stream| {
624            let event = BufferCompletedEvent {
625                stream,
626                request_cookie: req.cookie(),
627                request_sequence: req.sequence(),
628                buffer_ptr: req.find_buffer(&stream).map_or(0, |p| p as usize),
629            };
630            let _ = tx.send(event);
631        });
632        rx
633    }
634
635    /// Sets a callback for camera disconnected events.
636    pub fn on_disconnected(&mut self, cb: impl FnMut() + Send + 'd) {
637        {
638            let mut state = self.state.lock().unwrap();
639            state.disconnected_cb = Some(Box::new(cb));
640        }
641        if self.disconnected_handle.is_null() {
642            let data = self.state.as_mut() as *mut Mutex<ActiveCameraState<'_>> as *mut _;
643            self.disconnected_handle =
644                unsafe { libcamera_camera_disconnected_connect(self.ptr.as_ptr(), Some(camera_disconnected_cb), data) };
645        }
646    }
647
648    /// Applies camera configuration.
649    ///
650    /// Default configuration can be obtained from [Camera::generate_configuration()] and then adjusted as needed.
651    pub fn configure(&mut self, config: &mut CameraConfiguration) -> io::Result<()> {
652        let ret = unsafe { libcamera_camera_configure(self.ptr.as_ptr(), config.ptr.as_ptr()) };
653        if ret < 0 {
654            Err(io::Error::from_raw_os_error(-ret))
655        } else {
656            Ok(())
657        }
658    }
659
660    /// Creates a capture [`Request`].
661    ///
662    /// To perform a capture, it must firstly be initialized by attaching a framebuffer with [Request::add_buffer()] and
663    /// then queued for execution by [ActiveCamera::queue_request()].
664    ///
665    /// # Arguments
666    ///
667    /// * `cookie` - An optional user-provided u64 identifier that can be used to uniquely identify request in request
668    ///   completed callback.
669    pub fn create_request(&mut self, cookie: Option<u64>) -> Option<Request> {
670        let req = unsafe { libcamera_camera_create_request(self.ptr.as_ptr(), cookie.unwrap_or(0)) };
671        NonNull::new(req).map(|p| unsafe { Request::from_ptr(p) })
672    }
673
674    /// Queues [`Request`] for execution. Completed requests are returned in request completed callback, set by the
675    /// `ActiveCamera::on_request_completed()`.
676    ///
677    /// Requests that do not have attached framebuffers are invalid and are rejected without being queued.
678    pub fn queue_request(&self, req: Request) -> Result<(), (Request, io::Error)> {
679        let ptr = req.ptr.as_ptr();
680        // Keep the request alive locally until we know queuing succeeded.
681        let mut pending = Some(req);
682        let ret = unsafe { libcamera_camera_queue_request(self.ptr.as_ptr(), ptr) };
683
684        if ret < 0 {
685            Err((pending.take().unwrap(), io::Error::from_raw_os_error(-ret)))
686        } else {
687            self.state.lock().unwrap().requests.insert(ptr, pending.take().unwrap());
688            Ok(())
689        }
690    }
691
692    /// Starts camera capture session.
693    ///
694    /// Once started, [ActiveCamera::queue_request()] is permitted and camera configuration can no longer be changed.
695    pub fn start(&mut self, controls: Option<&ControlList>) -> io::Result<()> {
696        let ctrl_ptr = controls.map(|c| c.ptr()).unwrap_or(core::ptr::null_mut());
697        let ret = unsafe { libcamera_camera_start(self.ptr.as_ptr(), ctrl_ptr) };
698        if ret < 0 {
699            Err(io::Error::from_raw_os_error(-ret))
700        } else {
701            Ok(())
702        }
703    }
704
705    /// Stops camera capture session.
706    ///
707    /// Once stopped, [ActiveCamera::queue_request()] is no longer permitted and camera configuration can be adjusted.
708    pub fn stop(&mut self) -> io::Result<()> {
709        let ret = unsafe { libcamera_camera_stop(self.ptr.as_ptr()) };
710        if ret < 0 {
711            Err(io::Error::from_raw_os_error(-ret))
712        } else {
713            Ok(())
714        }
715    }
716}
717
718impl<'d> Deref for ActiveCamera<'d> {
719    type Target = Camera<'d>;
720
721    fn deref(&self) -> &Self::Target {
722        &self.cam
723    }
724}
725
726impl DerefMut for ActiveCamera<'_> {
727    fn deref_mut(&mut self) -> &mut Self::Target {
728        &mut self.cam
729    }
730}
731
732impl Drop for ActiveCamera<'_> {
733    fn drop(&mut self) {
734        unsafe {
735            libcamera_camera_request_completed_disconnect(self.ptr.as_ptr(), self.request_completed_handle);
736            if !self.buffer_completed_handle.is_null() {
737                libcamera_camera_buffer_completed_disconnect(self.ptr.as_ptr(), self.buffer_completed_handle);
738            }
739            if !self.disconnected_handle.is_null() {
740                libcamera_camera_disconnected_disconnect(self.ptr.as_ptr(), self.disconnected_handle);
741            }
742            libcamera_camera_stop(self.ptr.as_ptr());
743            libcamera_camera_release(self.ptr.as_ptr());
744        }
745    }
746}