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