Only allow for private construction of `SupportedStreamConfig`.

This should give the user a higher confidence that, if they have a
`SupportedStreamConfig` format type that it is actually supported.

Also updates the `raw` stream builder methods to take a `StreamConfig`
and `SampleFormat` as separate arguments for flexibility.

**Backends Updated**

- [x] null
- [x] alsa
- [ ] emscripten
- [ ] coreaudio
- [ ] wasapi
- [ ] asio
This commit is contained in:
mitchmindtree 2020-02-02 18:28:38 +01:00
parent 07b66f52f3
commit 476f6c4c2c
8 changed files with 92 additions and 58 deletions

View File

@ -10,7 +10,7 @@ fn main() -> Result<(), anyhow::Error> {
.expect("failed to find a default output device"); .expect("failed to find a default output device");
let config = device.default_output_config()?; let config = device.default_output_config()?;
match config.sample_format { match config.sample_format() {
cpal::SampleFormat::F32 => run::<f32>(&device, &config.into())?, cpal::SampleFormat::F32 => run::<f32>(&device, &config.into())?,
cpal::SampleFormat::I16 => run::<i16>(&device, &config.into())?, cpal::SampleFormat::I16 => run::<i16>(&device, &config.into())?,
cpal::SampleFormat::U16 => run::<u16>(&device, &config.into())?, cpal::SampleFormat::U16 => run::<u16>(&device, &config.into())?,

View File

@ -22,8 +22,8 @@ fn main() -> Result<(), anyhow::Error> {
println!(" {}. \"{}\"", device_index + 1, device.name()?); println!(" {}. \"{}\"", device_index + 1, device.name()?);
// Input configs // Input configs
if let Ok(fmt) = device.default_input_config() { if let Ok(conf) = device.default_input_config() {
println!(" Default input stream config:\n {:?}", fmt); println!(" Default input stream config:\n {:?}", conf);
} }
let mut input_configs = match device.supported_input_configs() { let mut input_configs = match device.supported_input_configs() {
Ok(f) => f.peekable(), Ok(f) => f.peekable(),
@ -45,8 +45,8 @@ fn main() -> Result<(), anyhow::Error> {
} }
// Output configs // Output configs
if let Ok(fmt) = device.default_output_config() { if let Ok(conf) = device.default_output_config() {
println!(" Default output stream config:\n {:?}", fmt); println!(" Default output stream config:\n {:?}", conf);
} }
let mut output_configs = match device.supported_output_configs() { let mut output_configs = match device.supported_output_configs() {
Ok(f) => f.peekable(), Ok(f) => f.peekable(),

View File

@ -40,7 +40,7 @@ fn main() -> Result<(), anyhow::Error> {
eprintln!("an error occurred on stream: {}", err); eprintln!("an error occurred on stream: {}", err);
}; };
let stream = match config.sample_format { let stream = match config.sample_format() {
cpal::SampleFormat::F32 => device.build_input_stream( cpal::SampleFormat::F32 => device.build_input_stream(
&config.into(), &config.into(),
move |data| write_input_data::<f32, f32>(data, &writer_2), move |data| write_input_data::<f32, f32>(data, &writer_2),
@ -78,10 +78,10 @@ fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {
fn wav_spec_from_config(config: &cpal::SupportedStreamConfig) -> hound::WavSpec { fn wav_spec_from_config(config: &cpal::SupportedStreamConfig) -> hound::WavSpec {
hound::WavSpec { hound::WavSpec {
channels: config.channels as _, channels: config.channels() as _,
sample_rate: config.sample_rate.0 as _, sample_rate: config.sample_rate().0 as _,
bits_per_sample: (config.sample_format.sample_size() * 8) as _, bits_per_sample: (config.sample_format().sample_size() * 8) as _,
sample_format: sample_format(config.sample_format), sample_format: sample_format(config.sample_format()),
} }
} }

View File

@ -4,7 +4,8 @@ extern crate libc;
use crate::{ use crate::{
BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate,
StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError,
}; };
use std::sync::Arc; use std::sync::Arc;
use std::thread::{self, JoinHandle}; use std::thread::{self, JoinHandle};
@ -82,7 +83,8 @@ impl DeviceTrait for Device {
fn build_input_stream_raw<D, E>( fn build_input_stream_raw<D, E>(
&self, &self,
conf: &SupportedStreamConfig, conf: &StreamConfig,
sample_format: SampleFormat,
data_callback: D, data_callback: D,
error_callback: E, error_callback: E,
) -> Result<Self::Stream, BuildStreamError> ) -> Result<Self::Stream, BuildStreamError>
@ -90,14 +92,16 @@ impl DeviceTrait for Device {
D: FnMut(&Data) + Send + 'static, D: FnMut(&Data) + Send + 'static,
E: FnMut(StreamError) + Send + 'static, E: FnMut(StreamError) + Send + 'static,
{ {
let stream_inner = self.build_stream_inner(conf, alsa::SND_PCM_STREAM_CAPTURE)?; let stream_inner =
self.build_stream_inner(conf, sample_format, alsa::SND_PCM_STREAM_CAPTURE)?;
let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback); let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback);
Ok(stream) Ok(stream)
} }
fn build_output_stream_raw<D, E>( fn build_output_stream_raw<D, E>(
&self, &self,
conf: &SupportedStreamConfig, conf: &StreamConfig,
sample_format: SampleFormat,
data_callback: D, data_callback: D,
error_callback: E, error_callback: E,
) -> Result<Self::Stream, BuildStreamError> ) -> Result<Self::Stream, BuildStreamError>
@ -105,7 +109,8 @@ impl DeviceTrait for Device {
D: FnMut(&mut Data) + Send + 'static, D: FnMut(&mut Data) + Send + 'static,
E: FnMut(StreamError) + Send + 'static, E: FnMut(StreamError) + Send + 'static,
{ {
let stream_inner = self.build_stream_inner(conf, alsa::SND_PCM_STREAM_PLAYBACK)?; let stream_inner =
self.build_stream_inner(conf, sample_format, alsa::SND_PCM_STREAM_PLAYBACK)?;
let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback); let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback);
Ok(stream) Ok(stream)
} }
@ -161,7 +166,8 @@ pub struct Device(String);
impl Device { impl Device {
fn build_stream_inner( fn build_stream_inner(
&self, &self,
conf: &SupportedStreamConfig, conf: &StreamConfig,
sample_format: SampleFormat,
stream_type: alsa::snd_pcm_stream_t, stream_type: alsa::snd_pcm_stream_t,
) -> Result<StreamInner, BuildStreamError> { ) -> Result<StreamInner, BuildStreamError> {
let name = ffi::CString::new(self.0.clone()).expect("unable to clone device"); let name = ffi::CString::new(self.0.clone()).expect("unable to clone device");
@ -185,7 +191,7 @@ impl Device {
}; };
let can_pause = unsafe { let can_pause = unsafe {
let hw_params = HwParams::alloc(); let hw_params = HwParams::alloc();
set_hw_params_from_format(handle, &hw_params, conf) set_hw_params_from_format(handle, &hw_params, conf, sample_format)
.map_err(|description| BackendSpecificError { description })?; .map_err(|description| BackendSpecificError { description })?;
alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1 alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1
@ -213,7 +219,7 @@ impl Device {
let stream_inner = StreamInner { let stream_inner = StreamInner {
channel: handle, channel: handle,
sample_format: conf.sample_format, sample_format,
num_descriptors, num_descriptors,
num_channels: conf.channels as u16, num_channels: conf.channels as u16,
buffer_len, buffer_len,
@ -904,7 +910,8 @@ fn get_available_samples(stream: &StreamInner) -> Result<usize, BackendSpecificE
unsafe fn set_hw_params_from_format( unsafe fn set_hw_params_from_format(
pcm_handle: *mut alsa::snd_pcm_t, pcm_handle: *mut alsa::snd_pcm_t,
hw_params: &HwParams, hw_params: &HwParams,
format: &SupportedStreamConfig, config: &StreamConfig,
sample_format: SampleFormat,
) -> Result<(), String> { ) -> Result<(), String> {
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) { if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) {
return Err(format!("errors on pcm handle: {}", e)); return Err(format!("errors on pcm handle: {}", e));
@ -918,13 +925,13 @@ unsafe fn set_hw_params_from_format(
} }
let sample_format = if cfg!(target_endian = "big") { let sample_format = if cfg!(target_endian = "big") {
match format.sample_format { match sample_format {
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_BE, SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_BE,
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_BE, SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_BE,
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_BE, SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_BE,
} }
} else { } else {
match format.sample_format { match sample_format {
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_LE, SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_LE,
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_LE, SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_LE,
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_LE, SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_LE,
@ -941,7 +948,7 @@ unsafe fn set_hw_params_from_format(
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate( if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(
pcm_handle, pcm_handle,
hw_params.0, hw_params.0,
format.sample_rate.0 as libc::c_uint, config.sample_rate.0 as libc::c_uint,
0, 0,
)) { )) {
return Err(format!("sample rate could not be set: {}", e)); return Err(format!("sample rate could not be set: {}", e));
@ -949,7 +956,7 @@ unsafe fn set_hw_params_from_format(
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels( if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(
pcm_handle, pcm_handle,
hw_params.0, hw_params.0,
format.channels as libc::c_uint, config.channels as libc::c_uint,
)) { )) {
return Err(format!("channel count could not be set: {}", e)); return Err(format!("channel count could not be set: {}", e));
} }
@ -973,7 +980,7 @@ unsafe fn set_hw_params_from_format(
unsafe fn set_sw_params_from_format( unsafe fn set_sw_params_from_format(
pcm_handle: *mut alsa::snd_pcm_t, pcm_handle: *mut alsa::snd_pcm_t,
format: &SupportedStreamConfig, config: &StreamConfig,
) -> Result<(usize, usize), String> { ) -> Result<(usize, usize), String> {
let mut sw_params = ptr::null_mut(); // TODO: RAII let mut sw_params = ptr::null_mut(); // TODO: RAII
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) { if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) {
@ -1009,8 +1016,8 @@ unsafe fn set_sw_params_from_format(
)) { )) {
return Err(format!("snd_pcm_sw_params_set_avail_min failed: {}", e)); return Err(format!("snd_pcm_sw_params_set_avail_min failed: {}", e));
} }
let buffer = buffer as usize * format.channels as usize; let buffer = buffer as usize * config.channels as usize;
let period = period as usize * format.channels as usize; let period = period as usize * config.channels as usize;
(buffer, period) (buffer, period)
}; };

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
PauseStreamError, PlayStreamError, StreamError, SupportedStreamConfig, PauseStreamError, PlayStreamError, SampleFormat, StreamConfig, StreamError,
SupportedStreamConfigRange, SupportedStreamConfigsError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
}; };
use traits::{DeviceTrait, HostTrait, StreamTrait}; use traits::{DeviceTrait, HostTrait, StreamTrait};
@ -68,7 +68,8 @@ impl DeviceTrait for Device {
fn build_input_stream_raw<D, E>( fn build_input_stream_raw<D, E>(
&self, &self,
_format: &SupportedStreamConfig, _config: &StreamConfig,
_sample_format: SampleFormat,
_data_callback: D, _data_callback: D,
_error_callback: E, _error_callback: E,
) -> Result<Self::Stream, BuildStreamError> ) -> Result<Self::Stream, BuildStreamError>
@ -82,7 +83,8 @@ impl DeviceTrait for Device {
/// Create an output stream. /// Create an output stream.
fn build_output_stream_raw<D, E>( fn build_output_stream_raw<D, E>(
&self, &self,
_format: &SupportedStreamConfig, _config: &StreamConfig,
_sample_format: SampleFormat,
_data_callback: D, _data_callback: D,
_error_callback: E, _error_callback: E,
) -> Result<Self::Stream, BuildStreamError> ) -> Result<Self::Stream, BuildStreamError>

View File

@ -94,7 +94,7 @@
//! # let device = host.default_output_device().unwrap(); //! # let device = host.default_output_device().unwrap();
//! # let supported_config = device.default_output_config().unwrap(); //! # let supported_config = device.default_output_config().unwrap();
//! let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err); //! let err_fn = |err| eprintln!("an error occurred on the output audio stream: {}", err);
//! let sample_format = supported_config.sample_format; //! let sample_format = supported_config.sample_format();
//! let config = supported_config.into(); //! let config = supported_config.into();
//! let stream = match sample_format { //! let stream = match sample_format {
//! SampleFormat::F32 => device.build_output_stream(&config, write_silence::<f32>, err_fn), //! SampleFormat::F32 => device.build_output_stream(&config, write_silence::<f32>, err_fn),
@ -117,10 +117,11 @@
//! # let host = cpal::default_host(); //! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap(); //! # let device = host.default_output_device().unwrap();
//! # let supported_config = device.default_output_config().unwrap(); //! # let supported_config = device.default_output_config().unwrap();
//! # let sample_format = supported_config.sample_format();
//! # let config = supported_config.into(); //! # let config = supported_config.into();
//! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let data_fn = move |_data: &mut cpal::Data| {};
//! # let err_fn = move |_err| {}; //! # let err_fn = move |_err| {};
//! # let stream = device.build_output_stream_raw(&config, data_fn, err_fn).unwrap(); //! # let stream = device.build_output_stream_raw(&config, sample_format, data_fn, err_fn).unwrap();
//! stream.play().unwrap(); //! stream.play().unwrap();
//! ``` //! ```
//! //!
@ -132,10 +133,11 @@
//! # let host = cpal::default_host(); //! # let host = cpal::default_host();
//! # let device = host.default_output_device().unwrap(); //! # let device = host.default_output_device().unwrap();
//! # let supported_config = device.default_output_config().unwrap(); //! # let supported_config = device.default_output_config().unwrap();
//! # let sample_format = supported_config.sample_format();
//! # let config = supported_config.into(); //! # let config = supported_config.into();
//! # let data_fn = move |_data: &mut cpal::Data| {}; //! # let data_fn = move |_data: &mut cpal::Data| {};
//! # let err_fn = move |_err| {}; //! # let err_fn = move |_err| {};
//! # let stream = device.build_output_stream_raw(&config, data_fn, err_fn).unwrap(); //! # let stream = device.build_output_stream_raw(&config, sample_format, data_fn, err_fn).unwrap();
//! stream.pause().unwrap(); //! stream.pause().unwrap();
//! ``` //! ```
@ -202,9 +204,9 @@ pub struct SupportedStreamConfigRange {
/// `SupportedStreamConfigRange` instance or one of the `Device::default_input/output_config` methods. /// `SupportedStreamConfigRange` instance or one of the `Device::default_input/output_config` methods.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct SupportedStreamConfig { pub struct SupportedStreamConfig {
pub channels: ChannelCount, channels: ChannelCount,
pub sample_rate: SampleRate, sample_rate: SampleRate,
pub sample_format: SampleFormat, sample_format: SampleFormat,
} }
/// A buffer of dynamically typed audio data, passed to raw stream callbacks. /// A buffer of dynamically typed audio data, passed to raw stream callbacks.
@ -219,12 +221,22 @@ pub struct Data {
} }
impl SupportedStreamConfig { impl SupportedStreamConfig {
/// Construct a `SupportedStreamConfig` from an existing `StreamConfig`. pub fn channels(&self) -> ChannelCount {
pub fn from_config(conf: &StreamConfig, fmt: SampleFormat) -> Self { self.channels
Self { }
channels: conf.channels,
sample_rate: conf.sample_rate, pub fn sample_rate(&self) -> SampleRate {
sample_format: fmt, self.sample_rate
}
pub fn sample_format(&self) -> SampleFormat {
self.sample_format
}
pub fn config(&self) -> StreamConfig {
StreamConfig {
channels: self.channels,
sample_rate: self.sample_rate,
} }
} }
} }
@ -406,12 +418,7 @@ impl SupportedStreamConfigRange {
impl From<SupportedStreamConfig> for StreamConfig { impl From<SupportedStreamConfig> for StreamConfig {
fn from(conf: SupportedStreamConfig) -> Self { fn from(conf: SupportedStreamConfig) -> Self {
let channels = conf.channels; conf.config()
let sample_rate = conf.sample_rate;
StreamConfig {
channels,
sample_rate,
}
} }
} }

View File

@ -257,7 +257,8 @@ macro_rules! impl_platform_host {
fn build_input_stream_raw<D, E>( fn build_input_stream_raw<D, E>(
&self, &self,
format: &crate::SupportedStreamConfig, config: &crate::StreamConfig,
sample_format: crate::SampleFormat,
data_callback: D, data_callback: D,
error_callback: E, error_callback: E,
) -> Result<Self::Stream, crate::BuildStreamError> ) -> Result<Self::Stream, crate::BuildStreamError>
@ -267,7 +268,13 @@ macro_rules! impl_platform_host {
{ {
match self.0 { match self.0 {
$( $(
DeviceInner::$HostVariant(ref d) => d.build_input_stream_raw(format, data_callback, error_callback) DeviceInner::$HostVariant(ref d) => d
.build_input_stream_raw(
config,
sample_format,
data_callback,
error_callback,
)
.map(StreamInner::$HostVariant) .map(StreamInner::$HostVariant)
.map(Stream::from), .map(Stream::from),
)* )*
@ -276,7 +283,8 @@ macro_rules! impl_platform_host {
fn build_output_stream_raw<D, E>( fn build_output_stream_raw<D, E>(
&self, &self,
format: &crate::SupportedStreamConfig, config: &crate::StreamConfig,
sample_format: crate::SampleFormat,
data_callback: D, data_callback: D,
error_callback: E, error_callback: E,
) -> Result<Self::Stream, crate::BuildStreamError> ) -> Result<Self::Stream, crate::BuildStreamError>
@ -286,7 +294,13 @@ macro_rules! impl_platform_host {
{ {
match self.0 { match self.0 {
$( $(
DeviceInner::$HostVariant(ref d) => d.build_output_stream_raw(format, data_callback, error_callback) DeviceInner::$HostVariant(ref d) => d
.build_output_stream_raw(
config,
sample_format,
data_callback,
error_callback,
)
.map(StreamInner::$HostVariant) .map(StreamInner::$HostVariant)
.map(Stream::from), .map(Stream::from),
)* )*

View File

@ -2,8 +2,8 @@
use { use {
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, InputDevices, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError, InputDevices,
OutputDevices, PauseStreamError, PlayStreamError, Sample, StreamConfig, StreamError, OutputDevices, PauseStreamError, PlayStreamError, Sample, SampleFormat, StreamConfig,
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
}; };
/// A **Host** provides access to the available audio devices on the system. /// A **Host** provides access to the available audio devices on the system.
@ -126,7 +126,8 @@ pub trait DeviceTrait {
E: FnMut(StreamError) + Send + 'static, E: FnMut(StreamError) + Send + 'static,
{ {
self.build_input_stream_raw( self.build_input_stream_raw(
&SupportedStreamConfig::from_config(config, T::FORMAT), config,
T::FORMAT,
move |data| { move |data| {
data_callback( data_callback(
data.as_slice() data.as_slice()
@ -150,7 +151,8 @@ pub trait DeviceTrait {
E: FnMut(StreamError) + Send + 'static, E: FnMut(StreamError) + Send + 'static,
{ {
self.build_output_stream_raw( self.build_output_stream_raw(
&SupportedStreamConfig::from_config(config, T::FORMAT), config,
T::FORMAT,
move |data| { move |data| {
data_callback( data_callback(
data.as_slice_mut() data.as_slice_mut()
@ -164,7 +166,8 @@ pub trait DeviceTrait {
/// Create a dynamically typed input stream. /// Create a dynamically typed input stream.
fn build_input_stream_raw<D, E>( fn build_input_stream_raw<D, E>(
&self, &self,
format: &SupportedStreamConfig, config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D, data_callback: D,
error_callback: E, error_callback: E,
) -> Result<Self::Stream, BuildStreamError> ) -> Result<Self::Stream, BuildStreamError>
@ -175,7 +178,8 @@ pub trait DeviceTrait {
/// Create a dynamically typed output stream. /// Create a dynamically typed output stream.
fn build_output_stream_raw<D, E>( fn build_output_stream_raw<D, E>(
&self, &self,
format: &SupportedStreamConfig, config: &StreamConfig,
sample_format: SampleFormat,
data_callback: D, data_callback: D,
error_callback: E, error_callback: E,
) -> Result<Self::Stream, BuildStreamError> ) -> Result<Self::Stream, BuildStreamError>