1use std::{
2 env,
3 num::NonZero,
4 path::{Path, PathBuf},
5 sync::{Arc, Mutex},
6 time::Duration,
7};
8
9use anyhow::{Context, Result};
10use cpal::DeviceId;
11use cpal::traits::{DeviceTrait, StreamTrait};
12use rodio::{buffer::SamplesBuffer, conversions::SampleTypeConverter};
13use util::ResultExt;
14
15pub struct CaptureInput {
16 pub name: String,
17 pub input_device: Option<DeviceId>,
18 config: cpal::SupportedStreamConfig,
19 samples: Arc<Mutex<Vec<i16>>>,
20 _stream: cpal::Stream,
21}
22
23impl CaptureInput {
24 pub fn start(input_device: Option<DeviceId>) -> anyhow::Result<Self> {
25 let (device, config) = crate::default_device(true, input_device.as_ref())?;
26 let name = device
27 .description()
28 .map(|desc| desc.name().to_string())
29 .unwrap_or("<unknown>".to_string());
30 log::info!("Using microphone: {}", name);
31
32 let samples = Arc::new(Mutex::new(Vec::new()));
33 let stream = start_capture(device, config.clone(), samples.clone())?;
34
35 Ok(Self {
36 name,
37 input_device,
38 _stream: stream,
39 config,
40 samples,
41 })
42 }
43
44 pub fn finish(self) -> Result<PathBuf> {
45 let name = self.name;
46 let mut path = env::current_dir().context("Could not get current dir")?;
47 path.push(&format!("test_recording_{name}.wav"));
48 log::info!("Test recording written to: {}", path.display());
49 write_out(self.samples, self.config, &path)?;
50 Ok(path)
51 }
52}
53
54fn start_capture(
55 device: cpal::Device,
56 config: cpal::SupportedStreamConfig,
57 samples: Arc<Mutex<Vec<i16>>>,
58) -> Result<cpal::Stream> {
59 let stream = device
60 .build_input_stream_raw(
61 &config.config(),
62 config.sample_format(),
63 move |data, _: &_| {
64 let data = crate::get_sample_data(config.sample_format(), data).log_err();
65 let Some(data) = data else {
66 return;
67 };
68 samples
69 .try_lock()
70 .expect("Only locked after stream ends")
71 .extend_from_slice(&data);
72 },
73 |err| log::error!("error capturing audio track: {:?}", err),
74 Some(Duration::from_millis(100)),
75 )
76 .context("failed to build input stream")?;
77
78 stream.play()?;
79 Ok(stream)
80}
81
82fn write_out(
83 samples: Arc<Mutex<Vec<i16>>>,
84 config: cpal::SupportedStreamConfig,
85 path: &Path,
86) -> Result<()> {
87 let samples = std::mem::take(
88 &mut *samples
89 .try_lock()
90 .expect("Stream has ended, callback cant hold the lock"),
91 );
92 let samples: Vec<f32> = SampleTypeConverter::<_, f32>::new(samples.into_iter()).collect();
93 let mut samples = SamplesBuffer::new(
94 NonZero::new(config.channels()).expect("config channel is never zero"),
95 NonZero::new(config.sample_rate()).expect("config sample_rate is never zero"),
96 samples,
97 );
98 match rodio::wav_to_file(&mut samples, path) {
99 Ok(_) => Ok(()),
100 Err(e) => Err(anyhow::anyhow!("Failed to write wav file: {}", e)),
101 }
102}