Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use flake
1 change: 1 addition & 0 deletions codebook.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
words = [
"cartney",
"cpal",
"decorrelated",
"decorrelating",
"gpdf",
Expand Down
2 changes: 1 addition & 1 deletion src/speakers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ mod builder;
mod config;

pub use builder::SpeakersBuilder;
pub use config::OutputConfig;
pub use config::{BufferSize, OutputConfig};

/// Error that can occur when we can not list the output devices
#[derive(Debug, thiserror::Error, Clone)]
Expand Down
34 changes: 21 additions & 13 deletions src/speakers/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use crate::{
FixedSource, MixerDeviceSink, SampleRate,
};

use super::BufferSize;

mod buffer_duration;

/// Error configuring or opening speakers output
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error, Clone)]
Expand Down Expand Up @@ -210,7 +214,7 @@ where
})?
.into();

// Lets try getting f32 output from the default config, as thats
// Lets try getting f32 output from the default config, as that's
// what rodio uses internally
let config = if self
.check_config(&default_config.with_f32_samples())
Expand All @@ -234,12 +238,12 @@ where
///
/// # Example
/// ```no_run
/// # use rodio::speakers::{SpeakersBuilder, OutputConfig};
/// # use rodio::speakers::{SpeakersBuilder, OutputConfig, BufferSize};
/// # use std::num::NonZero;
/// let config = OutputConfig {
/// sample_rate: NonZero::new(44_100).expect("44100 is not zero"),
/// channel_count: NonZero::new(2).expect("2 is not zero"),
/// buffer_size: cpal::BufferSize::Fixed(42_000),
/// buffer_size: BufferSize::FrameCount(4096),
/// sample_format: cpal::SampleFormat::U16,
/// };
/// let builder = SpeakersBuilder::new()
Expand Down Expand Up @@ -404,7 +408,7 @@ where
/// let builder = SpeakersBuilder::new()
/// .default_device()?
/// .default_config()?
/// // We want mono, if thats not possible give
/// // We want mono, if that's not possible give
/// // us the lowest channel count
/// .prefer_channel_counts([
/// 1.try_into().expect("not zero"),
Expand All @@ -424,9 +428,11 @@ where

/// Sets the buffer size for the output.
///
/// This has no impact on latency, though a too small buffer can lead to audio
/// artifacts if your program can not get samples out of the buffer before they
/// get overridden again.
/// Note: You probably want to use [`SpeakersBuilder::try_buffer_duration`]
///
/// Larger buffer sizes will increase the maximum latency. A too small
/// buffer can lead to audio artifacts if your program can not get samples
/// into the buffer at a consistent pace.
///
/// Normally the default output config will have this set up correctly.
///
Expand All @@ -441,10 +447,10 @@ where
/// ```
pub fn try_buffer_size(
&self,
buffer_size: u32,
frame_count: u32,
) -> Result<SpeakersBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
let mut new_config = self.config.expect("ConfigIsSet");
new_config.buffer_size = cpal::BufferSize::Fixed(buffer_size);
new_config.buffer_size = BufferSize::FrameCount(frame_count);
self.check_config(&new_config)?;

