use parking_lot::Mutex;
use std::sync::Arc;
#[cfg(target_os = "windows")]
mod imp {
use super::*;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{Sample, SampleFormat, StreamConfig};
#[allow(dead_code)] pub struct LoopbackStream {
_stream: cpal::Stream,
pub source_sample_rate: u32,
pub source_channels: u16,
}
pub fn try_start(buf: Arc<Mutex<Vec<f32>>>) -> anyhow::Result<LoopbackStream> {
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or_else(|| anyhow::anyhow!("no default output device for WASAPI loopback"))?;
let config = device
.default_output_config()
.map_err(|e| anyhow::anyhow!("default_output_config: {e}"))?;
let sr = config.sample_rate();
let ch = config.channels();
let stream_config = StreamConfig {
channels: ch,
sample_rate: sr,
buffer_size: cpal::BufferSize::Default,
};
let buf_cb = buf.clone();
let mut resampler =
crate::resampler::Linear::new(sr.0, crate::capture::TARGET_SAMPLE_RATE, ch, 1);
log::info!(
"[loopback] opening default render device — {} Hz / {} ch / {:?}",
sr.0,
ch,
config.sample_format()
);
let stream = match config.sample_format() {
SampleFormat::F32 => device.build_input_stream(
&stream_config,
move |data: &[f32], _| {
let resampled = resampler.process(data);
buf_cb.lock().extend_from_slice(&resampled);
},
|err| log::warn!("[loopback] cpal err: {err}"),
None,
),
SampleFormat::I16 => device.build_input_stream(
&stream_config,
move |data: &[i16], _| {
let samples: Vec<f32> = data.iter().map(|&s| s.to_sample::<f32>()).collect();
let resampled = resampler.process(&samples);
buf_cb.lock().extend_from_slice(&resampled);
},
|err| log::warn!("[loopback] cpal err: {err}"),
None,
),
SampleFormat::U16 => device.build_input_stream(
&stream_config,
move |data: &[u16], _| {
let samples: Vec<f32> = data.iter().map(|&s| s.to_sample::<f32>()).collect();
let resampled = resampler.process(&samples);
buf_cb.lock().extend_from_slice(&resampled);
},
|err| log::warn!("[loopback] cpal err: {err}"),
None,
),
other => anyhow::bail!("unsupported loopback sample format: {other:?}"),
}
.map_err(|e| anyhow::anyhow!("build_input_stream (loopback): {e}"))?;
stream
.play()
.map_err(|e| anyhow::anyhow!("loopback stream.play: {e}"))?;
Ok(LoopbackStream {
_stream: stream,
source_sample_rate: sr.0,
source_channels: ch,
})
}
}
#[cfg(not(target_os = "windows"))]
mod imp {
use super::*;
#[allow(dead_code)] pub struct LoopbackStream {
pub source_sample_rate: u32,
pub source_channels: u16,
}
pub fn try_start(_buf: Arc<Mutex<Vec<f32>>>) -> anyhow::Result<LoopbackStream> {
anyhow::bail!("loopback not supported on this platform")
}
}
#[allow(unused_imports)]
pub use imp::{try_start, LoopbackStream};