use std::sync::{
Arc, Mutex, OnceLock,
atomic::{AtomicU64, Ordering},
};
use cranpose_core::{EventStream, State, rememberEventStream};
use crate::registry::ServiceRegistry;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum FrameFormat {
#[default]
Rgba8,
Rgb8,
Nv12,
}
impl FrameFormat {
pub fn byte_len(self, width: u32, height: u32) -> Option<usize> {
let pixels = (width as usize).checked_mul(height as usize)?;
match self {
FrameFormat::Rgba8 => pixels.checked_mul(4),
FrameFormat::Rgb8 => pixels.checked_mul(3),
FrameFormat::Nv12 => {
if !width.is_multiple_of(2) || !height.is_multiple_of(2) {
return None;
}
pixels.checked_add(pixels / 2)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CameraFrame {
pub width: u32,
pub height: u32,
pub format: FrameFormat,
pub rotation_degrees: u16,
pub sequence: u64,
pub bytes: Vec<u8>,
}
impl CameraFrame {
pub fn new(
width: u32,
height: u32,
format: FrameFormat,
rotation_degrees: u16,
sequence: u64,
bytes: Vec<u8>,
) -> Option<Self> {
if format.byte_len(width, height)? != bytes.len() {
return None;
}
Some(Self {
width,
height,
format,
rotation_degrees: rotation_degrees % 360,
sequence,
bytes,
})
}
pub fn to_rgba8(&self) -> Vec<u8> {
match self.format {
FrameFormat::Rgba8 => self.bytes.clone(),
FrameFormat::Rgb8 => rgb8_to_rgba8(&self.bytes),
FrameFormat::Nv12 => nv12_to_rgba8(self.width, self.height, &self.bytes),
}
}
pub fn upright_rgba8(&self) -> UprightRgba {
let rotation = self.rotation_degrees;
if !matches!(rotation, 90 | 180 | 270) {
return UprightRgba {
width: self.width,
height: self.height,
rgba: self.to_rgba8(),
};
}
let (width, height) = (self.width as usize, self.height as usize);
let (out_width, out_height) = match rotation {
90 | 270 => (self.height, self.width),
_ => (self.width, self.height),
};
let mut rgba = vec![0u8; width * height * 4];
match self.format {
FrameFormat::Rgba8 => {
for y in 0..height {
let row = &self.bytes[y * width * 4..(y + 1) * width * 4];
for x in 0..width {
let src = &row[x * 4..x * 4 + 4];
let dst = turned_index(rotation, width, height, x, y) * 4;
rgba[dst..dst + 4].copy_from_slice(src);
}
}
}
FrameFormat::Rgb8 => {
for y in 0..height {
let row = &self.bytes[y * width * 3..(y + 1) * width * 3];
for x in 0..width {
let src = &row[x * 3..x * 3 + 3];
let dst = turned_index(rotation, width, height, x, y) * 4;
rgba[dst..dst + 3].copy_from_slice(src);
rgba[dst + 3] = 255;
}
}
}
FrameFormat::Nv12 => {
let pixels = width * height;
if self.bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
return UprightRgba {
width: out_width,
height: out_height,
rgba,
};
}
let (luma, chroma) = self.bytes.split_at(pixels);
for y in 0..height {
let luma_row = &luma[y * width..(y + 1) * width];
let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
for x in 0..width {
let luminance = luma_row[x] as i32;
let blue_difference = chroma_row[x & !1] as i32 - 128;
let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
let dst = turned_index(rotation, width, height, x, y) * 4;
rgba[dst] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
rgba[dst + 1] = clamp_byte(
luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16),
);
rgba[dst + 2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
rgba[dst + 3] = 255;
}
}
}
}
UprightRgba {
width: out_width,
height: out_height,
rgba,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UprightRgba {
pub width: u32,
pub height: u32,
pub rgba: Vec<u8>,
}
#[inline]
fn turned_index(rotation: u16, width: usize, height: usize, x: usize, y: usize) -> usize {
match rotation {
90 => x * height + (height - 1 - y),
180 => (height - 1 - y) * width + (width - 1 - x),
270 => (width - 1 - x) * height + y,
_ => y * width + x,
}
}
fn rgb8_to_rgba8(bytes: &[u8]) -> Vec<u8> {
let mut rgba = Vec::with_capacity(bytes.len() / 3 * 4);
for [red, green, blue] in bytes.as_chunks::<3>().0 {
rgba.extend_from_slice(&[*red, *green, *blue, 255]);
}
rgba
}
fn nv12_to_rgba8(width: u32, height: u32, bytes: &[u8]) -> Vec<u8> {
let (width, height) = (width as usize, height as usize);
let pixels = width * height;
let mut rgba = vec![0u8; pixels * 4];
if bytes.len() < pixels + pixels / 2 || width == 0 || height == 0 {
return rgba;
}
let (luma, chroma) = bytes.split_at(pixels);
for y in 0..height {
let luma_row = &luma[y * width..(y + 1) * width];
let chroma_row = &chroma[(y / 2) * width..(y / 2 + 1) * width];
let out_row = &mut rgba[y * width * 4..(y + 1) * width * 4];
for x in 0..width {
let luminance = luma_row[x] as i32;
let blue_difference = chroma_row[x & !1] as i32 - 128;
let red_difference = chroma_row[(x & !1) + 1] as i32 - 128;
let out = &mut out_row[x * 4..x * 4 + 4];
out[0] = clamp_byte(luminance + ((91881 * red_difference) >> 16));
out[1] =
clamp_byte(luminance - ((22554 * blue_difference + 46802 * red_difference) >> 16));
out[2] = clamp_byte(luminance + ((116130 * blue_difference) >> 16));
out[3] = 255;
}
}
rgba
}
fn clamp_byte(value: i32) -> u8 {
value.clamp(0, 255) as u8
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CameraStill {
pub jpeg: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CameraLens {
pub id: String,
pub name: String,
pub facing: LensFacing,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum LensFacing {
#[default]
Back,
Front,
External,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CameraLenses {
pub lenses: Vec<CameraLens>,
pub active: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum FlashMode {
#[default]
Off,
Auto,
On,
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum CameraError {
#[error("live camera capture is not supported here")]
Unsupported,
#[error("camera permission denied")]
PermissionDenied,
#[error("the camera is not running")]
NotRunning,
#[error("{0}")]
Failed(String),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum CameraState {
#[default]
Idle,
Starting,
Running {
device: String,
},
Stopped,
Failed(CameraError),
}
impl CameraState {
pub fn is_running(&self) -> bool {
matches!(self, CameraState::Running { .. })
}
pub fn is_active(&self) -> bool {
matches!(self, CameraState::Starting | CameraState::Running { .. })
}
pub fn failure(&self) -> Option<&CameraError> {
match self {
CameraState::Failed(error) => Some(error),
_ => None,
}
}
}
pub trait Camera: Send + Sync {
fn start(&self) -> Result<(), CameraError>;
fn stop(&self);
fn request_still(&self) -> Result<(), CameraError> {
Err(CameraError::Unsupported)
}
fn set_torch(&self, _on: bool) -> bool {
false
}
fn lenses(&self) -> Vec<CameraLens> {
Vec::new()
}
fn lens(&self) -> Option<String> {
None
}
fn use_lens(&self, _id: &str) -> bool {
false
}
fn has_flash(&self) -> bool {
false
}
fn set_flash(&self, _mode: FlashMode) -> bool {
false
}
}
pub type CameraRef = Arc<dyn Camera>;
static PLATFORM_CAMERA: ServiceRegistry<dyn Camera> = ServiceRegistry::new();
pub fn set_platform_camera(camera: CameraRef) {
PLATFORM_CAMERA.set(camera);
}
pub fn clear_platform_camera() {
PLATFORM_CAMERA.clear();
if let Ok(mut observers) = frame_observers().lock() {
observers.clear();
}
if let Ok(mut observers) = state_observers().lock() {
observers.clear();
}
if let Ok(mut observers) = still_observers().lock() {
observers.clear();
}
if let Ok(mut observers) = lens_observers().lock() {
observers.clear();
}
if let Ok(mut latest) = latest_frame_slot().lock() {
*latest = None;
}
if let Ok(mut state) = state_slot().lock() {
*state = CameraState::Idle;
}
if let Ok(mut lenses) = lenses_slot().lock() {
*lenses = CameraLenses::default();
}
DROPPED_FRAMES.store(0, Ordering::Release);
}
pub fn camera() -> Option<CameraRef> {
PLATFORM_CAMERA.get()
}
pub fn camera_supported() -> bool {
PLATFORM_CAMERA.get().is_some()
}
fn state_slot() -> &'static Mutex<CameraState> {
static SLOT: OnceLock<Mutex<CameraState>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(CameraState::Idle))
}
fn latest_frame_slot() -> &'static Mutex<Option<CameraFrame>> {
static SLOT: OnceLock<Mutex<Option<CameraFrame>>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(None))
}
fn lenses_slot() -> &'static Mutex<CameraLenses> {
static SLOT: OnceLock<Mutex<CameraLenses>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(CameraLenses::default()))
}
static DROPPED_FRAMES: AtomicU64 = AtomicU64::new(0);
type FrameObserver = Arc<dyn Fn(CameraFrame) + Send + Sync>;
type StateObserver = Arc<dyn Fn(CameraState) + Send + Sync>;
type StillObserver = Arc<dyn Fn(Result<CameraStill, CameraError>) + Send + Sync>;
type LensObserver = Arc<dyn Fn(CameraLenses) + Send + Sync>;
fn frame_observers() -> &'static Mutex<Vec<(u64, FrameObserver)>> {
static SLOT: OnceLock<Mutex<Vec<(u64, FrameObserver)>>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(Vec::new()))
}
fn state_observers() -> &'static Mutex<Vec<(u64, StateObserver)>> {
static SLOT: OnceLock<Mutex<Vec<(u64, StateObserver)>>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(Vec::new()))
}
fn still_observers() -> &'static Mutex<Vec<(u64, StillObserver)>> {
static SLOT: OnceLock<Mutex<Vec<(u64, StillObserver)>>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(Vec::new()))
}
fn lens_observers() -> &'static Mutex<Vec<(u64, LensObserver)>> {
static SLOT: OnceLock<Mutex<Vec<(u64, LensObserver)>>> = OnceLock::new();
SLOT.get_or_init(|| Mutex::new(Vec::new()))
}
static NEXT_OBSERVER: AtomicU64 = AtomicU64::new(1);
pub struct CameraObserver {
id: u64,
kind: ObserverKind,
}
#[derive(Clone, Copy)]
enum ObserverKind {
Frame,
State,
Still,
Lenses,
}
impl Drop for CameraObserver {
fn drop(&mut self) {
match self.kind {
ObserverKind::Frame => retain_without(frame_observers(), self.id),
ObserverKind::State => retain_without(state_observers(), self.id),
ObserverKind::Still => retain_without(still_observers(), self.id),
ObserverKind::Lenses => retain_without(lens_observers(), self.id),
}
}
}
fn retain_without<T>(slot: &'static Mutex<Vec<(u64, T)>>, id: u64) {
if let Ok(mut observers) = slot.lock() {
observers.retain(|(observer, _)| *observer != id);
}
}
fn snapshot<T: Clone>(slot: &'static Mutex<Vec<(u64, T)>>) -> Vec<T> {
slot.lock()
.map(|observers| {
observers
.iter()
.map(|(_, observer)| observer.clone())
.collect()
})
.unwrap_or_default()
}
pub fn latest_camera_frame() -> Option<CameraFrame> {
latest_frame_slot()
.lock()
.map(|frame| frame.clone())
.unwrap_or(None)
}
pub fn camera_state() -> CameraState {
state_slot()
.lock()
.map(|state| state.clone())
.unwrap_or_default()
}
pub fn dropped_camera_frames() -> u64 {
DROPPED_FRAMES.load(Ordering::Acquire)
}
pub fn publish_camera_frame(frame: CameraFrame) {
if let Ok(mut latest) = latest_frame_slot().lock() {
*latest = Some(frame.clone());
}
let observers = snapshot(frame_observers());
if observers.is_empty() {
return;
}
for observer in observers {
observer(frame.clone());
}
}
pub fn record_dropped_camera_frame() {
DROPPED_FRAMES.fetch_add(1, Ordering::AcqRel);
}
pub fn publish_camera_state(state: CameraState) {
{
let Ok(mut current) = state_slot().lock() else {
return;
};
if *current == state {
return;
}
*current = state.clone();
}
if matches!(state, CameraState::Idle | CameraState::Starting) {
DROPPED_FRAMES.store(0, Ordering::Release);
if let Ok(mut latest) = latest_frame_slot().lock() {
*latest = None;
}
}
for observer in snapshot(state_observers()) {
observer(state.clone());
}
}
pub fn publish_camera_still(still: Result<CameraStill, CameraError>) {
for observer in snapshot(still_observers()) {
observer(still.clone());
}
}
pub fn publish_camera_lenses(lenses: CameraLenses) {
{
let Ok(mut current) = lenses_slot().lock() else {
return;
};
if *current == lenses {
return;
}
*current = lenses.clone();
}
for observer in snapshot(lens_observers()) {
observer(lenses.clone());
}
}
pub fn camera_lenses() -> CameraLenses {
lenses_slot()
.lock()
.map(|lenses| lenses.clone())
.unwrap_or_default()
}
pub fn observe_camera_frames(
observer: impl Fn(CameraFrame) + Send + Sync + 'static,
) -> CameraObserver {
let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut observers) = frame_observers().lock() {
observers.push((id, Arc::new(observer)));
}
CameraObserver {
id,
kind: ObserverKind::Frame,
}
}
pub fn observe_camera_state(
observer: impl Fn(CameraState) + Send + Sync + 'static,
) -> CameraObserver {
let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
let observer: StateObserver = Arc::new(observer);
if let Ok(mut observers) = state_observers().lock() {
observers.push((id, Arc::clone(&observer)));
}
observer(camera_state());
CameraObserver {
id,
kind: ObserverKind::State,
}
}
pub fn observe_camera_stills(
observer: impl Fn(Result<CameraStill, CameraError>) + Send + Sync + 'static,
) -> CameraObserver {
let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
if let Ok(mut observers) = still_observers().lock() {
observers.push((id, Arc::new(observer)));
}
CameraObserver {
id,
kind: ObserverKind::Still,
}
}
pub fn observe_camera_lenses(
observer: impl Fn(CameraLenses) + Send + Sync + 'static,
) -> CameraObserver {
let id = NEXT_OBSERVER.fetch_add(1, Ordering::Relaxed);
let observer: LensObserver = Arc::new(observer);
if let Ok(mut observers) = lens_observers().lock() {
observers.push((id, Arc::clone(&observer)));
}
observer(camera_lenses());
CameraObserver {
id,
kind: ObserverKind::Lenses,
}
}
#[allow(non_snake_case)]
#[track_caller]
pub fn rememberCameraState() -> State<CameraState> {
let updates = rememberEventStream((), |sender| {
observe_camera_state(move |state| sender.send(state))
});
cranpose_core::collectAsState(updates, (), camera_state())
}
#[allow(non_snake_case)]
#[track_caller]
pub fn rememberCameraFrames() -> EventStream<CameraFrame> {
rememberEventStream((), |sender| {
observe_camera_frames(move |frame| sender.send(frame))
})
}
#[allow(non_snake_case)]
#[track_caller]
pub fn rememberCameraStills() -> EventStream<Result<CameraStill, CameraError>> {
rememberEventStream((), |sender| {
observe_camera_stills(move |still| sender.send(still))
})
}
#[allow(non_snake_case)]
#[track_caller]
pub fn rememberCameraLenses() -> State<CameraLenses> {
let updates = rememberEventStream((), |sender| {
observe_camera_lenses(move |lenses| sender.send(lenses))
});
cranpose_core::collectAsState(updates, (), camera_lenses())
}
pub fn start_camera() -> Result<(), CameraError> {
let Some(camera) = camera() else {
publish_camera_state(CameraState::Failed(CameraError::Unsupported));
return Err(CameraError::Unsupported);
};
publish_camera_state(CameraState::Starting);
camera.start().inspect_err(|error| {
publish_camera_state(CameraState::Failed(error.clone()));
})
}
pub fn stop_camera() {
if let Some(camera) = camera() {
camera.stop();
}
publish_camera_state(CameraState::Stopped);
}
pub fn request_camera_still() -> Result<(), CameraError> {
let Some(camera) = camera() else {
return Err(CameraError::Unsupported);
};
if !camera_state().is_running() {
return Err(CameraError::NotRunning);
}
camera.request_still()
}
pub async fn capture_camera_still() -> Result<CameraStill, CameraError> {
let signal = crate::async_io::Signal::new();
let deliver = signal.clone();
let observer = observe_camera_stills(move |result| deliver.set(result));
request_camera_still()?;
let arrived = signal.wait().await;
drop(observer);
arrived.unwrap_or(Err(CameraError::NotRunning))
}
#[cfg(test)]
mod tests {
use super::*;
struct FakeCamera {
started: AtomicU64,
stopped: AtomicU64,
stills: AtomicU64,
fails: bool,
}
impl FakeCamera {
fn new() -> Arc<Self> {
Arc::new(Self {
started: AtomicU64::new(0),
stopped: AtomicU64::new(0),
stills: AtomicU64::new(0),
fails: false,
})
}
fn failing() -> Arc<Self> {
Arc::new(Self {
started: AtomicU64::new(0),
stopped: AtomicU64::new(0),
stills: AtomicU64::new(0),
fails: true,
})
}
}
impl Camera for FakeCamera {
fn start(&self) -> Result<(), CameraError> {
self.started.fetch_add(1, Ordering::Relaxed);
if self.fails {
return Err(CameraError::PermissionDenied);
}
publish_camera_state(CameraState::Running {
device: "fake".to_string(),
});
Ok(())
}
fn stop(&self) {
self.stopped.fetch_add(1, Ordering::Relaxed);
}
fn request_still(&self) -> Result<(), CameraError> {
self.stills.fetch_add(1, Ordering::Relaxed);
publish_camera_still(Ok(CameraStill {
jpeg: vec![0xff, 0xd8],
}));
Ok(())
}
}
fn rgba_frame(sequence: u64) -> CameraFrame {
CameraFrame::new(2, 2, FrameFormat::Rgba8, 90, sequence, vec![7; 16])
.expect("a well-formed frame")
}
#[test]
fn a_frame_size_is_the_one_its_format_implies() {
assert_eq!(FrameFormat::Rgba8.byte_len(4, 2), Some(32));
assert_eq!(FrameFormat::Nv12.byte_len(4, 2), Some(12));
assert_eq!(
FrameFormat::Nv12.byte_len(3, 2),
None,
"NV12 has no half column for an odd width"
);
assert_eq!(FrameFormat::Nv12.byte_len(4, 3), None);
}
#[test]
fn a_frame_that_does_not_match_its_size_is_refused() {
assert!(CameraFrame::new(2, 2, FrameFormat::Rgba8, 0, 0, vec![0; 15]).is_none());
assert!(CameraFrame::new(2, 2, FrameFormat::Nv12, 0, 0, vec![0; 5]).is_none());
assert!(CameraFrame::new(2, 2, FrameFormat::Rgba8, 0, 0, vec![0; 16]).is_some());
}
#[test]
fn a_rotation_is_kept_inside_one_turn() {
let frame = CameraFrame::new(2, 2, FrameFormat::Rgba8, 450, 0, vec![0; 16])
.expect("a well-formed frame");
assert_eq!(frame.rotation_degrees, 90);
}
#[test]
fn an_rgba_frame_is_handed_over_unchanged() {
let frame = rgba_frame(0);
assert_eq!(frame.to_rgba8(), frame.bytes);
}
#[test]
fn nv12_black_white_and_primaries_convert_to_the_expected_colours() {
fn convert(luma: u8, blue: u8, red: u8) -> [u8; 4] {
let bytes = vec![luma, luma, luma, luma, blue, red];
let frame = CameraFrame::new(2, 2, FrameFormat::Nv12, 0, 0, bytes)
.expect("a well-formed frame");
let rgba = frame.to_rgba8();
[rgba[0], rgba[1], rgba[2], rgba[3]]
}
assert_eq!(convert(0, 128, 128), [0, 0, 0, 255], "black");
assert_eq!(convert(255, 128, 128), [255, 255, 255, 255], "white");
let red = convert(76, 84, 255);
assert!(
red[0] > 240 && red[1] < 20 && red[2] < 20,
"red, got {red:?}"
);
let blue = convert(29, 255, 107);
assert!(
blue[2] > 240 && blue[0] < 20 && blue[1] < 20,
"blue, got {blue:?}"
);
assert!(
convert(128, 128, 128).iter().all(|value| *value > 0),
"a grey frame must not clamp to black"
);
}
#[test]
fn a_short_nv12_frame_converts_to_black_rather_than_reading_past_its_end() {
let rgba = nv12_to_rgba8(4, 4, &[0u8; 3]);
assert_eq!(rgba.len(), 4 * 4 * 4);
assert!(rgba.iter().all(|value| *value == 0));
}
#[test]
fn a_platform_without_a_camera_says_so_rather_than_pretending() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
assert!(!camera_supported());
assert_eq!(start_camera(), Err(CameraError::Unsupported));
assert_eq!(
camera_state(),
CameraState::Failed(CameraError::Unsupported)
);
assert_eq!(request_camera_still(), Err(CameraError::Unsupported));
clear_platform_camera();
}
#[test]
fn the_session_reports_starting_before_it_reports_running() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
let seen = Arc::new(Mutex::new(Vec::new()));
let recorder = Arc::clone(&seen);
let observer = observe_camera_state(move |state| {
recorder
.lock()
.unwrap_or_else(|error| error.into_inner())
.push(state)
});
set_platform_camera(FakeCamera::new());
start_camera().expect("the session starts");
assert_eq!(
*seen.lock().unwrap_or_else(|error| error.into_inner()),
vec![
CameraState::Idle,
CameraState::Starting,
CameraState::Running {
device: "fake".to_string()
}
]
);
assert!(camera_state().is_running());
assert!(camera_state().is_active());
drop(observer);
clear_platform_camera();
}
#[test]
fn a_session_that_cannot_open_reports_why() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
set_platform_camera(FakeCamera::failing());
assert_eq!(start_camera(), Err(CameraError::PermissionDenied));
assert_eq!(
camera_state().failure(),
Some(&CameraError::PermissionDenied)
);
assert!(!camera_state().is_active());
clear_platform_camera();
}
#[test]
fn stopping_releases_the_device_and_says_so() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
let backend = FakeCamera::new();
set_platform_camera(backend.clone());
start_camera().expect("the session starts");
stop_camera();
assert_eq!(backend.stopped.load(Ordering::Relaxed), 1);
assert_eq!(camera_state(), CameraState::Stopped);
clear_platform_camera();
}
#[test]
fn the_stored_frame_is_always_the_newest_one() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
assert_eq!(latest_camera_frame(), None);
publish_camera_frame(rgba_frame(1));
publish_camera_frame(rgba_frame(2));
publish_camera_frame(rgba_frame(3));
assert_eq!(latest_camera_frame().map(|frame| frame.sequence), Some(3));
clear_platform_camera();
}
#[test]
fn frame_observers_see_frames_and_stop_when_dropped() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
let seen = Arc::new(Mutex::new(Vec::new()));
let recorder = Arc::clone(&seen);
let observer = observe_camera_frames(move |frame| {
recorder
.lock()
.unwrap_or_else(|error| error.into_inner())
.push(frame.sequence)
});
publish_camera_frame(rgba_frame(1));
publish_camera_frame(rgba_frame(2));
drop(observer);
publish_camera_frame(rgba_frame(3));
assert_eq!(
*seen.lock().unwrap_or_else(|error| error.into_inner()),
vec![1, 2]
);
clear_platform_camera();
}
#[test]
fn frames_the_platform_could_not_deliver_are_counted_rather_than_queued() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
assert_eq!(dropped_camera_frames(), 0);
record_dropped_camera_frame();
record_dropped_camera_frame();
assert_eq!(dropped_camera_frames(), 2);
publish_camera_state(CameraState::Starting);
assert_eq!(dropped_camera_frames(), 0);
assert_eq!(latest_camera_frame(), None);
clear_platform_camera();
}
#[test]
fn a_still_is_asked_for_and_arrives_separately() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
let backend = FakeCamera::new();
set_platform_camera(backend.clone());
assert_eq!(
request_camera_still(),
Err(CameraError::NotRunning),
"nothing is running, so there is nothing to photograph"
);
let seen = Arc::new(Mutex::new(Vec::new()));
let recorder = Arc::clone(&seen);
let observer = observe_camera_stills(move |still| {
recorder
.lock()
.unwrap_or_else(|error| error.into_inner())
.push(still)
});
start_camera().expect("the session starts");
request_camera_still().expect("a still is asked for");
assert_eq!(backend.stills.load(Ordering::Relaxed), 1);
assert_eq!(
*seen.lock().unwrap_or_else(|error| error.into_inner()),
vec![Ok(CameraStill {
jpeg: vec![0xff, 0xd8]
})]
);
drop(observer);
clear_platform_camera();
}
#[test]
fn a_backend_that_lists_no_lens_and_no_flash_says_so() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
set_platform_camera(FakeCamera::new());
let backend = camera().expect("registered");
assert!(backend.lenses().is_empty());
assert_eq!(backend.lens(), None);
assert!(!backend.use_lens("0"));
assert!(!backend.has_flash());
assert!(!backend.set_flash(FlashMode::On));
assert!(!backend.set_torch(true));
clear_platform_camera();
}
#[test]
fn a_backend_that_lists_two_lenses_hands_them_over_in_order() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
struct TwoLenses;
impl Camera for TwoLenses {
fn start(&self) -> Result<(), CameraError> {
Ok(())
}
fn stop(&self) {}
fn lenses(&self) -> Vec<CameraLens> {
vec![
CameraLens {
id: "u".into(),
name: "Ultra wide".into(),
facing: LensFacing::Back,
},
CameraLens {
id: "w".into(),
name: "Wide".into(),
facing: LensFacing::Back,
},
]
}
fn lens(&self) -> Option<String> {
Some("w".into())
}
fn use_lens(&self, id: &str) -> bool {
id == "u" || id == "w"
}
}
set_platform_camera(Arc::new(TwoLenses));
let backend = camera().expect("registered");
let lenses = backend.lenses();
assert_eq!(lenses.len(), 2);
assert_eq!(lenses[0].name, "Ultra wide");
assert_eq!(backend.lens().as_deref(), Some("w"));
assert!(backend.use_lens("u"));
assert!(!backend.use_lens("tele"));
clear_platform_camera();
}
fn two_pixel_frame(rotation: u16) -> CameraFrame {
let mut bytes = vec![10u8, 10, 10, 255];
bytes.extend_from_slice(&[20, 20, 20, 255]);
CameraFrame::new(2, 1, FrameFormat::Rgba8, rotation, 0, bytes).expect("a well-formed frame")
}
fn pixel_values(image: &UprightRgba) -> Vec<u8> {
image.rgba.iter().step_by(4).copied().collect()
}
#[test]
fn a_frame_turns_upright_by_its_rotation() {
let unturned = two_pixel_frame(0).upright_rgba8();
assert_eq!((unturned.width, unturned.height), (2, 1));
assert_eq!(pixel_values(&unturned), vec![10, 20]);
let quarter = two_pixel_frame(90).upright_rgba8();
assert_eq!((quarter.width, quarter.height), (1, 2));
assert_eq!(pixel_values(&quarter), vec![10, 20]);
let half = two_pixel_frame(180).upright_rgba8();
assert_eq!((half.width, half.height), (2, 1));
assert_eq!(pixel_values(&half), vec![20, 10]);
let three_quarters = two_pixel_frame(270).upright_rgba8();
assert_eq!((three_quarters.width, three_quarters.height), (1, 2));
assert_eq!(pixel_values(&three_quarters), vec![20, 10]);
}
#[test]
fn an_nv12_frame_turns_and_converts_in_one_pass() {
let bytes = vec![0, 255, 0, 0, 128, 128];
let frame =
CameraFrame::new(2, 2, FrameFormat::Nv12, 90, 0, bytes).expect("a well-formed frame");
let upright = frame.upright_rgba8();
assert_eq!((upright.width, upright.height), (2, 2));
let values = pixel_values(&upright);
assert_eq!(values[0], 0, "top-left stays dark");
assert_eq!(
values[3], 255,
"the bright top-right pixel lands bottom-right"
);
}
#[test]
fn an_rgb8_frame_turns_upright_with_a_full_alpha() {
let frame = CameraFrame::new(
2,
1,
FrameFormat::Rgb8,
180,
0,
vec![10, 10, 10, 20, 20, 20],
)
.expect("a well-formed frame");
let upright = frame.upright_rgba8();
assert_eq!(pixel_values(&upright), vec![20, 10]);
assert!(upright.rgba.iter().skip(3).step_by(4).all(|a| *a == 255));
}
#[test]
fn a_turn_that_is_not_a_quarter_is_left_alone() {
let frame = CameraFrame::new(2, 1, FrameFormat::Rgba8, 45, 0, vec![7; 8])
.expect("a well-formed frame");
let upright = frame.upright_rgba8();
assert_eq!((upright.width, upright.height), (2, 1));
assert_eq!(upright.rgba, frame.to_rgba8());
}
#[test]
fn the_lens_list_is_published_and_observed() {
let _guard = crate::registry::test_service_guard();
clear_platform_camera();
assert_eq!(camera_lenses(), CameraLenses::default());
let seen = Arc::new(Mutex::new(Vec::new()));
let recorder = Arc::clone(&seen);
let observer = observe_camera_lenses(move |lenses| {
recorder
.lock()
.unwrap_or_else(|error| error.into_inner())
.push(lenses)
});
let published = CameraLenses {
lenses: vec![CameraLens {
id: "0".into(),
name: "Back".into(),
facing: LensFacing::Back,
}],
active: Some("0".into()),
};
publish_camera_lenses(published.clone());
publish_camera_lenses(published.clone());
assert_eq!(camera_lenses(), published);
assert_eq!(
*seen.lock().unwrap_or_else(|error| error.into_inner()),
vec![CameraLenses::default(), published.clone()],
"the current list arrives at once, and a repeat is not re-delivered"
);
drop(observer);
publish_camera_lenses(CameraLenses::default());
assert_eq!(
seen.lock().unwrap_or_else(|error| error.into_inner()).len(),
2,
"a dropped observer hears nothing more"
);
clear_platform_camera();
assert_eq!(camera_lenses(), CameraLenses::default());
}
}