openentropy_core/sources/
camera.rs1use crate::source::{EntropySource, 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::Hardware,
20 platform_requirements: &["macos"],
21 entropy_rate_estimate: 50000.0,
22 composite: false,
23};
24
25pub struct CameraNoiseSource;
27
28impl EntropySource for CameraNoiseSource {
29 fn info(&self) -> &SourceInfo {
30 &CAMERA_NOISE_INFO
31 }
32
33 fn is_available(&self) -> bool {
34 cfg!(target_os = "macos") && command_exists("ffmpeg")
35 }
36
37 fn collect(&self, n_samples: usize) -> Vec<u8> {
38 let result = std::process::Command::new("ffmpeg")
41 .args([
42 "-f",
43 "avfoundation",
44 "-i",
45 "0",
46 "-frames:v",
47 "1",
48 "-f",
49 "rawvideo",
50 "-pix_fmt",
51 "gray",
52 "pipe:1",
53 ])
54 .stdin(std::process::Stdio::null())
55 .stdout(std::process::Stdio::piped())
56 .stderr(std::process::Stdio::null())
57 .output();
58
59 let raw_frame = match result {
60 Ok(output) if output.status.success() => output.stdout,
61 _ => return Vec::new(),
62 };
63
64 let nibbles = raw_frame.iter().map(|pixel| pixel & 0x0F);
66 pack_nibbles(nibbles, n_samples)
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn camera_noise_info() {
76 let src = CameraNoiseSource;
77 assert_eq!(src.name(), "camera_noise");
78 assert_eq!(src.info().category, SourceCategory::Hardware);
79 assert_eq!(src.info().entropy_rate_estimate, 50000.0);
80 }
81}