1use std::sync::{Arc, Mutex, OnceLock, atomic::Ordering};
25
26use cranpose_core::{EventStream, State, rememberEventStream};
27use portable_atomic::AtomicU64;
28
29use crate::registry::ServiceRegistry;
30
31#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
33pub enum FrameFormat {
34 #[default]
36 Rgba8,
37 Rgb8,
43 Nv12,
50}
51
52impl FrameFormat {
53 pub fn byte_len(self, width: u32, height: u32) -> Option<usize> {
56 let pixels = (width as usize).checked_mul(height as usize)?;
57 match self {
58 FrameFormat::Rgba8 => pixels.checked_mul(4),
59 FrameFormat::Rgb8 => pixels.checked_mul(3),
60 FrameFormat::Nv12 => {
61 if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
62 return None;
63 }
64 pixels.checked_add(pixels / 2)
65 }
66 }
67 }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct CameraFrame {
73 pub width: u32,
75 pub height: u32,
77 pub format: FrameFormat,
79 pub rotation_degrees: u16,
82 pub sequence: u64,
85 pub bytes: Vec<u8>,
87}
88
89impl CameraFrame {
90 pub fn new(
94 width: u32,
95 height: u32,
96 format: FrameFormat,
97 rotation_degrees: u16,
98 sequence: u64,
99 bytes: Vec<u8>,
100 ) -> Option<Self> {
101 if format.byte_len(width, height)? != bytes.len() {
102 return None;
103 }
104 Some(Self {
105 width,
106 height,
107 format,
108 rotation_degrees: rotation_degrees % 360,
109 sequence,
110 bytes,
111 })
112 }
113
114 pub fn to_rgba8(&self) -> Vec<u8> {
120 match self.format {
121 FrameFormat::Rgba8 => self.bytes.clone(),
122 FrameFormat::Rgb8 => rgb8_to_rgba8(&self.bytes),
123 FrameFormat::Nv12 => nv12_to_rgba8(self.width, self.height, &self.bytes),
124 }
125 }
126
127 pub fn upright_rgba8(&self) -> UprightRgba {
136 let rotation = self.rotation_degrees;
137 if !matches!(rotation, 90 | 180 | 270) {
138 return UprightRgba {
139 width: self.width,
140 height: self.height,
141 rgba: self.to_rgba8(),
142 };
143 }
144 let (width, height) = (self.width as usize, self.height as usize);
145 let (out_width, out_height) = match rotation {
146 90 | 270 => (self.height, self.width),
147 _ => (self.width, self.height),
148 };
149 let mut rgba = vec![0u8; width * height * 4];
150 match self.format {
151 FrameFormat::Rgba8 => {
152 for y in 0..height {
153 let row = &self.bytes[y * width * 4..(y + 1) * width * 4];
154 for x in 0..width {
155 let src = &row[x * 4..x * 4 + 4];
156 let dst = turned_index(rotation, width, height, x, y) * 4;
157 rgba[dst..dst + 4].copy_from_slice(src);
158 }
159 }
160 }
161 FrameFormat::Rgb8 => {
162 for y in 0..height {
163 let row = &self.bytes[y * width * 3..(y + 1) * width * 3];
164 for x in 0..width {
165 let src = &row[x * 3..x * 3 + 3];
166 let dst = turned_index(rotation, width, height, x, y) * 4;
167 rgba[dst..dst + 3].copy_from_slice(src);
168 rgba[dst + 3] = 255;
169 }
170 }
171 }
172 FrameFormat::Nv12 => {
173 let pixels = width * height;
174 if self.bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
175 return UprightRgba {
176 width: out_width,
177 height: out_height,
178 rgba,
179 };
180 }
181 let (luma, chroma) = self.bytes.split_at(pixels);
182 for y in 0..height {
183 let luma_row = &luma[y * width..(y + 1) * width];
184 let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
185 for x in 0..width {
186 let luminance = luma_row[x] as i32;
187 let blue_difference = chroma_row[x & !1] as i32 - 128;
188 let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
189 let dst = turned_index(rotation, width, height, x, y) * 4;
190 rgba[dst] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
191 rgba[dst + 1] = clamp_byte(
192 luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16),
193 );
194 rgba[dst + 2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
195 rgba[dst + 3] = 255;
196 }
197 }
198 }
199 }
200 UprightRgba {
201 width: out_width,
202 height: out_height,
203 rgba,
204 }
205 }
206}
207
208#[derive(Clone, Debug, PartialEq, Eq)]
213pub struct UprightRgba {
214 pub width: u32,
216 pub height: u32,
218 pub rgba: Vec<u8>,
220}
221
222#[inline]
223fn turned_index(rotation: u16, width: usize, height: usize, x: usize, y: usize) -> usize {
224 match rotation {
225 90 => x * height + (height - 1 - y),
226 180 => (height - 1 - y) * width + (width - 1 - x),
227 270 => (width - 1 - x) * height + y,
228 _ => y * width + x,
229 }
230}
231
232fn rgb8_to_rgba8(bytes: &[u8]) -> Vec<u8> {
233 let mut rgba = Vec::with_capacity(bytes.len() / 3 * 4);
234 for [red, green, blue] in bytes.as_chunks::<3>().0 {
235 rgba.extend_from_slice(&[*red, *green, *blue, 255]);
236 }
237 rgba
238}
239
240fn nv12_to_rgba8(width: u32, height: u32, bytes: &[u8]) -> Vec<u8> {
241 let (width, height) = (width as usize, height as usize);
242 let pixels = width * height;
243 let mut rgba = vec![0u8; pixels * 4];
244 if bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
245 return rgba;
246 }
247 let (luma, chroma) = bytes.split_at(pixels);
248
249 for y in 0..height {
250 let luma_row = &luma[y * width..(y + 1) * width];
251 let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
252 let out_row = &mut rgba[y * width * 4..(y + 1) * width * 4];
253 for x in 0..width {
254 let luminance = luma_row[x] as i32;
255 let blue_difference = chroma_row[x & !1] as i32 - 128;
256 let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
257 let out = &mut out_row[x * 4..x * 4 + 4];
258 out[0] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
259 out[1] =
260 clamp_byte(luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16));
261 out[2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
262 out[3] = 255;
263 }
264 }
265 rgba
266}
267
268fn clamp_byte(value: i32) -> u8 {
269 value.clamp(0, 255) as u8
270}
271
272#[derive(Clone, Debug, PartialEq, Eq)]
279pub struct CameraStill {
280 pub jpeg: Vec<u8>,
282}
283
284#[derive(Clone, Debug, PartialEq, Eq)]
291pub struct CameraLens {
292 pub id: String,
294 pub name: String,
296 pub facing: LensFacing,
299}
300
301#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
303pub enum LensFacing {
304 #[default]
306 Back,
307 Front,
309 External,
311}
312
313#[derive(Clone, Debug, Default, PartialEq, Eq)]
321pub struct CameraLenses {
322 pub lenses: Vec<CameraLens>,
324 pub active: Option<String>,
327}
328
329#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
335pub enum FlashMode {
336 #[default]
338 Off,
339 Auto,
341 On,
343}
344
345#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
347pub enum CameraError {
348 #[error("live camera capture is not supported here")]
350 Unsupported,
351 #[error("camera permission denied")]
353 PermissionDenied,
354 #[error("the camera is not running")]
356 NotRunning,
357 #[error("{0}")]
359 Failed(String),
360}
361
362#[derive(Clone, Debug, Default, PartialEq, Eq)]
364pub enum CameraState {
365 #[default]
367 Idle,
368 Starting,
371 Running {
373 device: String,
375 },
376 Stopped,
378 Failed(CameraError),
380}
381
382impl CameraState {
383 pub fn is_running(&self) -> bool {
385 matches!(self, CameraState::Running { .. })
386 }
387
388 pub fn is_active(&self) -> bool {
391 matches!(self, CameraState::Starting | CameraState::Running { .. })
392 }
393
394 pub fn failure(&self) -> Option<&CameraError> {
396 match self {
397 CameraState::Failed(error) => Some(error),
398 _ => None,
399 }
400 }
401}
402
403pub trait Camera: Send + Sync {
409 fn start(&self) -> Result<(), CameraError>;
415
416 fn stop(&self);
418
419 fn request_still(&self) -> Result<(), CameraError> {
425 Err(CameraError::Unsupported)
426 }
427
428 fn set_torch(&self, _on: bool) -> bool {
434 false
435 }
436
437 fn lenses(&self) -> Vec<CameraLens> {
446 Vec::new()
447 }
448
449 fn lens(&self) -> Option<String> {
452 None
453 }
454
455 fn use_lens(&self, _id: &str) -> bool {
458 false
459 }
460
461 fn has_flash(&self) -> bool {
463 false
464 }
465
466 fn set_flash(&self, _mode: FlashMode) -> bool {
469 false
470 }
471}
472
473pub type CameraRef = Arc<dyn Camera>;
475
476static PLATFORM_CAMERA: ServiceRegistry<dyn Camera> = ServiceRegistry::new();
477
478pub fn set_platform_camera(camera: CameraRef) {
480 PLATFORM_CAMERA.set(camera);
481}
482
483pub fn clear_platform_camera() {
486 PLATFORM_CAMERA.clear();
487 if let Ok(mut observers) = frame_observers().lock() {
488 observers.clear();
489 }
490 if let Ok(mut observers) = state_observers().lock() {
491 observers.clear();
492 }
493 if let Ok(mut observers) = still_observers().lock() {
494 observers.clear();
495 }
496 if let Ok(mut observers) = lens_observers().lock() {
497 observers.clear();
498 }
499 if let Ok(mut latest) = latest_frame_slot().lock() {
500 *latest = None;
501 }
502 if let Ok(mut state) = state_slot().lock() {
503 *state = CameraState::Idle;
504 }
505 if let Ok(mut lenses) = lenses_slot().lock() {
506 *lenses = CameraLenses::default();
507 }
508 DROPPED_FRAMES.store(0, Ordering::Release);
509}
510
511pub fn camera() -> Option<CameraRef> {
514 PLATFORM_CAMERA.get()
515}
516
517pub fn camera_supported() -> bool {
519 PLATFORM_CAMERA.get().is_some()
520}
521
522fn state_slot() -> &'static Mutex<CameraState> {
523 static SLOT: OnceLock<Mutex<CameraState>> = OnceLock::new();
524 SLOT.get_or_init(|| Mutex::new(CameraState::Idle))
525}
526
527fn latest_frame_slot() -> &'static Mutex<Option<CameraFrame>> {
528 static SLOT: OnceLock<Mutex<Option<CameraFrame>>> = OnceLock::new();
529 SLOT.get_or_init(|| Mutex::new(None))
530}
531
532fn lenses_slot() -> &'static Mutex<CameraLenses> {
533 static SLOT: OnceLock<Mutex<CameraLenses>> = OnceLock::new();
534 SLOT.get_or_init(|| Mutex::new(CameraLenses::default()))
535}
536
537static DROPPED_FRAMES: AtomicU64 = AtomicU64::new(0);
538
539type FrameObserver = Arc<dyn Fn(CameraFrame) + Send + Sync>;
540type StateObserver = Arc<dyn Fn(CameraState) + Send + Sync>;
541type StillObserver = Arc<dyn Fn(Result<CameraStill, CameraError>) + Send + Sync>;
542type LensObserver = Arc<dyn Fn(CameraLenses) + Send + Sync>;
543
544fn frame_observers() -> &'static Mutex<Vec<(u64, FrameObserver)>> {
545 static SLOT: OnceLock<Mutex<Vec<(u64, FrameObserver)>>> = OnceLock::new();
546 SLOT.get_or_init(|| Mutex::new(Vec::new()))
547}
548
549fn state_observers() -> &'static Mutex<Vec<(u64, StateObserver)>> {
550 static SLOT: OnceLock<Mutex<Vec<(u64, StateObserver)>>> = OnceLock::new();
551 SLOT.get_or_init(|| Mutex::new(Vec::new()))
552}
553
554fn still_observers() -> &'static Mutex<Vec<(u64, StillObserver)>> {
555 static SLOT: OnceLock<Mutex<Vec<(u64, StillObserver)>>> = OnceLock::new();
556 SLOT.get_or_init(|| Mutex::new(Vec::new()))
557}
558
559fn lens_observers() -> &'static Mutex<Vec<(u64, LensObserver)>> {
560 static SLOT: OnceLock<Mutex<Vec<(u64, LensObserver)>>> = OnceLock::new();
561 SLOT.get_or_init(|| Mutex::new(Vec::new()))
562}
563
564static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
565
566pub struct CameraObserver {
568 id: u64,
569 kind: ObserverKind,
570}
571
572#[derive(Clone, Copy)]
573enum ObserverKind {
574 Frame,
575 State,
576 Still,
577 Lenses,
578}
579
580impl Drop for CameraObserver {
581 fn drop(&mut self) {
582 match self.kind {
583 ObserverKind::Frame => retain_without(frame_observers(), self.id),
584 ObserverKind::State => retain_without(state_observers(), self.id),
585 ObserverKind::Still => retain_without(still_observers(), self.id),
586 ObserverKind::Lenses => retain_without(lens_observers(), self.id),
587 }
588 }
589}
590
591fn retain_without<T>(slot: &'static Mutex<Vec<(u64, T)>>, id: u64) {
592 if let Ok(mut observers) = slot.lock() {
593 observers.retain(|(observer, _)| *observer != id);
594 }
595}
596
597fn snapshot<T: Clone>(slot: &'static Mutex<Vec<(u64, T)>>) -> Vec<T> {
598 slot.lock()
599 .map(|observers| {
600 observers
601 .iter()
602 .map(|(_, observer)| observer.clone())
603 .collect()
604 })
605 .unwrap_or_default()
606}
607
608pub fn latest_camera_frame() -> Option<CameraFrame> {
613 latest_frame_slot()
614 .lock()
615 .map_or(None, |frame| frame.clone())
616}
617
618pub fn camera_state() -> CameraState {
620 state_slot()
621 .lock()
622 .map(|state| state.clone())
623 .unwrap_or_default()
624}
625
626pub fn dropped_camera_frames() -> u64 {
628 DROPPED_FRAMES.load(Ordering::Acquire)
629}
630
631pub fn publish_camera_frame(frame: CameraFrame) {
638 if let Ok(mut latest) = latest_frame_slot().lock() {
639 *latest = Some(frame.clone());
640 }
641 let observers = snapshot(frame_observers());
642 if observers.is_empty() {
643 return;
644 }
645 for observer in observers {
646 observer(frame.clone());
647 }
648}
649
650pub fn record_dropped_camera_frame() {
655 DROPPED_FRAMES.fetch_add(1, Ordering::AcqRel);
656}
657
658pub fn publish_camera_state(state: CameraState) {
660 {
661 let Ok(mut current) = state_slot().lock() else {
662 return;
663 };
664 if *current == state {
665 return;
666 }
667 *current = state.clone();
668 }
669 if matches!(state, CameraState::Idle | CameraState::Starting) {
670 DROPPED_FRAMES.store(0, Ordering::Release);
671 if let Ok(mut latest) = latest_frame_slot().lock() {
672 *latest = None;
673 }
674 }
675 for observer in snapshot(state_observers()) {
676 observer(state.clone());
677 }
678}
679
680pub fn publish_camera_still(still: Result<CameraStill, CameraError>) {
682 for observer in snapshot(still_observers()) {
683 observer(still.clone());
684 }
685}
686
687pub fn publish_camera_lenses(lenses: CameraLenses) {
690 {
691 let Ok(mut current) = lenses_slot().lock() else {
692 return;
693 };
694 if *current == lenses {
695 return;
696 }
697 *current = lenses.clone();
698 }
699 for observer in snapshot(lens_observers()) {
700 observer(lenses.clone());
701 }
702}
703
704pub fn camera_lenses() -> CameraLenses {
707 lenses_slot()
708 .lock()
709 .map(|lenses| lenses.clone())
710 .unwrap_or_default()
711}
712
713pub fn observe_camera_frames(
716 observer: impl Fn(CameraFrame) + Send + Sync + 'static,
717) -> CameraObserver {
718 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
719 if let Ok(mut observers) = frame_observers().lock() {
720 observers.push((id, Arc::new(observer)));
721 }
722 CameraObserver {
723 id,
724 kind: ObserverKind::Frame,
725 }
726}
727
728pub fn observe_camera_state(
732 observer: impl Fn(CameraState) + Send + Sync + 'static,
733) -> CameraObserver {
734 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
735 let observer: StateObserver = Arc::new(observer);
736 if let Ok(mut observers) = state_observers().lock() {
737 observers.push((id, Arc::clone(&observer)));
738 }
739 observer(camera_state());
740 CameraObserver {
741 id,
742 kind: ObserverKind::State,
743 }
744}
745
746pub fn observe_camera_stills(
748 observer: impl Fn(Result<CameraStill, CameraError>) + Send + Sync + 'static,
749) -> CameraObserver {
750 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
751 if let Ok(mut observers) = still_observers().lock() {
752 observers.push((id, Arc::new(observer)));
753 }
754 CameraObserver {
755 id,
756 kind: ObserverKind::Still,
757 }
758}
759
760pub fn observe_camera_lenses(
764 observer: impl Fn(CameraLenses) + Send + Sync + 'static,
765) -> CameraObserver {
766 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
767 let observer: LensObserver = Arc::new(observer);
768 if let Ok(mut observers) = lens_observers().lock() {
769 observers.push((id, Arc::clone(&observer)));
770 }
771 observer(camera_lenses());
772 CameraObserver {
773 id,
774 kind: ObserverKind::Lenses,
775 }
776}
777
778#[expect(non_snake_case)]
781#[track_caller]
782pub fn rememberCameraState() -> State<CameraState> {
783 let updates = rememberEventStream((), |sender| {
784 observe_camera_state(move |state| sender.send(state))
785 });
786 cranpose_core::collectAsState(updates, (), camera_state())
787}
788
789#[expect(non_snake_case)]
796#[track_caller]
797pub fn rememberCameraFrames() -> EventStream<CameraFrame> {
798 rememberEventStream((), |sender| {
799 observe_camera_frames(move |frame| sender.send(frame))
800 })
801}
802
803#[expect(non_snake_case)]
805#[track_caller]
806pub fn rememberCameraStills() -> EventStream<Result<CameraStill, CameraError>> {
807 rememberEventStream((), |sender| {
808 observe_camera_stills(move |still| sender.send(still))
809 })
810}
811
812#[expect(non_snake_case)]
815#[track_caller]
816pub fn rememberCameraLenses() -> State<CameraLenses> {
817 let updates = rememberEventStream((), |sender| {
818 observe_camera_lenses(move |lenses| sender.send(lenses))
819 });
820 cranpose_core::collectAsState(updates, (), camera_lenses())
821}
822
823pub fn start_camera() -> Result<(), CameraError> {
826 let Some(camera) = camera() else {
827 publish_camera_state(CameraState::Failed(CameraError::Unsupported));
828 return Err(CameraError::Unsupported);
829 };
830 publish_camera_state(CameraState::Starting);
831 camera.start().inspect_err(|error| {
832 publish_camera_state(CameraState::Failed(error.clone()));
833 })
834}
835
836pub fn stop_camera() {
838 if let Some(camera) = camera() {
839 camera.stop();
840 }
841 publish_camera_state(CameraState::Stopped);
842}
843
844pub fn request_camera_still() -> Result<(), CameraError> {
847 let Some(camera) = camera() else {
848 return Err(CameraError::Unsupported);
849 };
850 if !camera_state().is_running() {
851 return Err(CameraError::NotRunning);
852 }
853 camera.request_still()
854}
855
856pub async fn capture_camera_still() -> Result<CameraStill, CameraError> {
866 let signal = crate::async_io::Signal::new();
867 let deliver = signal.clone();
868 let observer = observe_camera_stills(move |result| deliver.set(result));
869 request_camera_still()?;
870 let arrived = signal.wait().await;
871 drop(observer);
872 arrived.unwrap_or(Err(CameraError::NotRunning))
873}
874
875#[cfg(test)]
876#[path = "tests/camera_tests.rs"]
877mod tests;