1mod backends;
2mod resampler;
3
4use resampler::Resampler;
5use thiserror::Error;
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
9pub enum Channels {
10 #[default]
11 Mono,
12 Stereo,
13}
14
15#[derive(Debug, Clone)]
16pub struct AecConfig {
17 pub sample_rate: u32,
19 pub channels: Channels,
21}
22
23impl Default for AecConfig {
24 fn default() -> Self {
25 Self {
26 sample_rate: 48000,
27 channels: Channels::Mono,
28 }
29 }
30}
31
32#[derive(Debug, Error)]
33pub enum AecError {
34 #[error("audio device unavailable")]
35 DeviceUnavailable,
36
37 #[error("microphone permission denied")]
38 PermissionDenied,
39
40 #[error("AEC not supported on this device")]
41 AecNotSupported,
42
43 #[error("invalid configuration: {0}")]
44 InvalidConfig(String),
45
46 #[error("backend error: {0}")]
47 BackendError(String),
48}
49
50pub struct CaptureHandle {
53 receiver: flume::Receiver<Result<Vec<f32>, AecError>>,
54 sample_rate: u32,
55}
56
57impl CaptureHandle {
58 pub fn new(config: AecConfig) -> Result<Self, AecError> {
61 if config.sample_rate == 0 {
62 return Err(AecError::InvalidConfig(
63 "sample_rate must be non-zero".to_string(),
64 ));
65 }
66
67 let (backend_tx, backend_rx) = flume::bounded::<Vec<f32>>(32);
68 let (native_rate, _buffer_size) = backends::create_backend(backend_tx)?;
69
70 let (public_tx, public_rx) = flume::bounded::<Result<Vec<f32>, AecError>>(32);
71 let target_rate = config.sample_rate;
72 let target_channels = config.channels;
73
74 let needs_stereo = target_channels == Channels::Stereo;
75 let needs_resampling = native_rate != target_rate;
76
77 let resampler = if needs_resampling {
78 Some(
79 Resampler::new(native_rate, target_rate)
80 .map_err(|e| AecError::BackendError(format!("resampler init: {e:?}")))?,
81 )
82 } else {
83 None
84 };
85
86 tokio::spawn(async move {
87 let mut resampler = resampler;
88
89 while let Ok(samples) = backend_rx.recv_async().await {
90 let processed = match process_audio_chunk(samples, &mut resampler, needs_stereo) {
91 Ok(p) => p,
92 Err(e) => {
93 let _ = public_tx.send_async(Err(AecError::BackendError(e))).await;
94 break;
95 }
96 };
97 if public_tx.send_async(Ok(processed)).await.is_err() {
98 break;
99 }
100 }
101 });
102
103 Ok(Self {
104 receiver: public_rx,
105 sample_rate: target_rate,
106 })
107 }
108
109 pub async fn recv(&self) -> Option<Result<Vec<f32>, AecError>> {
112 self.receiver.recv_async().await.ok()
113 }
114
115 pub fn recv_blocking(&self) -> Option<Result<Vec<f32>, AecError>> {
118 self.receiver.recv().ok()
119 }
120
121 pub fn try_recv(&self) -> Option<Result<Vec<f32>, AecError>> {
124 self.receiver.try_recv().ok()
125 }
126
127 pub fn native_sample_rate(&self) -> u32 {
130 self.sample_rate
131 }
132}
133
134fn process_audio_chunk(
137 samples: Vec<f32>,
138 resampler: &mut Option<Resampler>,
139 needs_stereo: bool,
140) -> Result<Vec<f32>, String> {
141 let samples = if let Some(r) = resampler {
142 r.process(&samples)
143 .map_err(|e| format!("resample: {e:?}"))?
144 } else {
145 samples
146 };
147
148 if needs_stereo {
149 Ok(samples.iter().flat_map(|&s| [s, s]).collect())
150 } else {
151 Ok(samples)
152 }
153}