Ok(SpeakersBuilder {
Expand All @@ -459,6 +465,8 @@ where
/// See the docs of [`try_buffer_size`](SpeakersBuilder::try_buffer_size)
/// for more.
///
/// Note: You probably want to use [`SpeakersBuilder::prefer_buffer_durations`]
///
/// Try multiple buffer sizes, fall back to the default it non match. The
/// buffer sizes are in order of preference. If the first can be supported
/// the second will never be tried.
Expand Down Expand Up @@ -491,12 +499,12 @@ where
/// ```
pub fn prefer_buffer_sizes(
&self,
buffer_sizes: impl IntoIterator<Item = u32>,
frame_counts: impl IntoIterator<Item = u32>,
) -> SpeakersBuilder<DeviceIsSet, ConfigIsSet, E> {
let buffer_sizes = buffer_sizes.into_iter().take_while(|size| *size < 100_000);
let frame_counts = frame_counts.into_iter().take_while(|size| *size < 100_000);

self.set_preferred_if_supported(buffer_sizes, |config, size| {
config.buffer_size = cpal::BufferSize::Fixed(size)
self.set_preferred_if_supported(frame_counts, |config, frame_count| {
config.buffer_size = BufferSize::FrameCount(frame_count)
})
}
}
Expand Down
195 changes: 195 additions & 0 deletions src/speakers/builder/buffer_duration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
use std::marker::PhantomData;
use std::ops::{Range, RangeFrom, RangeTo};
use std::time::Duration;

use cpal::traits::DeviceTrait;

use crate::speakers::builder::Error;
use crate::speakers::BufferSize;

use super::SpeakersBuilder;
use super::{ConfigIsSet, DeviceIsSet};

impl<E> SpeakersBuilder<DeviceIsSet, ConfigIsSet, E>
where
E: FnMut(cpal::StreamError) + Send + Clone + 'static,
{
/// Sets the buffer duration for the output. The buffer size is calculated
/// from this and the sample rate and channel count when we build the
/// output. Prefer this to [`SpeakersBuilder::try_buffer_size`].
///
/// Long buffers will cause noticeable latency. A buffer that is too short
/// however leads to audio artifacts when your machine can not generate
/// a buffer of samples on time.
///
/// Normally the default output config will have this set up correctly. You
/// may want to tweak this to get lower latency or compensate for a
/// inconsistent audio pipeline.
///
/// # Example
/// ```no_run
/// # use rodio::speakers::SpeakersBuilder;
/// # use std::time::Duration;
/// let builder = SpeakersBuilder::new()
/// .default_device()?
/// .default_config()?
/// .try_buffer_duration(Duration::from_millis(20))?;
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn try_buffer_duration(
&self,
duration: Duration,
) -> Result<SpeakersBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
let mut new_config = self.config.expect("ConfigIsSet");
new_config.buffer_size = BufferSize::Duration(duration);
self.check_config(&new_config)?;

Ok(SpeakersBuilder {
device: self.device.clone(),
config: Some(new_config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}

/// See the docs of [`try_buffer_duration`](SpeakersBuilder::try_buffer_duration)
/// for more.
///
/// Try multiple buffer durations, fall back to the default if non match. The
/// buffer durations are in order of preference. If the first can be supported
/// the second will never be tried.
///
/// # Note
/// We will not try buffer durations longer then ten seconds to prevent this
/// from hanging too long on open ranges.
///
/// # Example
/// ```no_run
/// # use rodio::speakers::SpeakersBuilder;
/// # use std::time::Duration;
/// let builder = SpeakersBuilder::new()
/// .default_device()?
/// .default_config()?
/// .prefer_buffer_durations([
/// Duration::from_millis(10),
/// Duration::from_millis(50),
/// ]);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// Get the smallest buffer that holds more then 10 ms of audio.
/// ```no_run
/// # use rodio::speakers::SpeakersBuilder;
/// # use std::time::Duration;
/// let builder = SpeakersBuilder::new()
/// .default_device()?
/// .default_config()?
/// .prefer_buffer_durations(Duration::from_millis(10)..);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn prefer_buffer_durations(
&self,
durations: impl IntoBufferSizeRange,
) -> Result<SpeakersBuilder<DeviceIsSet, ConfigIsSet, E>, Error> {
let mut config = self.config.expect("ConfigIsSet");

let (mut found_min, mut found_max) = (None, None);
let (device, supported_configs) = self.device.as_ref().expect("DeviceIsSet");
for supported in supported_configs {
if config.channel_count.get() != supported.channels()
|| config.sample_format != supported.sample_format()
|| !(supported.min_sample_rate()..=supported.max_sample_rate())
.contains(&config.sample_rate.get())
{
continue;
}

if let cpal::SupportedBufferSize::Range { min, max } = supported.buffer_size() {
found_min = found_min.min(Some(*min));
found_max = found_max.max(Some(*max));
};
}

// Sometimes an OS reports a crazy maximum that does not actually works
// (we've spotted u32::MAX in the wild) but it will happily try and
// break. Thus limit the buffer size to something sensible.
let (min, max) = (
found_min.unwrap_or(1),
(found_max.unwrap_or(u32::MAX)).min(16384),
);
let min = Duration::from_secs_f64(min as f64 / config.sample_rate.get() as f64);
let max = Duration::from_secs_f64(max as f64 / config.sample_rate.get() as f64);
let supported = min..=max;

use BufferSizeRange as B;
let buffer_size = match &durations.into_buffer_size_range() {
B::RangeFrom(RangeFrom { start }) if supported.contains(start) => Some(start),
B::RangeFrom(RangeFrom { .. }) => Some(supported.start()),
B::RangeTo(RangeTo { end }) if supported.start() > end => None,
B::RangeTo(RangeTo { .. }) => Some(supported.start()),
B::Range(Range { start, .. }) if supported.contains(start) => Some(start),
B::Range(Range { end, .. }) if supported.contains(end) => Some(supported.start()),
B::Range(Range { .. }) => None,
B::Iter(durations) => durations.iter().find(|d| supported.contains(d)),
}
.copied()
.ok_or(Error::UnsupportedByDevice {
device_name: device
.description()
.map_or("unknown".to_string(), |d| d.name().to_string()),
})?;

config.buffer_size = BufferSize::Duration(buffer_size);
Ok(SpeakersBuilder {
device: self.device.clone(),
config: Some(config),
error_callback: self.error_callback.clone(),
device_set: PhantomData,
config_set: PhantomData,
})
}
}

pub enum BufferSizeRange {
RangeFrom(RangeFrom<Duration>),
RangeTo(RangeTo<Duration>),
Range(Range<Duration>),
Iter(Vec<Duration>),
}

pub trait IntoBufferSizeRange {
fn into_buffer_size_range(self) -> BufferSizeRange;
}

impl IntoBufferSizeRange for RangeFrom<Duration> {
fn into_buffer_size_range(self) -> BufferSizeRange {
BufferSizeRange::RangeFrom(self)
}
}
impl IntoBufferSizeRange for std::ops::Range<Duration> {
fn into_buffer_size_range(self) -> BufferSizeRange {
BufferSizeRange::Range(self)
}
}
impl IntoBufferSizeRange for std::ops::RangeTo<Duration> {
fn into_buffer_size_range(self) -> BufferSizeRange {
BufferSizeRange::RangeTo(self)
}
}
impl<const N: usize> IntoBufferSizeRange for [Duration; N] {
fn into_buffer_size_range(self) -> BufferSizeRange {
BufferSizeRange::Iter(self.to_vec())
}
}

impl IntoBufferSizeRange for Vec<Duration> {
fn into_buffer_size_range(self) -> BufferSizeRange {
BufferSizeRange::Iter(self)
}
}
impl IntoBufferSizeRange for Duration {
fn into_buffer_size_range(self) -> BufferSizeRange {
BufferSizeRange::Iter(vec![self])
}
}
Loading