openentropy_core/sources/
camera.rs1use crate::source::{EntropySource, Platform, Requirement, SourceCategory, SourceInfo};
8
9use super::helpers::{command_exists, pack_nibbles};
10
11static CAMERA_NOISE_INFO: SourceInfo = SourceInfo {
12 name: "camera_noise",
13 description: "Camera sensor dark current and shot noise via ffmpeg",
14 physics: "Captures frames from the camera sensor in darkness. The sensor's photodiodes \
15 generate dark current from thermal electron-hole pair generation in silicon \
16 \u{2014} a quantum process. Read noise from the amplifier adds further randomness. \
17 The LSBs of pixel values in dark frames are dominated by shot noise \
18 (Poisson-distributed photon counting).",
19 category: SourceCategory::Sensor,
20 platform: Platform::MacOS,
21 requirements: &[Requirement::Camera],
22 entropy_rate_estimate: 50000.0,
23 composite: false,
24};
25
26pub struct CameraNoiseSource;
28
29impl EntropySource for CameraNoiseSource {
30 fn info(&self) -> &SourceInfo {
31 &CAMERA_NOISE_INFO
32 }
33
34 fn is_available(&self) -> bool {
35 cfg!(target_os = "macos") && command_exists("ffmpeg")
36 }
37
38 fn collect(&self, n_samples: usize) -> Vec<u8> {
39 let result = std::process::Command::new("ffmpeg")
42 .args([
43 "-f",
44 "avfoundation",
45 "-i",
46 "0",
47 "-frames:v",
48 "1",
49 "-f",
50 "rawvideo",
51 "-pix_fmt",
52 "gray",
53 "pipe:1",
54 ])
55 .stdin(std::process::Stdio::null())
56 .stdout(std::process::Stdio::piped())
57 .stderr(std::process::Stdio::null())
58 .output();
59
60 let raw_frame = match result {
61 Ok(output) if output.status.success() => output.stdout,
62 _ => return Vec::new(),
63 };
64
65 let nibbles = raw_frame.iter().map(|pixel| pixel & 0x0F);
67 pack_nibbles(nibbles, n_samples)
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn camera_noise_info() {
77 let src = CameraNoiseSource;
78 assert_eq!(src.name(), "camera_noise");
79 assert_eq!(src.info().category, SourceCategory::Sensor);
80 assert_eq!(src.info().entropy_rate_estimate, 50000.0);
81 assert!(!src.info().composite);
82 }
83
84 #[test]
85 #[cfg(target_os = "macos")]
86 #[ignore] fn camera_noise_collects_bytes() {
88 let src = CameraNoiseSource;
89 if src.is_available() {
90 let data = src.collect(64);
91 assert!(!data.is_empty());
92 assert!(data.len() <= 64);
93 }
94 }
95}