From 98b931edff8ebada11aab2208cb4d534b42f129d Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Tue, 1 Sep 2015 14:17:57 +0200 Subject: [PATCH] Add proper error handling --- examples/beep.rs | 2 +- src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++++++--- src/wasapi/mod.rs | 31 +++++++++++++++++----- src/wasapi/voice.rs | 47 +++++++++++++++++++++------------ 4 files changed, 117 insertions(+), 27 deletions(-) diff --git a/examples/beep.rs b/examples/beep.rs index 5179b2a..09e3312 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -2,7 +2,7 @@ extern crate cpal; fn main() { let endpoint = cpal::get_default_endpoint().unwrap(); - let format = endpoint.get_supported_formats_list().next().unwrap(); + let format = endpoint.get_supported_formats_list().unwrap().next().unwrap(); let mut channel = cpal::Voice::new(&endpoint, &format).unwrap(); // Produce a sinusoid of maximum amplitude. diff --git a/src/lib.rs b/src/lib.rs index 2e680b3..92e9b0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,7 @@ extern crate lazy_static; pub use samples_formats::{SampleFormat, Sample}; +use std::fmt; use std::error::Error; use std::ops::{Deref, DerefMut}; @@ -87,8 +88,10 @@ pub struct Endpoint(cpal_impl::Endpoint); impl Endpoint { /// Returns an iterator that produces the list of formats that are supported by the backend. - pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator { - SupportedFormatsIterator(self.0.get_supported_formats_list()) + pub fn get_supported_formats_list(&self) -> Result + { + Ok(SupportedFormatsIterator(try!(self.0.get_supported_formats_list()))) } } @@ -146,6 +149,61 @@ pub enum UnknownTypeBuffer<'a> { F32(Buffer<'a, f32>), } +/// Error that can happen when enumerating the list of supported formats. +#[derive(Debug)] +pub enum FormatsEnumerationError { + /// The device no longer exists. This can happen if the device is disconnected while the + /// program is running. + DeviceNotAvailable, +} + +impl fmt::Display for FormatsEnumerationError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "{}", self.description()) + } +} + +impl Error for FormatsEnumerationError { + fn description(&self) -> &str { + match self { + &FormatsEnumerationError::DeviceNotAvailable => { + "The requested device is no longer available (for example, it has been unplugged)." + }, + } + } +} + +/// Error that can happen when creating a `Voice`. +#[derive(Debug)] +pub enum CreationError { + /// The device no longer exists. This can happen if the device is disconnected while the + /// program is running. + DeviceNotAvailable, + + /// The required format is not supported. + FormatNotSupported, +} + +impl fmt::Display for CreationError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "{}", self.description()) + } +} + +impl Error for CreationError { + 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." + }, + } + } +} + /// Controls a sound output. A typical application has one `Voice` for each sound /// it wants to output. /// @@ -163,7 +221,7 @@ pub struct Voice(cpal_impl::Voice); impl Voice { /// Builds a new channel. #[inline] - pub fn new(endpoint: &Endpoint, format: &Format) -> Result> { + pub fn new(endpoint: &Endpoint, format: &Format) -> Result { let channel = try!(cpal_impl::Voice::new(&endpoint.0, format)); Ok(Voice(channel)) } diff --git a/src/wasapi/mod.rs b/src/wasapi/mod.rs index b40184c..7729a44 100644 --- a/src/wasapi/mod.rs +++ b/src/wasapi/mod.rs @@ -8,6 +8,7 @@ use std::ptr; use std::mem; use Format; +use FormatsEnumerationError; use SamplesRate; use SampleFormat; @@ -58,27 +59,39 @@ impl Endpoint { } /// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it. - fn ensure_future_audio_client(&self) -> MutexGuard> { + fn ensure_future_audio_client(&self) -> Result>, IoError> { let mut lock = self.future_audio_client.lock().unwrap(); if lock.is_some() { - return lock; + return Ok(lock); } let audio_client: *mut winapi::IAudioClient = unsafe { let mut audio_client = mem::uninitialized(); let hresult = (*self.device).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL, ptr::null_mut(), &mut audio_client); + // can fail if the device has been disconnected since we enumerated it, or if // the device doesn't support playback for some reason - check_result(hresult).unwrap(); // FIXME: don't unwrap + try!(check_result(hresult)); + assert!(!audio_client.is_null()); audio_client as *mut _ }; *lock = Some(IAudioClientWrapper(audio_client)); - lock + Ok(lock) } - pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator { + /// Returns an uninitialized `IAudioClient`. + fn build_audioclient(&self) -> Result<*mut winapi::IAudioClient, IoError> { + let mut lock = try!(self.ensure_future_audio_client()); + let client = lock.unwrap().0; + *lock = None; + Ok(client) + } + + pub fn get_supported_formats_list(&self) + -> Result + { // We always create voices in shared mode, therefore all samples go through an audio // processor to mix them together. // However there is no way to query the list of all formats that are supported by the @@ -88,7 +101,11 @@ impl Endpoint { // initializing COM because we call `CoTaskMemFree` com::com_initialized(); - let lock = self.ensure_future_audio_client(); + let lock = match self.ensure_future_audio_client() { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(FormatsEnumerationError::DeviceNotAvailable), + e => e.unwrap(), + }; let client = lock.unwrap().0; unsafe { @@ -108,7 +125,7 @@ impl Endpoint { ole32::CoTaskMemFree(format_ptr as *mut _); - Some(format).into_iter() + Ok(Some(format).into_iter()) } } } diff --git a/src/wasapi/voice.rs b/src/wasapi/voice.rs index ee46c1b..79e81f0 100644 --- a/src/wasapi/voice.rs +++ b/src/wasapi/voice.rs @@ -4,13 +4,13 @@ use super::winapi; use super::Endpoint; use super::check_result; -use std::io::Error as IoError; use std::cmp; use std::slice; use std::mem; use std::ptr; use std::marker::PhantomData; +use CreationError; use Format; pub struct Voice { @@ -28,22 +28,18 @@ unsafe impl Send for Voice {} unsafe impl Sync for Voice {} impl Voice { - pub fn new(end_point: &Endpoint, format: &Format) -> Result { + pub fn new(end_point: &Endpoint, format: &Format) -> Result { // FIXME: release everything unsafe { // making sure that COM is initialized // it's not actually sure that this is required, but when in doubt do it com::com_initialized(); - // activating the end point in order to get a `IAudioClient` - let audio_client: *mut winapi::IAudioClient = { - let mut audio_client = mem::uninitialized(); - let hresult = (*end_point.device).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL, - ptr::null_mut(), &mut audio_client); - // can fail if the device has been disconnected since we enumerated it, or if - // the device doesn't support playback for some reason - try!(check_result(hresult)); - audio_client as *mut _ + // obtaining a `IAudioClient` + let audio_client = match end_point.build_audioclient() { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), }; // computing the format and initializing the device @@ -61,7 +57,12 @@ impl Voice { let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized(); let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, &format_attempt, &mut format_ptr); - try!(check_result(hresult)); + + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; let format = if format_ptr.is_null() { &format_attempt @@ -78,7 +79,11 @@ impl Voice { ole32::CoTaskMemFree(format_ptr as *mut _); } - try!(check_result(hresult)); + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; format_copy }; @@ -87,7 +92,11 @@ impl Voice { let max_frames_in_buffer = { let mut max_frames_in_buffer = mem::uninitialized(); let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); - try!(check_result(hresult)); + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; max_frames_in_buffer }; @@ -95,8 +104,14 @@ impl Voice { let render_client = { let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized(); let hresult = (*audio_client).GetService(&winapi::IID_IAudioRenderClient, - mem::transmute(&mut render_client)); - try!(check_result(hresult)); + &mut render_client as *mut *mut winapi::IAudioRenderClient + as *mut _); + match check_result(hresult) { + Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) => + return Err(CreationError::DeviceNotAvailable), + e => e.unwrap(), + }; + &mut *render_client };