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,
74 pub height: u32,
75 pub format: FrameFormat,
76 pub rotation_degrees: u16,
79 pub sequence: u64,
82 pub bytes: Vec<u8>,
84}
85
86impl CameraFrame {
87 pub fn new(
91 width: u32,
92 height: u32,
93 format: FrameFormat,
94 rotation_degrees: u16,
95 sequence: u64,
96 bytes: Vec<u8>,
97 ) -> Option<Self> {
98 if format.byte_len(width, height)? != bytes.len() {
99 return None;
100 }
101 Some(Self {
102 width,
103 height,
104 format,
105 rotation_degrees: rotation_degrees % 360,
106 sequence,
107 bytes,
108 })
109 }
110
111 pub fn to_rgba8(&self) -> Vec<u8> {
117 match self.format {
118 FrameFormat::Rgba8 => self.bytes.clone(),
119 FrameFormat::Rgb8 => rgb8_to_rgba8(&self.bytes),
120 FrameFormat::Nv12 => nv12_to_rgba8(self.width, self.height, &self.bytes),
121 }
122 }
123
124 pub fn upright_rgba8(&self) -> UprightRgba {
133 let rotation = self.rotation_degrees;
134 if !matches!(rotation, 90 | 180 | 270) {
135 return UprightRgba {
136 width: self.width,
137 height: self.height,
138 rgba: self.to_rgba8(),
139 };
140 }
141 let (width, height) = (self.width as usize, self.height as usize);
142 let (out_width, out_height) = match rotation {
143 90 | 270 => (self.height, self.width),
144 _ => (self.width, self.height),
145 };
146 let mut rgba = vec![0u8; width * height * 4];
147 match self.format {
148 FrameFormat::Rgba8 => {
149 for y in 0..height {
150 let row = &self.bytes[y * width * 4..(y + 1) * width * 4];
151 for x in 0..width {
152 let src = &row[x * 4..x * 4 + 4];
153 let dst = turned_index(rotation, width, height, x, y) * 4;
154 rgba[dst..dst + 4].copy_from_slice(src);
155 }
156 }
157 }
158 FrameFormat::Rgb8 => {
159 for y in 0..height {
160 let row = &self.bytes[y * width * 3..(y + 1) * width * 3];
161 for x in 0..width {
162 let src = &row[x * 3..x * 3 + 3];
163 let dst = turned_index(rotation, width, height, x, y) * 4;
164 rgba[dst..dst + 3].copy_from_slice(src);
165 rgba[dst + 3] = 255;
166 }
167 }
168 }
169 FrameFormat::Nv12 => {
170 let pixels = width * height;
171 if self.bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
172 return UprightRgba {
173 width: out_width,
174 height: out_height,
175 rgba,
176 };
177 }
178 let (luma, chroma) = self.bytes.split_at(pixels);
179 for y in 0..height {
180 let luma_row = &luma[y * width..(y + 1) * width];
181 let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
182 for x in 0..width {
183 let luminance = luma_row[x] as i32;
184 let blue_difference = chroma_row[x & !1] as i32 - 128;
185 let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
186 let dst = turned_index(rotation, width, height, x, y) * 4;
187 rgba[dst] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
188 rgba[dst + 1] = clamp_byte(
189 luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16),
190 );
191 rgba[dst + 2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
192 rgba[dst + 3] = 255;
193 }
194 }
195 }
196 }
197 UprightRgba {
198 width: out_width,
199 height: out_height,
200 rgba,
201 }
202 }
203}
204
205#[derive(Clone, Debug, PartialEq, Eq)]
210pub struct UprightRgba {
211 pub width: u32,
212 pub height: u32,
213 pub rgba: Vec<u8>,
214}
215
216#[inline]
217fn turned_index(rotation: u16, width: usize, height: usize, x: usize, y: usize) -> usize {
218 match rotation {
219 90 => x * height + (height - 1 - y),
220 180 => (height - 1 - y) * width + (width - 1 - x),
221 270 => (width - 1 - x) * height + y,
222 _ => y * width + x,
223 }
224}
225
226fn rgb8_to_rgba8(bytes: &[u8]) -> Vec<u8> {
227 let mut rgba = Vec::with_capacity(bytes.len() / 3 * 4);
228 for [red, green, blue] in bytes.as_chunks::<3>().0 {
229 rgba.extend_from_slice(&[*red, *green, *blue, 255]);
230 }
231 rgba
232}
233
234fn nv12_to_rgba8(width: u32, height: u32, bytes: &[u8]) -> Vec<u8> {
235 let (width, height) = (width as usize, height as usize);
236 let pixels = width * height;
237 let mut rgba = vec![0u8; pixels * 4];
238 if bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
239 return rgba;
240 }
241 let (luma, chroma) = bytes.split_at(pixels);
242
243 for y in 0..height {
244 let luma_row = &luma[y * width..(y + 1) * width];
245 let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
246 let out_row = &mut rgba[y * width * 4..(y + 1) * width * 4];
247 for x in 0..width {
248 let luminance = luma_row[x] as i32;
249 let blue_difference = chroma_row[x & !1] as i32 - 128;
250 let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
251 let out = &mut out_row[x * 4..x * 4 + 4];
252 out[0] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
253 out[1] =
254 clamp_byte(luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16));
255 out[2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
256 out[3] = 255;
257 }
258 }
259 rgba
260}
261
262fn clamp_byte(value: i32) -> u8 {
263 value.clamp(0, 255) as u8
264}
265
266#[derive(Clone, Debug, PartialEq, Eq)]
273pub struct CameraStill {
274 pub jpeg: Vec<u8>,
275}
276
277#[derive(Clone, Debug, PartialEq, Eq)]
284pub struct CameraLens {
285 pub id: String,
286 pub name: String,
287 pub facing: LensFacing,
290}
291
292#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
294pub enum LensFacing {
295 #[default]
297 Back,
298 Front,
300 External,
302}
303
304#[derive(Clone, Debug, Default, PartialEq, Eq)]
312pub struct CameraLenses {
313 pub lenses: Vec<CameraLens>,
315 pub active: Option<String>,
318}
319
320#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
326pub enum FlashMode {
327 #[default]
328 Off,
329 Auto,
330 On,
331}
332
333#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
334pub enum CameraError {
335 #[error("live camera capture is not supported here")]
337 Unsupported,
338 #[error("camera permission denied")]
340 PermissionDenied,
341 #[error("the camera is not running")]
343 NotRunning,
344 #[error("{0}")]
346 Failed(String),
347}
348
349#[derive(Clone, Debug, Default, PartialEq, Eq)]
351pub enum CameraState {
352 #[default]
354 Idle,
355 Starting,
358 Running {
360 device: String,
362 },
363 Stopped,
365 Failed(CameraError),
367}
368
369impl CameraState {
370 pub fn is_running(&self) -> bool {
372 matches!(self, CameraState::Running { .. })
373 }
374
375 pub fn is_active(&self) -> bool {
378 matches!(self, CameraState::Starting | CameraState::Running { .. })
379 }
380
381 pub fn failure(&self) -> Option<&CameraError> {
383 match self {
384 CameraState::Failed(error) => Some(error),
385 _ => None,
386 }
387 }
388}
389
390pub trait Camera: Send + Sync {
396 fn start(&self) -> Result<(), CameraError>;
402
403 fn stop(&self);
405
406 fn request_still(&self) -> Result<(), CameraError> {
412 Err(CameraError::Unsupported)
413 }
414
415 fn set_torch(&self, _on: bool) -> bool {
421 false
422 }
423
424 fn lenses(&self) -> Vec<CameraLens> {
433 Vec::new()
434 }
435
436 fn lens(&self) -> Option<String> {
439 None
440 }
441
442 fn use_lens(&self, _id: &str) -> bool {
445 false
446 }
447
448 fn has_flash(&self) -> bool {
450 false
451 }
452
453 fn set_flash(&self, _mode: FlashMode) -> bool {
456 false
457 }
458}
459
460pub type CameraRef = Arc<dyn Camera>;
462
463static PLATFORM_CAMERA: ServiceRegistry<dyn Camera> = ServiceRegistry::new();
464
465pub fn set_platform_camera(camera: CameraRef) {
467 PLATFORM_CAMERA.set(camera);
468}
469
470pub fn clear_platform_camera() {
473 PLATFORM_CAMERA.clear();
474 if let Ok(mut observers) = frame_observers().lock() {
475 observers.clear();
476 }
477 if let Ok(mut observers) = state_observers().lock() {
478 observers.clear();
479 }
480 if let Ok(mut observers) = still_observers().lock() {
481 observers.clear();
482 }
483 if let Ok(mut observers) = lens_observers().lock() {
484 observers.clear();
485 }
486 if let Ok(mut latest) = latest_frame_slot().lock() {
487 *latest = None;
488 }
489 if let Ok(mut state) = state_slot().lock() {
490 *state = CameraState::Idle;
491 }
492 if let Ok(mut lenses) = lenses_slot().lock() {
493 *lenses = CameraLenses::default();
494 }
495 DROPPED_FRAMES.store(0, Ordering::Release);
496}
497
498pub fn camera() -> Option<CameraRef> {
501 PLATFORM_CAMERA.get()
502}
503
504pub fn camera_supported() -> bool {
506 PLATFORM_CAMERA.get().is_some()
507}
508
509fn state_slot() -> &'static Mutex<CameraState> {
510 static SLOT: OnceLock<Mutex<CameraState>> = OnceLock::new();
511 SLOT.get_or_init(|| Mutex::new(CameraState::Idle))
512}
513
514fn latest_frame_slot() -> &'static Mutex<Option<CameraFrame>> {
515 static SLOT: OnceLock<Mutex<Option<CameraFrame>>> = OnceLock::new();
516 SLOT.get_or_init(|| Mutex::new(None))
517}
518
519fn lenses_slot() -> &'static Mutex<CameraLenses> {
520 static SLOT: OnceLock<Mutex<CameraLenses>> = OnceLock::new();
521 SLOT.get_or_init(|| Mutex::new(CameraLenses::default()))
522}
523
524static DROPPED_FRAMES: AtomicU64 = AtomicU64::new(0);
525
526type FrameObserver = Arc<dyn Fn(CameraFrame) + Send + Sync>;
527type StateObserver = Arc<dyn Fn(CameraState) + Send + Sync>;
528type StillObserver = Arc<dyn Fn(Result<CameraStill, CameraError>) + Send + Sync>;
529type LensObserver = Arc<dyn Fn(CameraLenses) + Send + Sync>;
530
531fn frame_observers() -> &'static Mutex<Vec<(u64, FrameObserver)>> {
532 static SLOT: OnceLock<Mutex<Vec<(u64, FrameObserver)>>> = OnceLock::new();
533 SLOT.get_or_init(|| Mutex::new(Vec::new()))
534}
535
536fn state_observers() -> &'static Mutex<Vec<(u64, StateObserver)>> {
537 static SLOT: OnceLock<Mutex<Vec<(u64, StateObserver)>>> = OnceLock::new();
538 SLOT.get_or_init(|| Mutex::new(Vec::new()))
539}
540
541fn still_observers() -> &'static Mutex<Vec<(u64, StillObserver)>> {
542 static SLOT: OnceLock<Mutex<Vec<(u64, StillObserver)>>> = OnceLock::new();
543 SLOT.get_or_init(|| Mutex::new(Vec::new()))
544}
545
546fn lens_observers() -> &'static Mutex<Vec<(u64, LensObserver)>> {
547 static SLOT: OnceLock<Mutex<Vec<(u64, LensObserver)>>> = OnceLock::new();
548 SLOT.get_or_init(|| Mutex::new(Vec::new()))
549}
550
551static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
552
553pub struct CameraObserver {
555 id: u64,
556 kind: ObserverKind,
557}
558
559#[derive(Clone, Copy)]
560enum ObserverKind {
561 Frame,
562 State,
563 Still,
564 Lenses,
565}
566
567impl Drop for CameraObserver {
568 fn drop(&mut self) {
569 match self.kind {
570 ObserverKind::Frame => retain_without(frame_observers(), self.id),
571 ObserverKind::State => retain_without(state_observers(), self.id),
572 ObserverKind::Still => retain_without(still_observers(), self.id),
573 ObserverKind::Lenses => retain_without(lens_observers(), self.id),
574 }
575 }
576}
577
578fn retain_without<T>(slot: &'static Mutex<Vec<(u64, T)>>, id: u64) {
579 if let Ok(mut observers) = slot.lock() {
580 observers.retain(|(observer, _)| *observer != id);
581 }
582}
583
584fn snapshot<T: Clone>(slot: &'static Mutex<Vec<(u64, T)>>) -> Vec<T> {
585 slot.lock()
586 .map(|observers| {
587 observers
588 .iter()
589 .map(|(_, observer)| observer.clone())
590 .collect()
591 })
592 .unwrap_or_default()
593}
594
595pub fn latest_camera_frame() -> Option<CameraFrame> {
600 latest_frame_slot()
601 .lock()
602 .map_or(None, |frame| frame.clone())
603}
604
605pub fn camera_state() -> CameraState {
607 state_slot()
608 .lock()
609 .map(|state| state.clone())
610 .unwrap_or_default()
611}
612
613pub fn dropped_camera_frames() -> u64 {
615 DROPPED_FRAMES.load(Ordering::Acquire)
616}
617
618pub fn publish_camera_frame(frame: CameraFrame) {
625 if let Ok(mut latest) = latest_frame_slot().lock() {
626 *latest = Some(frame.clone());
627 }
628 let observers = snapshot(frame_observers());
629 if observers.is_empty() {
630 return;
631 }
632 for observer in observers {
633 observer(frame.clone());
634 }
635}
636
637pub fn record_dropped_camera_frame() {
642 DROPPED_FRAMES.fetch_add(1, Ordering::AcqRel);
643}
644
645pub fn publish_camera_state(state: CameraState) {
647 {
648 let Ok(mut current) = state_slot().lock() else {
649 return;
650 };
651 if *current == state {
652 return;
653 }
654 *current = state.clone();
655 }
656 if matches!(state, CameraState::Idle | CameraState::Starting) {
657 DROPPED_FRAMES.store(0, Ordering::Release);
658 if let Ok(mut latest) = latest_frame_slot().lock() {
659 *latest = None;
660 }
661 }
662 for observer in snapshot(state_observers()) {
663 observer(state.clone());
664 }
665}
666
667pub fn publish_camera_still(still: Result<CameraStill, CameraError>) {
669 for observer in snapshot(still_observers()) {
670 observer(still.clone());
671 }
672}
673
674pub fn publish_camera_lenses(lenses: CameraLenses) {
677 {
678 let Ok(mut current) = lenses_slot().lock() else {
679 return;
680 };
681 if *current == lenses {
682 return;
683 }
684 *current = lenses.clone();
685 }
686 for observer in snapshot(lens_observers()) {
687 observer(lenses.clone());
688 }
689}
690
691pub fn camera_lenses() -> CameraLenses {
694 lenses_slot()
695 .lock()
696 .map(|lenses| lenses.clone())
697 .unwrap_or_default()
698}
699
700pub fn observe_camera_frames(
703 observer: impl Fn(CameraFrame) + Send + Sync + 'static,
704) -> CameraObserver {
705 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
706 if let Ok(mut observers) = frame_observers().lock() {
707 observers.push((id, Arc::new(observer)));
708 }
709 CameraObserver {
710 id,
711 kind: ObserverKind::Frame,
712 }
713}
714
715pub fn observe_camera_state(
719 observer: impl Fn(CameraState) + Send + Sync + 'static,
720) -> CameraObserver {
721 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
722 let observer: StateObserver = Arc::new(observer);
723 if let Ok(mut observers) = state_observers().lock() {
724 observers.push((id, Arc::clone(&observer)));
725 }
726 observer(camera_state());
727 CameraObserver {
728 id,
729 kind: ObserverKind::State,
730 }
731}
732
733pub fn observe_camera_stills(
735 observer: impl Fn(Result<CameraStill, CameraError>) + Send + Sync + 'static,
736) -> CameraObserver {
737 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
738 if let Ok(mut observers) = still_observers().lock() {
739 observers.push((id, Arc::new(observer)));
740 }
741 CameraObserver {
742 id,
743 kind: ObserverKind::Still,
744 }
745}
746
747pub fn observe_camera_lenses(
751 observer: impl Fn(CameraLenses) + Send + Sync + 'static,
752) -> CameraObserver {
753 let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
754 let observer: LensObserver = Arc::new(observer);
755 if let Ok(mut observers) = lens_observers().lock() {
756 observers.push((id, Arc::clone(&observer)));
757 }
758 observer(camera_lenses());
759 CameraObserver {
760 id,
761 kind: ObserverKind::Lenses,
762 }
763}
764
765#[expect(non_snake_case)]
768#[track_caller]
769pub fn rememberCameraState() -> State<CameraState> {
770 let updates = rememberEventStream((), |sender| {
771 observe_camera_state(move |state| sender.send(state))
772 });
773 cranpose_core::collectAsState(updates, (), camera_state())
774}
775
776#[expect(non_snake_case)]
783#[track_caller]
784pub fn rememberCameraFrames() -> EventStream<CameraFrame> {
785 rememberEventStream((), |sender| {
786 observe_camera_frames(move |frame| sender.send(frame))
787 })
788}
789
790#[expect(non_snake_case)]
792#[track_caller]
793pub fn rememberCameraStills() -> EventStream<Result<CameraStill, CameraError>> {
794 rememberEventStream((), |sender| {
795 observe_camera_stills(move |still| sender.send(still))
796 })
797}
798
799#[expect(non_snake_case)]
802#[track_caller]
803pub fn rememberCameraLenses() -> State<CameraLenses> {
804 let updates = rememberEventStream((), |sender| {
805 observe_camera_lenses(move |lenses| sender.send(lenses))
806 });
807 cranpose_core::collectAsState(updates, (), camera_lenses())
808}
809
810pub fn start_camera() -> Result<(), CameraError> {
813 let Some(camera) = camera() else {
814 publish_camera_state(CameraState::Failed(CameraError::Unsupported));
815 return Err(CameraError::Unsupported);
816 };
817 publish_camera_state(CameraState::Starting);
818 camera.start().inspect_err(|error| {
819 publish_camera_state(CameraState::Failed(error.clone()));
820 })
821}
822
823pub fn stop_camera() {
825 if let Some(camera) = camera() {
826 camera.stop();
827 }
828 publish_camera_state(CameraState::Stopped);
829}
830
831pub fn request_camera_still() -> Result<(), CameraError> {
834 let Some(camera) = camera() else {
835 return Err(CameraError::Unsupported);
836 };
837 if !camera_state().is_running() {
838 return Err(CameraError::NotRunning);
839 }
840 camera.request_still()
841}
842
843pub async fn capture_camera_still() -> Result<CameraStill, CameraError> {
853 let signal = crate::async_io::Signal::new();
854 let deliver = signal.clone();
855 let observer = observe_camera_stills(move |result| deliver.set(result));
856 request_camera_still()?;
857 let arrived = signal.wait().await;
858 drop(observer);
859 arrived.unwrap_or(Err(CameraError::NotRunning))
860}
861
862#[cfg(test)]
863#[path = "tests/camera_tests.rs"]
864mod tests;