kataglyphis_rustprojecttemplate/api/
webcam.rs1use crate::frb_generated::StreamSink;
8
9pub struct CameraDesc {
10 pub index: u32,
11 pub name: String,
12}
13
14pub struct DetectionBox {
18 pub x1: f32,
19 pub y1: f32,
20 pub x2: f32,
21 pub y2: f32,
22 pub score: f32,
23 pub class_id: i64,
24}
25
26#[cfg(all(feature = "gstreamer", onnx))]
27impl From<crate::Detection> for DetectionBox {
28 fn from(d: crate::Detection) -> Self {
29 Self {
30 x1: d.x1,
31 y1: d.y1,
32 x2: d.x2,
33 y2: d.y2,
34 score: d.score,
35 class_id: d.class_id,
36 }
37 }
38}
39
40pub struct WebcamStreamConfig {
41 pub use_test_source: bool,
43 pub device_index: Option<u32>,
45 pub width: u32,
46 pub height: u32,
47 pub framerate: u32,
48 pub model_path: String,
49 pub score_threshold: f32,
50 pub texture_id: i64,
52 pub texture_library: Option<String>,
54}
55
56pub struct DetectionEvent {
57 pub detections: Vec<DetectionBox>,
58 pub frame_sequence: u64,
59 pub pts_ms: Option<u64>,
60 pub inference_ms: f64,
61 pub fps: f32,
62}
63
64#[flutter_rust_bridge::frb(sync)]
65pub fn list_cameras() -> Result<Vec<CameraDesc>, String> {
66 #[cfg(feature = "gstreamer")]
67 {
68 kataglyphis_media::list_cameras()
69 .map(|cams| {
70 cams.into_iter()
71 .map(|c| CameraDesc {
72 index: c.index,
73 name: c.display_name,
74 })
75 .collect()
76 })
77 .map_err(|e| format!("camera enumeration failed: {e:#}"))
78 }
79 #[cfg(not(feature = "gstreamer"))]
80 {
81 Err("webcam capture is disabled. Build with --features gstreamer".into())
82 }
83}
84
85pub fn start_webcam_inference(
88 config: WebcamStreamConfig,
89 sink: StreamSink<DetectionEvent>,
90) -> Result<(), String> {
91 #[cfg(all(feature = "gstreamer", onnx))]
92 {
93 enabled::start(config, sink).map_err(|e| {
94 log::error!("start_webcam_inference failed: {e:#}");
95 format!("start_webcam_inference failed: {e:#}")
96 })
97 }
98 #[cfg(not(all(feature = "gstreamer", onnx)))]
99 {
100 let (_, _) = (config, sink);
101 Err("webcam inference is disabled. Build with --features gstreamer,onnxruntime*".into())
102 }
103}
104
105#[flutter_rust_bridge::frb(sync)]
107pub fn stop_webcam_inference() {
108 #[cfg(all(feature = "gstreamer", onnx))]
109 enabled::stop();
110}
111
112#[cfg(all(feature = "gstreamer", onnx))]
113mod enabled {
114 use std::sync::{Mutex, OnceLock};
115
116 use anyhow::Result;
117 use kataglyphis_media::CameraSource;
118
119 use super::{DetectionEvent, StreamSink, WebcamStreamConfig};
120 use crate::webcam_engine::{EngineConfig, TextureTarget, WebcamEngine};
121
122 static ENGINE: OnceLock<Mutex<Option<WebcamEngine>>> = OnceLock::new();
123
124 fn engine_slot() -> &'static Mutex<Option<WebcamEngine>> {
125 ENGINE.get_or_init(|| Mutex::new(None))
126 }
127
128 fn lock() -> std::sync::MutexGuard<'static, Option<WebcamEngine>> {
129 engine_slot()
130 .lock()
131 .unwrap_or_else(|poisoned| poisoned.into_inner())
132 }
133
134 pub(super) fn start(
135 config: WebcamStreamConfig,
136 sink: StreamSink<DetectionEvent>,
137 ) -> Result<()> {
138 let mut slot = lock();
139 if slot.is_some() {
140 anyhow::bail!("webcam inference is already running; stop it first");
141 }
142
143 let source = if config.use_test_source {
144 CameraSource::Test
145 } else {
146 match config.device_index {
147 Some(index) => CameraSource::Device(index),
148 None => CameraSource::Auto,
149 }
150 };
151
152 let texture = (config.texture_id != 0).then(|| TextureTarget {
153 library: config
154 .texture_library
155 .clone()
156 .unwrap_or_else(default_texture_library),
157 texture_id: config.texture_id,
158 });
159
160 let engine = WebcamEngine::start(
161 EngineConfig {
162 source,
163 width: config.width,
164 height: config.height,
165 framerate: config.framerate,
166 model_path: config.model_path,
167 score_threshold: config.score_threshold,
168 texture,
169 },
170 Box::new(move |event| {
171 let _ = sink.add(DetectionEvent {
172 detections: event.detections.into_iter().map(Into::into).collect(),
173 frame_sequence: event.frame_sequence,
174 pts_ms: event.pts_ms,
175 inference_ms: event.inference_ms,
176 fps: event.fps,
177 });
178 }),
179 )?;
180
181 *slot = Some(engine);
182 Ok(())
183 }
184
185 pub(super) fn stop() {
186 let engine = lock().take();
187 if let Some(mut engine) = engine {
188 engine.stop();
189 }
190 }
191
192 fn default_texture_library() -> String {
193 if cfg!(windows) {
194 "kataglyphis_native_inference_plugin.dll".into()
195 } else {
196 "libkataglyphis_native_inference_plugin.so".into()
197 }
198 }
199}