
Adds component NotificationFrame. It implements a subset of MessageNotification as a Component and refactors MessageNotification to use NotificationFrame. Having some notification UI Component is nice as it allows us to easily build new types of notifications. Uses the new NotificationFrame component for CaptureAudioNotification. Adds a CaptureAudio action in the dev namespace (not meant for end-users). It records 10 seconds of audio and saves that to a wav file. Release Notes: - N/A --------- Co-authored-by: Mikayla <mikayla@zed.dev>
91 lines
2.7 KiB
Rust
91 lines
2.7 KiB
Rust
use std::{
|
|
env,
|
|
path::{Path, PathBuf},
|
|
sync::{Arc, Mutex},
|
|
time::Duration,
|
|
};
|
|
|
|
use anyhow::{Context, Result};
|
|
use cpal::traits::{DeviceTrait, StreamTrait};
|
|
use rodio::{buffer::SamplesBuffer, conversions::SampleTypeConverter};
|
|
use util::ResultExt;
|
|
|
|
pub struct CaptureInput {
|
|
pub name: String,
|
|
config: cpal::SupportedStreamConfig,
|
|
samples: Arc<Mutex<Vec<i16>>>,
|
|
_stream: cpal::Stream,
|
|
}
|
|
|
|
impl CaptureInput {
|
|
pub fn start() -> anyhow::Result<Self> {
|
|
let (device, config) = crate::default_device(true)?;
|
|
let name = device.name().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,
|
|
_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(config.channels(), config.sample_rate().0, samples);
|
|
match rodio::output_to_wav(&mut samples, path) {
|
|
Ok(_) => Ok(()),
|
|
Err(e) => Err(anyhow::anyhow!("Failed to write wav file: {}", e)),
|
|
}
|
|
}
|