1use core::{
2 fmt::Debug,
3 num::{NonZeroU32, NonZeroUsize},
4 str::FromStr,
5 time::Duration,
6 u32,
7};
8use std::sync::mpsc;
9
10pub use cpal;
11
12use bevy_platform::time::Instant;
13use cpal::{
14 traits::{DeviceTrait, HostTrait, StreamTrait},
15 DeviceId, HostId, HostUnavailable,
16};
17use firewheel_core::{node::StreamStatus, StreamInfo};
18use firewheel_graph::{
19 backend::{AudioBackend, BackendProcessInfo, DeviceInfoSimple, SimpleStreamConfig},
20 processor::FirewheelProcessor,
21};
22use fixed_resample::{ReadStatus, ResamplingChannelConfig};
23use ringbuf::traits::{Consumer, Producer, Split};
24
25#[cfg(all(feature = "log", not(feature = "tracing")))]
26use log::{error, info, warn};
27#[cfg(feature = "tracing")]
28use tracing::{error, info, warn};
29
30const DEFAULT_MAX_BLOCK_FRAMES: u32 = 1024;
33const INPUT_ALLOC_BLOCK_FRAMES: usize = 4096;
34const BUILD_STREAM_TIMEOUT: Duration = Duration::from_secs(5);
35const MSG_CHANNEL_CAPACITY: usize = 4;
36const MAX_INPUT_CHANNELS: usize = 16;
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct CpalOutputConfig {
41 pub host: Option<cpal::HostId>,
44
45 pub device_id: Option<DeviceId>,
50
51 pub desired_sample_rate: Option<u32>,
56
57 pub desired_block_frames: Option<u32>,
67
68 pub fallback: bool,
73}
74
75impl Default for CpalOutputConfig {
76 fn default() -> Self {
77 Self {
78 host: None,
79 device_id: None,
80 desired_sample_rate: None,
81 desired_block_frames: Some(DEFAULT_MAX_BLOCK_FRAMES),
82 fallback: true,
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq)]
89pub struct CpalInputConfig {
90 pub host: Option<cpal::HostId>,
93
94 pub device_id: Option<DeviceId>,
99
100 pub desired_block_frames: Option<u32>,
110
111 pub channel_config: ResamplingChannelConfig,
113
114 pub fallback: bool,
119
120 pub fail_on_no_input: bool,
126}
127
128impl Default for CpalInputConfig {
129 fn default() -> Self {
130 Self {
131 host: None,
132 device_id: None,
133 desired_block_frames: Some(DEFAULT_MAX_BLOCK_FRAMES),
134 channel_config: ResamplingChannelConfig::default(),
135 fallback: true,
136 fail_on_no_input: false,
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq)]
143pub struct CpalConfig {
144 pub output: CpalOutputConfig,
146
147 pub input: Option<CpalInputConfig>,
153}
154
155impl Default for CpalConfig {
156 fn default() -> Self {
157 Self {
158 output: CpalOutputConfig::default(),
159 input: None,
160 }
161 }
162}
163
164pub struct CpalEnumerator;
167
168impl CpalEnumerator {
169 pub fn available_hosts(&self) -> Vec<cpal::HostId> {
173 cpal::available_hosts()
174 }
175
176 pub fn default_host(&self) -> HostEnumerator {
179 HostEnumerator {
180 host: cpal::default_host(),
181 }
182 }
183
184 pub fn get_host(&self, api: HostId) -> Result<HostEnumerator, HostUnavailable> {
187 cpal::host_from_id(api).map(|host| HostEnumerator { host })
188 }
189}
190
191pub struct HostEnumerator {
194 pub host: cpal::Host,
195}
196
197impl HostEnumerator {
198 pub fn host_id(&self) -> cpal::HostId {
200 self.host.id()
201 }
202
203 pub fn input_devices(&self) -> Vec<DeviceInfo> {
205 let mut devices = Vec::with_capacity(8);
206
207 let default_device = self.host.default_input_device();
208 let default_device_id = default_device.and_then(|d| match d.id() {
209 Ok(id) => Some(id),
210 Err(e) => {
211 warn!("Failed to get ID of default audio input device: {}", e);
212 None
213 }
214 });
215
216 match self.host.input_devices() {
217 Ok(input_devices) => {
218 for device in input_devices {
219 let Ok(id) = device.id() else {
220 continue;
221 };
222
223 let is_default = if let Some(default_device_id) = &default_device_id {
224 &id == default_device_id
225 } else {
226 false
227 };
228
229 let name = device.description().map(|d| d.name().to_string()).ok();
230
231 devices.push(DeviceInfo {
232 id,
233 name,
234 is_default,
235 })
236 }
237 }
238 Err(e) => {
239 error!("Failed to get input audio devices: {}", e);
240 }
241 }
242
243 devices
244 }
245
246 pub fn output_devices(&self) -> Vec<DeviceInfo> {
248 let mut devices = Vec::with_capacity(8);
249
250 let default_device = self.host.default_output_device();
251 let default_device_id = default_device.and_then(|d| match d.id() {
252 Ok(id) => Some(id),
253 Err(e) => {
254 warn!("Failed to get ID of default audio output device: {}", e);
255 None
256 }
257 });
258
259 match self.host.output_devices() {
260 Ok(output_devices) => {
261 for device in output_devices {
262 let Ok(id) = device.id() else {
263 continue;
264 };
265
266 let is_default = if let Some(default_device_id) = &default_device_id {
267 &id == default_device_id
268 } else {
269 false
270 };
271
272 let name = device.description().map(|d| d.name().to_string()).ok();
273
274 devices.push(DeviceInfo {
275 id,
276 name,
277 is_default,
278 })
279 }
280 }
281 Err(e) => {
282 error!("Failed to get output audio devices: {}", e);
283 }
284 }
285
286 devices
287 }
288
289 pub fn get_device(&self, device_id: &cpal::DeviceId) -> Option<cpal::Device> {
294 self.host.device_by_id(device_id)
295 }
296}
297
298#[derive(Debug, Clone, PartialEq)]
300pub struct DeviceInfo {
301 pub id: cpal::DeviceId,
309 pub name: Option<String>,
311 pub is_default: bool,
313}
314
315pub struct CpalBackend {
317 from_err_rx: mpsc::Receiver<cpal::StreamError>,
318 to_stream_tx: ringbuf::HeapProd<CtxToStreamMsg>,
319 _out_stream_handle: cpal::Stream,
320 _in_stream_handle: Option<cpal::Stream>,
321}
322
323impl AudioBackend for CpalBackend {
324 type Enumerator = CpalEnumerator;
325 type Config = CpalConfig;
326 type StartStreamError = StreamStartError;
327 type StreamError = cpal::StreamError;
328 type Instant = bevy_platform::time::Instant;
329
330 fn enumerator() -> Self::Enumerator {
331 CpalEnumerator {}
332 }
333
334 fn input_devices_simple(&mut self) -> Vec<DeviceInfoSimple> {
335 let enumerator = CpalEnumerator {};
336 let host_enumerator = enumerator.default_host();
337
338 let mut default_device_index = None;
339
340 let mut devices: Vec<DeviceInfoSimple> = host_enumerator
341 .input_devices()
342 .iter()
343 .enumerate()
344 .map(|(i, info)| {
345 if info.is_default {
346 default_device_index = Some(i);
347 }
348
349 DeviceInfoSimple {
350 name: info.name.clone().unwrap_or_else(|| String::from("unkown")),
351 id: format!("{}", info.id),
352 }
353 })
354 .collect();
355
356 if let Some(i) = default_device_index {
358 devices.swap(0, i);
359 }
360
361 devices
362 }
363
364 fn output_devices_simple(&mut self) -> Vec<DeviceInfoSimple> {
365 let enumerator = CpalEnumerator {};
366 let host_enumerator = enumerator.default_host();
367
368 let mut default_device_index = None;
369
370 let mut devices: Vec<DeviceInfoSimple> = host_enumerator
371 .output_devices()
372 .iter()
373 .enumerate()
374 .map(|(i, info)| {
375 if info.is_default {
376 default_device_index = Some(i);
377 }
378
379 DeviceInfoSimple {
380 name: info.name.clone().unwrap_or_else(|| String::from("unkown")),
381 id: format!("{}", info.id),
382 }
383 })
384 .collect();
385
386 if let Some(i) = default_device_index {
388 devices.swap(0, i);
389 }
390
391 devices
392 }
393
394 fn convert_simple_config(&mut self, config: &SimpleStreamConfig) -> Self::Config {
395 let string_to_id = |s: Option<&String>| -> Option<DeviceId> {
396 s.and_then(|s| match DeviceId::from_str(s) {
397 Ok(id) => Some(id),
398 Err(e) => {
399 warn!(
400 "Failed to convert string to DeviceID, falling back to default device: {}",
401 e
402 );
403 None
404 }
405 })
406 };
407
408 CpalConfig {
409 output: CpalOutputConfig {
410 device_id: string_to_id(config.output.device.as_ref()),
411 desired_sample_rate: config.desired_sample_rate,
412 desired_block_frames: config.desired_block_frames,
413 ..Default::default()
414 },
415 input: config.input.as_ref().map(|input_config| CpalInputConfig {
416 device_id: string_to_id(input_config.device.as_ref()),
417 desired_block_frames: config.desired_block_frames,
418 ..Default::default()
419 }),
420 }
421 }
422
423 fn start_stream(config: Self::Config) -> Result<(Self, StreamInfo), Self::StartStreamError> {
424 info!("Attempting to start CPAL audio stream...");
425
426 let host = if let Some(host_id) = config.output.host {
427 match cpal::host_from_id(host_id) {
428 Ok(host) => host,
429 Err(e) => {
430 warn!("Requested audio host {:?} is not available: {}. Falling back to default host...", &host_id, e);
431 cpal::default_host()
432 }
433 }
434 } else {
435 cpal::default_host()
436 };
437
438 let mut out_device = None;
439 if let Some(device_id) = &config.output.device_id {
440 if let Some(device) = host.device_by_id(device_id) {
441 if device.supports_output() {
442 out_device = Some(device);
443 }
444 }
445
446 if out_device.is_none() {
447 warn!("Could not find requested audio output device: {}. Falling back to default device...", &device_id);
448 }
449 }
450
451 if out_device.is_none() {
452 let Some(default_device) = host.default_output_device() else {
453 return Err(StreamStartError::DefaultOutputDeviceNotFound);
454 };
455 out_device = Some(default_device);
456 }
457 let out_device = out_device.unwrap();
458
459 let output_device_id = out_device.id().map(|d| d.to_string()).unwrap_or_else(|e| {
460 warn!("Failed to get id of output audio device: {}", e);
461 String::from("unknown")
462 });
463
464 let default_config = out_device.default_output_config()?;
465
466 let default_sample_rate = default_config.sample_rate();
467 let try_common_sample_rates = default_sample_rate != 44100 && default_sample_rate != 48000;
469
470 #[cfg(not(target_os = "ios"))]
471 let desired_block_frames =
472 if let &cpal::SupportedBufferSize::Range { min, max } = default_config.buffer_size() {
473 config
474 .output
475 .desired_block_frames
476 .map(|f| f.clamp(min, max))
477 } else {
478 None
479 };
480
481 #[cfg(target_os = "ios")]
485 let desired_block_frames: Option<u32> = None;
486
487 let mut supports_desired_sample_rate = false;
488 let mut supports_44100 = false;
489 let mut supports_48000 = false;
490
491 if config.output.desired_sample_rate.is_some() || try_common_sample_rates {
492 for cpal_config in out_device.supported_output_configs()? {
493 if let Some(sr) = config.output.desired_sample_rate {
494 if !supports_desired_sample_rate {
495 if cpal_config.try_with_sample_rate(sr).is_some() {
496 supports_desired_sample_rate = true;
497 break;
498 }
499 }
500 }
501
502 if try_common_sample_rates {
503 if !supports_44100 {
504 if cpal_config.try_with_sample_rate(44100).is_some() {
505 supports_44100 = true;
506 }
507 }
508 if !supports_48000 {
509 if cpal_config.try_with_sample_rate(48000).is_some() {
510 supports_48000 = true;
511 }
512 }
513 }
514 }
515 }
516
517 let sample_rate = if supports_desired_sample_rate {
518 config.output.desired_sample_rate.unwrap()
519 } else if try_common_sample_rates {
520 if supports_44100 {
521 44100
522 } else if supports_48000 {
523 48000
524 } else {
525 default_sample_rate
526 }
527 } else {
528 default_sample_rate
529 };
530
531 let num_out_channels = default_config.channels() as usize;
532 assert_ne!(num_out_channels, 0);
533
534 let desired_buffer_size = if let Some(samples) = desired_block_frames {
535 cpal::BufferSize::Fixed(samples)
536 } else {
537 cpal::BufferSize::Default
538 };
539
540 let out_stream_config = cpal::StreamConfig {
541 channels: num_out_channels as u16,
542 sample_rate,
543 buffer_size: desired_buffer_size,
544 };
545
546 let max_block_frames = match out_stream_config.buffer_size {
547 cpal::BufferSize::Default => DEFAULT_MAX_BLOCK_FRAMES as usize,
548 cpal::BufferSize::Fixed(f) => f as usize,
549 };
550
551 let (err_to_cx_tx, from_err_rx) = mpsc::channel();
552
553 let mut input_stream = StartInputStreamResult::NotStarted;
554 if let Some(input_config) = &config.input {
555 input_stream = start_input_stream(
556 input_config,
557 out_stream_config.sample_rate,
558 err_to_cx_tx.clone(),
559 )?;
560 }
561
562 let (
563 input_stream_handle,
564 input_stream_cons,
565 num_stream_in_channels,
566 input_device_id,
567 input_to_output_latency_seconds,
568 ) = if let StartInputStreamResult::Started {
569 stream_handle,
570 cons,
571 num_stream_in_channels,
572 input_device_id,
573 } = input_stream
574 {
575 let input_to_output_latency_seconds = cons.latency_seconds();
576
577 (
578 Some(stream_handle),
579 Some(cons),
580 num_stream_in_channels,
581 Some(input_device_id),
582 input_to_output_latency_seconds,
583 )
584 } else {
585 (None, None, 0, None, 0.0)
586 };
587
588 let (to_stream_tx, from_cx_rx) =
589 ringbuf::HeapRb::<CtxToStreamMsg>::new(MSG_CHANNEL_CAPACITY).split();
590
591 let mut data_callback = DataCallback::new(
592 num_out_channels,
593 from_cx_rx,
594 out_stream_config.sample_rate,
595 input_stream_cons,
596 );
597
598 info!(
599 "Starting output audio stream with device \"{}\" with configuration {:?}",
600 &output_device_id, &out_stream_config
601 );
602
603 let out_stream_handle = out_device.build_output_stream(
604 &out_stream_config,
605 move |output: &mut [f32], info: &cpal::OutputCallbackInfo| {
606 data_callback.callback(output, info);
607 },
608 move |err| {
609 let _ = err_to_cx_tx.send(err);
610 },
611 Some(BUILD_STREAM_TIMEOUT),
612 )?;
613
614 out_stream_handle.play()?;
615
616 let stream_info = StreamInfo {
617 sample_rate: NonZeroU32::new(out_stream_config.sample_rate).unwrap(),
618 max_block_frames: NonZeroU32::new(max_block_frames as u32).unwrap(),
619 num_stream_in_channels,
620 num_stream_out_channels: num_out_channels as u32,
621 input_to_output_latency_seconds,
622 output_device_id,
623 input_device_id,
624 ..Default::default()
626 };
627
628 Ok((
629 Self {
630 from_err_rx,
631 to_stream_tx,
632 _out_stream_handle: out_stream_handle,
633 _in_stream_handle: input_stream_handle,
634 },
635 stream_info,
636 ))
637 }
638
639 fn set_processor(&mut self, processor: FirewheelProcessor<Self>) {
640 if let Err(_) = self
641 .to_stream_tx
642 .try_push(CtxToStreamMsg::NewProcessor(processor))
643 {
644 panic!("Failed to send new processor to cpal stream");
645 }
646 }
647
648 fn poll_status(&mut self) -> Result<(), Self::StreamError> {
649 if let Ok(e) = self.from_err_rx.try_recv() {
650 Err(e)
651 } else {
652 Ok(())
653 }
654 }
655
656 fn delay_from_last_process(&self, process_timestamp: Self::Instant) -> Option<Duration> {
657 Some(process_timestamp.elapsed())
658 }
659}
660
661fn start_input_stream(
662 config: &CpalInputConfig,
663 output_sample_rate: cpal::SampleRate,
664 err_to_cx_tx: mpsc::Sender<cpal::StreamError>,
665) -> Result<StartInputStreamResult, StreamStartError> {
666 let host = if let Some(host_id) = config.host {
667 match cpal::host_from_id(host_id) {
668 Ok(host) => host,
669 Err(e) => {
670 warn!("Requested audio host {:?} is not available: {}. Falling back to default host...", &host_id, e);
671 cpal::default_host()
672 }
673 }
674 } else {
675 cpal::default_host()
676 };
677
678 let mut in_device = None;
679 if let Some(device_id) = &config.device_id {
680 if let Some(device) = host.device_by_id(device_id) {
681 if device.supports_input() {
682 in_device = Some(device);
683 }
684 }
685
686 if in_device.is_none() {
687 if config.fallback {
688 warn!("Could not find requested audio input device: {}. Falling back to default device...", &device_id);
689 } else {
690 warn!("Could not find requested audio input device: {}. No input stream will be started.", &device_id);
691 return Ok(StartInputStreamResult::NotStarted);
692 }
693 }
694 }
695
696 if in_device.is_none() {
697 if let Some(default_device) = host.default_input_device() {
698 in_device = Some(default_device);
699 } else if config.fail_on_no_input {
700 return Err(StreamStartError::DefaultInputDeviceNotFound);
701 } else {
702 warn!("No default audio input device found. Input stream will not be started.");
703 return Ok(StartInputStreamResult::NotStarted);
704 }
705 }
706 let in_device = in_device.unwrap();
707
708 let in_device_id = in_device.id().map(|id| id.to_string()).unwrap_or_else(|e| {
709 warn!("Failed to get ID of input audio device: {}", e);
710 String::from("unknown")
711 });
712
713 let default_config = in_device.default_input_config()?;
714
715 #[cfg(not(target_os = "ios"))]
716 let desired_block_frames =
717 if let &cpal::SupportedBufferSize::Range { min, max } = default_config.buffer_size() {
718 config.desired_block_frames.map(|f| f.clamp(min, max))
719 } else {
720 None
721 };
722
723 #[cfg(target_os = "ios")]
727 let desired_block_frames: Option<u32> = None;
728
729 let supported_configs = in_device.supported_input_configs()?;
730
731 let mut min_sample_rate = u32::MAX;
732 let mut max_sample_rate = 0;
733 for config in supported_configs.into_iter() {
734 min_sample_rate = min_sample_rate.min(config.min_sample_rate());
735 max_sample_rate = max_sample_rate.max(config.max_sample_rate());
736 }
737 let sample_rate = output_sample_rate.clamp(min_sample_rate, max_sample_rate);
738
739 #[cfg(not(feature = "resample_inputs"))]
740 if sample_rate != output_sample_rate {
741 if config.fail_on_no_input {
742 return Err(StreamStartError::CouldNotMatchSampleRate(
743 output_sample_rate,
744 ));
745 } else {
746 warn!("Could not use output sample rate {} for the input sample rate. Input stream will not be started", output_sample_rate);
747 return Ok(StartInputStreamResult::NotStarted);
748 }
749 }
750
751 let num_in_channels = default_config.channels() as usize;
752 assert_ne!(num_in_channels, 0);
753
754 let desired_buffer_size = if let Some(samples) = desired_block_frames {
755 cpal::BufferSize::Fixed(samples)
756 } else {
757 cpal::BufferSize::Default
758 };
759
760 let stream_config = cpal::StreamConfig {
761 channels: num_in_channels as u16,
762 sample_rate,
763 buffer_size: desired_buffer_size,
764 };
765
766 let (mut prod, cons) = fixed_resample::resampling_channel::<f32, MAX_INPUT_CHANNELS>(
767 NonZeroUsize::new(num_in_channels).unwrap(),
768 sample_rate,
769 output_sample_rate,
770 config.channel_config,
771 );
772
773 info!(
774 "Starting input audio stream with device \"{}\" with configuration {:?}",
775 &in_device_id, &stream_config
776 );
777
778 let stream_handle = match in_device.build_input_stream(
779 &stream_config,
780 move |input: &[f32], _info: &cpal::InputCallbackInfo| {
781 let _ = prod.push_interleaved(input);
782 },
783 move |err| {
784 let _ = err_to_cx_tx.send(err);
785 },
786 Some(BUILD_STREAM_TIMEOUT),
787 ) {
788 Ok(s) => s,
789 Err(e) => {
790 if config.fail_on_no_input {
791 return Err(StreamStartError::BuildStreamError(e));
792 } else {
793 error!(
794 "Failed to build input audio stream, input stream will not be started. {}",
795 e
796 );
797 return Ok(StartInputStreamResult::NotStarted);
798 }
799 }
800 };
801
802 if let Err(e) = stream_handle.play() {
803 if config.fail_on_no_input {
804 return Err(StreamStartError::PlayStreamError(e));
805 } else {
806 error!(
807 "Failed to start input audio stream, input stream will not be started. {}",
808 e
809 );
810 return Ok(StartInputStreamResult::NotStarted);
811 }
812 }
813
814 Ok(StartInputStreamResult::Started {
815 stream_handle,
816 cons,
817 num_stream_in_channels: num_in_channels as u32,
818 input_device_id: in_device_id,
819 })
820}
821
822enum StartInputStreamResult {
823 NotStarted,
824 Started {
825 stream_handle: cpal::Stream,
826 cons: fixed_resample::ResamplingCons<f32>,
827 num_stream_in_channels: u32,
828 input_device_id: String,
829 },
830}
831
832struct DataCallback {
833 num_out_channels: usize,
834 from_cx_rx: ringbuf::HeapCons<CtxToStreamMsg>,
835 processor: Option<FirewheelProcessor<CpalBackend>>,
836 sample_rate: u32,
837 sample_rate_recip: f64,
838 predicted_delta_time: Duration,
841 prev_instant: Option<Instant>,
842 stream_start_instant: Instant,
843 input_stream_cons: Option<fixed_resample::ResamplingCons<f32>>,
844 input_buffer: Vec<f32>,
845}
846
847impl DataCallback {
848 fn new(
849 num_out_channels: usize,
850 from_cx_rx: ringbuf::HeapCons<CtxToStreamMsg>,
851 sample_rate: u32,
852 input_stream_cons: Option<fixed_resample::ResamplingCons<f32>>,
853 ) -> Self {
854 let stream_start_instant = Instant::now();
855
856 let input_buffer = if let Some(cons) = &input_stream_cons {
857 let mut v = Vec::new();
858 v.reserve_exact(INPUT_ALLOC_BLOCK_FRAMES * cons.num_channels().get());
859 v.resize(INPUT_ALLOC_BLOCK_FRAMES * cons.num_channels().get(), 0.0);
860 v
861 } else {
862 Vec::new()
863 };
864
865 Self {
866 num_out_channels,
867 from_cx_rx,
868 processor: None,
869 sample_rate,
870 sample_rate_recip: f64::from(sample_rate).recip(),
871 predicted_delta_time: Duration::default(),
874 prev_instant: None,
875 stream_start_instant,
876 input_stream_cons,
877 input_buffer,
878 }
879 }
880
881 fn callback(&mut self, output: &mut [f32], _info: &cpal::OutputCallbackInfo) {
882 let process_timestamp = bevy_platform::time::Instant::now();
883
884 for msg in self.from_cx_rx.pop_iter() {
885 let CtxToStreamMsg::NewProcessor(p) = msg;
886 self.processor = Some(p);
887 }
888
889 let frames = output.len() / self.num_out_channels;
890
891 let (underflow, dropped_frames) = if let Some(prev_instant) = self.prev_instant {
892 let delta_time = process_timestamp - prev_instant;
893
894 let underflow = delta_time > self.predicted_delta_time;
895
896 let dropped_frames = if underflow {
897 (delta_time.as_secs_f64() * self.sample_rate as f64).round() as u32
898 } else {
899 0
900 };
901
902 (underflow, dropped_frames)
903 } else {
904 self.prev_instant = Some(process_timestamp);
905 (false, 0)
906 };
907
908 self.predicted_delta_time =
913 Duration::from_secs_f64(frames as f64 * self.sample_rate_recip * 1.5);
914
915 let duration_since_stream_start =
916 process_timestamp.duration_since(self.stream_start_instant);
917
918 let (num_in_channels, input_stream_status) = if let Some(cons) = &mut self.input_stream_cons
972 {
973 let num_in_channels = cons.num_channels().get();
974
975 let num_input_samples = frames * num_in_channels;
976 if num_input_samples > self.input_buffer.len() {
980 self.input_buffer.resize(num_input_samples, 0.0);
981 }
982
983 let status = cons.read_interleaved(&mut self.input_buffer[..num_input_samples]);
984
985 let status = match status {
986 ReadStatus::UnderflowOccurred { num_frames_read: _ } => {
987 StreamStatus::OUTPUT_UNDERFLOW
988 }
989 ReadStatus::OverflowCorrected {
990 num_frames_discarded: _,
991 } => StreamStatus::INPUT_OVERFLOW,
992 _ => StreamStatus::empty(),
993 };
994
995 (num_in_channels, status)
996 } else {
997 (0, StreamStatus::empty())
998 };
999
1000 if let Some(processor) = &mut self.processor {
1001 let mut output_stream_status = StreamStatus::empty();
1002
1003 if underflow {
1004 output_stream_status.insert(StreamStatus::OUTPUT_UNDERFLOW);
1005 }
1006
1007 processor.process_interleaved(
1008 &self.input_buffer[..frames * num_in_channels],
1009 output,
1010 BackendProcessInfo {
1011 num_in_channels,
1012 num_out_channels: self.num_out_channels,
1013 frames,
1014 process_timestamp,
1015 duration_since_stream_start,
1016 input_stream_status,
1017 output_stream_status,
1018 dropped_frames,
1019 },
1020 );
1021 } else {
1022 output.fill(0.0);
1023 return;
1024 }
1025 }
1026}
1027
1028enum CtxToStreamMsg {
1029 NewProcessor(FirewheelProcessor<CpalBackend>),
1030}
1031
1032#[derive(Debug, thiserror::Error)]
1034pub enum StreamStartError {
1035 #[error("The requested audio input device was not found: {0}")]
1036 InputDeviceNotFound(String),
1037 #[error("The requested audio output device was not found: {0}")]
1038 OutputDeviceNotFound(String),
1039 #[error("Could not get audio devices: {0}")]
1040 FailedToGetDevices(#[from] cpal::DevicesError),
1041 #[error("Failed to get default input output device")]
1042 DefaultInputDeviceNotFound,
1043 #[error("Failed to get default audio output device")]
1044 DefaultOutputDeviceNotFound,
1045 #[error("Failed to get audio device configs: {0}")]
1046 FailedToGetConfigs(#[from] cpal::SupportedStreamConfigsError),
1047 #[error("Failed to get audio device config: {0}")]
1048 FailedToGetConfig(#[from] cpal::DefaultStreamConfigError),
1049 #[error("Failed to build audio stream: {0}")]
1050 BuildStreamError(#[from] cpal::BuildStreamError),
1051 #[error("Failed to play audio stream: {0}")]
1052 PlayStreamError(#[from] cpal::PlayStreamError),
1053
1054 #[cfg(not(feature = "resample_inputs"))]
1055 #[error("Not able to use a samplerate of {0} for the input audio device")]
1056 CouldNotMatchSampleRate(u32),
1057}