Skip to main content

codewandler_audio/
capture.rs

1use crate::channel::new_audio_channel;
2use crate::{AudioSource, AudioSourceFanOut, IntoAudioSource};
3use anyhow::anyhow;
4use crossbeam_channel::{Receiver, Sender};
5use rodio::cpal::StreamConfig;
6use rodio::cpal::traits::{HostTrait, StreamTrait};
7use rodio::{DeviceTrait, cpal};
8use rubato::{FftFixedInOut, Resampler};
9use std::collections::VecDeque;
10use std::sync::{Arc, Mutex};
11use tracing::debug;
12
13pub struct AudioCapture {
14    _input_stream: cpal::Stream,
15    _tx: Sender<f32>,
16    rx: Option<Receiver<f32>>,
17}
18
19impl IntoAudioSource for AudioCapture {
20    type Format = f32;
21    fn into_audio_source(&mut self) -> anyhow::Result<Box<dyn AudioSource<Format = f32>>> {
22        match self.rx.take() {
23            Some(x) => Ok(Box::new(x)),
24            None => Err(anyhow::anyhow!("Audio capture already taken")),
25        }
26    }
27}
28
29/// Start audio capture in mono from default input device.
30pub fn audio_capture(
31    output_sample_rate: u32,
32) -> anyhow::Result<AudioSourceFanOut<f32, AudioCapture>> {
33    // ---- Capture setup ----
34    let host = cpal::default_host();
35    let device = host
36        .default_input_device()
37        .ok_or_else(|| anyhow::anyhow!("No input device available"))?;
38
39    let config = device.default_input_config()?;
40
41    // Only allow f32 format as required
42    if config.sample_format() != cpal::SampleFormat::F32 {
43        return Err(anyhow!(
44            "Expected f32 input, got {:?}",
45            config.sample_format()
46        ));
47    }
48
49    let input_config: StreamConfig = config.clone().into();
50    let input_sample_rate = input_config.sample_rate.0;
51    let channels = input_config.channels;
52
53    debug!(
54        "Input device: {}, Sample rate: {}, Channels: {}, Output rate: {}",
55        device.name()?,
56        input_sample_rate,
57        channels,
58        output_sample_rate,
59    );
60
61    // Resampler
62    let resampler = Arc::new(Mutex::new(FftFixedInOut::<f32>::new(
63        input_sample_rate as usize,
64        output_sample_rate as usize,
65        1024,
66        1,
67    )?));
68
69    // resample config
70    let buf_size = resampler.lock().unwrap().input_frames_next();
71    let sample_buffer = Arc::new(Mutex::new(VecDeque::<f32>::new()));
72    let resampler_clone = resampler.clone();
73    let buffer_clone = sample_buffer.clone();
74
75    // TODO: use a ring buffer instead of a vector
76
77    let (tx, rx) = new_audio_channel::<f32>();
78    let tx_cb = tx.clone();
79    let input_stream = device.build_input_stream(
80        &config.into(),
81        move |data: &[f32], _: &cpal::InputCallbackInfo| {
82            // Downmix stereo to mono if needed
83            let mono = if channels == 1 {
84                data.to_vec()
85            } else if channels == 2 {
86                stereo_to_mono(data)
87            } else {
88                eprintln!("Unsupported channel count: {}", channels);
89                return;
90            };
91
92            let mut buf = buffer_clone.lock().unwrap();
93            buf.extend(mono);
94
95            while buf.len() >= buf_size {
96                let input_block: Vec<f32> = buf.drain(..buf_size).collect();
97                let mut resampler = resampler_clone.lock().unwrap();
98                match resampler.process(&[input_block], None) {
99                    Ok(output) => {
100                        let resampled = &output[0];
101                        for sample in resampled {
102                            tx_cb.send(*sample).unwrap();
103                        }
104                    }
105                    Err(e) => {
106                        eprintln!("Resample error: {:?}", e);
107                    }
108                }
109            }
110        },
111        |err| eprintln!("Input stream error: {}", err),
112        None,
113    )?;
114
115    input_stream.play()?;
116
117    let cap = AudioCapture {
118        _input_stream: input_stream,
119        _tx: tx,
120        rx: Some(rx),
121    };
122
123    Ok(AudioSourceFanOut::new(cap))
124}
125
126/// Convert stereo interleaved f32 to mono (average L + R)
127fn stereo_to_mono(input: &[f32]) -> Vec<f32> {
128    input
129        .chunks_exact(2)
130        .map(|chunk| 0.5 * (chunk[0] + chunk[1]))
131        .collect()
132}