1use std::sync::{
25 Arc, Mutex, OnceLock,
26 atomic::{AtomicU64, Ordering},
27};
28
29use cranpose_core::{EventStream, State, rememberEventStream};
30
31use crate::registry::ServiceRegistry;
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
35pub enum FrameFormat {
36 #[default]
38 Rgba8,
39 Rgb8,
45 Nv12,
52}
53
54impl FrameFormat {
55 pub fn byte_len(self, width: u32, height: u32) -> Option<usize> {
58 let pixels = (width as usize).checked_mul(height as usize)?;
59 match self {
60 FrameFormat::Rgba8 => pixels.checked_mul(4),
61 FrameFormat::Rgb8 => pixels.checked_mul(3),
62 FrameFormat::Nv12 => {
63 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
64 return None;
65 }
66 pixels.checked_add(pixels / 2)
67 }
68 }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct CameraFrame {
75 pub width: u32,
76 pub height: u32,
77 pub format: FrameFormat,
78 pub rotation_degrees: u16,
81 pub sequence: u64,
84 pub bytes: Vec<u8>,
86}
87
88impl CameraFrame {
89 pub fn new(
93 width: u32,
94 height: u32,
95 format: FrameFormat,
96 rotation_degrees: u16,
97 sequence: u64,
98 bytes: Vec<u8>,
99 ) -> Option<Self> {
100 if format.byte_len(width, height)? != bytes.len() {
101 return None;
102 }
103 Some(Self {
104 width,
105 height,
106 format,
107 rotation_degrees: rotation_degrees % 360,
108 sequence,
109 bytes,
110 })
111 }
112
113 pub fn to_rgba8(&self) -> Vec<u8> {
119 match self.format {
120 FrameFormat::Rgba8 => self.bytes.clone(),
121 FrameFormat::Rgb8 => rgb8_to_rgba8(&self.bytes),
122 FrameFormat::Nv12 => nv12_to_rgba8(self.width, self.height, &self.bytes),
123 }
124 }
125
126 pub fn upright_rgba8(&self) -> UprightRgba {
135 let rotation = self.rotation_degrees;
136 if !matches!(rotation, 90 | 180 | 270) {
137 return UprightRgba {
138 width: self.width,
139 height: self.height,
140 rgba: self.to_rgba8(),
141 };
142 }
143 let (width, height) = (self.width as usize, self.height as usize);
144 let (out_width, out_height) = match rotation {
145 90 | 270 => (self.height, self.width),
146 _ => (self.width, self.height),
147 };
148 let mut rgba = vec![0u8; width * height * 4];
149 match self.format {
150 FrameFormat::Rgba8 => {
151 for y in 0..height {
152 let row = &self.bytes[y * width * 4..(y + 1) * width * 4];
153 for x in 0..width {
154 let src = &row[x * 4..x * 4 + 4];
155 let dst = turned_index(rotation, width, height, x, y) * 4;
156 rgba[dst..dst + 4].copy_from_slice(src);
157 }
158 }
159 }
160 FrameFormat::Rgb8 => {
161 for y in 0..height {
162 let row = &self.bytes[y * width * 3..(y + 1) * width * 3];
163 for x in 0..width {
164 let src = &row[x * 3..x * 3 + 3];
165 let dst = turned_index(rotation, width, height, x, y) * 4;
166 rgba[dst..dst + 3].copy_from_slice(src);
167 rgba[dst + 3] = 255;
168 }
169 }
170 }
171 FrameFormat::Nv12 => {
172 let pixels = width * height;
173 if self.bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
174 return UprightRgba {
175 width: out_width,
176 height: out_height,
177 rgba,
178 };
179 }
180 let (luma, chroma) = self.bytes.split_at(pixels);
181 for y in 0..height {
182 let luma_row = &luma[y * width..(y + 1) * width];
183 let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
184 for x in 0..width {
185 let luminance = luma_row[x] as i32;
186 let blue_difference = chroma_row[x & !1] as i32 - 128;
187 let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
188 let dst = turned_index(rotation, width, height, x, y) * 4;
189 rgba[dst] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
190 rgba[dst + 1] = clamp_byte(
191 luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16),
192 );
193 rgba[dst + 2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
194 rgba[dst + 3] = 255;
195 }
196 }
197 }
198 }
199 UprightRgba {
200 width: out_width,
201 height: out_height,
202 rgba,
203 }
204 }
205}
206
207#[derive(Clone, Debug, PartialEq, Eq)]
212pub struct UprightRgba {
213 pub width: u32,
214 pub height: u32,
215 pub rgba: Vec<u8>,
216}
217
218#[inline]
219fn turned_index(rotation: u16, width: usize, height: usize, x: usize, y: usize) -> usize {
220 match rotation {
221 90 => x * height + (height - 1 - y),
222 180 => (height - 1 - y) * width + (width - 1 - x),
223 270 => (width - 1 - x) * height + y,
224 _ => y * width + x,
225 }
226}
227
228fn rgb8_to_rgba8(bytes: &[u8]) -> Vec<u8> {
229 let mut rgba = Vec::with_capacity(bytes.len() / 3 * 4);
230 for [red, green, blue] in bytes.as_chunks::<3>().0 {
231 rgba.extend_from_slice(&[*red, *green, *blue, 255]);
232 }
233 rgba
234}
235
236fn nv12_to_rgba8(width: u32, height: u32, bytes: &[u8]) -> Vec<u8> {
237 let (width, height) = (width as usize, height as usize);
238 let pixels = width * height;
239 let mut rgba = vec![0u8; pixels * 4];
240 if bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
241 return rgba;
242 }
243 let (luma, chroma) = bytes.split_at(pixels);
244
245 for y in 0..height {
246 let luma_row = &luma[y * width..(y + 1) * width];
247 let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
248 let out_row = &mut rgba[y * width * 4..(y + 1) * width * 4];
249 for x in 0..width {
250 let luminance = luma_row[x] as i32;
251 let blue_difference = chroma_row[x & !1] as i32 - 128;
252 let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
253 let out = &mut out_row[x * 4..x * 4 + 4];
254 out[0] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
255 out[1] =
256 clamp_byte(luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16));
257 out[2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
258 out[3] = 255;
259 }
260 }
261 rgba
262}
263
264fn clamp_byte(value: i32) -> u8 {
265 value.clamp(0, 255) as u8
266}
267
268#[derive(Clone, Debug, PartialEq, Eq)]
275pub struct CameraStill {
276 pub jpeg: Vec<u8>,
277}
278
279#[derive(Clone, Debug, PartialEq, Eq)]
286pub struct CameraLens {
287 pub id: String,
288 pub name: String,
289 pub facing: LensFacing,
292}
293
294#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
296pub enum LensFacing {
297 #[default]
299 Back,
300 Front,
302 External,
304}
305
306#[derive(Clone, Debug, Default, PartialEq, Eq)]
314pub struct CameraLenses {
315 pub lenses: Vec<CameraLens>,
317 pub active: Option<String>,
320}
321
322#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
328pub enum FlashMode {
329 #[default]
330 Off,
331 Auto,
332 On,
333}
334
335#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
336pub enum CameraError {
337 #[error("live camera capture is not supported here")]
339 Unsupported,
340 #[error("camera permission denied")]
342 PermissionDenied,
343 #[error("the camera is not running")]
345 NotRunning,
346 #[error("{0}")]
348 Failed(String),
349}
350
351#[derive(Clone, Debug, Default, PartialEq, Eq)]
353pub enum CameraState {
354 #[default]
356 Idle,
357 Starting,
360 Running {
362 device: String,
364 },
365 Stopped,
367 Failed(CameraError),
369}
370
371impl CameraState {
372 pub fn is_running(&self) -> bool {
374 matches!(self, CameraState::Running { .. })
375 }
376
377 pub fn is_active(&self) -> bool {
380 matches!(self, CameraState::Starting | CameraState::Running { .. })
381 }
382
383 pub fn failure(&self) -> Option<&CameraError> {
385 match self {
386 CameraState::Failed(error) => Some(error),
387 _ => None,
388 }
389 }
390}
391
392pub trait Camera: Send + Sync {
398 fn start(&self) -> Result<(), CameraError>;
404
405 fn stop(&self);
407
408 fn request_still(&self) -> Result<(), CameraError> {
414 Err(CameraError::Unsupported)
415 }
416
417 fn set_torch(&self, _on: bool) -> bool {
423 false
424 }
425
426 fn lenses(&self) -> Vec<CameraLens> {
435 Vec::new()
436 }
437
438 fn lens(&self) -> Option<String> {
441 None
442 }
443
444 fn use_lens(&self, _id: &str) -> bool {
447 false
448 }
449
450 fn has_flash(&self) -> bool {
452 false
453 }
454
455 fn set_flash(&self, _mode: FlashMode) -> bool {
458 false
459 }
460}
461
462pub type CameraRef = Arc<dyn Camera>;
464
465static PLATFORM_CAMERA: ServiceRegistry<dyn Camera> = ServiceRegistry::new();
466
467pub fn set_platform_camera(camera: CameraRef) {
469 PLATFORM_CAMERA.set(camera);
470}
471
472pub fn clear_platform_camera() {
475 PLATFORM_CAMERA.clear();
476 if let Ok(mut observers) = frame_observers().lock() {
477 observers.clear();
478 }
479 if let Ok(mut observers) = state_observers().lock() {
480 observers.clear();
481 }
482 if let Ok(mut observers) = still_observers().lock() {
483 observers.clear();
484 }
485 if let Ok(mut observers) = lens_observers().lock() {
486 observers.clear();
487 }
488 if let Ok(mut latest) = latest_frame_slot().lock() {
489 *latest = None;
490 }
491 if let Ok(mut state) = state_slot().lock() {
492 *state = CameraState::Idle;
493 }
494 if let Ok(mut lenses) = lenses_slot().lock() {
495 *lenses = CameraLenses::default();
496 }
497 DROPPED_FRAMES.store(0, Ordering::Release);
498}
499
500pub fn camera() -> Option<CameraRef> {
503 PLATFORM_CAMERA.get()
504}
505
506pub fn camera_supported() -> bool {
508 PLATFORM_CAMERA.get().is_some()
509}
510
511fn state_slot() -> &'static Mutex<CameraState> {
512 static SLOT: OnceLock<Mutex<CameraState>> = OnceLock::new();
513 SLOT.get_or_init(|| Mutex::new(CameraState::Idle))
514}
515
516fn latest_frame_slot() -> &'static Mutex<Option<CameraFrame>> {
517 static SLOT: OnceLock<Mutex<Option<CameraFrame>>> = OnceLock::new();
518 SLOT.get_or_init(|| Mutex::new(None))
519}
520
521fn lenses_slot() -> &'static Mutex<CameraLenses> {
522 static SLOT: OnceLock<Mutex<CameraLenses>> = OnceLock::new();
523 SLOT.get_or_init(|| Mutex::new(CameraLenses::default()))
524}
525
526static DROPPED_FRAMES: AtomicU64 = AtomicU64::new(0);
527
528type FrameObserver = Arc<dyn Fn(CameraFrame) + Send + Sync>;
529type StateObserver = Arc<dyn Fn(CameraState) + Send + Sync>;
530type StillObserver = Arc<dyn Fn(Result<CameraStill, CameraError>) + Send + Sync>;
531type LensObserver = Arc<dyn Fn(CameraLenses) + Send + Sync>;
532
533fn frame_observers() -> &'static Mutex<Vec<(u64, FrameObserver)>> {
534 static SLOT: OnceLock<Mutex<Vec<(u64, FrameObserver)>>> = OnceLock::new();
535 SLOT.get_or_init(|| Mutex::new(Vec::new()))
536}
537
538fn state_observers() -> &'static Mutex<Vec<(u64, StateObserver)>> {
539 static SLOT: OnceLock<Mutex<Vec<(u64, StateObserver)>>> = OnceLock::new();
540 SLOT.get_or_init(|| Mutex::new(Vec::new()))
541}
542
543fn still_observers() -> &'static Mutex<Vec<(u64, StillObserver)>> {
544 static SLOT: OnceLock<Mutex<Vec<(u64, StillObserver)>>> = OnceLock::new();
545 SLOT.get_or_init(|| Mutex::new(Vec::new()))
546}
547
548fn lens_observers() -> &'static Mutex<Vec<(u64, LensObserver)>> {
549 static SLOT: OnceLock<Mutex<Vec<(u64, LensObserver)>>> = OnceLock::new();
550 SLOT.get_or_init(|| Mutex::new(Vec::new()))
551}
552
553static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
554
555pub struct CameraObserver {
557 id: u64,
558 kind: ObserverKind,
559}
560
561#[derive(Clone, Copy)]
562enum ObserverKind {
563 Frame,
564 State,
565 Still,
566 Lenses,
567}
568
569impl Drop for CameraObserver {
570 fn drop(&mut self) {
571 match self.kind {
572 ObserverKind::Frame => retain_without(frame_observers(), self.id),
573 ObserverKind::State => retain_without(state_observers(), self.id),
574 ObserverKind::Still => retain_without(still_observers(), self.id),
575 ObserverKind::Lenses => retain_without(lens_observers(), self.id),
576 }
577 }
578}
579
580fn retain_without<T>(slot: &'static Mutex<Vec<(u64, T)>>, id: u64) {
581 if let Ok(mut observers) = slot.lock() {
582 observers.retain(|(observer, _)| *observer != id);
583 }
584}
585
586fn snapshot<T: Clone>(slot: &'static Mutex<Vec<(u64, T)>>) -> Vec<T> {
587 slot.lock()
588 .map(|observers| {
589 observers
590 .iter()
591 .map(|(_, observer)| observer.clone())
592 .collect()
593 })
594 .unwrap_or_default()
595}
596
597pub fn latest_camera_frame() -> Option<CameraFrame> {
602 latest_frame_slot()
603 .lock()
604 .map_or(None, |frame| frame.clone())
605}
606
607pub fn camera_state() -> CameraState {
609 state_slot()
610 .lock()
611 .map(|state| state.clone())
612 .unwrap_or_default()
613}
614
615pub fn dropped_camera_frames() -> u64 {
617 DROPPED_FRAMES.load(Ordering::Acquire)
618}
619
620pub fn publish_camera_frame(frame: CameraFrame) {
627 if let Ok(mut latest) = latest_frame_slot().lock() {
628 *latest = Some(frame.clone());
629 }
630 let observers = snapshot(frame_observers());
631 if observers.is_empty() {
632 return;
633 }
634 for observer in observers {
635 observer(frame.clone());
636 }
637}
638
639pub fn record_dropped_camera_frame() {
644 DROPPED_FRAMES.fetch_add(1, Ordering::AcqRel);
645}
646
647pub fn publish_camera_state(state: CameraState) {
649 {
650 let Ok(mut current) = state_slot().lock() else {
651 return;
652 };
653 if *current == state {
654 return;
655 }
656 *current = state.clone();
657 }
658 if matches!(state, CameraState::Idle | CameraState::Starting) {
659 DROPPED_FRAMES.store(0, Ordering::Release);
660 if let Ok(mut latest) = latest_frame_slot().lock() {
661 *latest = None;
662 }
663 }
664 for observer in snapshot(state_observers()) {
665 observer(state.clone());
666 }
667}
668
669pub fn publish_camera_still(still: Result<CameraStill, CameraError>) {
671 for observer in snapshot(still_observers()) {
672 observer(still.clone());
673 }
674}
675
676pub fn publish_camera_lenses(lenses: CameraLenses) {
679 {
680 let Ok(mut current) = lenses_slot().lock() else {
681 return;
682 };
683 if *current == lenses {
684 return;
685 }
686 *current = lenses.clone();
687 }
688 for observer in snapshot(lens_observers()) {
689 observer(lenses.clone());
690 }
691}
692
693pub fn camera_lenses() -> CameraLenses {
696 lenses_slot()
697 .lock()
698 .map(|lenses| lenses.clone())
699 .unwrap_or_default()
700}
701
702pub fn observe_camera_frames(
705 observer: impl Fn(CameraFrame) + Send + Sync + 'static,
706) -> CameraObserver {
707 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
708 if let Ok(mut observers) = frame_observers().lock() {
709 observers.push((id, Arc::new(observer)));
710 }
711 CameraObserver {
712 id,
713 kind: ObserverKind::Frame,
714 }
715}
716
717pub fn observe_camera_state(
721 observer: impl Fn(CameraState) + Send + Sync + 'static,
722) -> CameraObserver {
723 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
724 let observer: StateObserver = Arc::new(observer);
725 if let Ok(mut observers) = state_observers().lock() {
726 observers.push((id, Arc::clone(&observer)));
727 }
728 observer(camera_state());
729 CameraObserver {
730 id,
731 kind: ObserverKind::State,
732 }
733}
734
735pub fn observe_camera_stills(
737 observer: impl Fn(Result<CameraStill, CameraError>) + Send + Sync + 'static,
738) -> CameraObserver {
739 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
740 if let Ok(mut observers) = still_observers().lock() {
741 observers.push((id, Arc::new(observer)));
742 }
743 CameraObserver {
744 id,
745 kind: ObserverKind::Still,
746 }
747}
748
749pub fn observe_camera_lenses(
753 observer: impl Fn(CameraLenses) + Send + Sync + 'static,
754) -> CameraObserver {
755 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
756 let observer: LensObserver = Arc::new(observer);
757 if let Ok(mut observers) = lens_observers().lock() {
758 observers.push((id, Arc::clone(&observer)));
759 }
760 observer(camera_lenses());
761 CameraObserver {
762 id,
763 kind: ObserverKind::Lenses,
764 }
765}
766
767#[expect(non_snake_case)]
770#[track_caller]
771pub fn rememberCameraState() -> State<CameraState> {
772 let updates = rememberEventStream((), |sender| {
773 observe_camera_state(move |state| sender.send(state))
774 });
775 cranpose_core::collectAsState(updates, (), camera_state())
776}
777
778#[expect(non_snake_case)]
785#[track_caller]
786pub fn rememberCameraFrames() -> EventStream<CameraFrame> {
787 rememberEventStream((), |sender| {
788 observe_camera_frames(move |frame| sender.send(frame))
789 })
790}
791
792#[expect(non_snake_case)]
794#[track_caller]
795pub fn rememberCameraStills() -> EventStream<Result<CameraStill, CameraError>> {
796 rememberEventStream((), |sender| {
797 observe_camera_stills(move |still| sender.send(still))
798 })
799}
800
801#[expect(non_snake_case)]
804#[track_caller]
805pub fn rememberCameraLenses() -> State<CameraLenses> {
806 let updates = rememberEventStream((), |sender| {
807 observe_camera_lenses(move |lenses| sender.send(lenses))
808 });
809 cranpose_core::collectAsState(updates, (), camera_lenses())
810}
811
812pub fn start_camera() -> Result<(), CameraError> {
815 let Some(camera) = camera() else {
816 publish_camera_state(CameraState::Failed(CameraError::Unsupported));
817 return Err(CameraError::Unsupported);
818 };
819 publish_camera_state(CameraState::Starting);
820 camera.start().inspect_err(|error| {
821 publish_camera_state(CameraState::Failed(error.clone()));
822 })
823}
824
825pub fn stop_camera() {
827 if let Some(camera) = camera() {
828 camera.stop();
829 }
830 publish_camera_state(CameraState::Stopped);
831}
832
833pub fn request_camera_still() -> Result<(), CameraError> {
836 let Some(camera) = camera() else {
837 return Err(CameraError::Unsupported);
838 };
839 if !camera_state().is_running() {
840 return Err(CameraError::NotRunning);
841 }
842 camera.request_still()
843}
844
845pub async fn capture_camera_still() -> Result<CameraStill, CameraError> {
855 let signal = crate::async_io::Signal::new();
856 let deliver = signal.clone();
857 let observer = observe_camera_stills(move |result| deliver.set(result));
858 request_camera_still()?;
859 let arrived = signal.wait().await;
860 drop(observer);
861 arrived.unwrap_or(Err(CameraError::NotRunning))
862}
863
864#[cfg(test)]
865#[path = "tests/camera_tests.rs"]
866mod tests;