1use std::sync::atomic::Ordering;
3use std::sync::Arc;
4use std::sync::Mutex;
5
6use cpal::{
7 traits::{DeviceTrait, HostTrait, StreamTrait},
8 BuildStreamError, Device, OutputCallbackInfo, SampleFormat, Stream, StreamConfig,
9 SupportedBufferSize,
10};
11use crossbeam_channel::Receiver;
12
13use super::{AudioBackendManager, RenderThreadInit};
14
15use crate::buffer::AudioBuffer;
16use crate::context::AudioContextLatencyCategory;
17use crate::context::AudioContextOptions;
18use crate::io::microphone::MicrophoneRender;
19use crate::media_devices::{MediaDeviceInfo, MediaDeviceInfoKind};
20use crate::render::RenderThread;
21use crate::{AtomicF64, MAX_CHANNELS};
22
23mod private {
27 use super::*;
28
29 #[derive(Clone)]
30 pub struct ThreadSafeClosableStream(Arc<Mutex<Option<Stream>>>);
31
32 impl ThreadSafeClosableStream {
33 pub fn new(stream: Stream) -> Self {
34 #[allow(clippy::arc_with_non_send_sync)]
35 Self(Arc::new(Mutex::new(Some(stream))))
36 }
37
38 pub fn close(&self) {
39 self.0.lock().unwrap().take(); }
41
42 pub fn resume(&self) -> bool {
43 if let Some(s) = self.0.lock().unwrap().as_ref() {
44 if let Err(e) = s.play() {
45 panic!("Error resuming cpal stream: {:?}", e);
46 }
47 return true;
48 }
49
50 false
51 }
52
53 pub fn suspend(&self) -> bool {
54 if let Some(s) = self.0.lock().unwrap().as_ref() {
55 if let Err(e) = s.pause() {
56 panic!("Error suspending cpal stream: {:?}", e);
57 }
58 return true;
59 }
60
61 false
62 }
63 }
64
65 unsafe impl Sync for ThreadSafeClosableStream {}
70 unsafe impl Send for ThreadSafeClosableStream {}
71}
72use private::ThreadSafeClosableStream;
73
74fn get_host() -> cpal::Host {
75 #[cfg(feature = "cpal-jack")]
76 {
77 if let Some(jack_id) = cpal::available_hosts()
80 .into_iter()
81 .find(|id| *id == cpal::HostId::Jack)
82 {
83 let jack_host = cpal::host_from_id(jack_id).unwrap();
84
85 return match jack_host.devices() {
88 Ok(devices) => {
89 if devices.count() == 0 {
91 log::warn!("No jack devices found, fallback to default host");
92 cpal::default_host()
93 } else {
94 jack_host
95 }
96 }
97 Err(_) => cpal::default_host(),
100 };
101 }
102 }
103
104 cpal::default_host()
105}
106
107#[derive(Clone)]
109#[allow(unused)]
110pub(crate) struct CpalBackend {
111 stream: ThreadSafeClosableStream,
112 output_latency: Arc<AtomicF64>,
113 sample_rate: f32,
114 number_of_channels: usize,
115 sink_id: String,
116}
117
118impl AudioBackendManager for CpalBackend {
119 fn build_output(options: AudioContextOptions, render_thread_init: RenderThreadInit) -> Self
120 where
121 Self: Sized,
122 {
123 let host = get_host();
124
125 log::info!("Audio Output Host: cpal {:?}", host.id());
126
127 let RenderThreadInit {
128 state,
129 frames_played,
130 ctrl_msg_recv,
131 load_value_send,
132 event_send,
133 } = render_thread_init;
134
135 let device = if options.sink_id.is_empty() {
136 host.default_output_device()
137 .expect("InvalidStateError - no output device available")
138 } else {
139 Self::enumerate_devices_sync()
140 .into_iter()
141 .find(|e| e.device_id() == options.sink_id)
142 .map(|e| *e.device().downcast::<cpal::Device>().unwrap())
143 .unwrap_or_else(|| {
144 host.default_output_device()
145 .expect("InvalidStateError - no output device available")
146 })
147 };
148
149 log::info!("Output device: {:?}", device.name());
150
151 let default_device_config = device
152 .default_output_config()
153 .expect("InvalidStateError - error while querying device output config");
154
155 let number_of_channels = usize::from(default_device_config.channels()).min(MAX_CHANNELS);
158
159 let mut preferred_config: StreamConfig = default_device_config.clone().into();
162 preferred_config.channels = number_of_channels as u16;
164
165 if let Some(sample_rate) = options.sample_rate {
167 crate::assert_valid_sample_rate(sample_rate);
168 preferred_config.sample_rate.0 = sample_rate as u32;
169 }
170
171 let buffer_size = super::buffer_size_for_latency_category(
173 options.latency_hint,
174 preferred_config.sample_rate.0 as f32,
175 ) as u32;
176
177 let clamped_buffer_size: u32 = match default_device_config.buffer_size() {
178 SupportedBufferSize::Unknown => buffer_size,
179 SupportedBufferSize::Range { min, max } => buffer_size.clamp(*min, *max),
180 };
181
182 preferred_config.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size);
183
184 if cfg!(target_os = "android") {
187 if let AudioContextLatencyCategory::Balanced
188 | AudioContextLatencyCategory::Interactive = options.latency_hint
189 {
190 preferred_config.buffer_size = cpal::BufferSize::Default;
191 }
192 }
193
194 let mut sample_rate = preferred_config.sample_rate.0 as f32;
198
199 let output_latency = Arc::new(AtomicF64::new(0.));
201
202 let mut renderer = RenderThread::new(
203 sample_rate,
204 preferred_config.channels as usize,
205 ctrl_msg_recv.clone(),
206 Arc::clone(&state),
207 Arc::clone(&frames_played),
208 event_send.clone(),
209 );
210 renderer.set_load_value_sender(load_value_send.clone());
211 renderer.spawn_garbage_collector_thread();
212
213 log::debug!(
214 "Attempt output stream with preferred config: {:?}",
215 &preferred_config
216 );
217
218 let spawned = spawn_output_stream(
219 &device,
220 default_device_config.sample_format(),
221 &preferred_config,
222 renderer,
223 Arc::clone(&output_latency),
224 );
225
226 let stream = match spawned {
227 Ok(stream) => {
228 log::debug!("Output stream set up successfully");
229 stream
230 }
231 Err(e) => {
232 log::warn!("Output stream build failed with preferred config: {}", e);
233
234 let mut supported_config: StreamConfig = default_device_config.clone().into();
235 supported_config.channels = number_of_channels as u16;
237 sample_rate = supported_config.sample_rate.0 as f32;
239
240 log::debug!(
241 "Attempt output stream with fallback config: {:?}",
242 &supported_config
243 );
244
245 let mut renderer = RenderThread::new(
246 sample_rate,
247 supported_config.channels as usize,
248 ctrl_msg_recv,
249 state,
250 frames_played,
251 event_send,
252 );
253 renderer.set_load_value_sender(load_value_send);
254 renderer.spawn_garbage_collector_thread();
255
256 let spawned = spawn_output_stream(
257 &device,
258 default_device_config.sample_format(),
259 &supported_config,
260 renderer,
261 Arc::clone(&output_latency),
262 );
263
264 spawned
265 .expect("InvalidStateError - Unable to spawn output stream with default config")
266 }
267 };
268
269 stream
271 .play()
272 .expect("InvalidStateError - Output stream refused to play");
273
274 CpalBackend {
275 stream: ThreadSafeClosableStream::new(stream),
276 output_latency,
277 sample_rate,
278 number_of_channels,
279 sink_id: options.sink_id,
280 }
281 }
282
283 fn build_input(
284 options: AudioContextOptions,
285 number_of_channels: Option<u32>,
286 ) -> (Self, Receiver<AudioBuffer>)
287 where
288 Self: Sized,
289 {
290 let host = get_host();
291
292 log::info!("Audio Input Host: cpal {:?}", host.id());
293
294 let device = if options.sink_id.is_empty() {
295 host.default_input_device()
296 .expect("InvalidStateError - no input device available")
297 } else {
298 Self::enumerate_devices_sync()
299 .into_iter()
300 .find(|e| e.device_id() == options.sink_id)
301 .map(|e| *e.device().downcast::<cpal::Device>().unwrap())
302 .unwrap_or_else(|| {
303 host.default_input_device()
304 .expect("InvalidStateError - no input device available")
305 })
306 };
307
308 log::info!("Input device: {:?}", device.name());
309
310 let supported = device
311 .default_input_config()
312 .expect("InvalidStateError - error while querying device input config");
313
314 let mut preferred: StreamConfig = supported.clone().into();
316
317 if let Some(number_of_channels) = number_of_channels {
318 preferred.channels = number_of_channels as u16;
319 }
320
321 if let Some(sample_rate) = options.sample_rate {
323 crate::assert_valid_sample_rate(sample_rate);
324 preferred.sample_rate.0 = sample_rate as u32;
325 }
326
327 let buffer_size = super::buffer_size_for_latency_category(
329 options.latency_hint,
330 preferred.sample_rate.0 as f32,
331 ) as u32;
332
333 let clamped_buffer_size: u32 = match supported.buffer_size() {
334 SupportedBufferSize::Unknown => buffer_size,
335 SupportedBufferSize::Range { min, max } => buffer_size.clamp(*min, *max),
336 };
337
338 preferred.buffer_size = cpal::BufferSize::Fixed(clamped_buffer_size);
339 let mut sample_rate = preferred.sample_rate.0 as f32;
340 let mut number_of_channels = preferred.channels as usize;
341
342 let smoothing = 3; let (sender, mut receiver) = crossbeam_channel::bounded(smoothing);
344 let renderer = MicrophoneRender::new(number_of_channels, sample_rate, sender);
345
346 log::debug!(
347 "Attempt input stream with preferred config: {:?}",
348 &preferred
349 );
350
351 let spawned = spawn_input_stream(&device, supported.sample_format(), &preferred, renderer);
352
353 let stream = match spawned {
356 Ok(stream) => {
357 log::debug!("Input stream set up successfully");
358 stream
359 }
360 Err(e) => {
361 log::warn!("Output stream build failed with preferred config: {}", e);
362
363 let supported_config: StreamConfig = supported.clone().into();
364 number_of_channels = usize::from(supported_config.channels);
366 sample_rate = supported_config.sample_rate.0 as f32;
367
368 log::debug!(
369 "Attempt output stream with fallback config: {:?}",
370 &supported_config
371 );
372
373 let (sender, receiver2) = crossbeam_channel::bounded(smoothing);
375 receiver = receiver2; let renderer = MicrophoneRender::new(number_of_channels, sample_rate, sender);
378
379 let spawned = spawn_input_stream(
380 &device,
381 supported.sample_format(),
382 &supported_config,
383 renderer,
384 );
385 spawned
386 .expect("InvalidStateError - Unable to spawn input stream with default config")
387 }
388 };
389
390 stream
392 .play()
393 .expect("InvalidStateError - Input stream refused to play");
394
395 let backend = CpalBackend {
396 stream: ThreadSafeClosableStream::new(stream),
397 output_latency: Arc::new(AtomicF64::new(0.)),
398 sample_rate,
399 number_of_channels,
400 sink_id: options.sink_id,
401 };
402
403 (backend, receiver)
404 }
405
406 fn resume(&self) -> bool {
407 self.stream.resume()
408 }
409
410 fn suspend(&self) -> bool {
411 self.stream.suspend()
412 }
413
414 fn close(&self) {
415 self.stream.close()
416 }
417
418 fn sample_rate(&self) -> f32 {
419 self.sample_rate
420 }
421
422 fn number_of_channels(&self) -> usize {
423 self.number_of_channels
424 }
425
426 fn output_latency(&self) -> f64 {
427 self.output_latency.load(Ordering::Relaxed)
428 }
429
430 fn sink_id(&self) -> &str {
431 self.sink_id.as_str()
432 }
433
434 fn enumerate_devices_sync() -> Vec<MediaDeviceInfo>
435 where
436 Self: Sized,
437 {
438 let host = get_host();
439
440 let input_devices = host.input_devices().unwrap().map(|d| {
441 let num_channels = d.default_input_config().unwrap().channels();
442 (d, MediaDeviceInfoKind::AudioInput, num_channels)
443 });
444
445 let output_devices = host.output_devices().unwrap().map(|d| {
446 let num_channels = d.default_output_config().unwrap().channels();
447 (d, MediaDeviceInfoKind::AudioOutput, num_channels)
448 });
449
450 let mut list = Vec::<MediaDeviceInfo>::new();
452
453 for (device, kind, num_channels) in input_devices.chain(output_devices) {
454 let mut index = 0;
455
456 loop {
457 let device_id = crate::media_devices::DeviceId::as_string(
458 kind,
459 "cpal".to_string(),
460 device.name().unwrap(),
461 num_channels,
462 index,
463 );
464
465 if !list.iter().any(|d| d.device_id() == device_id) {
466 let device = MediaDeviceInfo::new(
467 device_id,
468 None,
469 kind,
470 device.name().unwrap(),
471 Box::new(device),
472 );
473
474 list.push(device);
475 break;
476 } else {
477 index += 1;
478 }
479 }
480 }
481
482 list
483 }
484}
485
486fn latency_in_seconds(infos: &OutputCallbackInfo) -> f64 {
487 let timestamp = infos.timestamp();
488 timestamp
489 .playback
490 .duration_since(×tamp.callback)
491 .map(|delta| delta.as_secs() as f64 + delta.subsec_nanos() as f64 * 1e-9)
492 .unwrap_or(0.0)
493}
494
495fn spawn_output_stream(
504 device: &Device,
505 sample_format: SampleFormat,
506 config: &StreamConfig,
507 mut render: RenderThread,
508 output_latency: Arc<AtomicF64>,
509) -> Result<Stream, BuildStreamError> {
510 let err_fn = |err| log::error!("an error occurred on the output audio stream: {}", err);
511
512 match sample_format {
513 SampleFormat::F32 => device.build_output_stream(
514 config,
515 move |d: &mut [f32], i: &OutputCallbackInfo| {
516 render.render(d);
517 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
518 },
519 err_fn,
520 None,
521 ),
522 SampleFormat::F64 => device.build_output_stream(
523 config,
524 move |d: &mut [f64], i: &OutputCallbackInfo| {
525 render.render(d);
526 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
527 },
528 err_fn,
529 None,
530 ),
531 SampleFormat::U8 => device.build_output_stream(
532 config,
533 move |d: &mut [u8], i: &OutputCallbackInfo| {
534 render.render(d);
535 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
536 },
537 err_fn,
538 None,
539 ),
540 SampleFormat::U16 => device.build_output_stream(
541 config,
542 move |d: &mut [u16], i: &OutputCallbackInfo| {
543 render.render(d);
544 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
545 },
546 err_fn,
547 None,
548 ),
549 SampleFormat::U32 => device.build_output_stream(
550 config,
551 move |d: &mut [u32], i: &OutputCallbackInfo| {
552 render.render(d);
553 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
554 },
555 err_fn,
556 None,
557 ),
558 SampleFormat::U64 => device.build_output_stream(
559 config,
560 move |d: &mut [u64], i: &OutputCallbackInfo| {
561 render.render(d);
562 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
563 },
564 err_fn,
565 None,
566 ),
567 SampleFormat::I8 => device.build_output_stream(
568 config,
569 move |d: &mut [i8], i: &OutputCallbackInfo| {
570 render.render(d);
571 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
572 },
573 err_fn,
574 None,
575 ),
576 SampleFormat::I16 => device.build_output_stream(
577 config,
578 move |d: &mut [i16], i: &OutputCallbackInfo| {
579 render.render(d);
580 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
581 },
582 err_fn,
583 None,
584 ),
585 SampleFormat::I32 => device.build_output_stream(
586 config,
587 move |d: &mut [i32], i: &OutputCallbackInfo| {
588 render.render(d);
589 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
590 },
591 err_fn,
592 None,
593 ),
594 SampleFormat::I64 => device.build_output_stream(
595 config,
596 move |d: &mut [i64], i: &OutputCallbackInfo| {
597 render.render(d);
598 output_latency.store(latency_in_seconds(i), Ordering::Relaxed);
599 },
600 err_fn,
601 None,
602 ),
603 _ => panic!("Unknown cpal output sample format"),
604 }
605}
606
607fn spawn_input_stream(
616 device: &Device,
617 sample_format: SampleFormat,
618 config: &StreamConfig,
619 render: MicrophoneRender,
620) -> Result<Stream, BuildStreamError> {
621 let err_fn = |err| log::error!("an error occurred on the input audio stream: {}", err);
622
623 match sample_format {
624 SampleFormat::F32 => {
625 device.build_input_stream(config, move |d: &[f32], _c| render.render(d), err_fn, None)
626 }
627 SampleFormat::F64 => {
628 device.build_input_stream(config, move |d: &[f64], _c| render.render(d), err_fn, None)
629 }
630 SampleFormat::U8 => {
631 device.build_input_stream(config, move |d: &[u8], _c| render.render(d), err_fn, None)
632 }
633 SampleFormat::U16 => {
634 device.build_input_stream(config, move |d: &[u16], _c| render.render(d), err_fn, None)
635 }
636 SampleFormat::U32 => {
637 device.build_input_stream(config, move |d: &[u32], _c| render.render(d), err_fn, None)
638 }
639 SampleFormat::U64 => {
640 device.build_input_stream(config, move |d: &[u64], _c| render.render(d), err_fn, None)
641 }
642 SampleFormat::I8 => {
643 device.build_input_stream(config, move |d: &[i8], _c| render.render(d), err_fn, None)
644 }
645 SampleFormat::I16 => {
646 device.build_input_stream(config, move |d: &[i16], _c| render.render(d), err_fn, None)
647 }
648 SampleFormat::I32 => {
649 device.build_input_stream(config, move |d: &[i32], _c| render.render(d), err_fn, None)
650 }
651 SampleFormat::I64 => {
652 device.build_input_stream(config, move |d: &[i64], _c| render.render(d), err_fn, None)
653 }
654 _ => panic!("Unknown cpal input sample format"),
655 }
656}