openentropy_core/sources/
audio.rs1use crate::source::{EntropySource, SourceCategory, SourceInfo};
8
9use super::helpers::{command_exists, pack_nibbles};
10
11const CAPTURE_DURATION: &str = "0.1";
13
14const SAMPLE_RATE: &str = "44100";
16
17static AUDIO_NOISE_INFO: SourceInfo = SourceInfo {
18 name: "audio_noise",
19 description: "Microphone ADC thermal noise (Johnson-Nyquist) via ffmpeg",
20 physics: "Records from the microphone ADC with no signal present. The LSBs capture \
21 Johnson-Nyquist noise \u{2014} thermal agitation of electrons in the input \
22 impedance. This is genuine quantum-origin entropy: random electron motion \
23 in a resistor at temperature T produces voltage noise proportional to \
24 \u{221a}(4kT R \u{0394}f).",
25 category: SourceCategory::Hardware,
26 platform_requirements: &["macos"],
27 entropy_rate_estimate: 10000.0,
28 composite: false,
29};
30
31pub struct AudioNoiseSource;
33
34impl EntropySource for AudioNoiseSource {
35 fn info(&self) -> &SourceInfo {
36 &AUDIO_NOISE_INFO
37 }
38
39 fn is_available(&self) -> bool {
40 cfg!(target_os = "macos") && command_exists("ffmpeg")
41 }
42
43 fn collect(&self, n_samples: usize) -> Vec<u8> {
44 let result = std::process::Command::new("ffmpeg")
47 .args([
48 "-f",
49 "avfoundation",
50 "-i",
51 ":0",
52 "-t",
53 CAPTURE_DURATION,
54 "-f",
55 "s16le",
56 "-ar",
57 SAMPLE_RATE,
58 "-ac",
59 "1",
60 "pipe:1",
61 ])
62 .stdin(std::process::Stdio::null())
63 .stdout(std::process::Stdio::piped())
64 .stderr(std::process::Stdio::null())
65 .output();
66
67 let raw_audio = match result {
68 Ok(output) if output.status.success() => output.stdout,
69 _ => return Vec::new(),
70 };
71
72 let nibbles = raw_audio.chunks_exact(2).map(|chunk| {
75 let sample = i16::from_le_bytes([chunk[0], chunk[1]]);
76 (sample & 0x0F) as u8
77 });
78
79 pack_nibbles(nibbles, n_samples)
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn audio_noise_info() {
89 let src = AudioNoiseSource;
90 assert_eq!(src.name(), "audio_noise");
91 assert_eq!(src.info().category, SourceCategory::Hardware);
92 assert_eq!(src.info().entropy_rate_estimate, 10000.0);
93 }
94}