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 PlaybackStreamHandle {
53 chunk_tx: flume::Sender<Vec<f32>>,
54}
55
56impl PlaybackStreamHandle {
57 pub fn send(&self, chunk: Vec<f32>) -> Result<(), AecError> {
59 self.chunk_tx
60 .send(chunk)
61 .map_err(|_| AecError::BackendError("playback stream closed".to_string()))
62 }
63
64 pub async fn send_async(&self, chunk: Vec<f32>) -> Result<(), AecError> {
66 self.chunk_tx
67 .send_async(chunk)
68 .await
69 .map_err(|_| AecError::BackendError("playback stream closed".to_string()))
70 }
71}
72
73pub struct CaptureHandle {
76 receiver: flume::Receiver<Result<Vec<f32>, AecError>>,
77 backend: backends::BackendHandle,
78 sample_rate: u32,
79}
80
81impl CaptureHandle {
82 pub fn new(config: AecConfig) -> Result<Self, AecError> {
85 if config.sample_rate == 0 {
86 return Err(AecError::InvalidConfig(
87 "sample_rate must be non-zero".to_string(),
88 ));
89 }
90
91 let (backend_tx, backend_rx) = flume::bounded::<Vec<f32>>(32);
92 let (native_rate, _buffer_size, backend_handle) = backends::create_backend(backend_tx)?;
93
94 let (public_tx, public_rx) = flume::bounded::<Result<Vec<f32>, AecError>>(32);
95 let target_rate = config.sample_rate;
96 let target_channels = config.channels;
97
98 let needs_stereo = target_channels == Channels::Stereo;
99 let needs_resampling = native_rate != target_rate;
100
101 let resampler = if needs_resampling {
102 Some(
103 Resampler::new(native_rate, target_rate)
104 .map_err(|e| AecError::BackendError(format!("resampler init: {e:?}")))?,
105 )
106 } else {
107 None
108 };
109
110 tokio::spawn(async move {
111 let mut resampler = resampler;
112
113 while let Ok(samples) = backend_rx.recv_async().await {
114 let processed = match process_audio_chunk(samples, &mut resampler, needs_stereo) {
115 Ok(p) => p,
116 Err(e) => {
117 let _ = public_tx.send_async(Err(AecError::BackendError(e))).await;
118 break;
119 }
120 };
121 if public_tx.send_async(Ok(processed)).await.is_err() {
122 break;
123 }
124 }
125 });
126
127 Ok(Self {
128 receiver: public_rx,
129 backend: backend_handle,
130 sample_rate: target_rate,
131 })
132 }
133
134 pub async fn recv(&self) -> Option<Result<Vec<f32>, AecError>> {
137 self.receiver.recv_async().await.ok()
138 }
139
140 pub fn recv_blocking(&self) -> Option<Result<Vec<f32>, AecError>> {
143 self.receiver.recv().ok()
144 }
145
146 pub fn try_recv(&self) -> Option<Result<Vec<f32>, AecError>> {
149 self.receiver.try_recv().ok()
150 }
151
152 pub fn native_sample_rate(&self) -> u32 {
155 self.sample_rate
156 }
157
158 pub fn play_audio(&self, samples: Vec<f32>, sample_rate: u32) -> Result<(), AecError> {
162 self.backend.play_audio(samples, sample_rate)
163 }
164
165 pub fn start_playback_stream(
169 &self,
170 sample_rate: u32,
171 ) -> Result<PlaybackStreamHandle, AecError> {
172 let chunk_tx = self.backend.start_playback_stream(sample_rate)?;
173 Ok(PlaybackStreamHandle { chunk_tx })
174 }
175}
176
177fn process_audio_chunk(
180 samples: Vec<f32>,
181 resampler: &mut Option<Resampler>,
182 needs_stereo: bool,
183) -> Result<Vec<f32>, String> {
184 let samples = if let Some(r) = resampler {
185 r.process(&samples)
186 .map_err(|e| format!("resample: {e:?}"))?
187 } else {
188 samples
189 };
190
191 if needs_stereo {
192 Ok(samples.iter().flat_map(|&s| [s, s]).collect())
193 } else {
194 Ok(samples)
195 }
196}