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