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.
This commit is contained in:
mitchmindtree 2019-06-20 22:37:36 +02:00
parent cf84ab906f
commit 42fc702f53
8 changed files with 140 additions and 63 deletions

View File

@ -4,7 +4,7 @@ fn main() {
println!("Default Input Device:\n {:?}", cpal::default_input_device().map(|e| e.name())); 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 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: "); println!("Devices: ");
for (device_index, device) in devices.enumerate() { for (device_index, device) in devices.enumerate() {
println!("{}. \"{}\"", println!("{}. \"{}\"",

View File

@ -1,3 +1,4 @@
use {BackendSpecificError, DevicesError};
use super::Device; use super::Device;
use super::alsa; use super::alsa;
use super::check_errors; use super::check_errors;
@ -13,6 +14,28 @@ pub struct Devices {
next_str: *const *const u8, next_str: *const *const u8,
} }
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
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 Send for Devices {
} }
unsafe impl Sync 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 { impl Iterator for Devices {
type Item = Device; type Item = Device;

View File

@ -1,4 +1,4 @@
use SupportedFormat; use {BackendSpecificError, DevicesError, SupportedFormat};
use std::mem; use std::mem;
use std::ptr::null; use std::ptr::null;
use std::vec::IntoIter as VecIntoIter; use std::vec::IntoIter as VecIntoIter;
@ -64,20 +64,27 @@ unsafe fn audio_devices() -> Result<Vec<AudioDeviceID>, OSStatus> {
pub struct Devices(VecIntoIter<AudioDeviceID>); pub struct Devices(VecIntoIter<AudioDeviceID>);
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
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 Send for Devices {
} }
unsafe impl Sync 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 { impl Iterator for Devices {
type Item = Device; type Item = Device;
fn next(&mut self) -> Option<Device> { fn next(&mut self) -> Option<Device> {

View File

@ -10,6 +10,7 @@ use stdweb::web::set_timeout;
use BuildStreamError; use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DevicesError;
use Format; use Format;
use SupportedFormatsError; use SupportedFormatsError;
use StreamData; use StreamData;
@ -183,6 +184,13 @@ fn is_webaudio_available() -> bool {
// Content is false if the iterator is empty. // Content is false if the iterator is empty.
pub struct Devices(bool); pub struct Devices(bool);
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Self::default())
}
}
impl Default for Devices { impl Default for Devices {
fn default() -> Devices { fn default() -> Devices {
// We produce an empty iterator if the WebAudio API isn't available. // We produce an empty iterator if the WebAudio API isn't available.

View File

@ -279,6 +279,37 @@ pub struct SupportedInputFormats(cpal_impl::SupportedInputFormats);
/// See [`Device::supported_output_formats()`](struct.Device.html#method.supported_output_formats). /// See [`Device::supported_output_formats()`](struct.Device.html#method.supported_output_formats).
pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats); 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. /// Error that can happen when enumerating the list of supported formats.
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
pub enum SupportedFormatsError { pub enum SupportedFormatsError {
@ -331,34 +362,34 @@ pub enum BuildStreamError {
/// ///
/// Can be empty if the system does not support audio in general. /// Can be empty if the system does not support audio in general.
#[inline] #[inline]
pub fn devices() -> Devices { pub fn devices() -> Result<Devices, DevicesError> {
Devices(Default::default()) Ok(Devices(cpal_impl::Devices::new()?))
} }
/// An iterator yielding all `Device`s currently available to the system that support one or more /// An iterator yielding all `Device`s currently available to the system that support one or more
/// input stream formats. /// input stream formats.
/// ///
/// Can be empty if the system does not support audio input. /// Can be empty if the system does not support audio input.
pub fn input_devices() -> InputDevices { pub fn input_devices() -> Result<InputDevices, DevicesError> {
fn supports_input(device: &Device) -> bool { fn supports_input(device: &Device) -> bool {
device.supported_input_formats() device.supported_input_formats()
.map(|mut iter| iter.next().is_some()) .map(|mut iter| iter.next().is_some())
.unwrap_or(false) .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 /// An iterator yielding all `Device`s currently available to the system that support one or more
/// output stream formats. /// output stream formats.
/// ///
/// Can be empty if the system does not support audio output. /// Can be empty if the system does not support audio output.
pub fn output_devices() -> OutputDevices { pub fn output_devices() -> Result<OutputDevices, DevicesError> {
fn supports_output(device: &Device) -> bool { fn supports_output(device: &Device) -> bool {
device.supported_output_formats() device.supported_output_formats()
.map(|mut iter| iter.next().is_some()) .map(|mut iter| iter.next().is_some())
.unwrap_or(false) .unwrap_or(false)
} }
devices().filter(supports_output) Ok(devices()?.filter(supports_output))
} }
/// The default input audio device on the system. /// The default input audio device on the system.
@ -704,6 +735,12 @@ impl Iterator for SupportedOutputFormats {
} }
} }
impl From<BackendSpecificError> 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 // 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. // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa.
// //

View File

@ -4,6 +4,7 @@ use std::marker::PhantomData;
use BuildStreamError; use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DevicesError;
use Format; use Format;
use SupportedFormatsError; use SupportedFormatsError;
use StreamData; use StreamData;
@ -56,6 +57,12 @@ pub struct StreamId;
#[derive(Default)] #[derive(Default)]
pub struct Devices; pub struct Devices;
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Devices)
}
}
impl Iterator for Devices { impl Iterator for Devices {
type Item = Device; type Item = Device;

View File

@ -9,7 +9,9 @@ use std::ptr;
use std::slice; use std::slice;
use std::sync::{Arc, Mutex, MutexGuard}; use std::sync::{Arc, Mutex, MutexGuard};
use BackendSpecificError;
use DefaultFormatError; use DefaultFormatError;
use DevicesError;
use Format; use Format;
use SupportedFormatsError; use SupportedFormatsError;
use SampleFormat; use SampleFormat;
@ -18,6 +20,7 @@ use SupportedFormat;
use COMMON_SAMPLE_RATES; use COMMON_SAMPLE_RATES;
use super::check_result; use super::check_result;
use super::check_result_backend_specific;
use super::com; use super::com;
use super::winapi::Interface; use super::winapi::Interface;
use super::winapi::ctypes::c_void; use super::winapi::ctypes::c_void;
@ -688,6 +691,32 @@ pub struct Devices {
next_item: u32, next_item: u32,
} }
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
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 Send for Devices {
} }
unsafe impl Sync 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 { impl Iterator for Devices {
type Item = Device; type Item = Device;

View File

@ -1,10 +1,10 @@
extern crate winapi; extern crate winapi;
use BackendSpecificError;
use self::winapi::um::winnt::HRESULT;
use std::io::Error as IoError; use std::io::Error as IoError;
pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device};
pub use self::stream::{EventLoop, StreamId}; pub use self::stream::{EventLoop, StreamId};
use self::winapi::um::winnt::HRESULT;
mod com; mod com;
mod device; mod device;
@ -18,3 +18,13 @@ fn check_result(result: HRESULT) -> Result<(), IoError> {
Ok(()) 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 }
}
}
}