rodio/
stream.rs

1//! Output audio via the OS via mixers or play directly
2//!
3//! This module provides a builder that's used to configure and open audio output. Once
4//! opened sources can be mixed into the output via `OutputStream::mixer`.
5//!
6//! There is also a convenience function `play` for using that output mixer to
7//! play a single sound.
8use crate::common::{ChannelCount, SampleRate};
9use crate::decoder;
10use crate::mixer::{mixer, Mixer, MixerSource};
11use crate::sink::Sink;
12use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
13use cpal::{BufferSize, Sample, SampleFormat, StreamConfig};
14use std::io::{Read, Seek};
15use std::marker::Sync;
16use std::{error, fmt};
17
18const HZ_44100: SampleRate = 44_100;
19
20/// `cpal::Stream` container. Use `mixer()` method to control output.
21///
22/// <div class="warning">When dropped playback will end, and the associated
23/// output stream will be disposed</div>
24///
25/// # Note
26/// On drop this will print a message to stderr or emit a log msg when tracing is
27/// enabled. Though we recommend you do not you can disable that print/log with:
28/// [`OutputStream::log_on_drop(false)`](OutputStream::log_on_drop).
29/// If the `OutputStream` is dropped because the program is panicking we do not print
30/// or log anything.
31///
32/// # Example
33/// ```no_run
34/// # use rodio::OutputStreamBuilder;
35/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
36/// let mut stream_handle = OutputStreamBuilder::open_default_stream()?;
37/// stream_handle.log_on_drop(false); // Not recommended during development
38/// println!("Output config: {:?}", stream_handle.config());
39/// let mixer = stream_handle.mixer();
40/// # Ok(())
41/// # }
42/// ```
43pub struct OutputStream {
44    config: OutputStreamConfig,
45    mixer: Mixer,
46    log_on_drop: bool,
47    _stream: cpal::Stream,
48}
49
50impl OutputStream {
51    /// Access the output stream's mixer.
52    pub fn mixer(&self) -> &Mixer {
53        &self.mixer
54    }
55
56    /// Access the output stream's config.
57    pub fn config(&self) -> &OutputStreamConfig {
58        &self.config
59    }
60
61    /// When [`OutputStream`] is dropped a message is logged to stderr or
62    /// emitted through tracing if the tracing feature is enabled.
63    pub fn log_on_drop(&mut self, enabled: bool) {
64        self.log_on_drop = enabled;
65    }
66}
67
68impl Drop for OutputStream {
69    fn drop(&mut self) {
70        if self.log_on_drop && !std::thread::panicking() {
71            #[cfg(feature = "tracing")]
72            tracing::debug!("Dropping OutputStream, audio playing through this stream will stop");
73            #[cfg(not(feature = "tracing"))]
74            eprintln!("Dropping OutputStream, audio playing through this stream will stop")
75        }
76    }
77}
78
79/// Describes the output stream's configuration
80#[derive(Copy, Clone, Debug)]
81pub struct OutputStreamConfig {
82    channel_count: ChannelCount,
83    sample_rate: SampleRate,
84    buffer_size: BufferSize,
85    sample_format: SampleFormat,
86}
87
88impl Default for OutputStreamConfig {
89    fn default() -> Self {
90        Self {
91            channel_count: 2,
92            sample_rate: HZ_44100,
93            buffer_size: BufferSize::Default,
94            sample_format: SampleFormat::F32,
95        }
96    }
97}
98
99impl OutputStreamConfig {
100    /// Access the output stream config's channel count.
101    pub fn channel_count(&self) -> ChannelCount {
102        self.channel_count
103    }
104
105    /// Access the output stream config's sample rate.
106    pub fn sample_rate(&self) -> SampleRate {
107        self.sample_rate
108    }
109
110    /// Access the output stream config's buffer size.
111    pub fn buffer_size(&self) -> &BufferSize {
112        &self.buffer_size
113    }
114
115    /// Access the output stream config's sample format.
116    pub fn sample_format(&self) -> SampleFormat {
117        self.sample_format
118    }
119}
120
121impl core::fmt::Debug for OutputStreamBuilder {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        let device = if let Some(device) = &self.device {
124            "Some(".to_owned() + device.name().as_deref().unwrap_or("UnNamed") + ")"
125        } else {
126            "None".to_owned()
127        };
128
129        f.debug_struct("OutputStreamBuilder")
130            .field("device", &device)
131            .field("config", &self.config)
132            .finish()
133    }
134}
135
136fn default_error_callback(err: cpal::StreamError) {
137    #[cfg(feature = "tracing")]
138    tracing::error!("audio stream error: {err}");
139    #[cfg(not(feature = "tracing"))]
140    eprintln!("audio stream error: {err}");
141}
142
143/// Convenience builder for audio output stream.
144/// It provides methods to configure several parameters of the audio output and opening default
145/// device. See examples for use-cases.
146///
147/// <div class="warning">When the OutputStream is dropped playback will end, and the associated
148/// output stream will be disposed</div>
149pub struct OutputStreamBuilder<E = fn(cpal::StreamError)>
150where
151    E: FnMut(cpal::StreamError) + Send + 'static,
152{
153    device: Option<cpal::Device>,
154    config: OutputStreamConfig,
155    error_callback: E,
156}
157
158impl Default for OutputStreamBuilder {
159    fn default() -> Self {
160        Self {
161            device: None,
162            config: OutputStreamConfig::default(),
163            error_callback: default_error_callback,
164        }
165    }
166}
167
168impl OutputStreamBuilder {
169    /// Sets output device and its default parameters.
170    pub fn from_device(device: cpal::Device) -> Result<OutputStreamBuilder, StreamError> {
171        let default_config = device
172            .default_output_config()
173            .map_err(StreamError::DefaultStreamConfigError)?;
174
175        Ok(Self::default()
176            .with_device(device)
177            .with_supported_config(&default_config))
178    }
179
180    /// Sets default output stream parameters for default output audio device.
181    pub fn from_default_device() -> Result<OutputStreamBuilder, StreamError> {
182        let default_device = cpal::default_host()
183            .default_output_device()
184            .ok_or(StreamError::NoDevice)?;
185        Self::from_device(default_device)
186    }
187
188    /// Try to open a new output stream for the default output device with its default configuration.
189    /// Failing that attempt to open output stream with alternative configuration and/or non default
190    /// output devices. Returns stream for first of the tried configurations that succeeds.
191    /// If all attempts fail return the initial error.
192    pub fn open_default_stream() -> Result<OutputStream, StreamError> {
193        Self::from_default_device()
194            .and_then(|x| x.open_stream())
195            .or_else(|original_err| {
196                let mut devices = match cpal::default_host().output_devices() {
197                    Ok(devices) => devices,
198                    Err(err) => {
199                        #[cfg(feature = "tracing")]
200                        tracing::error!("error getting list of output devices: {err}");
201                        #[cfg(not(feature = "tracing"))]
202                        eprintln!("error getting list of output devices: {err}");
203                        return Err(original_err);
204                    }
205                };
206                devices
207                    .find_map(|d| {
208                        Self::from_device(d)
209                            .and_then(|x| x.open_stream_or_fallback())
210                            .ok()
211                    })
212                    .ok_or(original_err)
213            })
214    }
215}
216
217impl<E> OutputStreamBuilder<E>
218where
219    E: FnMut(cpal::StreamError) + Send + 'static,
220{
221    /// Sets output audio device keeping all existing stream parameters intact.
222    /// This method is useful if you want to set other parameters yourself.
223    /// To also set parameters that are appropriate for the device use [Self::from_device()] instead.
224    pub fn with_device(mut self, device: cpal::Device) -> OutputStreamBuilder<E> {
225        self.device = Some(device);
226        self
227    }
228
229    /// Sets number of output stream's channels.
230    pub fn with_channels(mut self, channel_count: ChannelCount) -> OutputStreamBuilder<E> {
231        assert!(channel_count > 0);
232        self.config.channel_count = channel_count;
233        self
234    }
235
236    /// Sets output stream's sample rate.
237    pub fn with_sample_rate(mut self, sample_rate: SampleRate) -> OutputStreamBuilder<E> {
238        self.config.sample_rate = sample_rate;
239        self
240    }
241
242    /// Sets preferred output buffer size.
243    ///
244    /// To play sound without any glitches the audio card may never receive a
245    /// sample to late. Some samples might take longer to generate then
246    /// others. For example because:
247    ///  - The OS preempts the thread creating the samples. This happens more
248    ///    often if the computer is under high load.
249    ///  - The decoder needs to read more data from disk.
250    ///  - Rodio code takes longer to run for some samples then others
251    ///  - The OS can only send audio samples in groups to the DAC.
252    ///
253    /// The OS solves this by buffering samples. The larger that buffer the
254    /// smaller the impact of variable sample generation time. On the other
255    /// hand Rodio controls audio by changing the value of samples. We can not
256    /// change a sample already in the OS buffer. That means there is a
257    /// minimum delay (latency) of `<buffer size>/<sample_rate*channel_count>`
258    /// seconds before a change made through rodio takes effect.
259    ///
260    /// # Large vs Small buffer
261    /// - A larger buffer size results in high latency. Changes made trough
262    ///   Rodio (volume/skip/effects etc) takes longer before they can be heard.
263    /// - A small buffer might cause:
264    ///   - Higher CPU usage
265    ///   - Playback interruptions such as buffer underruns.
266    ///   - Rodio to log errors like: `alsa::poll() returned POLLERR`
267    ///
268    /// # Recommendation
269    /// If low latency is important to you consider offering the user a method
270    /// to find the minimum buffer size that works well on their system under
271    /// expected conditions. A good example of this approach can be seen in
272    /// [mumble](https://www.mumble.info/documentation/user/audio-settings/)
273    /// (specifically the *Output Delay* & *Jitter buffer*.
274    ///
275    /// These are some typical values that are a good starting point. They may also
276    /// break audio completely, it depends on the system.
277    /// - Low-latency (audio production, live monitoring): 512-1024
278    /// - General use (games, media playback): 1024-2048
279    /// - Stability-focused (background music, non-interactive): 2048-4096
280    pub fn with_buffer_size(mut self, buffer_size: cpal::BufferSize) -> OutputStreamBuilder<E> {
281        self.config.buffer_size = buffer_size;
282        self
283    }
284
285    /// Select scalar type that will carry a sample.
286    pub fn with_sample_format(mut self, sample_format: SampleFormat) -> OutputStreamBuilder<E> {
287        self.config.sample_format = sample_format;
288        self
289    }
290
291    /// Set available parameters from a CPAL supported config. You can get a list of
292    /// such configurations for an output device using [crate::stream::supported_output_configs()]
293    pub fn with_supported_config(
294        mut self,
295        config: &cpal::SupportedStreamConfig,
296    ) -> OutputStreamBuilder<E> {
297        self.config = OutputStreamConfig {
298            channel_count: config.channels() as ChannelCount,
299            sample_rate: config.sample_rate().0 as SampleRate,
300            sample_format: config.sample_format(),
301            ..Default::default()
302        };
303        self
304    }
305
306    /// Set all output stream parameters at once from CPAL stream config.
307    pub fn with_config(mut self, config: &cpal::StreamConfig) -> OutputStreamBuilder<E> {
308        self.config = OutputStreamConfig {
309            channel_count: config.channels as ChannelCount,
310            sample_rate: config.sample_rate.0 as SampleRate,
311            buffer_size: config.buffer_size,
312            ..self.config
313        };
314        self
315    }
316
317    /// Set a callback that will be called when an error occurs with the stream
318    pub fn with_error_callback<F>(self, callback: F) -> OutputStreamBuilder<F>
319    where
320        F: FnMut(cpal::StreamError) + Send + 'static,
321    {
322        OutputStreamBuilder {
323            device: self.device,
324            config: self.config,
325            error_callback: callback,
326        }
327    }
328
329    /// Open output stream using parameters configured so far.
330    pub fn open_stream(self) -> Result<OutputStream, StreamError> {
331        let device = self.device.as_ref().expect("output device specified");
332
333        OutputStream::open(device, &self.config, self.error_callback)
334    }
335
336    /// Try opening a new output stream with the builder's current stream configuration.
337    /// Failing that attempt to open stream with other available configurations
338    /// supported by the device.
339    /// If all attempts fail returns initial error.
340    pub fn open_stream_or_fallback(&self) -> Result<OutputStream, StreamError>
341    where
342        E: Clone,
343    {
344        let device = self.device.as_ref().expect("output device specified");
345        let error_callback = &self.error_callback;
346
347        OutputStream::open(device, &self.config, error_callback.clone()).or_else(|err| {
348            for supported_config in supported_output_configs(device)? {
349                if let Ok(handle) = OutputStreamBuilder::default()
350                    .with_device(device.clone())
351                    .with_supported_config(&supported_config)
352                    .with_error_callback(error_callback.clone())
353                    .open_stream()
354                {
355                    return Ok(handle);
356                }
357            }
358            Err(err)
359        })
360    }
361}
362
363/// A convenience function. Plays a sound once.
364/// Returns a `Sink` that can be used to control the sound.
365pub fn play<R>(mixer: &Mixer, input: R) -> Result<Sink, PlayError>
366where
367    R: Read + Seek + Send + Sync + 'static,
368{
369    let input = decoder::Decoder::new(input)?;
370    let sink = Sink::connect_new(mixer);
371    sink.append(input);
372    Ok(sink)
373}
374
375impl From<&OutputStreamConfig> for StreamConfig {
376    fn from(config: &OutputStreamConfig) -> Self {
377        cpal::StreamConfig {
378            channels: config.channel_count as cpal::ChannelCount,
379            sample_rate: cpal::SampleRate(config.sample_rate),
380            buffer_size: config.buffer_size,
381        }
382    }
383}
384
385/// An error occurred while attempting to play a sound.
386#[derive(Debug)]
387pub enum PlayError {
388    /// Attempting to decode the audio failed.
389    DecoderError(decoder::DecoderError),
390    /// The output device was lost.
391    NoDevice,
392}
393
394impl From<decoder::DecoderError> for PlayError {
395    fn from(err: decoder::DecoderError) -> Self {
396        Self::DecoderError(err)
397    }
398}
399
400impl fmt::Display for PlayError {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        match self {
403            Self::DecoderError(e) => e.fmt(f),
404            Self::NoDevice => write!(f, "NoDevice"),
405        }
406    }
407}
408
409impl error::Error for PlayError {
410    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
411        match self {
412            Self::DecoderError(e) => Some(e),
413            Self::NoDevice => None,
414        }
415    }
416}
417
418/// Errors that might occur when interfacing with audio output.
419#[derive(Debug)]
420pub enum StreamError {
421    /// Could not start playing the stream, see [cpal::PlayStreamError] for
422    /// details.
423    PlayStreamError(cpal::PlayStreamError),
424    /// Failed to get the stream config for the given device. See
425    /// [cpal::DefaultStreamConfigError] for details.
426    DefaultStreamConfigError(cpal::DefaultStreamConfigError),
427    /// Error opening stream with OS. See [cpal::BuildStreamError] for details.
428    BuildStreamError(cpal::BuildStreamError),
429    /// Could not list supported stream configs for the device. Maybe it
430    /// disconnected. For details see: [cpal::SupportedStreamConfigsError].
431    SupportedStreamConfigsError(cpal::SupportedStreamConfigsError),
432    /// Could not find any output device
433    NoDevice,
434    /// New cpal sample format that rodio does not yet support please open
435    /// an issue if you run into this.
436    UnsupportedSampleFormat,
437}
438
439impl fmt::Display for StreamError {
440    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441        match self {
442            Self::PlayStreamError(e) => e.fmt(f),
443            Self::BuildStreamError(e) => e.fmt(f),
444            Self::DefaultStreamConfigError(e) => e.fmt(f),
445            Self::SupportedStreamConfigsError(e) => e.fmt(f),
446            Self::NoDevice => write!(f, "NoDevice"),
447            Self::UnsupportedSampleFormat => write!(f, "UnsupportedSampleFormat"),
448        }
449    }
450}
451
452impl error::Error for StreamError {
453    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
454        match self {
455            Self::PlayStreamError(e) => Some(e),
456            Self::BuildStreamError(e) => Some(e),
457            Self::DefaultStreamConfigError(e) => Some(e),
458            Self::SupportedStreamConfigsError(e) => Some(e),
459            Self::NoDevice => None,
460            Self::UnsupportedSampleFormat => None,
461        }
462    }
463}
464
465impl OutputStream {
466    fn validate_config(config: &OutputStreamConfig) {
467        if let BufferSize::Fixed(sz) = config.buffer_size {
468            assert!(sz > 0, "fixed buffer size is greater than zero");
469        }
470        assert!(config.sample_rate > 0, "sample rate is greater than zero");
471        assert!(
472            config.channel_count > 0,
473            "channel number is greater than zero"
474        );
475    }
476
477    fn open<E>(
478        device: &cpal::Device,
479        config: &OutputStreamConfig,
480        error_callback: E,
481    ) -> Result<OutputStream, StreamError>
482    where
483        E: FnMut(cpal::StreamError) + Send + 'static,
484    {
485        Self::validate_config(config);
486        let (controller, source) = mixer(config.channel_count, config.sample_rate);
487        Self::init_stream(device, config, source, error_callback).and_then(|stream| {
488            stream.play().map_err(StreamError::PlayStreamError)?;
489            Ok(Self {
490                _stream: stream,
491                mixer: controller,
492                config: *config,
493                log_on_drop: true,
494            })
495        })
496    }
497
498    fn init_stream<E>(
499        device: &cpal::Device,
500        config: &OutputStreamConfig,
501        mut samples: MixerSource,
502        error_callback: E,
503    ) -> Result<cpal::Stream, StreamError>
504    where
505        E: FnMut(cpal::StreamError) + Send + 'static,
506    {
507        let sample_format = config.sample_format;
508        let config = config.into();
509
510        match sample_format {
511            cpal::SampleFormat::F32 => device.build_output_stream::<f32, _, _>(
512                &config,
513                move |data, _| {
514                    data.iter_mut()
515                        .for_each(|d| *d = samples.next().unwrap_or(0f32))
516                },
517                error_callback,
518                None,
519            ),
520            cpal::SampleFormat::F64 => device.build_output_stream::<f64, _, _>(
521                &config,
522                move |data, _| {
523                    data.iter_mut()
524                        .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0f64))
525                },
526                error_callback,
527                None,
528            ),
529            cpal::SampleFormat::I8 => device.build_output_stream::<i8, _, _>(
530                &config,
531                move |data, _| {
532                    data.iter_mut()
533                        .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i8))
534                },
535                error_callback,
536                None,
537            ),
538            cpal::SampleFormat::I16 => device.build_output_stream::<i16, _, _>(
539                &config,
540                move |data, _| {
541                    data.iter_mut()
542                        .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i16))
543                },
544                error_callback,
545                None,
546            ),
547            cpal::SampleFormat::I32 => device.build_output_stream::<i32, _, _>(
548                &config,
549                move |data, _| {
550                    data.iter_mut()
551                        .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i32))
552                },
553                error_callback,
554                None,
555            ),
556            cpal::SampleFormat::I64 => device.build_output_stream::<i64, _, _>(
557                &config,
558                move |data, _| {
559                    data.iter_mut()
560                        .for_each(|d| *d = samples.next().map(Sample::from_sample).unwrap_or(0i64))
561                },
562                error_callback,
563                None,
564            ),
565            cpal::SampleFormat::U8 => device.build_output_stream::<u8, _, _>(
566                &config,
567                move |data, _| {
568                    data.iter_mut().for_each(|d| {
569                        *d = samples
570                            .next()
571                            .map(Sample::from_sample)
572                            .unwrap_or(u8::MAX / 2)
573                    })
574                },
575                error_callback,
576                None,
577            ),
578            cpal::SampleFormat::U16 => device.build_output_stream::<u16, _, _>(
579                &config,
580                move |data, _| {
581                    data.iter_mut().for_each(|d| {
582                        *d = samples
583                            .next()
584                            .map(Sample::from_sample)
585                            .unwrap_or(u16::MAX / 2)
586                    })
587                },
588                error_callback,
589                None,
590            ),
591            cpal::SampleFormat::U32 => device.build_output_stream::<u32, _, _>(
592                &config,
593                move |data, _| {
594                    data.iter_mut().for_each(|d| {
595                        *d = samples
596                            .next()
597                            .map(Sample::from_sample)
598                            .unwrap_or(u32::MAX / 2)
599                    })
600                },
601                error_callback,
602                None,
603            ),
604            cpal::SampleFormat::U64 => device.build_output_stream::<u64, _, _>(
605                &config,
606                move |data, _| {
607                    data.iter_mut().for_each(|d| {
608                        *d = samples
609                            .next()
610                            .map(Sample::from_sample)
611                            .unwrap_or(u64::MAX / 2)
612                    })
613                },
614                error_callback,
615                None,
616            ),
617            _ => return Err(StreamError::UnsupportedSampleFormat),
618        }
619        .map_err(StreamError::BuildStreamError)
620    }
621}
622
623/// Return all formats supported by the device.
624pub fn supported_output_configs(
625    device: &cpal::Device,
626) -> Result<impl Iterator<Item = cpal::SupportedStreamConfig>, StreamError> {
627    let mut supported: Vec<_> = device
628        .supported_output_configs()
629        .map_err(StreamError::SupportedStreamConfigsError)?
630        .collect();
631    supported.sort_by(|a, b| b.cmp_default_heuristics(a));
632
633    Ok(supported.into_iter().flat_map(|sf| {
634        let max_rate = sf.max_sample_rate();
635        let min_rate = sf.min_sample_rate();
636        let mut formats = vec![sf.with_max_sample_rate()];
637        let preferred_rate = cpal::SampleRate(HZ_44100);
638        if preferred_rate < max_rate && preferred_rate > min_rate {
639            formats.push(sf.with_sample_rate(preferred_rate))
640        }
641        formats.push(sf.with_sample_rate(min_rate));
642        formats
643    }))
644}