Skip to content

Commit

Permalink
Add BackendSpecificError. Add new DevicesError.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mitchmindtree committed Jun 20, 2019
1 parent cf84ab9 commit 42fc702
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 56 deletions.
2 changes: 1 addition & 1 deletion examples/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!("{}. \"{}\"",
Expand Down
41 changes: 23 additions & 18 deletions src/alsa/enumerate.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use {BackendSpecificError, DevicesError};
use super::Device;
use super::alsa;
use super::check_errors;
Expand All @@ -13,6 +14,28 @@ pub struct Devices {
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 Sync for Devices {
Expand All @@ -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;

Expand Down
27 changes: 17 additions & 10 deletions src/coreaudio/enumerate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use SupportedFormat;
use {BackendSpecificError, DevicesError, SupportedFormat};
use std::mem;
use std::ptr::null;
use std::vec::IntoIter as VecIntoIter;
Expand Down Expand Up @@ -64,20 +64,27 @@ unsafe fn audio_devices() -> Result<Vec<AudioDeviceID>, OSStatus> {

pub struct Devices(VecIntoIter<AudioDeviceID>);

unsafe impl Send for Devices {
}
unsafe impl Sync for Devices {
}

impl Default for Devices {
fn default() -> Self {
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
let devices = unsafe {
audio_devices().expect("failed to get audio output devices")
match audio_devices() {
Ok(devices) => devices,
Err(os_status) => {
let description = format!("{}", os_status);
let err = BackendSpecificError { description };
return Err(err.into());
}
}
};
Devices(devices.into_iter())
Ok(Devices(devices.into_iter()))
}
}

unsafe impl Send for Devices {
}
unsafe impl Sync for Devices {
}

impl Iterator for Devices {
type Item = Device;
fn next(&mut self) -> Option<Device> {
Expand Down
8 changes: 8 additions & 0 deletions src/emscripten/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use stdweb::web::set_timeout;

use BuildStreamError;
use DefaultFormatError;
use DevicesError;
use Format;
use SupportedFormatsError;
use StreamData;
Expand Down Expand Up @@ -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<Self, DevicesError> {
Ok(Self::default())
}
}

impl Default for Devices {
fn default() -> Devices {
// We produce an empty iterator if the WebAudio API isn't available.
Expand Down
49 changes: 43 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Devices, DevicesError> {
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<InputDevices, DevicesError> {
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<OutputDevices, DevicesError> {
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.
Expand Down Expand Up @@ -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
// of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa.
//
Expand Down
7 changes: 7 additions & 0 deletions src/null/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::marker::PhantomData;

use BuildStreamError;
use DefaultFormatError;
use DevicesError;
use Format;
use SupportedFormatsError;
use StreamData;
Expand Down Expand Up @@ -56,6 +57,12 @@ pub struct StreamId;
#[derive(Default)]
pub struct Devices;

impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Devices)
}
}

impl Iterator for Devices {
type Item = Device;

Expand Down
41 changes: 22 additions & 19 deletions src/wasapi/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -688,36 +691,22 @@ pub struct Devices {
next_item: u32,
}

unsafe impl Send for Devices {
}
unsafe impl Sync for Devices {
}

impl Drop for Devices {
#[inline]
fn drop(&mut self) {
unsafe {
(*self.collection).Release();
}
}
}

impl Default for Devices {
fn default() -> Devices {
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(
check_result_backend_specific(
(*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();
check_result_backend_specific((*collection).GetCount(&mut count))?;

Devices {
collection: collection,
Expand All @@ -728,6 +717,20 @@ impl Default for Devices {
}
}

unsafe impl Send for Devices {
}
unsafe impl Sync for Devices {
}

impl Drop for Devices {
#[inline]
fn drop(&mut self) {
unsafe {
(*self.collection).Release();
}
}
}

impl Iterator for Devices {
type Item = Device;

Expand Down
14 changes: 12 additions & 2 deletions src/wasapi/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 }
}
}
}

0 comments on commit 42fc702

Please sign in to comment.