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:
parent
cf84ab906f
commit
42fc702f53
|
@ -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!("{}. \"{}\"",
|
||||
|
|
|
@ -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<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 {
|
||||
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<Vec<AudioDeviceID>, OSStatus> {
|
|||
|
||||
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 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<Device> {
|
||||
|
|
|
@ -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<Self, DevicesError> {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Devices {
|
||||
fn default() -> Devices {
|
||||
// We produce an empty iterator if the WebAudio API isn't available.
|
||||
|
|
49
src/lib.rs
49
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<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.
|
||||
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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<Self, DevicesError> {
|
||||
Ok(Devices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Devices {
|
||||
type Item = Device;
|
||||
|
||||
|
|
|
@ -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<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 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;
|
||||
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue