1use crate::{
4 audio::{AudioBackend, AudioConfig, AudioStream},
5 error::{Error, Result},
6};
7use cpal::{
8 traits::{DeviceTrait, HostTrait, StreamTrait},
9 BufferSize, Device, Stream, StreamConfig, SupportedBufferSize,
10};
11
12fn clamp_block_to_buffer_size(supported: &SupportedBufferSize, block_size: u32) -> BufferSize {
17 match supported {
18 SupportedBufferSize::Range { min, max } => BufferSize::Fixed(block_size.clamp(*min, *max)),
19 SupportedBufferSize::Unknown => BufferSize::Default,
20 }
21}
22
23fn resolve_buffer_size(
32 ranges: impl Iterator<Item = cpal::SupportedStreamConfigRange>,
33 want_sr: u32,
34 want_ch: u16,
35 block_size: u32,
36) -> BufferSize {
37 let mut sr_only_fallback: Option<BufferSize> = None;
43 for range in ranges {
44 if want_sr < range.min_sample_rate() || want_sr > range.max_sample_rate() {
45 continue;
46 }
47 let resolved = clamp_block_to_buffer_size(range.buffer_size(), block_size);
48 if range.channels() == want_ch {
49 return resolved; }
51 sr_only_fallback.get_or_insert(resolved);
52 }
53 sr_only_fallback.unwrap_or(BufferSize::Default)
54}
55
56fn resolve_output_buffer_size(device: &Device, config: &AudioConfig) -> BufferSize {
58 match device.supported_output_configs() {
60 Ok(ranges) => resolve_buffer_size(
61 ranges,
62 config.sample_rate as u32,
63 config.output_channels as u16,
64 config.block_size as u32,
65 ),
66 Err(_) => BufferSize::Default,
67 }
68}
69
70fn resolve_input_buffer_size(device: &Device, config: &AudioConfig) -> BufferSize {
75 match device.supported_input_configs() {
76 Ok(ranges) => resolve_buffer_size(
77 ranges,
78 config.sample_rate as u32,
79 config.input_channels as u16,
80 config.block_size as u32,
81 ),
82 Err(_) => BufferSize::Default,
83 }
84}
85
86pub struct CpalStream {
93 stream: Option<Stream>,
95}
96
97impl AudioStream for CpalStream {
98 fn play(&self) -> std::result::Result<(), Box<dyn std::error::Error>> {
99 if let Some(ref stream) = self.stream {
100 stream
101 .play()
102 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
103 } else {
104 Err(Box::new(std::io::Error::other("Stream has been dropped")))
105 }
106 }
107
108 fn pause(&self) -> std::result::Result<(), Box<dyn std::error::Error>> {
109 if let Some(ref stream) = self.stream {
110 stream
111 .pause()
112 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
113 } else {
114 Err(Box::new(std::io::Error::other("Stream has been dropped")))
115 }
116 }
117}
118
119impl Drop for CpalStream {
120 fn drop(&mut self) {
121 self.stream.take();
123 }
124}
125
126pub struct CpalBackend {
128 host: cpal::Host,
129}
130
131impl CpalBackend {
132 pub fn new() -> Result<Self> {
134 Ok(Self {
135 host: cpal::default_host(),
136 })
137 }
138
139 pub fn list_output_devices(&self) -> Result<Vec<String>> {
141 let devices: Vec<String> = self
142 .host
143 .output_devices()
144 .map_err(|e| Error::AudioBackendError(format!("Failed to enumerate devices: {}", e)))?
145 .map(|d| d.to_string())
146 .collect();
147 Ok(devices)
148 }
149
150 pub fn list_input_devices(&self) -> Result<Vec<String>> {
152 let devices: Vec<String> = self
153 .host
154 .input_devices()
155 .map_err(|e| Error::AudioBackendError(format!("Failed to enumerate devices: {}", e)))?
156 .map(|d| d.to_string())
157 .collect();
158 Ok(devices)
159 }
160}
161
162impl AudioBackend for CpalBackend {
163 type Stream = CpalStream;
164 type Device = Device;
165 type Error = Error;
166
167 fn enumerate_output_devices(&self) -> Result<Vec<Self::Device>> {
168 let devices: Vec<Device> = self
169 .host
170 .output_devices()
171 .map_err(|e| {
172 Error::AudioBackendError(format!("Failed to enumerate output devices: {}", e))
173 })?
174 .collect();
175 Ok(devices)
176 }
177
178 fn enumerate_input_devices(&self) -> Result<Vec<Self::Device>> {
179 let devices: Vec<Device> = self
180 .host
181 .input_devices()
182 .map_err(|e| {
183 Error::AudioBackendError(format!("Failed to enumerate input devices: {}", e))
184 })?
185 .collect();
186 Ok(devices)
187 }
188
189 fn default_output_device(&self) -> Option<Self::Device> {
190 self.host.default_output_device()
191 }
192
193 fn default_input_device(&self) -> Option<Self::Device> {
194 self.host.default_input_device()
195 }
196
197 fn create_output_stream(
198 &self,
199 device: &Self::Device,
200 config: AudioConfig,
201 mut data_callback: Box<dyn FnMut(&mut [f32]) + Send>,
202 mut error_callback: Box<dyn FnMut(Self::Error) + Send>,
203 ) -> Result<Self::Stream> {
204 let stream_config = StreamConfig {
205 channels: config.output_channels as u16,
206 sample_rate: config.sample_rate as u32,
207 buffer_size: resolve_output_buffer_size(device, &config),
208 };
209
210 let stream = device
211 .build_output_stream(
212 stream_config,
213 move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
214 data_callback(data);
215 },
216 move |err| {
217 error_callback(Error::AudioBackendError(format!("Stream error: {}", err)));
218 },
219 None,
220 )
221 .map_err(|e| {
222 Error::AudioBackendError(format!("Failed to build output stream: {}", e))
223 })?;
224
225 Ok(CpalStream {
226 stream: Some(stream),
227 })
228 }
229
230 fn create_input_stream(
231 &self,
232 device: &Self::Device,
233 config: AudioConfig,
234 mut data_callback: Box<dyn FnMut(&[f32]) + Send>,
235 mut error_callback: Box<dyn FnMut(Self::Error) + Send>,
236 ) -> Result<Self::Stream> {
237 let stream_config = StreamConfig {
238 channels: config.input_channels as u16,
239 sample_rate: config.sample_rate as u32,
240 buffer_size: resolve_input_buffer_size(device, &config),
241 };
242
243 let stream = device
244 .build_input_stream(
245 stream_config,
246 move |data: &[f32], _: &cpal::InputCallbackInfo| {
247 data_callback(data);
248 },
249 move |err| {
250 error_callback(Error::AudioBackendError(format!("Stream error: {}", err)));
251 },
252 None,
253 )
254 .map_err(|e| {
255 Error::AudioBackendError(format!("Failed to build input stream: {}", e))
256 })?;
257
258 Ok(CpalStream {
259 stream: Some(stream),
260 })
261 }
262}
263
264impl Default for CpalBackend {
265 fn default() -> Self {
266 Self::new().expect("Failed to create CPAL backend")
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use cpal::{SampleFormat, SupportedStreamConfigRange};
274
275 fn range(
277 channels: u16,
278 min_sr: u32,
279 max_sr: u32,
280 buf_min: u32,
281 buf_max: u32,
282 ) -> SupportedStreamConfigRange {
283 SupportedStreamConfigRange::new(
284 channels,
285 min_sr,
286 max_sr,
287 SupportedBufferSize::Range {
288 min: buf_min,
289 max: buf_max,
290 },
291 SampleFormat::F32,
292 )
293 }
294
295 #[test]
296 fn resolve_exact_channel_and_sr_match_clamps() {
297 let ranges = vec![range(2, 44_100, 48_000, 64, 2048)];
298 assert_eq!(
299 resolve_buffer_size(ranges.into_iter(), 48_000, 2, 512),
300 BufferSize::Fixed(512)
301 );
302 }
303
304 #[test]
305 fn resolve_channel_mismatch_falls_back_to_sample_rate_match() {
306 let ranges = vec![range(8, 44_100, 96_000, 128, 1024)];
309 assert_eq!(
310 resolve_buffer_size(ranges.into_iter(), 48_000, 2, 4096),
311 BufferSize::Fixed(1024) );
313 }
314
315 #[test]
316 fn resolve_prefers_exact_channel_over_sr_only() {
317 let ranges = vec![
319 range(8, 44_100, 96_000, 128, 1024),
320 range(2, 44_100, 96_000, 64, 2048),
321 ];
322 assert_eq!(
323 resolve_buffer_size(ranges.into_iter(), 48_000, 2, 512),
324 BufferSize::Fixed(512)
325 );
326 }
327
328 #[test]
329 fn resolve_sample_rate_out_of_range_is_default() {
330 let ranges = vec![range(2, 44_100, 48_000, 64, 2048)];
331 assert_eq!(
332 resolve_buffer_size(ranges.into_iter(), 96_000, 2, 512),
333 BufferSize::Default
334 );
335 }
336
337 #[test]
338 fn resolve_no_ranges_is_default() {
339 let ranges: Vec<SupportedStreamConfigRange> = vec![];
340 assert_eq!(
341 resolve_buffer_size(ranges.into_iter(), 48_000, 2, 512),
342 BufferSize::Default
343 );
344 }
345
346 #[test]
347 fn clamp_within_range_keeps_requested_size() {
348 let supported = SupportedBufferSize::Range { min: 64, max: 2048 };
349 assert_eq!(
350 clamp_block_to_buffer_size(&supported, 512),
351 BufferSize::Fixed(512)
352 );
353 }
354
355 #[test]
356 fn clamp_below_min_raises_to_min() {
357 let supported = SupportedBufferSize::Range {
358 min: 256,
359 max: 2048,
360 };
361 assert_eq!(
362 clamp_block_to_buffer_size(&supported, 64),
363 BufferSize::Fixed(256)
364 );
365 }
366
367 #[test]
368 fn clamp_above_max_lowers_to_max() {
369 let supported = SupportedBufferSize::Range { min: 64, max: 1024 };
370 assert_eq!(
371 clamp_block_to_buffer_size(&supported, 4096),
372 BufferSize::Fixed(1024)
373 );
374 }
375
376 #[test]
377 fn clamp_unknown_range_falls_back_to_default() {
378 assert_eq!(
379 clamp_block_to_buffer_size(&SupportedBufferSize::Unknown, 512),
380 BufferSize::Default
381 );
382 }
383}