102 lines · 3.1 KB · Rust
RawDownload
use std::{    env,    num::NonZero,    path::{Path, PathBuf},    sync::{Arc, Mutex},    time::Duration,}; use anyhow::{Context, Result};use cpal::DeviceId;use cpal::traits::{DeviceTrait, StreamTrait};use rodio::{buffer::SamplesBuffer, conversions::SampleTypeConverter};use util::ResultExt; pub struct CaptureInput {    pub name: String,    pub input_device: Option<DeviceId>,    config: cpal::SupportedStreamConfig,    samples: Arc<Mutex<Vec<i16>>>,    _stream: cpal::Stream,} impl CaptureInput {    pub fn start(input_device: Option<DeviceId>) -> anyhow::Result<Self> {        let (device, config) = crate::default_device(true, input_device.as_ref())?;        let name = device            .description()            .map(|desc| desc.name().to_string())            .unwrap_or("<unknown>".to_string());        log::info!("Using microphone: {}", name);         let samples = Arc::new(Mutex::new(Vec::new()));        let stream = start_capture(device, config.clone(), samples.clone())?;         Ok(Self {            name,            input_device,            _stream: stream,            config,            samples,        })    }     pub fn finish(self) -> Result<PathBuf> {        let name = self.name;        let mut path = env::current_dir().context("Could not get current dir")?;        path.push(&format!("test_recording_{name}.wav"));        log::info!("Test recording written to: {}", path.display());        write_out(self.samples, self.config, &path)?;        Ok(path)    }} fn start_capture(    device: cpal::Device,    config: cpal::SupportedStreamConfig,    samples: Arc<Mutex<Vec<i16>>>,) -> Result<cpal::Stream> {    let stream = device        .build_input_stream_raw(            &config.config(),            config.sample_format(),            move |data, _: &_| {                let data = crate::get_sample_data(config.sample_format(), data).log_err();                let Some(data) = data else {                    return;                };                samples                    .try_lock()                    .expect("Only locked after stream ends")                    .extend_from_slice(&data);            },            |err| log::error!("error capturing audio track: {:?}", err),            Some(Duration::from_millis(100)),        )        .context("failed to build input stream")?;     stream.play()?;    Ok(stream)} fn write_out(    samples: Arc<Mutex<Vec<i16>>>,    config: cpal::SupportedStreamConfig,    path: &Path,) -> Result<()> {    let samples = std::mem::take(        &mut *samples            .try_lock()            .expect("Stream has ended, callback cant hold the lock"),    );    let samples: Vec<f32> = SampleTypeConverter::<_, f32>::new(samples.into_iter()).collect();    let mut samples = SamplesBuffer::new(        NonZero::new(config.channels()).expect("config channel is never zero"),        NonZero::new(config.sample_rate()).expect("config sample_rate is never zero"),        samples,    );    match rodio::wav_to_file(&mut samples, path) {        Ok(_) => Ok(()),        Err(e) => Err(anyhow::anyhow!("Failed to write wav file: {}", e)),    }}