Skip to main content

freya_camera/
capture.rs

1//! Background capture thread that drives [nokhwa] and publishes decoded frames.
2
3use std::sync::{
4    Arc,
5    Mutex,
6    Weak,
7};
8
9use blocking::unblock;
10use bytes::Bytes;
11use freya_core::notify::ArcNotify;
12use nokhwa::{
13    Camera,
14    pixel_format::RgbAFormat,
15    utils::{
16        CameraFormat as NokhwaCameraFormat,
17        FrameFormat,
18        RequestedFormat,
19        RequestedFormatType,
20        Resolution,
21    },
22};
23
24use crate::camera::{
25    CameraConfig,
26    CameraError,
27    CameraFormat,
28    StreamInfo,
29};
30
31/// A single decoded camera frame in `RGBA8` layout.
32#[derive(Clone)]
33pub struct CameraFrame {
34    pub width: u32,
35    pub height: u32,
36    pub data: Bytes,
37}
38
39/// Latest values published by the capture thread. New entries overwrite older ones.
40#[derive(Default)]
41pub struct CaptureState {
42    pub frame: Option<CameraFrame>,
43    pub info: Option<StreamInfo>,
44    pub error: Option<CameraError>,
45}
46
47/// Handle returned by [`spawn_capture`].
48pub struct CaptureHandle {
49    pub state: Arc<Mutex<CaptureState>>,
50    pub wake: ArcNotify,
51}
52
53/// Producer side of the capture channel.
54struct CaptureProducer {
55    state: Weak<Mutex<CaptureState>>,
56    wake: ArcNotify,
57}
58
59impl CaptureProducer {
60    /// Apply `update` to the slot and wake the consumer. Returns `false` if the consumer is gone.
61    fn publish(&self, update: impl FnOnce(&mut CaptureState)) -> bool {
62        let Some(slot) = self.state.upgrade() else {
63            return false;
64        };
65        update(&mut slot.lock().unwrap());
66        self.wake.notify();
67        true
68    }
69}
70
71/// Spawn the capture thread.
72pub fn spawn_capture(config: CameraConfig) -> CaptureHandle {
73    let handle = CaptureHandle {
74        state: Arc::new(Mutex::new(CaptureState::default())),
75        wake: ArcNotify::new(),
76    };
77
78    let producer = CaptureProducer {
79        state: Arc::downgrade(&handle.state),
80        wake: handle.wake.clone(),
81    };
82
83    unblock(move || {
84        if let Err(err) = run_capture(config, &producer) {
85            producer.publish(|slot| slot.error = Some(err));
86        }
87    })
88    .detach();
89
90    handle
91}
92
93fn run_capture(config: CameraConfig, producer: &CaptureProducer) -> Result<(), CameraError> {
94    let requested = RequestedFormat::new::<RgbAFormat>(config.format.into());
95    let mut camera = Camera::new(config.device, requested)?;
96    camera.open_stream()?;
97
98    let resolution = camera.resolution();
99    let info = StreamInfo {
100        width: resolution.width(),
101        height: resolution.height(),
102        frame_rate: camera.frame_rate(),
103    };
104
105    if !producer.publish(|slot| slot.info = Some(info)) {
106        return Ok(());
107    }
108
109    loop {
110        let buffer = camera.frame()?;
111        let decoded = buffer.decode_image::<RgbAFormat>()?;
112        let new_frame = CameraFrame {
113            width: decoded.width(),
114            height: decoded.height(),
115            data: Bytes::from(decoded.into_raw()),
116        };
117
118        if !producer.publish(|slot| slot.frame = Some(new_frame)) {
119            break;
120        }
121    }
122
123    Ok(())
124}
125
126impl From<CameraFormat> for RequestedFormatType {
127    fn from(format: CameraFormat) -> Self {
128        match format {
129            CameraFormat::HighestFrameRate => Self::AbsoluteHighestFrameRate,
130            CameraFormat::HighestResolution => Self::AbsoluteHighestResolution,
131            CameraFormat::Resolution { width, height } => {
132                Self::HighestResolution(Resolution::new(width, height))
133            }
134            CameraFormat::Exact {
135                width,
136                height,
137                frame_rate,
138            } => Self::Closest(NokhwaCameraFormat::new(
139                Resolution::new(width, height),
140                FrameFormat::MJPEG,
141                frame_rate,
142            )),
143        }
144    }
145}