1use crate::common::{assert_error_traits, ChannelCount, SampleRate};
22use crate::math::{nearest_multiple_of_two, nz};
23use crate::mixer::{mixer, Mixer};
24use crate::player::Player;
25use crate::{decoder, Source};
26use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
27use cpal::{BufferSize, Sample, SampleFormat, StreamConfig, I24};
28use std::fmt;
29use std::io::{Read, Seek};
30use std::marker::Sync;
31use std::num::NonZero;
32
33const HZ_44100: SampleRate = nz!(44_100);
34
35pub struct MixerDeviceSink {
59 config: DeviceSinkConfig,
60 mixer: Mixer,
61 log_on_drop: bool,
62 _stream: cpal::Stream,
63}
64
65impl MixerDeviceSink {
66 pub fn mixer(&self) -> &Mixer {
68 &self.mixer
69 }
70
71 pub fn config(&self) -> &DeviceSinkConfig {
73 &self.config
74 }
75
76 pub fn log_on_drop(&mut self, enabled: bool) {
79 self.log_on_drop = enabled;
80 }
81}
82
83impl Drop for MixerDeviceSink {
84 fn drop(&mut self) {
85 if self.log_on_drop && !std::thread::panicking() {
86 #[cfg(feature = "tracing")]
87 tracing::debug!("Dropping DeviceSink, audio playing through this sink will stop");
88 #[cfg(not(feature = "tracing"))]
89 eprintln!("Dropping DeviceSink, audio playing through this sink will stop, to prevent this message from appearing use tracing or call `.log_on_drop(false)` on this DeviceSink")
90 }
91 }
92}
93
94impl fmt::Debug for MixerDeviceSink {
95 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96 f.debug_struct("MixerDeviceSink")
97 .field("config", &self.config)
98 .finish_non_exhaustive()
99 }
100}
101
102#[derive(Copy, Clone, Debug)]
104pub struct DeviceSinkConfig {
105 pub(crate) channel_count: ChannelCount,
106 pub(crate) sample_rate: SampleRate,
107 pub(crate) buffer_size: BufferSize,
108 pub(crate) sample_format: SampleFormat,
109}
110
111impl Default for DeviceSinkConfig {
112 fn default() -> Self {
113 Self {
114 channel_count: nz!(2),
115 sample_rate: HZ_44100,
116 buffer_size: BufferSize::Default,
117 sample_format: SampleFormat::F32,
118 }
119 }
120}
121
122impl DeviceSinkConfig {
123 pub fn channel_count(&self) -> ChannelCount {
125 self.channel_count
126 }
127
128 pub fn sample_rate(&self) -> SampleRate {
130 self.sample_rate
131 }
132
133 pub fn buffer_size(&self) -> &BufferSize {
135 &self.buffer_size
136 }
137
138 pub fn sample_format(&self) -> SampleFormat {
140 self.sample_format
141 }
142}
143
144impl core::fmt::Debug for DeviceSinkBuilder {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 let device = if let Some(device) = &self.device {
147 "Some(".to_owned()
148 + &device
149 .description()
150 .ok()
151 .map_or("UnNamed".to_string(), |d| d.name().to_string())
152 + ")"
153 } else {
154 "None".to_owned()
155 };
156
157 f.debug_struct("DeviceSinkBuilder")
158 .field("device", &device)
159 .field("config", &self.config)
160 .finish()
161 }
162}
163
164fn default_error_callback(err: cpal::StreamError) {
165 #[cfg(feature = "tracing")]
166 tracing::error!("audio stream error: {err}");
167 #[cfg(not(feature = "tracing"))]
168 eprintln!("audio stream error: {err}");
169}
170
171pub struct DeviceSinkBuilder<E = fn(cpal::StreamError)>
178where
179 E: FnMut(cpal::StreamError) + Send + 'static,
180{
181 device: Option<cpal::Device>,
182 config: DeviceSinkConfig,
183 error_callback: E,
184}
185
186impl Default for DeviceSinkBuilder {
187 fn default() -> Self {
188 Self {
189 device: None,
190 config: DeviceSinkConfig::default(),
191 error_callback: default_error_callback,
192 }
193 }
194}
195
196impl DeviceSinkBuilder {
197 pub fn from_device(device: cpal::Device) -> Result<DeviceSinkBuilder, DeviceSinkError> {
199 let default_config = device
200 .default_output_config()
201 .map_err(DeviceSinkError::DefaultSinkConfigError)?;
202
203 let mut device = Self::default()
204 .with_device(device)
205 .with_supported_config(&default_config);
206
207 let sample_rate = device.config.sample_rate().get();
209 let safe_buffer_size = nearest_multiple_of_two(sample_rate / (1000 / 50));
210
211 device.config.buffer_size = match device.config.buffer_size {
215 BufferSize::Default => BufferSize::Fixed(safe_buffer_size),
216 fixed @ BufferSize::Fixed(_) => fixed,
217 };
218 Ok(device)
219 }
220
221 pub fn from_default_device() -> Result<DeviceSinkBuilder, DeviceSinkError> {
223 let default_device = cpal::default_host()
224 .default_output_device()
225 .ok_or(DeviceSinkError::NoDevice)?;
226 Self::from_device(default_device)
227 }
228
229 pub fn open_default_sink() -> Result<MixerDeviceSink, DeviceSinkError> {
234 Self::from_default_device()
235 .and_then(|x| x.open_stream())
236 .or_else(|original_err| {
237 let devices = match cpal::default_host().output_devices() {
238 Ok(devices) => devices,
239 Err(err) => {
240 #[cfg(feature = "tracing")]
241 tracing::error!("error getting list of output devices: {err}");
242 #[cfg(not(feature = "tracing"))]
243 eprintln!("error getting list of output devices: {err}");
244 return Err(original_err);
245 }
246 };
247 devices
248 .filter(|dev| {
249 dev.description()
250 .map(|desc| desc.driver().is_some_and(|driver| driver != "null"))
251 .unwrap_or(false)
252 })
253 .find_map(|d| {
254 Self::from_device(d)
255 .and_then(|x| x.open_sink_or_fallback())
256 .ok()
257 })
258 .ok_or(original_err)
259 })
260 }
261}
262
263impl<E> DeviceSinkBuilder<E>
264where
265 E: FnMut(cpal::StreamError) + Send + 'static,
266{
267 pub fn with_device(mut self, device: cpal::Device) -> DeviceSinkBuilder<E> {
271 self.device = Some(device);
272 self
273 }
274
275 pub fn with_channels(mut self, channel_count: ChannelCount) -> DeviceSinkBuilder<E> {
277 assert!(channel_count.get() > 0);
278 self.config.channel_count = channel_count;
279 self
280 }
281
282 pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> DeviceSinkBuilder<E> {
284 self.config.sample_rate = sample_rate;
285 self
286 }
287
288 pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> DeviceSinkBuilder<E> {
327 self.config.buffer_size = buffer_size;
328 self
329 }
330
331 pub fn with_sample_format(mut self, sample_format: SampleFormat) -> DeviceSinkBuilder<E> {
333 self.config.sample_format = sample_format;
334 self
335 }
336
337 pub fn with_supported_config(
340 mut self,
341 config: &cpal::SupportedStreamConfig,
342 ) -> DeviceSinkBuilder<E> {
343 self.config = DeviceSinkConfig {
344 channel_count: NonZero::new(config.channels())
345 .expect("no valid cpal config has zero channels"),
346 sample_rate: NonZero::new(config.sample_rate())
347 .expect("no valid cpal config has zero sample rate"),
348 sample_format: config.sample_format(),
349 ..Default::default()
350 };
351 self
352 }
353
354 pub fn with_config(mut self, config: &cpal::StreamConfig) -> DeviceSinkBuilder<E> {
356 self.config = DeviceSinkConfig {
357 channel_count: NonZero::new(config.channels)
358 .expect("no valid cpal config has zero channels"),
359 sample_rate: NonZero::new(config.sample_rate)
360 .expect("no valid cpal config has zero sample rate"),
361 buffer_size: config.buffer_size,
362 ..self.config
363 };
364 self
365 }
366
367 pub fn with_error_callback<F>(self, callback: F) -> DeviceSinkBuilder<F>
369 where
370 F: FnMut(cpal::StreamError) + Send + 'static,
371 {
372 DeviceSinkBuilder {
373 device: self.device,
374 config: self.config,
375 error_callback: callback,
376 }
377 }
378
379 pub fn open_stream(self) -> Result<MixerDeviceSink, DeviceSinkError> {
381 let device = self.device.as_ref().expect("No output device specified");
382
383 MixerDeviceSink::open(device, &self.config, self.error_callback)
384 }
385
386 pub fn open_sink_or_fallback(&self) -> Result<MixerDeviceSink, DeviceSinkError>
391 where
392 E: Clone,
393 {
394 let device = self.device.as_ref().expect("No output device specified");
395 let error_callback = &self.error_callback;
396
397 MixerDeviceSink::open(device, &self.config, error_callback.clone()).or_else(|err| {
398 for supported_config in supported_output_configs(device)? {
399 if let Ok(handle) = DeviceSinkBuilder::default()
400 .with_device(device.clone())
401 .with_supported_config(&supported_config)
402 .with_error_callback(error_callback.clone())
403 .open_stream()
404 {
405 return Ok(handle);
406 }
407 }
408 Err(err)
409 })
410 }
411}
412
413pub fn play<R>(mixer: &Mixer, input: R) -> Result<Player, PlayError>
416where
417 R: Read + Seek + Send + Sync + 'static,
418{
419 let input = decoder::Decoder::new(input)?;
420 let player = Player::connect_new(mixer);
421 player.append(input);
422 Ok(player)
423}
424
425impl From<&DeviceSinkConfig> for StreamConfig {
426 fn from(config: &DeviceSinkConfig) -> Self {
427 cpal::StreamConfig {
428 channels: config.channel_count.get() as cpal::ChannelCount,
429 sample_rate: config.sample_rate.get(),
430 buffer_size: config.buffer_size,
431 }
432 }
433}
434
435#[derive(Debug, thiserror::Error, Clone)]
437pub enum PlayError
438where
439 Self: Send + Sync + 'static,
440{
441 #[error("Failed to decode audio")]
443 DecoderError(
444 #[from]
445 #[source]
446 decoder::DecoderError,
447 ),
448 #[error("No output device")]
450 NoDevice,
451}
452assert_error_traits!(PlayError);
453
454#[derive(Debug, thiserror::Error)]
456pub enum DeviceSinkError {
457 #[error("Could not start playing the stream")]
460 PlayError(#[source] cpal::PlayStreamError),
461 #[error("Failed to get the config for the given device")]
464 DefaultSinkConfigError(#[source] cpal::DefaultStreamConfigError),
465 #[error("Error opening the stream with the OS")]
467 BuildError(#[source] cpal::BuildStreamError),
468 #[error("Could not list supported configs for the device. Maybe its disconnected?")]
471 SupportedConfigsError(#[source] cpal::SupportedStreamConfigsError),
472 #[error("Could not find any output device")]
474 NoDevice,
475 #[error("New cpal sample format that rodio does not yet support please open an issue if you run into this.")]
478 UnsupportedSampleFormat,
479}
480
481impl MixerDeviceSink {
482 fn validate_config(config: &DeviceSinkConfig) {
483 if let BufferSize::Fixed(sz) = config.buffer_size {
484 assert!(sz > 0, "fixed buffer size must be greater than zero");
485 }
486 }
487
488 pub(crate) fn open<E>(
489 device: &cpal::Device,
490 config: &DeviceSinkConfig,
491 error_callback: E,
492 ) -> Result<MixerDeviceSink, DeviceSinkError>
493 where
494 E: FnMut(cpal::StreamError) + Send + 'static,
495 {
496 Self::validate_config(config);
497 let (controller, source) = mixer(config.channel_count, config.sample_rate);
498 Self::init_stream(device, config, source, error_callback).and_then(|stream| {
499 stream.play().map_err(DeviceSinkError::PlayError)?;
500 Ok(Self {
501 _stream: stream,
502 mixer: controller,
503 config: *config,
504 log_on_drop: true,
505 })
506 })
507 }
508
509 fn init_stream<S, E>(
510 device: &cpal::Device,
511 config: &DeviceSinkConfig,
512 mut samples: S,
513 error_callback: E,
514 ) -> Result<cpal::Stream, DeviceSinkError>
515 where
516 S: Source + Send + 'static,
517 E: FnMut(cpal::StreamError) + Send + 'static,
518 {
519 let cpal_config = config.into();
520
521 macro_rules! build_output_streams {
522 ($($sample_format:tt, $generic:ty);+) => {
523 match config.sample_format {
524 $(
525 cpal::SampleFormat::$sample_format => device.build_output_stream::<$generic, _, _>(
526 &cpal_config,
527 move |data, _| {
528 data.iter_mut().for_each(|d| {
529 *d = samples
530 .next()
531 .map(Sample::from_sample)
532 .unwrap_or(<$generic>::EQUILIBRIUM)
533 })
534 },
535 error_callback,
536 None,
537 ),
538 )+
539 _ => return Err(DeviceSinkError::UnsupportedSampleFormat),
540 }
541 };
542 }
543
544 let result = build_output_streams!(
545 F32, f32;
546 F64, f64;
547 I8, i8;
548 I16, i16;
549 I24, I24;
550 I32, i32;
551 I64, i64;
552 U8, u8;
553 U16, u16;
554 U24, cpal::U24;
555 U32, u32;
556 U64, u64
557 );
558
559 result.map_err(DeviceSinkError::BuildError)
560 }
561}
562
563pub fn supported_output_configs(
565 device: &cpal::Device,
566) -> Result<impl Iterator<Item = cpal::SupportedStreamConfig>, DeviceSinkError> {
567 let mut supported: Vec<_> = device
568 .supported_output_configs()
569 .map_err(DeviceSinkError::SupportedConfigsError)?
570 .collect();
571 supported.sort_by(|a, b| b.cmp_default_heuristics(a));
572
573 Ok(supported.into_iter().flat_map(|sf| {
574 let max_rate = sf.max_sample_rate();
575 let min_rate = sf.min_sample_rate();
576 let mut formats = vec![sf.with_max_sample_rate()];
577 let preferred_rate = HZ_44100.get();
578 if preferred_rate < max_rate && preferred_rate > min_rate {
579 formats.push(sf.with_sample_rate(preferred_rate))
580 }
581 formats.push(sf.with_sample_rate(min_rate));
582 formats
583 }))
584}