Add proper error handling

This commit is contained in:
Pierre Krieger 2015-09-01 14:17:57 +02:00
parent 1985c346ac
commit 98b931edff
4 changed files with 117 additions and 27 deletions

View File

@ -2,7 +2,7 @@ extern crate cpal;
fn main() { fn main() {
let endpoint = cpal::get_default_endpoint().unwrap(); 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(); let mut channel = cpal::Voice::new(&endpoint, &format).unwrap();
// Produce a sinusoid of maximum amplitude. // Produce a sinusoid of maximum amplitude.

View File

@ -33,6 +33,7 @@ extern crate lazy_static;
pub use samples_formats::{SampleFormat, Sample}; pub use samples_formats::{SampleFormat, Sample};
use std::fmt;
use std::error::Error; use std::error::Error;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -87,8 +88,10 @@ pub struct Endpoint(cpal_impl::Endpoint);
impl Endpoint { impl Endpoint {
/// Returns an iterator that produces the list of formats that are supported by the backend. /// Returns an iterator that produces the list of formats that are supported by the backend.
pub fn get_supported_formats_list(&self) -> SupportedFormatsIterator { pub fn get_supported_formats_list(&self) -> Result<SupportedFormatsIterator,
SupportedFormatsIterator(self.0.get_supported_formats_list()) FormatsEnumerationError>
{
Ok(SupportedFormatsIterator(try!(self.0.get_supported_formats_list())))
} }
} }
@ -146,6 +149,61 @@ pub enum UnknownTypeBuffer<'a> {
F32(Buffer<'a, f32>), 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 /// Controls a sound output. A typical application has one `Voice` for each sound
/// it wants to output. /// it wants to output.
/// ///
@ -163,7 +221,7 @@ pub struct Voice(cpal_impl::Voice);
impl Voice { impl Voice {
/// Builds a new channel. /// Builds a new channel.
#[inline] #[inline]
pub fn new(endpoint: &Endpoint, format: &Format) -> Result<Voice, Box<Error>> { pub fn new(endpoint: &Endpoint, format: &Format) -> Result<Voice, CreationError> {
let channel = try!(cpal_impl::Voice::new(&endpoint.0, format)); let channel = try!(cpal_impl::Voice::new(&endpoint.0, format));
Ok(Voice(channel)) Ok(Voice(channel))
} }

View File

@ -8,6 +8,7 @@ use std::ptr;
use std::mem; use std::mem;
use Format; use Format;
use FormatsEnumerationError;
use SamplesRate; use SamplesRate;
use SampleFormat; use SampleFormat;
@ -58,27 +59,39 @@ impl Endpoint {
} }
/// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it. /// Ensures that `future_audio_client` contains a `Some` and returns a locked mutex to it.
fn ensure_future_audio_client(&self) -> MutexGuard<Option<IAudioClientWrapper>> { fn ensure_future_audio_client(&self) -> Result<MutexGuard<Option<IAudioClientWrapper>>, IoError> {
let mut lock = self.future_audio_client.lock().unwrap(); let mut lock = self.future_audio_client.lock().unwrap();
if lock.is_some() { if lock.is_some() {
return lock; return Ok(lock);
} }
let audio_client: *mut winapi::IAudioClient = unsafe { let audio_client: *mut winapi::IAudioClient = unsafe {
let mut audio_client = mem::uninitialized(); let mut audio_client = mem::uninitialized();
let hresult = (*self.device).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL, let hresult = (*self.device).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL,
ptr::null_mut(), &mut audio_client); ptr::null_mut(), &mut audio_client);
// can fail if the device has been disconnected since we enumerated it, or if // can fail if the device has been disconnected since we enumerated it, or if
// the device doesn't support playback for some reason // 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 _ audio_client as *mut _
}; };
*lock = Some(IAudioClientWrapper(audio_client)); *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<SupportedFormatsIterator, FormatsEnumerationError>
{
// We always create voices in shared mode, therefore all samples go through an audio // We always create voices in shared mode, therefore all samples go through an audio
// processor to mix them together. // processor to mix them together.
// However there is no way to query the list of all formats that are supported by the // 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` // initializing COM because we call `CoTaskMemFree`
com::com_initialized(); 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; let client = lock.unwrap().0;
unsafe { unsafe {
@ -108,7 +125,7 @@ impl Endpoint {
ole32::CoTaskMemFree(format_ptr as *mut _); ole32::CoTaskMemFree(format_ptr as *mut _);
Some(format).into_iter() Ok(Some(format).into_iter())
} }
} }
} }

View File

@ -4,13 +4,13 @@ use super::winapi;
use super::Endpoint; use super::Endpoint;
use super::check_result; use super::check_result;
use std::io::Error as IoError;
use std::cmp; use std::cmp;
use std::slice; use std::slice;
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use std::marker::PhantomData; use std::marker::PhantomData;
use CreationError;
use Format; use Format;
pub struct Voice { pub struct Voice {
@ -28,22 +28,18 @@ unsafe impl Send for Voice {}
unsafe impl Sync for Voice {} unsafe impl Sync for Voice {}
impl Voice { impl Voice {
pub fn new(end_point: &Endpoint, format: &Format) -> Result<Voice, IoError> { pub fn new(end_point: &Endpoint, format: &Format) -> Result<Voice, CreationError> {
// FIXME: release everything // FIXME: release everything
unsafe { unsafe {
// making sure that COM is initialized // making sure that COM is initialized
// it's not actually sure that this is required, but when in doubt do it // it's not actually sure that this is required, but when in doubt do it
com::com_initialized(); com::com_initialized();
// activating the end point in order to get a `IAudioClient` // obtaining a `IAudioClient`
let audio_client: *mut winapi::IAudioClient = { let audio_client = match end_point.build_audioclient() {
let mut audio_client = mem::uninitialized(); Err(ref e) if e.raw_os_error() == Some(winapi::AUDCLNT_E_DEVICE_INVALIDATED) =>
let hresult = (*end_point.device).Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL, return Err(CreationError::DeviceNotAvailable),
ptr::null_mut(), &mut audio_client); e => e.unwrap(),
// 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 _
}; };
// computing the format and initializing the device // computing the format and initializing the device
@ -61,7 +57,12 @@ impl Voice {
let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized(); let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized();
let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED, let hresult = (*audio_client).IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
&format_attempt, &mut format_ptr); &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() { let format = if format_ptr.is_null() {
&format_attempt &format_attempt
@ -78,7 +79,11 @@ impl Voice {
ole32::CoTaskMemFree(format_ptr as *mut _); 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 format_copy
}; };
@ -87,7 +92,11 @@ impl Voice {
let max_frames_in_buffer = { let max_frames_in_buffer = {
let mut max_frames_in_buffer = mem::uninitialized(); let mut max_frames_in_buffer = mem::uninitialized();
let hresult = (*audio_client).GetBufferSize(&mut max_frames_in_buffer); 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 max_frames_in_buffer
}; };
@ -95,8 +104,14 @@ impl Voice {
let render_client = { let render_client = {
let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized(); let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized();
let hresult = (*audio_client).GetService(&winapi::IID_IAudioRenderClient, let hresult = (*audio_client).GetService(&winapi::IID_IAudioRenderClient,
mem::transmute(&mut render_client)); &mut render_client as *mut *mut winapi::IAudioRenderClient
try!(check_result(hresult)); 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 &mut *render_client
}; };