A (planned) collection of lightweight tools for streaming.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at ec113892626f1feed9d65d2c71a1338d2b4065ae 169 lines 4.8 kB view raw
1use anyhow::{Result, anyhow}; 2use cpal::{ 3 SampleFormat, StreamConfig, 4 traits::{DeviceTrait, HostTrait, StreamTrait}, 5}; 6use std::{ 7 collections::VecDeque, 8 sync::{Arc, Mutex, atomic::AtomicBool}, 9 time, 10}; 11 12#[derive(Debug, Clone)] 13pub struct MicInfo { 14 pub name: String, 15 pub sample_rate: u32, 16 pub channels: u16, 17} 18 19impl Default for MicInfo { 20 fn default() -> Self { 21 Self::new() 22 } 23} 24 25impl MicInfo { 26 pub fn new() -> Self { 27 let host = cpal::default_host(); 28 let device = host.default_input_device().expect("no default input device"); 29 let supported_config = device.default_input_config().unwrap(); 30 let config: cpal::StreamConfig = supported_config.clone().into(); 31 32 Self { 33 name: device.name().unwrap_or_else(|_| "Unknown device".into()), 34 sample_rate: config.sample_rate.0, 35 channels: config.channels, 36 } 37 } 38} 39 40pub type SharedLevels = Arc<Mutex<VecDeque<f32>>>; 41 42fn process_input_f32(data: &[f32], active: &AtomicBool, levels: &SharedLevels) { 43 if data.is_empty() { 44 return; 45 } 46 47 let mut sum = 0.0; 48 for &s in data { 49 sum += s * s; 50 } 51 let rms = (sum / data.len() as f32).sqrt(); 52 53 let threshold = 0.02; 54 active.store(rms > threshold, std::sync::atomic::Ordering::Relaxed); 55 push_level(levels, rms); 56} 57 58fn process_input_i16(data: &[i16], active: &AtomicBool, levels: &SharedLevels) { 59 if data.is_empty() { 60 return; 61 } 62 let norm = i16::MAX as f32; 63 let mut sum = 0.0; 64 for &s in data { 65 let v = s as f32 / norm; 66 sum += v * v; 67 } 68 let rms = (sum / data.len() as f32).sqrt(); 69 let threshold = 0.02; 70 active.store(rms > threshold, std::sync::atomic::Ordering::Relaxed); 71 push_level(levels, rms); 72} 73 74fn process_input_u16(data: &[u16], active: &AtomicBool, levels: &SharedLevels) { 75 if data.is_empty() { 76 return; 77 } 78 let max = u16::MAX as f32; 79 let mid = max / 2.0; 80 let mut sum = 0.0; 81 for &s in data { 82 let v = (s as f32 - mid) / mid; 83 sum += v * v; 84 } 85 let rms = (sum / data.len() as f32).sqrt(); 86 let threshold = 0.02; 87 active.store(rms > threshold, std::sync::atomic::Ordering::Relaxed); 88 push_level(levels, rms); 89} 90 91fn push_level(levels: &SharedLevels, rms: f32) { 92 const MAX_SAMPLES: usize = 64; 93 94 if let Ok(mut buf) = levels.lock() { 95 buf.push_back(rms); 96 if buf.len() > MAX_SAMPLES { 97 buf.pop_front(); 98 } 99 } 100} 101 102pub fn spawn_mic_listener(active: Arc<AtomicBool>, levels: SharedLevels) -> Result<()> { 103 std::thread::spawn(move || { 104 if let Err(e) = mic_loop(active, levels) { 105 eprintln!("mic loop error: {e:?}"); 106 } 107 }); 108 Ok(()) 109} 110 111pub fn mic_loop(active: Arc<AtomicBool>, levels: SharedLevels) -> Result<()> { 112 let host = cpal::default_host(); 113 let device = host 114 .default_input_device() 115 .ok_or_else(|| anyhow::anyhow!("no default input device"))?; 116 println!("Using input device: {}", device.name()?); 117 118 let supported_config = device 119 .default_input_config() 120 .map_err(|e| anyhow::anyhow!("failed to get default input config: {e}"))?; 121 let sample_format = supported_config.sample_format(); 122 let config: StreamConfig = supported_config.into(); 123 124 let stream = match sample_format { 125 SampleFormat::F32 => { 126 let active = active.clone(); 127 let levels = levels.clone(); 128 let err_fn = |err| eprintln!("cpal input stream error: {err}"); 129 device.build_input_stream( 130 &config, 131 move |data: &[f32], _| process_input_f32(data, &active, &levels), 132 err_fn, 133 None, 134 )? 135 } 136 SampleFormat::I16 => { 137 let active = active.clone(); 138 let levels = levels.clone(); 139 let err_fn = |err| eprintln!("cpal input stream error: {err}"); 140 device.build_input_stream( 141 &config, 142 move |data: &[i16], _| process_input_i16(data, &active, &levels), 143 err_fn, 144 None, 145 )? 146 } 147 SampleFormat::U16 => { 148 let active = active.clone(); 149 let levels = levels.clone(); 150 let err_fn = |err| eprintln!("cpal input stream error: {err}"); 151 device.build_input_stream( 152 &config, 153 move |data: &[u16], _| process_input_u16(data, &active, &levels), 154 err_fn, 155 None, 156 )? 157 } 158 159 other => { 160 return Err(anyhow!("Unsupported sample format: {other:?}")); 161 } 162 }; 163 164 stream.play()?; 165 166 loop { 167 std::thread::sleep(time::Duration::from_secs(1)); 168 } 169}