From 1275db805bf6920e80e293254c1efe742c218061 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 7 Jun 2019 21:04:08 +0200 Subject: [PATCH 01/17] Remove std `Error` implementations in favour of using `failure` This will make adding new errors in the following commits towards better error handling a lot easier. --- Cargo.toml | 1 + src/lib.rs | 101 ++++++++++------------------------------------------- 2 files changed, 19 insertions(+), 83 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0b9dfc6..b6d42c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ license = "Apache-2.0" keywords = ["audio", "sound"] [dependencies] +failure = "0.1.5" lazy_static = "1.3" [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 932206d..badf130 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -114,10 +114,10 @@ #![recursion_limit = "512"] +extern crate failure; #[cfg(target_os = "windows")] #[macro_use] extern crate lazy_static; - // Extern crate declarations with `#[macro_use]` must unfortunately be at crate root. #[cfg(target_os = "emscripten")] #[macro_use] @@ -129,7 +129,7 @@ pub use samples_formats::{Sample, SampleFormat}; target_os = "macos", target_os = "ios", target_os = "emscripten")))] use null as cpal_impl; -use std::error::Error; +use failure::Fail; use std::fmt; use std::iter; use std::ops::{Deref, DerefMut}; @@ -280,39 +280,50 @@ pub struct SupportedInputFormats(cpal_impl::SupportedInputFormats); pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats); /// Error that can happen when enumerating the list of supported formats. -#[derive(Debug)] +#[derive(Debug, Fail)] pub enum FormatsEnumerationError { /// The device no longer exists. This can happen if the device is disconnected while the /// program is running. + #[fail(display = "The requested device is no longer available. For example, it has been unplugged.")] DeviceNotAvailable, /// We called something the C-Layer did not understand + #[fail(display = "Invalid argument passed to the backend. For example, this happens when trying to read capture capabilities when the device does not support it.")] InvalidArgument, /// The C-Layer returned an error we don't know about + #[fail(display = "An unknown error in the backend occured.")] Unknown } /// May occur when attempting to request the default input or output stream format from a `Device`. -#[derive(Debug)] +#[derive(Debug, Fail)] pub enum DefaultFormatError { /// The device no longer exists. This can happen if the device is disconnected while the /// program is running. + #[fail(display = "The requested device is no longer available. For example, it has been unplugged.")] DeviceNotAvailable, /// Returned if e.g. the default input format was requested on an output-only audio device. + #[fail(display = "The requested stream type is not supported by the device.")] StreamTypeNotSupported, } /// Error that can happen when creating a `Voice`. -#[derive(Debug)] +#[derive(Debug, Fail)] pub enum CreationError { /// The device no longer exists. This can happen if the device is disconnected while the /// program is running. + #[fail(display = "The requested device is no longer available. For example, it has been unplugged.")] DeviceNotAvailable, /// The required format is not supported. + #[fail(display = "The requested stream format is not supported by the device.")] FormatNotSupported, - /// An ALSA device function was called with a feature it does not support - /// (trying to use capture capabilities on an output only format yields this) + /// We called something the C-Layer did not understand + /// + /// On ALSA device functions called with a feature they do not support will yield this. E.g. + /// Trying to use capture capabilities on an output only format yields this. + #[fail(display = "The requested device does not support this capability (invalid argument)")] InvalidArgument, /// The C-Layer returned an error we don't know about + #[fail(display = "An unknown error in the Backend occured")] Unknown, } @@ -693,82 +704,6 @@ impl Iterator for SupportedOutputFormats { } } -impl fmt::Display for FormatsEnumerationError { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(fmt, "{}", self.description()) - } -} - -impl Error for FormatsEnumerationError { - #[inline] - fn description(&self) -> &str { - match self { - &FormatsEnumerationError::DeviceNotAvailable => { - "The requested device is no longer available (for example, it has been unplugged)." - }, - &FormatsEnumerationError::InvalidArgument => { - "Invalid argument passed to the Backend (This happens when trying to read for example capture capabilities but the device does not support it -> dmix on Linux)" - }, - &FormatsEnumerationError::Unknown => { - "An unknown error in the Backend occured" - }, - } - } -} - -impl fmt::Display for CreationError { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(fmt, "{}", self.description()) - } -} - -impl Error for CreationError { - #[inline] - fn description(&self) -> &str { - match self { - &CreationError::DeviceNotAvailable => { - "The requested device is no longer available (for example, it has been unplugged)." - }, - - &CreationError::FormatNotSupported => { - "The requested samples format is not supported by the device." - }, - - &CreationError::InvalidArgument => { - "The requested device does not support this capability (invalid argument)" - } - - &CreationError::Unknown => { - "An unknown error in the Backend occured" - }, - } - } -} - -impl fmt::Display for DefaultFormatError { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(fmt, "{}", self.description()) - } -} - -impl Error for DefaultFormatError { - #[inline] - fn description(&self) -> &str { - match self { - &DefaultFormatError::DeviceNotAvailable => { - CreationError::DeviceNotAvailable.description() - }, - - &DefaultFormatError::StreamTypeNotSupported => { - "The requested stream type is not supported by the device." - }, - } - } -} - // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // From 0f27c1e0bb04570f454c461c91a46447a77eef9a Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 20 Jun 2019 21:16:39 +0200 Subject: [PATCH 02/17] Rename `FormatsEnumerationError` to `SupportedFormatsError` This more tightly associates the error with the device method on which this might occur. --- src/alsa/enumerate.rs | 12 ++++++------ src/alsa/mod.rs | 22 +++++++++++----------- src/coreaudio/mod.rs | 8 ++++---- src/emscripten/mod.rs | 6 +++--- src/lib.rs | 6 +++--- src/null/mod.rs | 8 ++++---- src/wasapi/device.rs | 16 ++++++++-------- 7 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/alsa/enumerate.rs b/src/alsa/enumerate.rs index fe5cfee..3f18f57 100644 --- a/src/alsa/enumerate.rs +++ b/src/alsa/enumerate.rs @@ -30,13 +30,13 @@ impl Drop for Devices { impl Default for Devices { fn default() -> Devices { unsafe { - let mut hints = mem::uninitialized(); - // TODO: check in which situation this can fail - check_errors(alsa::snd_device_name_hint(-1, b"pcm\0".as_ptr() as *const _, &mut hints)) - .unwrap(); - + // TODO: check in which situation this can fail. + let card = -1; // -1 means all cards. + let iface = b"pcm\0"; // Interface identification. + let mut hints = mem::uninitialized(); // Array of device name hints. + let res = alsa::snd_device_name_hint(card, iface.as_ptr() as *const _, &mut hints); + check_errors(res).unwrap(); let hints = hints as *const *const u8; - Devices { global_list: hints, next_str: hints, diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index 2131aaf..22b93c8 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -7,7 +7,7 @@ use ChannelCount; use CreationError; use DefaultFormatError; use Format; -use FormatsEnumerationError; +use SupportedFormatsError; use SampleFormat; use SampleRate; use StreamData; @@ -80,7 +80,7 @@ impl Device { unsafe fn supported_formats( &self, stream_t: alsa::snd_pcm_stream_t, - ) -> Result, FormatsEnumerationError> + ) -> Result, SupportedFormatsError> { let mut handle = mem::uninitialized(); let device_name = ffi::CString::new(&self.0[..]).expect("Unable to get device name"); @@ -92,10 +92,10 @@ impl Device { alsa::SND_PCM_NONBLOCK, ) { -2 | - -16 /* determined empirically */ => return Err(FormatsEnumerationError::DeviceNotAvailable), - -22 => return Err(FormatsEnumerationError::InvalidArgument), + -16 /* determined empirically */ => return Err(SupportedFormatsError::DeviceNotAvailable), + -22 => return Err(SupportedFormatsError::InvalidArgument), e => if check_errors(e).is_err() { - return Err(FormatsEnumerationError::Unknown) + return Err(SupportedFormatsError::Unknown) } } @@ -251,13 +251,13 @@ impl Device { Ok(output.into_iter()) } - pub fn supported_input_formats(&self) -> Result { + pub fn supported_input_formats(&self) -> Result { unsafe { self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE) } } - pub fn supported_output_formats(&self) -> Result { + pub fn supported_output_formats(&self) -> Result { unsafe { self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK) } @@ -272,15 +272,15 @@ impl Device { { let mut formats: Vec<_> = unsafe { match self.supported_formats(stream_t) { - Err(FormatsEnumerationError::DeviceNotAvailable) => { + Err(SupportedFormatsError::DeviceNotAvailable) => { return Err(DefaultFormatError::DeviceNotAvailable); }, - Err(FormatsEnumerationError::InvalidArgument) => { + Err(SupportedFormatsError::InvalidArgument) => { // this happens sometimes when querying for input and output capabilities but // the device supports only one return Err(DefaultFormatError::StreamTypeNotSupported); } - Err(FormatsEnumerationError::Unknown) => { + Err(SupportedFormatsError::Unknown) => { return Err(DefaultFormatError::DeviceNotAvailable); } Ok(fmts) => fmts.collect(), @@ -940,4 +940,4 @@ unsafe fn cast_input_buffer(v: &[u8]) -> &[T] { unsafe fn cast_output_buffer(v: &mut [u8]) -> &mut [T] { debug_assert!(v.len() % std::mem::size_of::() == 0); std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::()) -} \ No newline at end of file +} diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index c363464..d87321e 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -5,7 +5,7 @@ use ChannelCount; use CreationError; use DefaultFormatError; use Format; -use FormatsEnumerationError; +use SupportedFormatsError; use Sample; use SampleFormat; use SampleRate; @@ -108,7 +108,7 @@ impl Device { fn supported_formats( &self, scope: AudioObjectPropertyScope, - ) -> Result + ) -> Result { let mut property_address = AudioObjectPropertyAddress { mSelector: kAudioDevicePropertyStreamConfiguration, @@ -212,11 +212,11 @@ impl Device { } } - pub fn supported_input_formats(&self) -> Result { + pub fn supported_input_formats(&self) -> Result { self.supported_formats(kAudioObjectPropertyScopeInput) } - pub fn supported_output_formats(&self) -> Result { + pub fn supported_output_formats(&self) -> Result { self.supported_formats(kAudioObjectPropertyScopeOutput) } diff --git a/src/emscripten/mod.rs b/src/emscripten/mod.rs index b95a5df..054a473 100644 --- a/src/emscripten/mod.rs +++ b/src/emscripten/mod.rs @@ -11,7 +11,7 @@ use stdweb::web::set_timeout; use CreationError; use DefaultFormatError; use Format; -use FormatsEnumerationError; +use SupportedFormatsError; use StreamData; use SupportedFormat; use UnknownTypeOutputBuffer; @@ -226,12 +226,12 @@ impl Device { } #[inline] - pub fn supported_input_formats(&self) -> Result { + pub fn supported_input_formats(&self) -> Result { unimplemented!(); } #[inline] - pub fn supported_output_formats(&self) -> Result { + pub fn supported_output_formats(&self) -> Result { // TODO: right now cpal's API doesn't allow flexibility here // "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if // this ever becomes more flexible, don't forget to change that diff --git a/src/lib.rs b/src/lib.rs index badf130..003a623 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -281,7 +281,7 @@ pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats); /// Error that can happen when enumerating the list of supported formats. #[derive(Debug, Fail)] -pub enum FormatsEnumerationError { +pub enum SupportedFormatsError { /// The device no longer exists. This can happen if the device is disconnected while the /// program is running. #[fail(display = "The requested device is no longer available. For example, it has been unplugged.")] @@ -386,7 +386,7 @@ impl Device { /// /// Can return an error if the device is no longer valid (eg. it has been disconnected). #[inline] - pub fn supported_input_formats(&self) -> Result { + pub fn supported_input_formats(&self) -> Result { Ok(SupportedInputFormats(self.0.supported_input_formats()?)) } @@ -394,7 +394,7 @@ impl Device { /// /// Can return an error if the device is no longer valid (eg. it has been disconnected). #[inline] - pub fn supported_output_formats(&self) -> Result { + pub fn supported_output_formats(&self) -> Result { Ok(SupportedOutputFormats(self.0.supported_output_formats()?)) } diff --git a/src/null/mod.rs b/src/null/mod.rs index 244f086..bec555f 100644 --- a/src/null/mod.rs +++ b/src/null/mod.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use CreationError; use DefaultFormatError; use Format; -use FormatsEnumerationError; +use SupportedFormatsError; use StreamData; use SupportedFormat; @@ -80,12 +80,12 @@ pub struct Device; impl Device { #[inline] - pub fn supported_input_formats(&self) -> Result { + pub fn supported_input_formats(&self) -> Result { unimplemented!() } #[inline] - pub fn supported_output_formats(&self) -> Result { + pub fn supported_output_formats(&self) -> Result { unimplemented!() } @@ -132,4 +132,4 @@ pub struct InputBuffer<'a, T: 'a> { pub struct OutputBuffer<'a, T: 'a> { marker: PhantomData<&'a mut T>, -} \ No newline at end of file +} diff --git a/src/wasapi/device.rs b/src/wasapi/device.rs index 9913f2a..49a852b 100644 --- a/src/wasapi/device.rs +++ b/src/wasapi/device.rs @@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex, MutexGuard}; use DefaultFormatError; use Format; -use FormatsEnumerationError; +use SupportedFormatsError; use SampleFormat; use SampleRate; use SupportedFormat; @@ -165,7 +165,7 @@ unsafe fn data_flow_from_immendpoint(endpoint: *const IMMEndpoint) -> EDataFlow pub unsafe fn is_format_supported( client: *const IAudioClient, waveformatex_ptr: *const mmreg::WAVEFORMATEX, -) -> Result +) -> Result { @@ -213,7 +213,7 @@ pub unsafe fn is_format_supported( // has been found, but not an exact match) so we also treat this as unsupported. match (result, check_result(result)) { (_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - return Err(FormatsEnumerationError::DeviceNotAvailable); + return Err(SupportedFormatsError::DeviceNotAvailable); }, (_, Err(_)) => { Ok(false) @@ -386,14 +386,14 @@ impl Device { // number of channels seems to be supported. Any more or less returns an invalid // parameter error. Thus we just assume that the default number of channels is the only // number supported. - fn supported_formats(&self) -> Result { + fn supported_formats(&self) -> Result { // initializing COM because we call `CoTaskMemFree` to release the format. com::com_initialized(); // Retrieve the `IAudioClient`. let lock = match self.ensure_future_audio_client() { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(FormatsEnumerationError::DeviceNotAvailable), + return Err(SupportedFormatsError::DeviceNotAvailable), e => e.unwrap(), }; let client = lock.unwrap().0; @@ -403,7 +403,7 @@ impl Device { let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized()); match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { - return Err(FormatsEnumerationError::DeviceNotAvailable); + return Err(SupportedFormatsError::DeviceNotAvailable); }, Err(e) => panic!("{:?}", e), Ok(()) => (), @@ -462,7 +462,7 @@ impl Device { } } - pub fn supported_input_formats(&self) -> Result { + pub fn supported_input_formats(&self) -> Result { if self.data_flow() == eCapture { self.supported_formats() // If it's an output device, assume no input formats. @@ -471,7 +471,7 @@ impl Device { } } - pub fn supported_output_formats(&self) -> Result { + pub fn supported_output_formats(&self) -> Result { if self.data_flow() == eRender { self.supported_formats() // If it's an input device, assume no output formats. From cf84ab906f59a92fe5d214734cb347181f60aae9 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 20 Jun 2019 21:31:15 +0200 Subject: [PATCH 03/17] Rename `CreationError` to `BuildStreamError` For clarity and to tie the name more closesly to the methods within from which it may be returned. --- src/alsa/mod.rs | 18 +++++++++--------- src/coreaudio/mod.rs | 16 ++++++++-------- src/emscripten/mod.rs | 6 +++--- src/lib.rs | 10 +++++----- src/null/mod.rs | 10 +++++----- src/wasapi/device.rs | 4 ++-- src/wasapi/stream.rs | 42 +++++++++++++++++++++--------------------- 7 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index 22b93c8..37bdc4d 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -4,7 +4,7 @@ extern crate libc; pub use self::enumerate::{Devices, default_input_device, default_output_device}; use ChannelCount; -use CreationError; +use BuildStreamError; use DefaultFormatError; use Format; use SupportedFormatsError; @@ -644,7 +644,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { unsafe { let name = ffi::CString::new(device.0.clone()).expect("unable to clone device"); @@ -656,10 +656,10 @@ impl EventLoop { alsa::SND_PCM_STREAM_CAPTURE, alsa::SND_PCM_NONBLOCK, ) { - -16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable), - -22 => return Err(CreationError::InvalidArgument), + -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), + -22 => return Err(BuildStreamError::InvalidArgument), e => if check_errors(e).is_err() { - return Err(CreationError::Unknown); + return Err(BuildStreamError::Unknown); } } let hw_params = HwParams::alloc(); @@ -708,7 +708,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { unsafe { let name = ffi::CString::new(device.0.clone()).expect("unable to clone device"); @@ -720,10 +720,10 @@ impl EventLoop { alsa::SND_PCM_STREAM_PLAYBACK, alsa::SND_PCM_NONBLOCK, ) { - -16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable), - -22 => return Err(CreationError::InvalidArgument), + -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), + -22 => return Err(BuildStreamError::InvalidArgument), e => if check_errors(e).is_err() { - return Err(CreationError::Unknown); + return Err(BuildStreamError::Unknown); } } let hw_params = HwParams::alloc(); diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index d87321e..3e8b498 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -2,7 +2,7 @@ extern crate coreaudio; extern crate core_foundation_sys; use ChannelCount; -use CreationError; +use BuildStreamError; use DefaultFormatError; use Format; use SupportedFormatsError; @@ -338,15 +338,15 @@ struct StreamInner { } // TODO need stronger error identification -impl From for CreationError { - fn from(err: coreaudio::Error) -> CreationError { +impl From for BuildStreamError { + fn from(err: coreaudio::Error) -> BuildStreamError { match err { coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat | coreaudio::Error::NoKnownSubtype | coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) | coreaudio::Error::AudioCodec(_) | - coreaudio::Error::AudioFormat(_) => CreationError::FormatNotSupported, - _ => CreationError::DeviceNotAvailable, + coreaudio::Error::AudioFormat(_) => BuildStreamError::FormatNotSupported, + _ => BuildStreamError::DeviceNotAvailable, } } } @@ -483,7 +483,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { // The scope and element for working with a device's input stream. let scope = Scope::Output; @@ -555,7 +555,7 @@ impl EventLoop { .iter() .position(|r| r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate); let range_index = match maybe_index { - None => return Err(CreationError::FormatNotSupported), + None => return Err(BuildStreamError::FormatNotSupported), Some(i) => i, }; @@ -700,7 +700,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { let mut audio_unit = audio_unit_from_device(device, false)?; diff --git a/src/emscripten/mod.rs b/src/emscripten/mod.rs index 054a473..d20dfb9 100644 --- a/src/emscripten/mod.rs +++ b/src/emscripten/mod.rs @@ -8,7 +8,7 @@ use stdweb::unstable::TryInto; use stdweb::web::TypedArray; use stdweb::web::set_timeout; -use CreationError; +use BuildStreamError; use DefaultFormatError; use Format; use SupportedFormatsError; @@ -118,12 +118,12 @@ impl EventLoop { } #[inline] - pub fn build_input_stream(&self, _: &Device, _format: &Format) -> Result { + pub fn build_input_stream(&self, _: &Device, _format: &Format) -> Result { unimplemented!(); } #[inline] - pub fn build_output_stream(&self, _: &Device, _format: &Format) -> Result { + pub fn build_output_stream(&self, _: &Device, _format: &Format) -> Result { let stream = js!(return new AudioContext()).into_reference().unwrap(); let mut streams = self.streams.lock().unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 003a623..4e47939 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,7 +160,7 @@ mod cpal_impl; #[derive(Clone, PartialEq, Eq)] pub struct Device(cpal_impl::Device); -/// Collection of voices managed together. +/// Collection of streams managed together. /// /// Created with the [`new`](struct.EventLoop.html#method.new) method. pub struct EventLoop(cpal_impl::EventLoop); @@ -306,9 +306,9 @@ pub enum DefaultFormatError { StreamTypeNotSupported, } -/// Error that can happen when creating a `Voice`. +/// Error that can happen when creating a `Stream`. #[derive(Debug, Fail)] -pub enum CreationError { +pub enum BuildStreamError { /// The device no longer exists. This can happen if the device is disconnected while the /// program is running. #[fail(display = "The requested device is no longer available. For example, it has been unplugged.")] @@ -435,7 +435,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { self.0.build_input_stream(&device.0, format).map(StreamId) } @@ -451,7 +451,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { self.0.build_output_stream(&device.0, format).map(StreamId) } diff --git a/src/null/mod.rs b/src/null/mod.rs index bec555f..0596b2e 100644 --- a/src/null/mod.rs +++ b/src/null/mod.rs @@ -2,7 +2,7 @@ use std::marker::PhantomData; -use CreationError; +use BuildStreamError; use DefaultFormatError; use Format; use SupportedFormatsError; @@ -25,13 +25,13 @@ impl EventLoop { } #[inline] - pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result { - Err(CreationError::DeviceNotAvailable) + pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result { + Err(BuildStreamError::DeviceNotAvailable) } #[inline] - pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result { - Err(CreationError::DeviceNotAvailable) + pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result { + Err(BuildStreamError::DeviceNotAvailable) } #[inline] diff --git a/src/wasapi/device.rs b/src/wasapi/device.rs index 49a852b..a565adc 100644 --- a/src/wasapi/device.rs +++ b/src/wasapi/device.rs @@ -187,7 +187,7 @@ pub unsafe fn is_format_supported( (_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); - return Err(CreationError::DeviceNotAvailable); + return Err(BuildStreamError::DeviceNotAvailable); }, (_, Err(e)) => { (*audio_client).Release(); @@ -195,7 +195,7 @@ pub unsafe fn is_format_supported( }, (winerror::S_FALSE, _) => { (*audio_client).Release(); - return Err(CreationError::FormatNotSupported); + return Err(BuildStreamError::FormatNotSupported); }, (_, Ok(())) => (), }; diff --git a/src/wasapi/stream.rs b/src/wasapi/stream.rs index 084c10a..3a140b5 100644 --- a/src/wasapi/stream.rs +++ b/src/wasapi/stream.rs @@ -20,7 +20,7 @@ use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; -use CreationError; +use BuildStreamError; use Format; use SampleFormat; use StreamData; @@ -114,7 +114,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { unsafe { // Making sure that COM is initialized. @@ -124,20 +124,20 @@ impl EventLoop { // Obtaining a `IAudioClient`. let audio_client = match device.build_audioclient() { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(CreationError::DeviceNotAvailable), + return Err(BuildStreamError::DeviceNotAvailable), e => e.unwrap(), }; // Computing the format and initializing the device. let waveformatex = { let format_attempt = format_to_waveformatextensible(format) - .ok_or(CreationError::FormatNotSupported)?; + .ok_or(BuildStreamError::FormatNotSupported)?; let share_mode = AUDCLNT_SHAREMODE_SHARED; // Ensure the format is supported. match super::device::is_format_supported(audio_client, &format_attempt.Format) { - Ok(false) => return Err(CreationError::FormatNotSupported), - Err(_) => return Err(CreationError::DeviceNotAvailable), + Ok(false) => return Err(BuildStreamError::FormatNotSupported), + Err(_) => return Err(BuildStreamError::DeviceNotAvailable), _ => (), } @@ -154,7 +154,7 @@ impl EventLoop { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); - return Err(CreationError::DeviceNotAvailable); + return Err(BuildStreamError::DeviceNotAvailable); }, Err(e) => { (*audio_client).Release(); @@ -175,7 +175,7 @@ impl EventLoop { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); - return Err(CreationError::DeviceNotAvailable); + return Err(BuildStreamError::DeviceNotAvailable); }, Err(e) => { (*audio_client).Release(); @@ -218,7 +218,7 @@ impl EventLoop { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); - return Err(CreationError::DeviceNotAvailable); + return Err(BuildStreamError::DeviceNotAvailable); }, Err(e) => { (*audio_client).Release(); @@ -261,7 +261,7 @@ impl EventLoop { &self, device: &Device, format: &Format, - ) -> Result + ) -> Result { unsafe { // Making sure that COM is initialized. @@ -271,20 +271,20 @@ impl EventLoop { // Obtaining a `IAudioClient`. let audio_client = match device.build_audioclient() { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(CreationError::DeviceNotAvailable), + return Err(BuildStreamError::DeviceNotAvailable), e => e.unwrap(), }; // Computing the format and initializing the device. let waveformatex = { let format_attempt = format_to_waveformatextensible(format) - .ok_or(CreationError::FormatNotSupported)?; + .ok_or(BuildStreamError::FormatNotSupported)?; let share_mode = AUDCLNT_SHAREMODE_SHARED; // Ensure the format is supported. match super::device::is_format_supported(audio_client, &format_attempt.Format) { - Ok(false) => return Err(CreationError::FormatNotSupported), - Err(_) => return Err(CreationError::DeviceNotAvailable), + Ok(false) => return Err(BuildStreamError::FormatNotSupported), + Err(_) => return Err(BuildStreamError::DeviceNotAvailable), _ => (), } @@ -299,7 +299,7 @@ impl EventLoop { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); - return Err(CreationError::DeviceNotAvailable); + return Err(BuildStreamError::DeviceNotAvailable); }, Err(e) => { (*audio_client).Release(); @@ -339,7 +339,7 @@ impl EventLoop { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); - return Err(CreationError::DeviceNotAvailable); + return Err(BuildStreamError::DeviceNotAvailable); }, Err(e) => { (*audio_client).Release(); @@ -363,7 +363,7 @@ impl EventLoop { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (*audio_client).Release(); - return Err(CreationError::DeviceNotAvailable); + return Err(BuildStreamError::DeviceNotAvailable); }, Err(e) => { (*audio_client).Release(); @@ -529,7 +529,7 @@ impl EventLoop { if hresult == AUDCLNT_S_BUFFER_EMPTY { continue; } debug_assert!(!buffer.is_null()); - let buffer_len = frames_available as usize + let buffer_len = frames_available as usize * stream.bytes_per_frame as usize / sample_size; // Simplify the capture callback sample format branches. @@ -568,9 +568,9 @@ impl EventLoop { &mut buffer as *mut *mut _, ); // FIXME: can return `AUDCLNT_E_DEVICE_INVALIDATED` - check_result(hresult).unwrap(); + check_result(hresult).unwrap(); debug_assert!(!buffer.is_null()); - let buffer_len = frames_available as usize + let buffer_len = frames_available as usize * stream.bytes_per_frame as usize / sample_size; // Simplify the render callback sample format branches. @@ -594,7 +594,7 @@ impl EventLoop { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => (), e => e.unwrap(), }; - }} + }} } match stream.sample_format { From 42fc702f5314fdce3145e8365f7b400901657191 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Thu, 20 Jun 2019 22:37:36 +0200 Subject: [PATCH 04/17] Add `BackendSpecificError`. Add new `DevicesError`. See the documentation for both new errors for details. The new `DevicesError` has been added to allow for returning errors when enumerating devices. This has allowed to remove multiple potential `panic!`s in each of the alsa, coreaudio and wasapi backends. --- examples/enumerate.rs | 2 +- src/alsa/enumerate.rs | 41 +++++++++++++++------------- src/coreaudio/enumerate.rs | 27 ++++++++++++------- src/emscripten/mod.rs | 8 ++++++ src/lib.rs | 49 ++++++++++++++++++++++++++++----- src/null/mod.rs | 7 +++++ src/wasapi/device.rs | 55 ++++++++++++++++++++------------------ src/wasapi/mod.rs | 14 ++++++++-- 8 files changed, 140 insertions(+), 63 deletions(-) diff --git a/examples/enumerate.rs b/examples/enumerate.rs index 9164b9c..01e0052 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -4,7 +4,7 @@ fn main() { println!("Default Input Device:\n {:?}", cpal::default_input_device().map(|e| e.name())); println!("Default Output Device:\n {:?}", cpal::default_output_device().map(|e| e.name())); - let devices = cpal::devices(); + let devices = cpal::devices().expect("failed to enumerate devices"); println!("Devices: "); for (device_index, device) in devices.enumerate() { println!("{}. \"{}\"", diff --git a/src/alsa/enumerate.rs b/src/alsa/enumerate.rs index 3f18f57..1c9ed04 100644 --- a/src/alsa/enumerate.rs +++ b/src/alsa/enumerate.rs @@ -1,3 +1,4 @@ +use {BackendSpecificError, DevicesError}; use super::Device; use super::alsa; use super::check_errors; @@ -13,6 +14,28 @@ pub struct Devices { next_str: *const *const u8, } +impl Devices { + pub fn new() -> Result { + unsafe { + // TODO: check in which situation this can fail. + let card = -1; // -1 means all cards. + let iface = b"pcm\0"; // Interface identification. + let mut hints = mem::uninitialized(); // Array of device name hints. + let res = alsa::snd_device_name_hint(card, iface.as_ptr() as *const _, &mut hints); + if let Err(description) = check_errors(res) { + let err = BackendSpecificError { description }; + return Err(err.into()); + } + let hints = hints as *const *const u8; + let devices = Devices { + global_list: hints, + next_str: hints, + }; + Ok(devices) + } + } +} + unsafe impl Send for Devices { } unsafe impl Sync for Devices { @@ -27,24 +50,6 @@ impl Drop for Devices { } } -impl Default for Devices { - fn default() -> Devices { - unsafe { - // TODO: check in which situation this can fail. - let card = -1; // -1 means all cards. - let iface = b"pcm\0"; // Interface identification. - let mut hints = mem::uninitialized(); // Array of device name hints. - let res = alsa::snd_device_name_hint(card, iface.as_ptr() as *const _, &mut hints); - check_errors(res).unwrap(); - let hints = hints as *const *const u8; - Devices { - global_list: hints, - next_str: hints, - } - } - } -} - impl Iterator for Devices { type Item = Device; diff --git a/src/coreaudio/enumerate.rs b/src/coreaudio/enumerate.rs index 32c2a38..96de9bc 100644 --- a/src/coreaudio/enumerate.rs +++ b/src/coreaudio/enumerate.rs @@ -1,4 +1,4 @@ -use SupportedFormat; +use {BackendSpecificError, DevicesError, SupportedFormat}; use std::mem; use std::ptr::null; use std::vec::IntoIter as VecIntoIter; @@ -64,20 +64,27 @@ unsafe fn audio_devices() -> Result, OSStatus> { pub struct Devices(VecIntoIter); +impl Devices { + pub fn new() -> Result { + let devices = unsafe { + match audio_devices() { + Ok(devices) => devices, + Err(os_status) => { + let description = format!("{}", os_status); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + } + }; + Ok(Devices(devices.into_iter())) + } +} + unsafe impl Send for Devices { } unsafe impl Sync for Devices { } -impl Default for Devices { - fn default() -> Self { - let devices = unsafe { - audio_devices().expect("failed to get audio output devices") - }; - Devices(devices.into_iter()) - } -} - impl Iterator for Devices { type Item = Device; fn next(&mut self) -> Option { diff --git a/src/emscripten/mod.rs b/src/emscripten/mod.rs index d20dfb9..3f5067b 100644 --- a/src/emscripten/mod.rs +++ b/src/emscripten/mod.rs @@ -10,6 +10,7 @@ use stdweb::web::set_timeout; use BuildStreamError; use DefaultFormatError; +use DevicesError; use Format; use SupportedFormatsError; use StreamData; @@ -183,6 +184,13 @@ fn is_webaudio_available() -> bool { // Content is false if the iterator is empty. pub struct Devices(bool); + +impl Devices { + pub fn new() -> Result { + Ok(Self::default()) + } +} + impl Default for Devices { fn default() -> Devices { // We produce an empty iterator if the WebAudio API isn't available. diff --git a/src/lib.rs b/src/lib.rs index 4e47939..926dcaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -279,6 +279,37 @@ pub struct SupportedInputFormats(cpal_impl::SupportedInputFormats); /// See [`Device::supported_output_formats()`](struct.Device.html#method.supported_output_formats). pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats); +/// Some error has occurred that is specific to the backend from which it was produced. +/// +/// This error is often used as a catch-all in cases where: +/// +/// - It is unclear exactly what error might be produced by the backend API. +/// - It does not make sense to add a variant to the enclosing error type. +/// - No error was expected to occur at all, but we return an error to avoid the possibility of a +/// `panic!` caused by some unforseen or unknown reason. +/// +/// **Note:** If you notice a `BackendSpecificError` that you believe could be better handled in a +/// cross-platform manner, please create an issue or submit a pull request with a patch that adds +/// the necessary error variant to the appropriate error enum. +#[derive(Debug, Fail)] +#[fail(display = "A backend-specific error has occurred: {}", description)] +pub struct BackendSpecificError { + pub description: String +} + +/// An error that might occur while attempting to enumerate the available devices on a system. +#[derive(Debug, Fail)] +pub enum DevicesError { + /// Some error that is specific to the backend from which it was produced. + /// + /// Note: This error is often used when + #[fail(display = "{}", err)] + BackendSpecific { + #[fail(cause)] + err: BackendSpecificError, + } +} + /// Error that can happen when enumerating the list of supported formats. #[derive(Debug, Fail)] pub enum SupportedFormatsError { @@ -331,34 +362,34 @@ pub enum BuildStreamError { /// /// Can be empty if the system does not support audio in general. #[inline] -pub fn devices() -> Devices { - Devices(Default::default()) +pub fn devices() -> Result { + Ok(Devices(cpal_impl::Devices::new()?)) } /// An iterator yielding all `Device`s currently available to the system that support one or more /// input stream formats. /// /// Can be empty if the system does not support audio input. -pub fn input_devices() -> InputDevices { +pub fn input_devices() -> Result { fn supports_input(device: &Device) -> bool { device.supported_input_formats() .map(|mut iter| iter.next().is_some()) .unwrap_or(false) } - devices().filter(supports_input) + Ok(devices()?.filter(supports_input)) } /// An iterator yielding all `Device`s currently available to the system that support one or more /// output stream formats. /// /// Can be empty if the system does not support audio output. -pub fn output_devices() -> OutputDevices { +pub fn output_devices() -> Result { fn supports_output(device: &Device) -> bool { device.supported_output_formats() .map(|mut iter| iter.next().is_some()) .unwrap_or(false) } - devices().filter(supports_output) + Ok(devices()?.filter(supports_output)) } /// The default input audio device on the system. @@ -704,6 +735,12 @@ impl Iterator for SupportedOutputFormats { } } +impl From for DevicesError { + fn from(err: BackendSpecificError) -> Self { + DevicesError::BackendSpecific { err } + } +} + // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // diff --git a/src/null/mod.rs b/src/null/mod.rs index 0596b2e..e7a5b3c 100644 --- a/src/null/mod.rs +++ b/src/null/mod.rs @@ -4,6 +4,7 @@ use std::marker::PhantomData; use BuildStreamError; use DefaultFormatError; +use DevicesError; use Format; use SupportedFormatsError; use StreamData; @@ -56,6 +57,12 @@ pub struct StreamId; #[derive(Default)] pub struct Devices; +impl Devices { + pub fn new() -> Result { + Ok(Devices) + } +} + impl Iterator for Devices { type Item = Device; diff --git a/src/wasapi/device.rs b/src/wasapi/device.rs index a565adc..a927d93 100644 --- a/src/wasapi/device.rs +++ b/src/wasapi/device.rs @@ -9,7 +9,9 @@ use std::ptr; use std::slice; use std::sync::{Arc, Mutex, MutexGuard}; +use BackendSpecificError; use DefaultFormatError; +use DevicesError; use Format; use SupportedFormatsError; use SampleFormat; @@ -18,6 +20,7 @@ use SupportedFormat; use COMMON_SAMPLE_RATES; use super::check_result; +use super::check_result_backend_specific; use super::com; use super::winapi::Interface; use super::winapi::ctypes::c_void; @@ -688,6 +691,32 @@ pub struct Devices { next_item: u32, } +impl Devices { + pub fn new() -> Result { + unsafe { + let mut collection: *mut IMMDeviceCollection = mem::uninitialized(); + // can fail because of wrong parameters (should never happen) or out of memory + check_result_backend_specific( + (*ENUMERATOR.0).EnumAudioEndpoints( + eAll, + DEVICE_STATE_ACTIVE, + &mut collection, + ) + )?; + + let mut count = mem::uninitialized(); + // can fail if the parameter is null, which should never happen + check_result_backend_specific((*collection).GetCount(&mut count))?; + + Devices { + collection: collection, + total_count: count, + next_item: 0, + } + } + } +} + unsafe impl Send for Devices { } unsafe impl Sync for Devices { @@ -702,32 +731,6 @@ impl Drop for Devices { } } -impl Default for Devices { - fn default() -> Devices { - unsafe { - let mut collection: *mut IMMDeviceCollection = mem::uninitialized(); - // can fail because of wrong parameters (should never happen) or out of memory - check_result( - (*ENUMERATOR.0).EnumAudioEndpoints( - eAll, - DEVICE_STATE_ACTIVE, - &mut collection, - ) - ).unwrap(); - - let mut count = mem::uninitialized(); - // can fail if the parameter is null, which should never happen - check_result((*collection).GetCount(&mut count)).unwrap(); - - Devices { - collection: collection, - total_count: count, - next_item: 0, - } - } - } -} - impl Iterator for Devices { type Item = Device; diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index 82026e7..7fe9de8 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -1,10 +1,10 @@ extern crate winapi; +use BackendSpecificError; +use self::winapi::um::winnt::HRESULT; use std::io::Error as IoError; - pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; pub use self::stream::{EventLoop, StreamId}; -use self::winapi::um::winnt::HRESULT; mod com; mod device; @@ -18,3 +18,13 @@ fn check_result(result: HRESULT) -> Result<(), IoError> { Ok(()) } } + +fn check_result_backend_specific(result: HRESULT) -> Result<(), BackendSpecificError> { + match check_result(result) { + Ok(()) => Ok(()) + Err(err) => { + let description = format!("{}", err); + return BackendSpecificError { description } + } + } +} From 78a7cb9e79fb7dba38f387854649c1ad2df04cb1 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 00:22:30 +0200 Subject: [PATCH 05/17] Change `SupportedFormatsError::Unknown` variant to `BackendSpecific` This allows for also passing through a description of the unknown/platform-specific error. --- src/alsa/mod.rs | 68 ++++++++++++++++++++++++++++++++------------ src/coreaudio/mod.rs | 31 ++++++++++++-------- src/lib.rs | 19 +++++++++---- src/wasapi/device.rs | 31 +++++++++++++++----- 4 files changed, 106 insertions(+), 43 deletions(-) diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index 37bdc4d..0b9f675 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -4,6 +4,7 @@ extern crate libc; pub use self::enumerate::{Devices, default_input_device, default_output_device}; use ChannelCount; +use BackendSpecificError; use BuildStreamError; use DefaultFormatError; use Format; @@ -83,7 +84,14 @@ impl Device { ) -> Result, SupportedFormatsError> { let mut handle = mem::uninitialized(); - let device_name = ffi::CString::new(&self.0[..]).expect("Unable to get device name"); + let device_name = match ffi::CString::new(&self.0[..]) { + Ok(name) => name, + Err(err) => { + let description = format!("failed to retrieve device name: {}", err); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + }; match alsa::snd_pcm_open( &mut handle, @@ -94,14 +102,18 @@ impl Device { -2 | -16 /* determined empirically */ => return Err(SupportedFormatsError::DeviceNotAvailable), -22 => return Err(SupportedFormatsError::InvalidArgument), - e => if check_errors(e).is_err() { - return Err(SupportedFormatsError::Unknown) + e => if let Err(description) = check_errors(e) { + let err = BackendSpecificError { description }; + return Err(err.into()) } } let hw_params = HwParams::alloc(); match check_errors(alsa::snd_pcm_hw_params_any(handle, hw_params.0)) { - Err(_) => return Ok(Vec::new().into_iter()), + Err(description) => { + let err = BackendSpecificError { description }; + return Err(err.into()); + } Ok(_) => (), }; @@ -158,15 +170,26 @@ impl Device { } let mut min_rate = mem::uninitialized(); - check_errors(alsa::snd_pcm_hw_params_get_rate_min(hw_params.0, - &mut min_rate, - ptr::null_mut())) - .expect("unable to get minimum supported rete"); + if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_min( + hw_params.0, + &mut min_rate, + ptr::null_mut(), + )) { + let description = format!("unable to get minimum supported rate: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + let mut max_rate = mem::uninitialized(); - check_errors(alsa::snd_pcm_hw_params_get_rate_max(hw_params.0, - &mut max_rate, - ptr::null_mut())) - .expect("unable to get maximum supported rate"); + if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_max( + hw_params.0, + &mut max_rate, + ptr::null_mut(), + )) { + let description = format!("unable to get maximum supported rate: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } let sample_rates = if min_rate == max_rate { vec![(min_rate, max_rate)] @@ -212,11 +235,19 @@ impl Device { }; let mut min_channels = mem::uninitialized(); - check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels)) - .expect("unable to get minimum supported channel count"); + if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels)) { + let description = format!("unable to get minimum supported channel count: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + let mut max_channels = mem::uninitialized(); - check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels)) - .expect("unable to get maximum supported channel count"); + if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels)) { + let description = format!("unable to get maximum supported channel count: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + let max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned let supported_channels = (min_channels .. max_channels + 1) .filter_map(|num| if alsa::snd_pcm_hw_params_test_channels( @@ -280,8 +311,9 @@ impl Device { // the device supports only one return Err(DefaultFormatError::StreamTypeNotSupported); } - Err(SupportedFormatsError::Unknown) => { - return Err(DefaultFormatError::DeviceNotAvailable); + Err(SupportedFormatsError::BackendSpecific { err }) => { + unimplemented!(); + //return Err(err.into()); } Ok(fmts) => fmts.collect(), } diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index 3e8b498..eedd0c1 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -2,6 +2,7 @@ extern crate coreaudio; extern crate core_foundation_sys; use ChannelCount; +use BackendSpecificError; use BuildStreamError; use DefaultFormatError; use Format; @@ -126,9 +127,8 @@ impl Device { null(), &data_size as *const _ as *mut _, ); - if status != kAudioHardwareNoError as i32 { - unimplemented!(); - } + check_os_status(status)?; + let mut audio_buffer_list: Vec = vec![]; audio_buffer_list.reserve_exact(data_size as usize); let status = AudioObjectGetPropertyData( @@ -139,9 +139,8 @@ impl Device { &data_size as *const _ as *mut _, audio_buffer_list.as_mut_ptr() as *mut _, ); - if status != kAudioHardwareNoError as i32 { - unimplemented!(); - } + check_os_status(status)?; + let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList; // If there's no buffers, skip. @@ -176,9 +175,8 @@ impl Device { null(), &data_size as *const _ as *mut _, ); - if status != kAudioHardwareNoError as i32 { - unimplemented!(); - } + check_os_status(status)?; + let n_ranges = data_size as usize / mem::size_of::(); let mut ranges: Vec = vec![]; ranges.reserve_exact(data_size as usize); @@ -190,9 +188,8 @@ impl Device { &data_size as *const _ as *mut _, ranges.as_mut_ptr() as *mut _, ); - if status != kAudioHardwareNoError as i32 { - unimplemented!(); - } + check_os_status(status)?; + let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _; let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges); @@ -796,3 +793,13 @@ impl EventLoop { } } } + +fn check_os_status(os_status: OSStatus) -> Result<(), BackendSpecificError> { + match coreaudio::Error::from_os_status(os_status) { + Ok(()) => Ok(()), + Err(err) => { + let description = std::error::Error::description(&err).to_string(); + Err(BackendSpecificError { description }) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 926dcaf..f25f0a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -300,9 +300,7 @@ pub struct BackendSpecificError { /// An error that might occur while attempting to enumerate the available devices on a system. #[derive(Debug, Fail)] pub enum DevicesError { - /// Some error that is specific to the backend from which it was produced. - /// - /// Note: This error is often used when + /// See the `BackendSpecificError` docs for more information about this error variant. #[fail(display = "{}", err)] BackendSpecific { #[fail(cause)] @@ -320,9 +318,12 @@ pub enum SupportedFormatsError { /// We called something the C-Layer did not understand #[fail(display = "Invalid argument passed to the backend. For example, this happens when trying to read capture capabilities when the device does not support it.")] InvalidArgument, - /// The C-Layer returned an error we don't know about - #[fail(display = "An unknown error in the backend occured.")] - Unknown + /// See the `BackendSpecificError` docs for more information about this error variant. + #[fail(display = "{}", err)] + BackendSpecific { + #[fail(cause)] + err: BackendSpecificError, + } } /// May occur when attempting to request the default input or output stream format from a `Device`. @@ -741,6 +742,12 @@ impl From for DevicesError { } } +impl From for SupportedFormatsError { + fn from(err: BackendSpecificError) -> Self { + SupportedFormatsError::BackendSpecific { err } + } +} + // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // diff --git a/src/wasapi/device.rs b/src/wasapi/device.rs index a927d93..786d80d 100644 --- a/src/wasapi/device.rs +++ b/src/wasapi/device.rs @@ -395,9 +395,15 @@ impl Device { // Retrieve the `IAudioClient`. let lock = match self.ensure_future_audio_client() { - Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(SupportedFormatsError::DeviceNotAvailable), - e => e.unwrap(), + Ok(lock) => lock, + Err(e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + return Err(SupportedFormatsError::DeviceNotAvailable) + } + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, }; let client = lock.unwrap().0; @@ -405,11 +411,15 @@ impl Device { // Retrieve the pointer to the default WAVEFORMATEX. let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized()); match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) { + Ok(()) => (), Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { return Err(SupportedFormatsError::DeviceNotAvailable); }, - Err(e) => panic!("{:?}", e), - Ok(()) => (), + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, }; // If the default format can't succeed we have no hope of finding other formats. @@ -453,8 +463,15 @@ impl Device { // TODO: Test the different sample formats? // Create the supported formats. - let mut format = format_from_waveformatex_ptr(default_waveformatex_ptr.0) - .expect("could not create a cpal::Format from a WAVEFORMATEX"); + let mut format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0) { + Some(fmt) => fmt, + None => { + let description = + "could not create a `cpal::Format` from a `WAVEFORMATEX`".to_string(); + let err = BackendSpecificError { description }; + return Err(err.into()); + } + }; let mut supported_formats = Vec::with_capacity(supported_sample_rates.len()); for rate in supported_sample_rates { format.sample_rate = SampleRate(rate as _); From 105086a108a6914aaa26b48befdfb2a971194e48 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 00:53:11 +0200 Subject: [PATCH 06/17] Add new `DeviceNameError` type The coreaudio and wasapi backends may both potentially fail to produce the name associated with a device. This changes the API to allow for returning the errors in these cases. --- src/alsa/mod.rs | 5 +++-- src/coreaudio/mod.rs | 14 ++++++++------ src/emscripten/mod.rs | 5 +++-- src/lib.rs | 19 ++++++++++++++++++- src/null/mod.rs | 5 +++-- src/wasapi/device.rs | 25 +++++++++++++++++++------ 6 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index 0b9f675..969dc14 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -7,6 +7,7 @@ use ChannelCount; use BackendSpecificError; use BuildStreamError; use DefaultFormatError; +use DeviceNameError; use Format; use SupportedFormatsError; use SampleFormat; @@ -74,8 +75,8 @@ pub struct Device(String); impl Device { #[inline] - pub fn name(&self) -> String { - self.0.clone() + pub fn name(&self) -> Result { + Ok(self.0.clone()) } unsafe fn supported_formats( diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index eedd0c1..828db72 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -5,6 +5,7 @@ use ChannelCount; use BackendSpecificError; use BuildStreamError; use DefaultFormatError; +use DeviceNameError; use Format; use SupportedFormatsError; use Sample; @@ -76,7 +77,7 @@ pub struct Device { } impl Device { - pub fn name(&self) -> String { + pub fn name(&self) -> Result { let property_address = AudioObjectPropertyAddress { mSelector: kAudioDevicePropertyDeviceNameCFString, mScope: kAudioDevicePropertyScopeOutput, @@ -93,16 +94,17 @@ impl Device { &data_size as *const _ as *mut _, &device_name as *const _ as *mut _, ); - if status != kAudioHardwareNoError as i32 { - return format!("", status); - } + check_os_status(status)?; + let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8); if c_string == null() { - return "".into(); + let description = "core foundation unexpectedly returned null string".to_string(); + let err = BackendSpecificError { description }; + return Err(err.into()); } CStr::from_ptr(c_string as *mut _) }; - c_str.to_string_lossy().into_owned() + Ok(c_str.to_string_lossy().into_owned()) } // Logic re-used between `supported_input_formats` and `supported_output_formats`. diff --git a/src/emscripten/mod.rs b/src/emscripten/mod.rs index 3f5067b..1138a39 100644 --- a/src/emscripten/mod.rs +++ b/src/emscripten/mod.rs @@ -10,6 +10,7 @@ use stdweb::web::set_timeout; use BuildStreamError; use DefaultFormatError; +use DeviceNameError; use DevicesError; use Format; use SupportedFormatsError; @@ -229,8 +230,8 @@ pub struct Device; impl Device { #[inline] - pub fn name(&self) -> String { - "Default Device".to_owned() + pub fn name(&self) -> Result { + Ok("Default Device".to_owned()) } #[inline] diff --git a/src/lib.rs b/src/lib.rs index f25f0a6..4f717ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -308,6 +308,17 @@ pub enum DevicesError { } } +/// An error that may occur while attempting to retrieve a device name. +#[derive(Debug, Fail)] +pub enum DeviceNameError { + /// See the `BackendSpecificError` docs for more information about this error variant. + #[fail(display = "{}", err)] + BackendSpecific { + #[fail(cause)] + err: BackendSpecificError, + } +} + /// Error that can happen when enumerating the list of supported formats. #[derive(Debug, Fail)] pub enum SupportedFormatsError { @@ -410,7 +421,7 @@ pub fn default_output_device() -> Option { impl Device { /// The human-readable name of the device. #[inline] - pub fn name(&self) -> String { + pub fn name(&self) -> Result { self.0.name() } @@ -742,6 +753,12 @@ impl From for DevicesError { } } +impl From for DeviceNameError { + fn from(err: BackendSpecificError) -> Self { + DeviceNameError::BackendSpecific { err } + } +} + impl From for SupportedFormatsError { fn from(err: BackendSpecificError) -> Self { SupportedFormatsError::BackendSpecific { err } diff --git a/src/null/mod.rs b/src/null/mod.rs index e7a5b3c..dc41f77 100644 --- a/src/null/mod.rs +++ b/src/null/mod.rs @@ -5,6 +5,7 @@ use std::marker::PhantomData; use BuildStreamError; use DefaultFormatError; use DevicesError; +use DeviceNameError; use Format; use SupportedFormatsError; use StreamData; @@ -107,8 +108,8 @@ impl Device { } #[inline] - pub fn name(&self) -> String { - "null".to_owned() + pub fn name(&self) -> Result { + Ok("null".to_owned()) } } diff --git a/src/wasapi/device.rs b/src/wasapi/device.rs index 786d80d..ded0722 100644 --- a/src/wasapi/device.rs +++ b/src/wasapi/device.rs @@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex, MutexGuard}; use BackendSpecificError; use DefaultFormatError; +use DeviceNameError; use DevicesError; use Format; use SupportedFormatsError; @@ -297,7 +298,7 @@ unsafe impl Sync for Device { } impl Device { - pub fn name(&self) -> String { + pub fn name(&self) -> Result { unsafe { // Open the device's property store. let mut property_store = ptr::null_mut(); @@ -305,15 +306,24 @@ impl Device { // Get the endpoint's friendly-name property. let mut property_value = mem::zeroed(); - check_result( + if let Err(err) = check_result( (*property_store).GetValue( &devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _, &mut property_value ) - ).expect("failed to get friendly-name from property store"); + ) { + let description = format!("failed to retrieve name from property store: {}", err); + let err = BackendSpecificError { description }; + return Err(err.into()); + } // Read the friendly-name from the union data field, expecting a *const u16. - assert_eq!(property_value.vt, wtypes::VT_LPWSTR as _); + if property_value.vt != wtypes::VT_LPWSTR as _ { + let description = + format!("property store produced invalid data: {:?}", property_value.vt); + let err = BackendSpecificError { description }; + return Err(err.into()); + } let ptr_usize: usize = *(&property_value.data as *const _ as *const usize); let ptr_utf16 = ptr_usize as *const u16; @@ -326,12 +336,15 @@ impl Device { // Create the utf16 slice and covert it into a string. let name_slice = slice::from_raw_parts(ptr_utf16, len as usize); let name_os_string: OsString = OsStringExt::from_wide(name_slice); - let name_string = name_os_string.into_string().unwrap(); + let name_string = match name_os_string.into_string() { + Ok(string) => string, + Err(os_string) => os_string.to_string_lossy().into(), + }; // Clean up the property. PropVariantClear(&mut property_value); - name_string + Ok(name_string) } } From fbb97f51efe68ce3c5f545ca5fe209351c8b316e Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 01:37:55 +0200 Subject: [PATCH 07/17] Update examples for DeviceNameError --- examples/enumerate.rs | 6 +++--- examples/feedback.rs | 4 ++-- examples/record_wav.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/enumerate.rs b/examples/enumerate.rs index 01e0052..c082904 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -1,15 +1,15 @@ extern crate cpal; fn main() { - println!("Default Input Device:\n {:?}", cpal::default_input_device().map(|e| e.name())); - println!("Default Output Device:\n {:?}", cpal::default_output_device().map(|e| e.name())); + println!("Default Input Device:\n {:?}", cpal::default_input_device().map(|e| e.name().unwrap())); + println!("Default Output Device:\n {:?}", cpal::default_output_device().map(|e| e.name().unwrap())); let devices = cpal::devices().expect("failed to enumerate devices"); println!("Devices: "); for (device_index, device) in devices.enumerate() { println!("{}. \"{}\"", device_index + 1, - device.name()); + device.name().unwrap()); // Input formats if let Ok(fmt) = device.default_input_format() { diff --git a/examples/feedback.rs b/examples/feedback.rs index ba7194e..3807b53 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -16,8 +16,8 @@ fn main() { // Default devices. let input_device = cpal::default_input_device().expect("Failed to get default input device"); let output_device = cpal::default_output_device().expect("Failed to get default output device"); - println!("Using default input device: \"{}\"", input_device.name()); - println!("Using default output device: \"{}\"", output_device.name()); + println!("Using default input device: \"{}\"", input_device.name().unwrap()); + println!("Using default output device: \"{}\"", output_device.name().unwrap()); // We'll try and use the same format between streams to keep it simple let mut format = input_device.default_input_format().expect("Failed to get default format"); diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 74da989..ad5fd5f 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -8,7 +8,7 @@ extern crate hound; fn main() { // Setup the default input device and stream with the default input format. let device = cpal::default_input_device().expect("Failed to get default input device"); - println!("Default input device: {}", device.name()); + println!("Default input device: {}", device.name().unwrap()); let format = device.default_input_format().expect("Failed to get default input format"); println!("Default input format: {:?}", format); let event_loop = cpal::EventLoop::new(); From f0e4e312c1c228e58f1565c21f15ee015b63a489 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 01:34:07 +0200 Subject: [PATCH 08/17] Improve handling of the `DefaultFormatError` --- src/alsa/mod.rs | 3 +-- src/coreaudio/mod.rs | 26 ++++++++++++++------------ src/lib.rs | 12 ++++++++++++ src/wasapi/device.rs | 18 ++++++++++++++---- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index 969dc14..1c8006e 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -313,8 +313,7 @@ impl Device { return Err(DefaultFormatError::StreamTypeNotSupported); } Err(SupportedFormatsError::BackendSpecific { err }) => { - unimplemented!(); - //return Err(err.into()); + return Err(err.into()); } Ok(fmts) => fmts.collect(), } diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index 828db72..feff675 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -224,18 +224,25 @@ impl Device { scope: AudioObjectPropertyScope, ) -> Result { - fn default_format_error_from_os_status(status: OSStatus) -> Option { + fn default_format_error_from_os_status(status: OSStatus) -> Result<(), DefaultFormatError> { let err = match coreaudio::Error::from_os_status(status) { Err(err) => err, - Ok(_) => return None, + Ok(_) => return Ok(()), }; match err { - coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat | - coreaudio::Error::NoKnownSubtype | coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) | coreaudio::Error::AudioCodec(_) | - coreaudio::Error::AudioFormat(_) => Some(DefaultFormatError::StreamTypeNotSupported), - _ => Some(DefaultFormatError::DeviceNotAvailable), + coreaudio::Error::AudioFormat(_) => { + Err(DefaultFormatError::StreamTypeNotSupported) + } + coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::NoConnection) => { + Err(DefaultFormatError::DeviceNotAvailable) + } + err => { + let description = format!("{}", std::error::Error::description(&err)); + let err = BackendSpecificError { description }; + Err(err.into()) + } } } @@ -256,12 +263,7 @@ impl Device { &data_size as *const _ as *mut _, &asbd as *const _ as *mut _, ); - - if status != kAudioHardwareNoError as i32 { - let err = default_format_error_from_os_status(status) - .expect("no known error for OSStatus"); - return Err(err); - } + default_format_error_from_os_status(status)?; let sample_format = { let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag( diff --git a/src/lib.rs b/src/lib.rs index 4f717ac..adeda83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -347,6 +347,12 @@ pub enum DefaultFormatError { /// Returned if e.g. the default input format was requested on an output-only audio device. #[fail(display = "The requested stream type is not supported by the device.")] StreamTypeNotSupported, + /// See the `BackendSpecificError` docs for more information about this error variant. + #[fail(display = "{}", err)] + BackendSpecific { + #[fail(cause)] + err: BackendSpecificError, + } } /// Error that can happen when creating a `Stream`. @@ -765,6 +771,12 @@ impl From for SupportedFormatsError { } } +impl From for DefaultFormatError { + fn from(err: BackendSpecificError) -> Self { + DefaultFormatError::BackendSpecific { err } + } +} + // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // diff --git a/src/wasapi/device.rs b/src/wasapi/device.rs index ded0722..2cac6aa 100644 --- a/src/wasapi/device.rs +++ b/src/wasapi/device.rs @@ -522,9 +522,15 @@ impl Device { com::com_initialized(); let lock = match self.ensure_future_audio_client() { - Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => - return Err(DefaultFormatError::DeviceNotAvailable), - e => e.unwrap(), + Ok(lock) => lock, + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + return Err(DefaultFormatError::DeviceNotAvailable) + } + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + } }; let client = lock.unwrap().0; @@ -534,7 +540,11 @@ impl Device { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { return Err(DefaultFormatError::DeviceNotAvailable); }, - Err(e) => panic!("{:?}", e), + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + }, Ok(()) => (), }; From ba8d354e93115b220370f716f2b6263b21c6cfc4 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 02:38:59 +0200 Subject: [PATCH 09/17] Improve handling of `BuildStreamError` throughout crate. --- src/alsa/mod.rs | 126 ++++++++++++++++++++++++++++--------------- src/coreaudio/mod.rs | 15 ++++-- src/lib.rs | 18 +++++-- src/wasapi/stream.rs | 74 +++++++++++++++++-------- 4 files changed, 160 insertions(+), 73 deletions(-) diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index 1c8006e..de5e691 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -690,29 +690,41 @@ impl EventLoop { ) { -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), -22 => return Err(BuildStreamError::InvalidArgument), - e => if check_errors(e).is_err() { - return Err(BuildStreamError::Unknown); + e => if let Err(description) = check_errors(e) { + let err = BackendSpecificError { description }; + return Err(err.into()); } } let hw_params = HwParams::alloc(); - set_hw_params_from_format(capture_handle, &hw_params, format); + set_hw_params_from_format(capture_handle, &hw_params, format) + .map_err(|description| BackendSpecificError { description })?; let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1; - let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format); + let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format) + .map_err(|description| BackendSpecificError { description })?; - check_errors(alsa::snd_pcm_prepare(capture_handle)) - .expect("could not get playback handle"); + if let Err(desc) = check_errors(alsa::snd_pcm_prepare(capture_handle)) { + let description = format!("could not get capture handle: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } let num_descriptors = { let num_descriptors = alsa::snd_pcm_poll_descriptors_count(capture_handle); - debug_assert!(num_descriptors >= 1); + if num_descriptors == 0 { + let description = "poll descriptor count for capture stream was 0".to_string(); + let err = BackendSpecificError { description }; + return Err(err.into()); + } num_descriptors as usize }; let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows + if new_stream_id.0 == usize::max_value() { + return Err(BuildStreamError::StreamIdOverflow); + } let stream_inner = StreamInner { id: new_stream_id.clone(), @@ -728,8 +740,11 @@ impl EventLoop { buffer: None, }; - check_errors(alsa::snd_pcm_start(capture_handle)) - .expect("could not start capture stream"); + if let Err(desc) = check_errors(alsa::snd_pcm_start(capture_handle)) { + let description = format!("could not start capture stream: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } self.push_command(Command::NewStream(stream_inner)); Ok(new_stream_id) @@ -754,29 +769,41 @@ impl EventLoop { ) { -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), -22 => return Err(BuildStreamError::InvalidArgument), - e => if check_errors(e).is_err() { - return Err(BuildStreamError::Unknown); + e => if let Err(description) = check_errors(e) { + let err = BackendSpecificError { description }; + return Err(err.into()) } } let hw_params = HwParams::alloc(); - set_hw_params_from_format(playback_handle, &hw_params, format); + set_hw_params_from_format(playback_handle, &hw_params, format) + .map_err(|description| BackendSpecificError { description })?; let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1; - let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format); + let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format) + .map_err(|description| BackendSpecificError { description })?; - check_errors(alsa::snd_pcm_prepare(playback_handle)) - .expect("could not get playback handle"); + if let Err(desc) = check_errors(alsa::snd_pcm_prepare(playback_handle)) { + let description = format!("could not get playback handle: {}", desc); + let err = BackendSpecificError { description }; + return Err(err.into()); + } let num_descriptors = { let num_descriptors = alsa::snd_pcm_poll_descriptors_count(playback_handle); - debug_assert!(num_descriptors >= 1); + if num_descriptors == 0 { + let description = "poll descriptor count for playback stream was 0".to_string(); + let err = BackendSpecificError { description }; + return Err(err.into()); + } num_descriptors as usize }; let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows + if new_stream_id.0 == usize::max_value() { + return Err(BuildStreamError::StreamIdOverflow); + } let stream_inner = StreamInner { id: new_stream_id.clone(), @@ -826,12 +853,12 @@ unsafe fn set_hw_params_from_format( format: &Format, ) -> Result<(), String> { if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) { - return Err("Errors on pcm handle".to_string()); + return Err(format!("errors on pcm handle: {}", e)); } if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_access(pcm_handle, hw_params.0, alsa::SND_PCM_ACCESS_RW_INTERLEAVED)) { - return Err("Handle not acessible".to_string()); + return Err(format!("handle not acessible: {}", e)); } let data_type = if cfg!(target_endian = "big") { @@ -851,29 +878,34 @@ unsafe fn set_hw_params_from_format( if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format(pcm_handle, hw_params.0, data_type)) { - return Err("Format could not be set".to_string()); + return Err(format!("format could not be set: {}", e)); } if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(pcm_handle, hw_params.0, format.sample_rate.0 as libc::c_uint, 0)) { - return Err("Sample rate could not be set".to_string()); + return Err(format!("sample rate could not be set: {}", e)); } if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(pcm_handle, hw_params.0, format.channels as libc::c_uint)) { - return Err("Channel count could not be set".to_string()); + return Err(format!("channel count could not be set: {}", e)); } + + // TODO: Review this. 200ms seems arbitrary... let mut max_buffer_size = format.sample_rate.0 as alsa::snd_pcm_uframes_t / format.channels as alsa::snd_pcm_uframes_t / 5; // 200ms of buffer - check_errors(alsa::snd_pcm_hw_params_set_buffer_size_max(pcm_handle, + if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_buffer_size_max(pcm_handle, hw_params.0, &mut max_buffer_size)) - .unwrap(); + { + return Err(format!("max buffer size could not be set: {}", e)); + } + if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) { - return Err("Hardware params could not be set.".to_string()); + return Err(format!("hardware params could not be set: {}", e)); } Ok(()) @@ -882,34 +914,42 @@ unsafe fn set_hw_params_from_format( unsafe fn set_sw_params_from_format( pcm_handle: *mut alsa::snd_pcm_t, format: &Format, -) -> (usize, usize) +) -> Result<(usize, usize), String> { let mut sw_params = mem::uninitialized(); // TODO: RAII - check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)).unwrap(); - check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)).unwrap(); - check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle, - sw_params, - 0)) - .unwrap(); + if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) { + return Err(format!("snd_pcm_sw_params_malloc failed: {}", e)); + } + if let Err(e) = check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)) { + return Err(format!("snd_pcm_sw_params_current failed: {}", e)); + } + if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, 0)) { + return Err(format!("snd_pcm_sw_params_set_start_threshold failed: {}", e)); + } let (buffer_len, period_len) = { let mut buffer = mem::uninitialized(); let mut period = mem::uninitialized(); - check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period)) - .expect("could not initialize buffer"); - assert!(buffer != 0); - check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle, - sw_params, - period)) - .unwrap(); + if let Err(e) = check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period)) { + return Err(format!("failed to initialize buffer: {}", e)); + } + if buffer == 0 { + return Err(format!("initialization resulted in a null buffer")); + } + if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle, sw_params, period)) { + return Err(format!("snd_pcm_sw_params_set_avail_min failed: {}", e)); + } let buffer = buffer as usize * format.channels as usize; let period = period as usize * format.channels as usize; (buffer, period) }; - check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)).unwrap(); + if let Err(e) = check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)) { + return Err(format!("snd_pcm_sw_params failed: {}", e)); + } + alsa::snd_pcm_sw_params_free(sw_params); - (buffer_len, period_len) + Ok((buffer_len, period_len)) } /// Wrapper around `hw_params`. @@ -945,8 +985,6 @@ impl Drop for StreamInner { #[inline] fn check_errors(err: libc::c_int) -> Result<(), String> { - use std::ffi; - if err < 0 { unsafe { let s = ffi::CStr::from_ptr(alsa::snd_strerror(err)) diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index feff675..a80d001 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -513,13 +513,16 @@ impl EventLoop { // If the requested sample rate is different to the device sample rate, update the device. if sample_rate as u32 != format.sample_rate.0 { - // In order to avoid breaking existing input streams we `panic!` if there is already an - // active input stream for this device with the actual sample rate. + // In order to avoid breaking existing input streams we return an error if there is + // already an active input stream for this device with the actual sample rate. for stream in &*self.streams.lock().unwrap() { if let Some(stream) = stream.as_ref() { if stream.device_id == device.audio_device_id { - panic!("cannot change device sample rate for stream as an existing stream \ - is already running at the current sample rate."); + let description = "cannot change device sample rate for stream as an \ + existing stream is already running at the current sample rate" + .into(); + let err = BackendSpecificError { description }; + return Err(err.into()); } } } @@ -618,7 +621,9 @@ impl EventLoop { let timer = ::std::time::Instant::now(); while sample_rate != reported_rate { if timer.elapsed() > ::std::time::Duration::from_secs(1) { - panic!("timeout waiting for sample rate update for device"); + let description = "timeout waiting for sample rate update for device".into(); + let err = BackendSpecificError { description }; + return Err(err.into()); } ::std::thread::sleep(::std::time::Duration::from_millis(5)); } diff --git a/src/lib.rs b/src/lib.rs index adeda83..6b1f6cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -371,9 +371,15 @@ pub enum BuildStreamError { /// Trying to use capture capabilities on an output only format yields this. #[fail(display = "The requested device does not support this capability (invalid argument)")] InvalidArgument, - /// The C-Layer returned an error we don't know about - #[fail(display = "An unknown error in the Backend occured")] - Unknown, + /// Occurs if adding a new Stream ID would cause an integer overflow. + #[fail(display = "Adding a new stream ID would cause an overflow")] + StreamIdOverflow, + /// See the `BackendSpecificError` docs for more information about this error variant. + #[fail(display = "{}", err)] + BackendSpecific { + #[fail(cause)] + err: BackendSpecificError, + } } /// An iterator yielding all `Device`s currently available to the system. @@ -777,6 +783,12 @@ impl From for DefaultFormatError { } } +impl From for BuildStreamError { + fn from(err: BackendSpecificError) -> Self { + BuildStreamError::BackendSpecific { err } + } +} + // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // diff --git a/src/wasapi/stream.rs b/src/wasapi/stream.rs index 3a140b5..ae13c26 100644 --- a/src/wasapi/stream.rs +++ b/src/wasapi/stream.rs @@ -20,6 +20,7 @@ use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +use BackendSpecificError; use BuildStreamError; use Format; use SampleFormat; @@ -123,9 +124,14 @@ impl EventLoop { // Obtaining a `IAudioClient`. let audio_client = match device.build_audioclient() { + Ok(client) => client, Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => return Err(BuildStreamError::DeviceNotAvailable), - e => e.unwrap(), + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + } }; // Computing the format and initializing the device. @@ -158,7 +164,9 @@ impl EventLoop { }, Err(e) => { (*audio_client).Release(); - panic!("{:?}", e); + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }, Ok(()) => (), }; @@ -179,7 +187,9 @@ impl EventLoop { }, Err(e) => { (*audio_client).Release(); - panic!("{:?}", e); + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }, Ok(()) => (), }; @@ -192,16 +202,17 @@ impl EventLoop { let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); if event == ptr::null_mut() { (*audio_client).Release(); - panic!("Failed to create event"); + let description = format!("failed to create event"); + let err = BackendSpecificError { description }; + return Err(err.into()); } - match check_result((*audio_client).SetEventHandle(event)) { - Err(_) => { - (*audio_client).Release(); - panic!("Failed to call SetEventHandle") - }, - Ok(_) => (), - }; + if let Err(e) = check_result((*audio_client).SetEventHandle(event)) { + (*audio_client).Release(); + let description = format!("failed to call SetEventHandle: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + } event }; @@ -222,7 +233,9 @@ impl EventLoop { }, Err(e) => { (*audio_client).Release(); - panic!("{:?}", e); + let description = format!("failed to build capture client: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }, Ok(()) => (), }; @@ -231,7 +244,9 @@ impl EventLoop { }; let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows + if new_stream_id.0 == usize::max_value() { + return Err(BuildStreamError::StreamIdOverflow); + } // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. @@ -270,9 +285,14 @@ impl EventLoop { // Obtaining a `IAudioClient`. let audio_client = match device.build_audioclient() { + Ok(client) => client, Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => return Err(BuildStreamError::DeviceNotAvailable), - e => e.unwrap(), + Err(e) => { + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); + } }; // Computing the format and initializing the device. @@ -303,7 +323,9 @@ impl EventLoop { }, Err(e) => { (*audio_client).Release(); - panic!("{:?}", e); + let description = format!("{}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }, Ok(()) => (), }; @@ -316,13 +338,17 @@ impl EventLoop { let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); if event == ptr::null_mut() { (*audio_client).Release(); - panic!("Failed to create event"); + let description = format!("failed to create event"); + let err = BackendSpecificError { description }; + return Err(err.into()); } match check_result((*audio_client).SetEventHandle(event)) { - Err(_) => { + Err(e) => { (*audio_client).Release(); - panic!("Failed to call SetEventHandle") + let description = format!("failed to call SetEventHandle: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }, Ok(_) => (), }; @@ -343,7 +369,9 @@ impl EventLoop { }, Err(e) => { (*audio_client).Release(); - panic!("{:?}", e); + let description = format!("failed to obtain buffer size: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }, Ok(()) => (), }; @@ -367,7 +395,9 @@ impl EventLoop { }, Err(e) => { (*audio_client).Release(); - panic!("{:?}", e); + let description = format!("failed to build render client: {}", e); + let err = BackendSpecificError { description }; + return Err(err.into()); }, Ok(()) => (), }; @@ -376,7 +406,9 @@ impl EventLoop { }; let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); - assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows + if new_stream_id.0 == usize::max_value() { + return Err(BuildStreamError::StreamIdOverflow); + } // Once we built the `StreamInner`, we add a command that will be picked up by the // `run()` method and added to the `RunContext`. From eae0e1871440e32a0f9aaf509c1668f8b766f2d0 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 03:03:03 +0200 Subject: [PATCH 10/17] Add `PlayStreamError` and `PauseStreamError`. This allows for properly handling potential failure on macOS. We should also consider propagating the mutex/channel poison errors through these new types, especially considering the potential removal of the event loop in favour of switching over to high-priority audio threads on windows and linux. --- examples/beep.rs | 2 +- examples/feedback.rs | 4 ++-- examples/record_wav.rs | 2 +- src/alsa/mod.rs | 8 ++++++-- src/coreaudio/mod.rs | 20 ++++++++++++++---- src/emscripten/mod.rs | 8 ++++++-- src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++++-- src/null/mod.rs | 6 ++++-- src/wasapi/stream.rs | 8 ++++++-- 9 files changed, 86 insertions(+), 18 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index ee3e125..82e154d 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -5,7 +5,7 @@ fn main() { let format = device.default_output_format().expect("Failed to get default output format"); let event_loop = cpal::EventLoop::new(); let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); - event_loop.play_stream(stream_id.clone()); + event_loop.play_stream(stream_id.clone()).unwrap(); let sample_rate = format.sample_rate.0 as f32; let mut sample_clock = 0f32; diff --git a/examples/feedback.rs b/examples/feedback.rs index 3807b53..eaa22a9 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -43,8 +43,8 @@ fn main() { // Play the streams. println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS); - event_loop.play_stream(input_stream_id.clone()); - event_loop.play_stream(output_stream_id.clone()); + event_loop.play_stream(input_stream_id.clone()).unwrap(); + event_loop.play_stream(output_stream_id.clone()).unwrap(); // Run the event loop on a separate thread. std::thread::spawn(move || { diff --git a/examples/record_wav.rs b/examples/record_wav.rs index ad5fd5f..f7aa019 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -14,7 +14,7 @@ fn main() { let event_loop = cpal::EventLoop::new(); let stream_id = event_loop.build_input_stream(&device, &format) .expect("Failed to build input stream"); - event_loop.play_stream(stream_id); + event_loop.play_stream(stream_id).unwrap(); // The WAV file we're recording to. const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav"); diff --git a/src/alsa/mod.rs b/src/alsa/mod.rs index de5e691..43a3191 100644 --- a/src/alsa/mod.rs +++ b/src/alsa/mod.rs @@ -9,6 +9,8 @@ use BuildStreamError; use DefaultFormatError; use DeviceNameError; use Format; +use PauseStreamError; +use PlayStreamError; use SupportedFormatsError; use SampleFormat; use SampleRate; @@ -837,13 +839,15 @@ impl EventLoop { } #[inline] - pub fn play_stream(&self, stream_id: StreamId) { + pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { self.push_command(Command::PlayStream(stream_id)); + Ok(()) } #[inline] - pub fn pause_stream(&self, stream_id: StreamId) { + pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { self.push_command(Command::PauseStream(stream_id)); + Ok(()) } } diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index a80d001..40ccf17 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -7,6 +7,8 @@ use BuildStreamError; use DefaultFormatError; use DeviceNameError; use Format; +use PauseStreamError; +use PlayStreamError; use SupportedFormatsError; use Sample; use SampleFormat; @@ -782,24 +784,34 @@ impl EventLoop { streams[stream_id.0] = None; } - pub fn play_stream(&self, stream: StreamId) { + pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { let mut streams = self.streams.lock().unwrap(); let stream = streams[stream.0].as_mut().unwrap(); if !stream.playing { - stream.audio_unit.start().unwrap(); + if let Err(e) = stream.audio_unit.start() { + let description = format!("{}", std::error::Error::description(e)); + let err = BackendSpecificError { description }; + return Err(err.into()); + } stream.playing = true; } + Ok(()) } - pub fn pause_stream(&self, stream: StreamId) { + pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { let mut streams = self.streams.lock().unwrap(); let stream = streams[stream.0].as_mut().unwrap(); if stream.playing { - stream.audio_unit.stop().unwrap(); + if let Err(e) = stream.audio_unit.stop() { + let description = format!("{}", std::error::Error::description(e)); + let err = BackendSpecificError { description }; + return Err(err.into()); + } stream.playing = false; } + Ok(()) } } diff --git a/src/emscripten/mod.rs b/src/emscripten/mod.rs index 1138a39..b828f7f 100644 --- a/src/emscripten/mod.rs +++ b/src/emscripten/mod.rs @@ -13,6 +13,8 @@ use DefaultFormatError; use DeviceNameError; use DevicesError; use Format; +use PauseStreamError; +use PlayStreamError; use SupportedFormatsError; use StreamData; use SupportedFormat; @@ -147,23 +149,25 @@ impl EventLoop { } #[inline] - pub fn play_stream(&self, stream_id: StreamId) { + pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { let streams = self.streams.lock().unwrap(); let stream = streams .get(stream_id.0) .and_then(|v| v.as_ref()) .expect("invalid stream ID"); js!(@{stream}.resume()); + Ok(()) } #[inline] - pub fn pause_stream(&self, stream_id: StreamId) { + pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { let streams = self.streams.lock().unwrap(); let stream = streams .get(stream_id.0) .and_then(|v| v.as_ref()) .expect("invalid stream ID"); js!(@{stream}.suspend()); + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 6b1f6cd..2a273bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -382,6 +382,36 @@ pub enum BuildStreamError { } } +/// Errors that might occur when calling `play_stream`. +/// +/// As of writing this, only macOS may immediately return an error while calling this method. This +/// is because both the alsa and wasapi backends only enqueue these commands and do not process +/// them immediately. +#[derive(Debug, Fail)] +pub enum PlayStreamError { + /// See the `BackendSpecificError` docs for more information about this error variant. + #[fail(display = "{}", err)] + BackendSpecific { + #[fail(cause)] + err: BackendSpecificError, + } +} + +/// Errors that might occur when calling `pause_stream`. +/// +/// As of writing this, only macOS may immediately return an error while calling this method. This +/// is because both the alsa and wasapi backends only enqueue these commands and do not process +/// them immediately. +#[derive(Debug, Fail)] +pub enum PauseStreamError { + /// See the `BackendSpecificError` docs for more information about this error variant. + #[fail(display = "{}", err)] + BackendSpecific { + #[fail(cause)] + err: BackendSpecificError, + } +} + /// An iterator yielding all `Device`s currently available to the system. /// /// Can be empty if the system does not support audio in general. @@ -522,7 +552,7 @@ impl EventLoop { /// If the stream does not exist, this function can either panic or be a no-op. /// #[inline] - pub fn play_stream(&self, stream: StreamId) { + pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { self.0.play_stream(stream.0) } @@ -537,7 +567,7 @@ impl EventLoop { /// If the stream does not exist, this function can either panic or be a no-op. /// #[inline] - pub fn pause_stream(&self, stream: StreamId) { + pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { self.0.pause_stream(stream.0) } @@ -789,6 +819,18 @@ impl From for BuildStreamError { } } +impl From for PlayStreamError { + fn from(err: BackendSpecificError) -> Self { + PlayStreamError::BackendSpecific { err } + } +} + +impl From for PauseStreamError { + fn from(err: BackendSpecificError) -> Self { + PauseStreamError::BackendSpecific { err } + } +} + // If a backend does not provide an API for retrieving supported formats, we query it with a bunch // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // diff --git a/src/null/mod.rs b/src/null/mod.rs index dc41f77..c7e4cb7 100644 --- a/src/null/mod.rs +++ b/src/null/mod.rs @@ -7,6 +7,8 @@ use DefaultFormatError; use DevicesError; use DeviceNameError; use Format; +use PauseStreamError; +use PlayStreamError; use SupportedFormatsError; use StreamData; use SupportedFormat; @@ -42,12 +44,12 @@ impl EventLoop { } #[inline] - pub fn play_stream(&self, _: StreamId) { + pub fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> { panic!() } #[inline] - pub fn pause_stream(&self, _: StreamId) { + pub fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> { panic!() } } diff --git a/src/wasapi/stream.rs b/src/wasapi/stream.rs index ae13c26..b8070a4 100644 --- a/src/wasapi/stream.rs +++ b/src/wasapi/stream.rs @@ -23,6 +23,8 @@ use std::sync::atomic::Ordering; use BackendSpecificError; use BuildStreamError; use Format; +use PauseStreamError; +use PlayStreamError; use SampleFormat; use StreamData; use UnknownTypeOutputBuffer; @@ -642,13 +644,15 @@ impl EventLoop { } #[inline] - pub fn play_stream(&self, stream: StreamId) { + pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { self.push_command(Command::PlayStream(stream)); + Ok(()) } #[inline] - pub fn pause_stream(&self, stream: StreamId) { + pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { self.push_command(Command::PauseStream(stream)); + Ok(()) } #[inline] From a733bdb4f9b37d9ff471a93275802ed17dace070 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 03:11:20 +0200 Subject: [PATCH 11/17] Fix compile errors on macOS --- src/coreaudio/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/coreaudio/mod.rs b/src/coreaudio/mod.rs index 40ccf17..1f5b831 100644 --- a/src/coreaudio/mod.rs +++ b/src/coreaudio/mod.rs @@ -10,7 +10,6 @@ use Format; use PauseStreamError; use PlayStreamError; use SupportedFormatsError; -use Sample; use SampleFormat; use SampleRate; use StreamData; @@ -55,7 +54,6 @@ use self::coreaudio::sys::{ kAudioFormatFlagIsFloat, kAudioFormatFlagIsPacked, kAudioFormatLinearPCM, - kAudioHardwareNoError, kAudioObjectPropertyElementMaster, kAudioObjectPropertyScopeOutput, kAudioOutputUnitProperty_CurrentDevice, @@ -790,7 +788,7 @@ impl EventLoop { if !stream.playing { if let Err(e) = stream.audio_unit.start() { - let description = format!("{}", std::error::Error::description(e)); + let description = format!("{}", std::error::Error::description(&e)); let err = BackendSpecificError { description }; return Err(err.into()); } @@ -805,7 +803,7 @@ impl EventLoop { if stream.playing { if let Err(e) = stream.audio_unit.stop() { - let description = format!("{}", std::error::Error::description(e)); + let description = format!("{}", std::error::Error::description(&e)); let err = BackendSpecificError { description }; return Err(err.into()); } From 0b6e66d38e69484b6c48508f011f378432436013 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 14:55:21 +0200 Subject: [PATCH 12/17] Update examples to use std Termination --- examples/beep.rs | 11 ++++++----- examples/enumerate.rs | 17 ++++++++++------- examples/feedback.rs | 24 +++++++++++++----------- examples/record_wav.rs | 15 ++++++++------- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index 82e154d..fe5dfb6 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,11 +1,12 @@ extern crate cpal; +extern crate failure; -fn main() { - let device = cpal::default_output_device().expect("Failed to get default output device"); - let format = device.default_output_format().expect("Failed to get default output format"); +fn main() -> Result<(), failure::Error> { + let device = cpal::default_output_device().expect("failed to find a default output device"); + let format = device.default_output_format()?; let event_loop = cpal::EventLoop::new(); - let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); - event_loop.play_stream(stream_id.clone()).unwrap(); + let stream_id = event_loop.build_output_stream(&device, &format)?; + event_loop.play_stream(stream_id.clone())?; let sample_rate = format.sample_rate.0 as f32; let mut sample_clock = 0f32; diff --git a/examples/enumerate.rs b/examples/enumerate.rs index c082904..ba2e5ab 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -1,15 +1,16 @@ extern crate cpal; +extern crate failure; -fn main() { - println!("Default Input Device:\n {:?}", cpal::default_input_device().map(|e| e.name().unwrap())); - println!("Default Output Device:\n {:?}", cpal::default_output_device().map(|e| e.name().unwrap())); +fn main() -> Result<(), failure::Error> { + let default_in = cpal::default_input_device().map(|e| e.name().unwrap()); + let default_out = cpal::default_output_device().map(|e| e.name().unwrap()); + println!("Default Input Device:\n {:?}", default_in); + println!("Default Output Device:\n {:?}", default_out); - let devices = cpal::devices().expect("failed to enumerate devices"); + let devices = cpal::devices()?; println!("Devices: "); for (device_index, device) in devices.enumerate() { - println!("{}. \"{}\"", - device_index + 1, - device.name().unwrap()); + println!("{}. \"{}\"", device_index + 1, device.name()?); // Input formats if let Ok(fmt) = device.default_input_format() { @@ -47,4 +48,6 @@ fn main() { } } } + + Ok(()) } diff --git a/examples/feedback.rs b/examples/feedback.rs index eaa22a9..18dd62a 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -7,26 +7,27 @@ //! precisely synchronised. extern crate cpal; +extern crate failure; const LATENCY_MS: f32 = 150.0; -fn main() { +fn main() -> Result<(), failure::Error> { let event_loop = cpal::EventLoop::new(); // Default devices. - let input_device = cpal::default_input_device().expect("Failed to get default input device"); - let output_device = cpal::default_output_device().expect("Failed to get default output device"); - println!("Using default input device: \"{}\"", input_device.name().unwrap()); - println!("Using default output device: \"{}\"", output_device.name().unwrap()); + let input_device = cpal::default_input_device().expect("failed to get default input device"); + let output_device = cpal::default_output_device().expect("failed to get default output device"); + println!("Using default input device: \"{}\"", input_device.name()?); + println!("Using default output device: \"{}\"", output_device.name()?); // We'll try and use the same format between streams to keep it simple - let mut format = input_device.default_input_format().expect("Failed to get default format"); + let mut format = input_device.default_input_format()?; format.data_type = cpal::SampleFormat::F32; // Build streams. println!("Attempting to build both streams with `{:?}`.", format); - let input_stream_id = event_loop.build_input_stream(&input_device, &format).unwrap(); - let output_stream_id = event_loop.build_output_stream(&output_device, &format).unwrap(); + let input_stream_id = event_loop.build_input_stream(&input_device, &format)?; + let output_stream_id = event_loop.build_output_stream(&output_device, &format)?; println!("Successfully built streams."); // Create a delay in case the input and output devices aren't synced. @@ -38,13 +39,13 @@ fn main() { // Fill the samples with 0.0 equal to the length of the delay. for _ in 0..latency_samples { - tx.send(0.0).unwrap(); + tx.send(0.0)?; } // Play the streams. println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS); - event_loop.play_stream(input_stream_id.clone()).unwrap(); - event_loop.play_stream(output_stream_id.clone()).unwrap(); + event_loop.play_stream(input_stream_id.clone())?; + event_loop.play_stream(output_stream_id.clone())?; // Run the event loop on a separate thread. std::thread::spawn(move || { @@ -87,4 +88,5 @@ fn main() { println!("Playing for 3 seconds... "); std::thread::sleep(std::time::Duration::from_secs(3)); println!("Done!"); + Ok(()) } diff --git a/examples/record_wav.rs b/examples/record_wav.rs index f7aa019..61489f0 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -3,23 +3,23 @@ //! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav". extern crate cpal; +extern crate failure; extern crate hound; -fn main() { +fn main() -> Result<(), failure::Error> { // Setup the default input device and stream with the default input format. let device = cpal::default_input_device().expect("Failed to get default input device"); - println!("Default input device: {}", device.name().unwrap()); + println!("Default input device: {}", device.name()?); let format = device.default_input_format().expect("Failed to get default input format"); println!("Default input format: {:?}", format); let event_loop = cpal::EventLoop::new(); - let stream_id = event_loop.build_input_stream(&device, &format) - .expect("Failed to build input stream"); - event_loop.play_stream(stream_id).unwrap(); + let stream_id = event_loop.build_input_stream(&device, &format)?; + event_loop.play_stream(stream_id)?; // The WAV file we're recording to. const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav"); let spec = wav_spec_from_format(&format); - let writer = hound::WavWriter::create(PATH, spec).unwrap(); + let writer = hound::WavWriter::create(PATH, spec)?; let writer = std::sync::Arc::new(std::sync::Mutex::new(Some(writer))); // A flag to indicate that recording is in progress. @@ -73,8 +73,9 @@ fn main() { // Let recording go for roughly three seconds. std::thread::sleep(std::time::Duration::from_secs(3)); recording.store(false, std::sync::atomic::Ordering::Relaxed); - writer.lock().unwrap().take().unwrap().finalize().unwrap(); + writer.lock().unwrap().take().unwrap().finalize()?; println!("Recording {} complete!", PATH); + Ok(()) } fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat { From 6164f8314730c1736e296e19fb94260f7eefe227 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 23:32:00 +1000 Subject: [PATCH 13/17] Fix compiler errors in wasapi backend after testing on windows --- src/wasapi/device.rs | 6 +++--- src/wasapi/mod.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wasapi/device.rs b/src/wasapi/device.rs index 2cac6aa..a70b591 100644 --- a/src/wasapi/device.rs +++ b/src/wasapi/device.rs @@ -409,7 +409,7 @@ impl Device { // Retrieve the `IAudioClient`. let lock = match self.ensure_future_audio_client() { Ok(lock) => lock, - Err(e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { + Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { return Err(SupportedFormatsError::DeviceNotAvailable) } Err(e) => { @@ -748,11 +748,11 @@ impl Devices { // can fail if the parameter is null, which should never happen check_result_backend_specific((*collection).GetCount(&mut count))?; - Devices { + Ok(Devices { collection: collection, total_count: count, next_item: 0, - } + }) } } } diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index 7fe9de8..6fb462b 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -21,10 +21,10 @@ fn check_result(result: HRESULT) -> Result<(), IoError> { fn check_result_backend_specific(result: HRESULT) -> Result<(), BackendSpecificError> { match check_result(result) { - Ok(()) => Ok(()) + Ok(()) => Ok(()), Err(err) => { let description = format!("{}", err); - return BackendSpecificError { description } + return Err(BackendSpecificError { description }); } } } From 61c3520d7099c91f749aa007366cf780de4f2aea Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 15:50:30 +0200 Subject: [PATCH 14/17] Try using a slightly more modern yml to address appveyor build bug This uses the script from here with all comments stripped: https://github.com/starkat99/appveyor-rust/ The beta targets have also been removed to keep build times low. --- appveyor.yml | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 4da63d8..fa825fb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,16 +1,38 @@ +os: Visual Studio 2015 + +environment: + matrix: + # MSVC + - channel: stable + target: x86_64-pc-windows-msvc + - channel: stable + target: i686-pc-windows-msvc + - channel: nightly + target: x86_64-pc-windows-msvc + - channel: nightly + target: i686-pc-windows-msvc + # GNU + - channel: stable + target: x86_64-pc-windows-gnu + - channel: stable + target: i686-pc-windows-gnu + - channel: nightly + target: x86_64-pc-windows-gnu + - channel: nightly + target: i686-pc-windows-gnu + +matrix: + allow_failures: + - channel: nightly + install: - - ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-nightly-i686-pc-windows-gnu.exe' - - ps: Start-FileDownload 'https://static.rust-lang.org/cargo-dist/cargo-nightly-i686-pc-windows-gnu.tar.gz' - - rust-nightly-i686-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" - - 7z e cargo-nightly-i686-pc-windows-gnu.tar.gz - - 7z x cargo-nightly-i686-pc-windows-gnu.tar - - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin - - SET PATH=%PATH%;%CD%\cargo-nightly-i686-pc-windows-gnu\bin - - rustc -V - - cargo -V + - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe + - rustup-init -yv --default-toolchain %channel% --default-host %target% + - set PATH=%PATH%;%USERPROFILE%\.cargo\bin + - rustc -vV + - cargo -vV build: false test_script: - - cargo build --verbose -# - cargo test --verbose +- cargo test --verbose %cargoflags% From 2667547a9c5da85afcee570822b69a68b5412d8f Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 15:57:15 +0200 Subject: [PATCH 15/17] Update lib docs to allow for testing on devices with no audio --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2a273bb..3b6c5c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ //! `default_*_device()` functions return an `Option` in case no device is available for that //! stream type on the system. //! -//! ``` +//! ```no_run //! let device = cpal::default_output_device().expect("no output device available"); //! ``` //! @@ -60,10 +60,10 @@ //! //! Now we must start the stream. This is done with the `play_stream()` method on the event loop. //! -//! ``` +//! ```no_run //! # let event_loop: cpal::EventLoop = return; //! # let stream_id: cpal::StreamId = return; -//! event_loop.play_stream(stream_id); +//! event_loop.play_stream(stream_id).expect("failed to play_stream"); //! ``` //! //! Now everything is ready! We call `run()` on the `event_loop` to begin processing. From d1a4f456de3dc0666f606e01857d3602a9ccd18a Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 16:08:01 +0200 Subject: [PATCH 16/17] Remove 32-bit appveyor targets. Only build nightly on MSVC. As appveyor only does one job at a time, this should help to keep appveyor builds under 5 minutes... Otherwise we're looking at 15+ min build times. --- appveyor.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index fa825fb..2b593fb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,21 +5,11 @@ environment: # MSVC - channel: stable target: x86_64-pc-windows-msvc - - channel: stable - target: i686-pc-windows-msvc - channel: nightly target: x86_64-pc-windows-msvc - - channel: nightly - target: i686-pc-windows-msvc # GNU - channel: stable target: x86_64-pc-windows-gnu - - channel: stable - target: i686-pc-windows-gnu - - channel: nightly - target: x86_64-pc-windows-gnu - - channel: nightly - target: i686-pc-windows-gnu matrix: allow_failures: From e2ec0eace2002b9e311ef11fa28fd26566446b1b Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Fri, 21 Jun 2019 16:17:58 +0200 Subject: [PATCH 17/17] Remove gnu target to avoid need to install gcc for cc crate --- appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 2b593fb..f77a534 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,9 +7,6 @@ environment: target: x86_64-pc-windows-msvc - channel: nightly target: x86_64-pc-windows-msvc - # GNU - - channel: stable - target: x86_64-pc-windows-gnu matrix: allow_failures: