Merge pull request #286 from mitchmindtree/error_handling

Error Handling Overhaul
This commit is contained in:
mitchmindtree 2019-06-21 16:30:18 +02:00 committed by GitHub
commit 9cc5df8805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 717 additions and 414 deletions

View File

@ -9,6 +9,7 @@ license = "Apache-2.0"
keywords = ["audio", "sound"] keywords = ["audio", "sound"]
[dependencies] [dependencies]
failure = "0.1.5"
lazy_static = "1.3" lazy_static = "1.3"
[dev-dependencies] [dev-dependencies]

View File

@ -1,16 +1,25 @@
os: Visual Studio 2015
environment:
matrix:
# MSVC
- channel: stable
target: x86_64-pc-windows-msvc
- channel: nightly
target: x86_64-pc-windows-msvc
matrix:
allow_failures:
- channel: nightly
install: install:
- ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-nightly-i686-pc-windows-gnu.exe' - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- ps: Start-FileDownload 'https://static.rust-lang.org/cargo-dist/cargo-nightly-i686-pc-windows-gnu.tar.gz' - rustup-init -yv --default-toolchain %channel% --default-host %target%
- rust-nightly-i686-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" - set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- 7z e cargo-nightly-i686-pc-windows-gnu.tar.gz - rustc -vV
- 7z x cargo-nightly-i686-pc-windows-gnu.tar - cargo -vV
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
- SET PATH=%PATH%;%CD%\cargo-nightly-i686-pc-windows-gnu\bin
- rustc -V
- cargo -V
build: false build: false
test_script: test_script:
- cargo build --verbose - cargo test --verbose %cargoflags%
# - cargo test --verbose

View File

@ -1,11 +1,12 @@
extern crate cpal; extern crate cpal;
extern crate failure;
fn main() { fn main() -> Result<(), failure::Error> {
let device = cpal::default_output_device().expect("Failed to get default output device"); let device = cpal::default_output_device().expect("failed to find a default output device");
let format = device.default_output_format().expect("Failed to get default output format"); let format = device.default_output_format()?;
let event_loop = cpal::EventLoop::new(); let event_loop = cpal::EventLoop::new();
let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); let stream_id = event_loop.build_output_stream(&device, &format)?;
event_loop.play_stream(stream_id.clone()); event_loop.play_stream(stream_id.clone())?;
let sample_rate = format.sample_rate.0 as f32; let sample_rate = format.sample_rate.0 as f32;
let mut sample_clock = 0f32; let mut sample_clock = 0f32;

View File

@ -1,15 +1,16 @@
extern crate cpal; extern crate cpal;
extern crate failure;
fn main() { fn main() -> Result<(), failure::Error> {
println!("Default Input Device:\n {:?}", cpal::default_input_device().map(|e| e.name())); let default_in = cpal::default_input_device().map(|e| e.name().unwrap());
println!("Default Output Device:\n {:?}", cpal::default_output_device().map(|e| e.name())); let default_out = cpal::default_output_device().map(|e| e.name().unwrap());
println!("Default Input Device:\n {:?}", default_in);
println!("Default Output Device:\n {:?}", default_out);
let devices = cpal::devices(); let devices = cpal::devices()?;
println!("Devices: "); println!("Devices: ");
for (device_index, device) in devices.enumerate() { for (device_index, device) in devices.enumerate() {
println!("{}. \"{}\"", println!("{}. \"{}\"", device_index + 1, device.name()?);
device_index + 1,
device.name());
// Input formats // Input formats
if let Ok(fmt) = device.default_input_format() { if let Ok(fmt) = device.default_input_format() {
@ -47,4 +48,6 @@ fn main() {
} }
} }
} }
Ok(())
} }

View File

@ -7,26 +7,27 @@
//! precisely synchronised. //! precisely synchronised.
extern crate cpal; extern crate cpal;
extern crate failure;
const LATENCY_MS: f32 = 150.0; const LATENCY_MS: f32 = 150.0;
fn main() { fn main() -> Result<(), failure::Error> {
let event_loop = cpal::EventLoop::new(); let event_loop = cpal::EventLoop::new();
// Default devices. // Default devices.
let input_device = cpal::default_input_device().expect("Failed to get default input device"); let input_device = cpal::default_input_device().expect("failed to get default input device");
let output_device = cpal::default_output_device().expect("Failed to get default output device"); let output_device = cpal::default_output_device().expect("failed to get default output device");
println!("Using default input device: \"{}\"", input_device.name()); println!("Using default input device: \"{}\"", input_device.name()?);
println!("Using default output device: \"{}\"", output_device.name()); println!("Using default output device: \"{}\"", output_device.name()?);
// We'll try and use the same format between streams to keep it simple // We'll try and use the same format between streams to keep it simple
let mut format = input_device.default_input_format().expect("Failed to get default format"); let mut format = input_device.default_input_format()?;
format.data_type = cpal::SampleFormat::F32; format.data_type = cpal::SampleFormat::F32;
// Build streams. // Build streams.
println!("Attempting to build both streams with `{:?}`.", format); println!("Attempting to build both streams with `{:?}`.", format);
let input_stream_id = event_loop.build_input_stream(&input_device, &format).unwrap(); let input_stream_id = event_loop.build_input_stream(&input_device, &format)?;
let output_stream_id = event_loop.build_output_stream(&output_device, &format).unwrap(); let output_stream_id = event_loop.build_output_stream(&output_device, &format)?;
println!("Successfully built streams."); println!("Successfully built streams.");
// Create a delay in case the input and output devices aren't synced. // Create a delay in case the input and output devices aren't synced.
@ -38,13 +39,13 @@ fn main() {
// Fill the samples with 0.0 equal to the length of the delay. // Fill the samples with 0.0 equal to the length of the delay.
for _ in 0..latency_samples { for _ in 0..latency_samples {
tx.send(0.0).unwrap(); tx.send(0.0)?;
} }
// Play the streams. // Play the streams.
println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS); println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS);
event_loop.play_stream(input_stream_id.clone()); event_loop.play_stream(input_stream_id.clone())?;
event_loop.play_stream(output_stream_id.clone()); event_loop.play_stream(output_stream_id.clone())?;
// Run the event loop on a separate thread. // Run the event loop on a separate thread.
std::thread::spawn(move || { std::thread::spawn(move || {
@ -87,4 +88,5 @@ fn main() {
println!("Playing for 3 seconds... "); println!("Playing for 3 seconds... ");
std::thread::sleep(std::time::Duration::from_secs(3)); std::thread::sleep(std::time::Duration::from_secs(3));
println!("Done!"); println!("Done!");
Ok(())
} }

View File

@ -3,23 +3,23 @@
//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav". //! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav".
extern crate cpal; extern crate cpal;
extern crate failure;
extern crate hound; extern crate hound;
fn main() { fn main() -> Result<(), failure::Error> {
// Setup the default input device and stream with the default input format. // Setup the default input device and stream with the default input format.
let device = cpal::default_input_device().expect("Failed to get default input device"); let device = cpal::default_input_device().expect("Failed to get default input device");
println!("Default input device: {}", device.name()); println!("Default input device: {}", device.name()?);
let format = device.default_input_format().expect("Failed to get default input format"); let format = device.default_input_format().expect("Failed to get default input format");
println!("Default input format: {:?}", format); println!("Default input format: {:?}", format);
let event_loop = cpal::EventLoop::new(); let event_loop = cpal::EventLoop::new();
let stream_id = event_loop.build_input_stream(&device, &format) let stream_id = event_loop.build_input_stream(&device, &format)?;
.expect("Failed to build input stream"); event_loop.play_stream(stream_id)?;
event_loop.play_stream(stream_id);
// The WAV file we're recording to. // The WAV file we're recording to.
const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav"); const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");
let spec = wav_spec_from_format(&format); let spec = wav_spec_from_format(&format);
let writer = hound::WavWriter::create(PATH, spec).unwrap(); let writer = hound::WavWriter::create(PATH, spec)?;
let writer = std::sync::Arc::new(std::sync::Mutex::new(Some(writer))); let writer = std::sync::Arc::new(std::sync::Mutex::new(Some(writer)));
// A flag to indicate that recording is in progress. // A flag to indicate that recording is in progress.
@ -73,8 +73,9 @@ fn main() {
// Let recording go for roughly three seconds. // Let recording go for roughly three seconds.
std::thread::sleep(std::time::Duration::from_secs(3)); std::thread::sleep(std::time::Duration::from_secs(3));
recording.store(false, std::sync::atomic::Ordering::Relaxed); recording.store(false, std::sync::atomic::Ordering::Relaxed);
writer.lock().unwrap().take().unwrap().finalize().unwrap(); writer.lock().unwrap().take().unwrap().finalize()?;
println!("Recording {} complete!", PATH); println!("Recording {} complete!", PATH);
Ok(())
} }
fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat { fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {

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 {
let mut hints = mem::uninitialized();
// TODO: check in which situation this can fail
check_errors(alsa::snd_device_name_hint(-1, b"pcm\0".as_ptr() as *const _, &mut hints))
.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

@ -4,10 +4,14 @@ extern crate libc;
pub use self::enumerate::{Devices, default_input_device, default_output_device}; pub use self::enumerate::{Devices, default_input_device, default_output_device};
use ChannelCount; use ChannelCount;
use CreationError; use BackendSpecificError;
use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DeviceNameError;
use Format; use Format;
use FormatsEnumerationError; use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use SampleFormat; use SampleFormat;
use SampleRate; use SampleRate;
use StreamData; use StreamData;
@ -73,17 +77,24 @@ pub struct Device(String);
impl Device { impl Device {
#[inline] #[inline]
pub fn name(&self) -> String { pub fn name(&self) -> Result<String, DeviceNameError> {
self.0.clone() Ok(self.0.clone())
} }
unsafe fn supported_formats( unsafe fn supported_formats(
&self, &self,
stream_t: alsa::snd_pcm_stream_t, stream_t: alsa::snd_pcm_stream_t,
) -> Result<VecIntoIter<SupportedFormat>, FormatsEnumerationError> ) -> Result<VecIntoIter<SupportedFormat>, SupportedFormatsError>
{ {
let mut handle = mem::uninitialized(); let mut handle = mem::uninitialized();
let device_name = ffi::CString::new(&self.0[..]).expect("Unable to get device name"); let device_name = match ffi::CString::new(&self.0[..]) {
Ok(name) => name,
Err(err) => {
let description = format!("failed to retrieve device name: {}", err);
let err = BackendSpecificError { description };
return Err(err.into());
}
};
match alsa::snd_pcm_open( match alsa::snd_pcm_open(
&mut handle, &mut handle,
@ -92,16 +103,20 @@ impl Device {
alsa::SND_PCM_NONBLOCK, alsa::SND_PCM_NONBLOCK,
) { ) {
-2 | -2 |
-16 /* determined empirically */ => return Err(FormatsEnumerationError::DeviceNotAvailable), -16 /* determined empirically */ => return Err(SupportedFormatsError::DeviceNotAvailable),
-22 => return Err(FormatsEnumerationError::InvalidArgument), -22 => return Err(SupportedFormatsError::InvalidArgument),
e => if check_errors(e).is_err() { e => if let Err(description) = check_errors(e) {
return Err(FormatsEnumerationError::Unknown) let err = BackendSpecificError { description };
return Err(err.into())
} }
} }
let hw_params = HwParams::alloc(); let hw_params = HwParams::alloc();
match check_errors(alsa::snd_pcm_hw_params_any(handle, hw_params.0)) { match check_errors(alsa::snd_pcm_hw_params_any(handle, hw_params.0)) {
Err(_) => return Ok(Vec::new().into_iter()), Err(description) => {
let err = BackendSpecificError { description };
return Err(err.into());
}
Ok(_) => (), Ok(_) => (),
}; };
@ -158,15 +173,26 @@ impl Device {
} }
let mut min_rate = mem::uninitialized(); let mut min_rate = mem::uninitialized();
check_errors(alsa::snd_pcm_hw_params_get_rate_min(hw_params.0, if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_min(
hw_params.0,
&mut min_rate, &mut min_rate,
ptr::null_mut())) ptr::null_mut(),
.expect("unable to get minimum supported rete"); )) {
let description = format!("unable to get minimum supported rate: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let mut max_rate = mem::uninitialized(); let mut max_rate = mem::uninitialized();
check_errors(alsa::snd_pcm_hw_params_get_rate_max(hw_params.0, if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_max(
hw_params.0,
&mut max_rate, &mut max_rate,
ptr::null_mut())) ptr::null_mut(),
.expect("unable to get maximum supported rate"); )) {
let description = format!("unable to get maximum supported rate: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let sample_rates = if min_rate == max_rate { let sample_rates = if min_rate == max_rate {
vec![(min_rate, max_rate)] vec![(min_rate, max_rate)]
@ -212,11 +238,19 @@ impl Device {
}; };
let mut min_channels = mem::uninitialized(); let mut min_channels = mem::uninitialized();
check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels)) if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels)) {
.expect("unable to get minimum supported channel count"); let description = format!("unable to get minimum supported channel count: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let mut max_channels = mem::uninitialized(); let mut max_channels = mem::uninitialized();
check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels)) if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels)) {
.expect("unable to get maximum supported channel count"); let description = format!("unable to get maximum supported channel count: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned let max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned
let supported_channels = (min_channels .. max_channels + 1) let supported_channels = (min_channels .. max_channels + 1)
.filter_map(|num| if alsa::snd_pcm_hw_params_test_channels( .filter_map(|num| if alsa::snd_pcm_hw_params_test_channels(
@ -251,13 +285,13 @@ impl Device {
Ok(output.into_iter()) Ok(output.into_iter())
} }
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> { pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unsafe { unsafe {
self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE) self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE)
} }
} }
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> { pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
unsafe { unsafe {
self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK) self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK)
} }
@ -272,16 +306,16 @@ impl Device {
{ {
let mut formats: Vec<_> = unsafe { let mut formats: Vec<_> = unsafe {
match self.supported_formats(stream_t) { match self.supported_formats(stream_t) {
Err(FormatsEnumerationError::DeviceNotAvailable) => { Err(SupportedFormatsError::DeviceNotAvailable) => {
return Err(DefaultFormatError::DeviceNotAvailable); return Err(DefaultFormatError::DeviceNotAvailable);
}, },
Err(FormatsEnumerationError::InvalidArgument) => { Err(SupportedFormatsError::InvalidArgument) => {
// this happens sometimes when querying for input and output capabilities but // this happens sometimes when querying for input and output capabilities but
// the device supports only one // the device supports only one
return Err(DefaultFormatError::StreamTypeNotSupported); return Err(DefaultFormatError::StreamTypeNotSupported);
} }
Err(FormatsEnumerationError::Unknown) => { Err(SupportedFormatsError::BackendSpecific { err }) => {
return Err(DefaultFormatError::DeviceNotAvailable); return Err(err.into());
} }
Ok(fmts) => fmts.collect(), Ok(fmts) => fmts.collect(),
} }
@ -644,7 +678,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
unsafe { unsafe {
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device"); let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
@ -656,31 +690,43 @@ impl EventLoop {
alsa::SND_PCM_STREAM_CAPTURE, alsa::SND_PCM_STREAM_CAPTURE,
alsa::SND_PCM_NONBLOCK, alsa::SND_PCM_NONBLOCK,
) { ) {
-16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable), -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
-22 => return Err(CreationError::InvalidArgument), -22 => return Err(BuildStreamError::InvalidArgument),
e => if check_errors(e).is_err() { e => if let Err(description) = check_errors(e) {
return Err(CreationError::Unknown); let err = BackendSpecificError { description };
return Err(err.into());
} }
} }
let hw_params = HwParams::alloc(); let hw_params = HwParams::alloc();
set_hw_params_from_format(capture_handle, &hw_params, format); set_hw_params_from_format(capture_handle, &hw_params, format)
.map_err(|description| BackendSpecificError { description })?;
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1; let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format); let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format)
.map_err(|description| BackendSpecificError { description })?;
check_errors(alsa::snd_pcm_prepare(capture_handle)) if let Err(desc) = check_errors(alsa::snd_pcm_prepare(capture_handle)) {
.expect("could not get playback handle"); let description = format!("could not get capture handle: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let num_descriptors = { let num_descriptors = {
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(capture_handle); let num_descriptors = alsa::snd_pcm_poll_descriptors_count(capture_handle);
debug_assert!(num_descriptors >= 1); if num_descriptors == 0 {
let description = "poll descriptor count for capture stream was 0".to_string();
let err = BackendSpecificError { description };
return Err(err.into());
}
num_descriptors as usize num_descriptors as usize
}; };
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows if new_stream_id.0 == usize::max_value() {
return Err(BuildStreamError::StreamIdOverflow);
}
let stream_inner = StreamInner { let stream_inner = StreamInner {
id: new_stream_id.clone(), id: new_stream_id.clone(),
@ -696,8 +742,11 @@ impl EventLoop {
buffer: None, buffer: None,
}; };
check_errors(alsa::snd_pcm_start(capture_handle)) if let Err(desc) = check_errors(alsa::snd_pcm_start(capture_handle)) {
.expect("could not start capture stream"); let description = format!("could not start capture stream: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
self.push_command(Command::NewStream(stream_inner)); self.push_command(Command::NewStream(stream_inner));
Ok(new_stream_id) Ok(new_stream_id)
@ -708,7 +757,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
unsafe { unsafe {
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device"); let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
@ -720,31 +769,43 @@ impl EventLoop {
alsa::SND_PCM_STREAM_PLAYBACK, alsa::SND_PCM_STREAM_PLAYBACK,
alsa::SND_PCM_NONBLOCK, alsa::SND_PCM_NONBLOCK,
) { ) {
-16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable), -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
-22 => return Err(CreationError::InvalidArgument), -22 => return Err(BuildStreamError::InvalidArgument),
e => if check_errors(e).is_err() { e => if let Err(description) = check_errors(e) {
return Err(CreationError::Unknown); let err = BackendSpecificError { description };
return Err(err.into())
} }
} }
let hw_params = HwParams::alloc(); let hw_params = HwParams::alloc();
set_hw_params_from_format(playback_handle, &hw_params, format); set_hw_params_from_format(playback_handle, &hw_params, format)
.map_err(|description| BackendSpecificError { description })?;
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1; let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format); let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format)
.map_err(|description| BackendSpecificError { description })?;
check_errors(alsa::snd_pcm_prepare(playback_handle)) if let Err(desc) = check_errors(alsa::snd_pcm_prepare(playback_handle)) {
.expect("could not get playback handle"); let description = format!("could not get playback handle: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let num_descriptors = { let num_descriptors = {
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(playback_handle); let num_descriptors = alsa::snd_pcm_poll_descriptors_count(playback_handle);
debug_assert!(num_descriptors >= 1); if num_descriptors == 0 {
let description = "poll descriptor count for playback stream was 0".to_string();
let err = BackendSpecificError { description };
return Err(err.into());
}
num_descriptors as usize num_descriptors as usize
}; };
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows if new_stream_id.0 == usize::max_value() {
return Err(BuildStreamError::StreamIdOverflow);
}
let stream_inner = StreamInner { let stream_inner = StreamInner {
id: new_stream_id.clone(), id: new_stream_id.clone(),
@ -778,13 +839,15 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, stream_id: StreamId) { pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
self.push_command(Command::PlayStream(stream_id)); self.push_command(Command::PlayStream(stream_id));
Ok(())
} }
#[inline] #[inline]
pub fn pause_stream(&self, stream_id: StreamId) { pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
self.push_command(Command::PauseStream(stream_id)); self.push_command(Command::PauseStream(stream_id));
Ok(())
} }
} }
@ -794,12 +857,12 @@ unsafe fn set_hw_params_from_format(
format: &Format, format: &Format,
) -> Result<(), String> { ) -> Result<(), String> {
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) { if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) {
return Err("Errors on pcm handle".to_string()); return Err(format!("errors on pcm handle: {}", e));
} }
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_access(pcm_handle, if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_access(pcm_handle,
hw_params.0, hw_params.0,
alsa::SND_PCM_ACCESS_RW_INTERLEAVED)) { alsa::SND_PCM_ACCESS_RW_INTERLEAVED)) {
return Err("Handle not acessible".to_string()); return Err(format!("handle not acessible: {}", e));
} }
let data_type = if cfg!(target_endian = "big") { let data_type = if cfg!(target_endian = "big") {
@ -819,29 +882,34 @@ unsafe fn set_hw_params_from_format(
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format(pcm_handle, if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format(pcm_handle,
hw_params.0, hw_params.0,
data_type)) { data_type)) {
return Err("Format could not be set".to_string()); return Err(format!("format could not be set: {}", e));
} }
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(pcm_handle, if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(pcm_handle,
hw_params.0, hw_params.0,
format.sample_rate.0 as libc::c_uint, format.sample_rate.0 as libc::c_uint,
0)) { 0)) {
return Err("Sample rate could not be set".to_string()); return Err(format!("sample rate could not be set: {}", e));
} }
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(pcm_handle, if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(pcm_handle,
hw_params.0, hw_params.0,
format.channels as format.channels as
libc::c_uint)) { libc::c_uint)) {
return Err("Channel count could not be set".to_string()); return Err(format!("channel count could not be set: {}", e));
} }
// TODO: Review this. 200ms seems arbitrary...
let mut max_buffer_size = format.sample_rate.0 as alsa::snd_pcm_uframes_t / let mut max_buffer_size = format.sample_rate.0 as alsa::snd_pcm_uframes_t /
format.channels as alsa::snd_pcm_uframes_t / format.channels as alsa::snd_pcm_uframes_t /
5; // 200ms of buffer 5; // 200ms of buffer
check_errors(alsa::snd_pcm_hw_params_set_buffer_size_max(pcm_handle, if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_buffer_size_max(pcm_handle,
hw_params.0, hw_params.0,
&mut max_buffer_size)) &mut max_buffer_size))
.unwrap(); {
return Err(format!("max buffer size could not be set: {}", e));
}
if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) { if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) {
return Err("Hardware params could not be set.".to_string()); return Err(format!("hardware params could not be set: {}", e));
} }
Ok(()) Ok(())
@ -850,34 +918,42 @@ unsafe fn set_hw_params_from_format(
unsafe fn set_sw_params_from_format( unsafe fn set_sw_params_from_format(
pcm_handle: *mut alsa::snd_pcm_t, pcm_handle: *mut alsa::snd_pcm_t,
format: &Format, format: &Format,
) -> (usize, usize) ) -> Result<(usize, usize), String>
{ {
let mut sw_params = mem::uninitialized(); // TODO: RAII let mut sw_params = mem::uninitialized(); // TODO: RAII
check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)).unwrap(); if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) {
check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)).unwrap(); return Err(format!("snd_pcm_sw_params_malloc failed: {}", e));
check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle, }
sw_params, if let Err(e) = check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)) {
0)) return Err(format!("snd_pcm_sw_params_current failed: {}", e));
.unwrap(); }
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, 0)) {
return Err(format!("snd_pcm_sw_params_set_start_threshold failed: {}", e));
}
let (buffer_len, period_len) = { let (buffer_len, period_len) = {
let mut buffer = mem::uninitialized(); let mut buffer = mem::uninitialized();
let mut period = mem::uninitialized(); let mut period = mem::uninitialized();
check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period)) if let Err(e) = check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period)) {
.expect("could not initialize buffer"); return Err(format!("failed to initialize buffer: {}", e));
assert!(buffer != 0); }
check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle, if buffer == 0 {
sw_params, return Err(format!("initialization resulted in a null buffer"));
period)) }
.unwrap(); if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle, sw_params, period)) {
return Err(format!("snd_pcm_sw_params_set_avail_min failed: {}", e));
}
let buffer = buffer as usize * format.channels as usize; let buffer = buffer as usize * format.channels as usize;
let period = period as usize * format.channels as usize; let period = period as usize * format.channels as usize;
(buffer, period) (buffer, period)
}; };
check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)).unwrap(); if let Err(e) = check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)) {
return Err(format!("snd_pcm_sw_params failed: {}", e));
}
alsa::snd_pcm_sw_params_free(sw_params); alsa::snd_pcm_sw_params_free(sw_params);
(buffer_len, period_len) Ok((buffer_len, period_len))
} }
/// Wrapper around `hw_params`. /// Wrapper around `hw_params`.
@ -913,8 +989,6 @@ impl Drop for StreamInner {
#[inline] #[inline]
fn check_errors(err: libc::c_int) -> Result<(), String> { fn check_errors(err: libc::c_int) -> Result<(), String> {
use std::ffi;
if err < 0 { if err < 0 {
unsafe { unsafe {
let s = ffi::CStr::from_ptr(alsa::snd_strerror(err)) let s = ffi::CStr::from_ptr(alsa::snd_strerror(err))

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

@ -2,11 +2,14 @@ extern crate coreaudio;
extern crate core_foundation_sys; extern crate core_foundation_sys;
use ChannelCount; use ChannelCount;
use CreationError; use BackendSpecificError;
use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DeviceNameError;
use Format; use Format;
use FormatsEnumerationError; use PauseStreamError;
use Sample; use PlayStreamError;
use SupportedFormatsError;
use SampleFormat; use SampleFormat;
use SampleRate; use SampleRate;
use StreamData; use StreamData;
@ -51,7 +54,6 @@ use self::coreaudio::sys::{
kAudioFormatFlagIsFloat, kAudioFormatFlagIsFloat,
kAudioFormatFlagIsPacked, kAudioFormatFlagIsPacked,
kAudioFormatLinearPCM, kAudioFormatLinearPCM,
kAudioHardwareNoError,
kAudioObjectPropertyElementMaster, kAudioObjectPropertyElementMaster,
kAudioObjectPropertyScopeOutput, kAudioObjectPropertyScopeOutput,
kAudioOutputUnitProperty_CurrentDevice, kAudioOutputUnitProperty_CurrentDevice,
@ -75,7 +77,7 @@ pub struct Device {
} }
impl Device { impl Device {
pub fn name(&self) -> String { pub fn name(&self) -> Result<String, DeviceNameError> {
let property_address = AudioObjectPropertyAddress { let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyDeviceNameCFString, mSelector: kAudioDevicePropertyDeviceNameCFString,
mScope: kAudioDevicePropertyScopeOutput, mScope: kAudioDevicePropertyScopeOutput,
@ -92,23 +94,24 @@ impl Device {
&data_size as *const _ as *mut _, &data_size as *const _ as *mut _,
&device_name as *const _ as *mut _, &device_name as *const _ as *mut _,
); );
if status != kAudioHardwareNoError as i32 { check_os_status(status)?;
return format!("<OSStatus: {:?}>", status);
}
let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8); let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8);
if c_string == null() { if c_string == null() {
return "<null>".into(); let description = "core foundation unexpectedly returned null string".to_string();
let err = BackendSpecificError { description };
return Err(err.into());
} }
CStr::from_ptr(c_string as *mut _) CStr::from_ptr(c_string as *mut _)
}; };
c_str.to_string_lossy().into_owned() Ok(c_str.to_string_lossy().into_owned())
} }
// Logic re-used between `supported_input_formats` and `supported_output_formats`. // Logic re-used between `supported_input_formats` and `supported_output_formats`.
fn supported_formats( fn supported_formats(
&self, &self,
scope: AudioObjectPropertyScope, scope: AudioObjectPropertyScope,
) -> Result<SupportedOutputFormats, FormatsEnumerationError> ) -> Result<SupportedOutputFormats, SupportedFormatsError>
{ {
let mut property_address = AudioObjectPropertyAddress { let mut property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamConfiguration, mSelector: kAudioDevicePropertyStreamConfiguration,
@ -126,9 +129,8 @@ impl Device {
null(), null(),
&data_size as *const _ as *mut _, &data_size as *const _ as *mut _,
); );
if status != kAudioHardwareNoError as i32 { check_os_status(status)?;
unimplemented!();
}
let mut audio_buffer_list: Vec<u8> = vec![]; let mut audio_buffer_list: Vec<u8> = vec![];
audio_buffer_list.reserve_exact(data_size as usize); audio_buffer_list.reserve_exact(data_size as usize);
let status = AudioObjectGetPropertyData( let status = AudioObjectGetPropertyData(
@ -139,9 +141,8 @@ impl Device {
&data_size as *const _ as *mut _, &data_size as *const _ as *mut _,
audio_buffer_list.as_mut_ptr() as *mut _, audio_buffer_list.as_mut_ptr() as *mut _,
); );
if status != kAudioHardwareNoError as i32 { check_os_status(status)?;
unimplemented!();
}
let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList; let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList;
// If there's no buffers, skip. // If there's no buffers, skip.
@ -176,9 +177,8 @@ impl Device {
null(), null(),
&data_size as *const _ as *mut _, &data_size as *const _ as *mut _,
); );
if status != kAudioHardwareNoError as i32 { check_os_status(status)?;
unimplemented!();
}
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>(); let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
let mut ranges: Vec<u8> = vec![]; let mut ranges: Vec<u8> = vec![];
ranges.reserve_exact(data_size as usize); ranges.reserve_exact(data_size as usize);
@ -190,9 +190,8 @@ impl Device {
&data_size as *const _ as *mut _, &data_size as *const _ as *mut _,
ranges.as_mut_ptr() as *mut _, ranges.as_mut_ptr() as *mut _,
); );
if status != kAudioHardwareNoError as i32 { check_os_status(status)?;
unimplemented!();
}
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _; let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges); let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges);
@ -212,11 +211,11 @@ impl Device {
} }
} }
pub fn supported_input_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> { pub fn supported_input_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
self.supported_formats(kAudioObjectPropertyScopeInput) self.supported_formats(kAudioObjectPropertyScopeInput)
} }
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> { pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
self.supported_formats(kAudioObjectPropertyScopeOutput) self.supported_formats(kAudioObjectPropertyScopeOutput)
} }
@ -225,18 +224,25 @@ impl Device {
scope: AudioObjectPropertyScope, scope: AudioObjectPropertyScope,
) -> Result<Format, DefaultFormatError> ) -> Result<Format, DefaultFormatError>
{ {
fn default_format_error_from_os_status(status: OSStatus) -> Option<DefaultFormatError> { fn default_format_error_from_os_status(status: OSStatus) -> Result<(), DefaultFormatError> {
let err = match coreaudio::Error::from_os_status(status) { let err = match coreaudio::Error::from_os_status(status) {
Err(err) => err, Err(err) => err,
Ok(_) => return None, Ok(_) => return Ok(()),
}; };
match err { match err {
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat |
coreaudio::Error::NoKnownSubtype |
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) | coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
coreaudio::Error::AudioCodec(_) | coreaudio::Error::AudioCodec(_) |
coreaudio::Error::AudioFormat(_) => Some(DefaultFormatError::StreamTypeNotSupported), coreaudio::Error::AudioFormat(_) => {
_ => Some(DefaultFormatError::DeviceNotAvailable), Err(DefaultFormatError::StreamTypeNotSupported)
}
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::NoConnection) => {
Err(DefaultFormatError::DeviceNotAvailable)
}
err => {
let description = format!("{}", std::error::Error::description(&err));
let err = BackendSpecificError { description };
Err(err.into())
}
} }
} }
@ -257,12 +263,7 @@ impl Device {
&data_size as *const _ as *mut _, &data_size as *const _ as *mut _,
&asbd as *const _ as *mut _, &asbd as *const _ as *mut _,
); );
default_format_error_from_os_status(status)?;
if status != kAudioHardwareNoError as i32 {
let err = default_format_error_from_os_status(status)
.expect("no known error for OSStatus");
return Err(err);
}
let sample_format = { let sample_format = {
let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag( let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag(
@ -338,15 +339,15 @@ struct StreamInner {
} }
// TODO need stronger error identification // TODO need stronger error identification
impl From<coreaudio::Error> for CreationError { impl From<coreaudio::Error> for BuildStreamError {
fn from(err: coreaudio::Error) -> CreationError { fn from(err: coreaudio::Error) -> BuildStreamError {
match err { match err {
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat | coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat |
coreaudio::Error::NoKnownSubtype | coreaudio::Error::NoKnownSubtype |
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) | coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
coreaudio::Error::AudioCodec(_) | coreaudio::Error::AudioCodec(_) |
coreaudio::Error::AudioFormat(_) => CreationError::FormatNotSupported, coreaudio::Error::AudioFormat(_) => BuildStreamError::FormatNotSupported,
_ => CreationError::DeviceNotAvailable, _ => BuildStreamError::DeviceNotAvailable,
} }
} }
} }
@ -483,7 +484,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
// The scope and element for working with a device's input stream. // The scope and element for working with a device's input stream.
let scope = Scope::Output; let scope = Scope::Output;
@ -512,13 +513,16 @@ impl EventLoop {
// If the requested sample rate is different to the device sample rate, update the device. // If the requested sample rate is different to the device sample rate, update the device.
if sample_rate as u32 != format.sample_rate.0 { if sample_rate as u32 != format.sample_rate.0 {
// In order to avoid breaking existing input streams we `panic!` if there is already an // In order to avoid breaking existing input streams we return an error if there is
// active input stream for this device with the actual sample rate. // already an active input stream for this device with the actual sample rate.
for stream in &*self.streams.lock().unwrap() { for stream in &*self.streams.lock().unwrap() {
if let Some(stream) = stream.as_ref() { if let Some(stream) = stream.as_ref() {
if stream.device_id == device.audio_device_id { if stream.device_id == device.audio_device_id {
panic!("cannot change device sample rate for stream as an existing stream \ let description = "cannot change device sample rate for stream as an \
is already running at the current sample rate."); existing stream is already running at the current sample rate"
.into();
let err = BackendSpecificError { description };
return Err(err.into());
} }
} }
} }
@ -555,7 +559,7 @@ impl EventLoop {
.iter() .iter()
.position(|r| r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate); .position(|r| r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate);
let range_index = match maybe_index { let range_index = match maybe_index {
None => return Err(CreationError::FormatNotSupported), None => return Err(BuildStreamError::FormatNotSupported),
Some(i) => i, Some(i) => i,
}; };
@ -617,7 +621,9 @@ impl EventLoop {
let timer = ::std::time::Instant::now(); let timer = ::std::time::Instant::now();
while sample_rate != reported_rate { while sample_rate != reported_rate {
if timer.elapsed() > ::std::time::Duration::from_secs(1) { if timer.elapsed() > ::std::time::Duration::from_secs(1) {
panic!("timeout waiting for sample rate update for device"); let description = "timeout waiting for sample rate update for device".into();
let err = BackendSpecificError { description };
return Err(err.into());
} }
::std::thread::sleep(::std::time::Duration::from_millis(5)); ::std::thread::sleep(::std::time::Duration::from_millis(5));
} }
@ -700,7 +706,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
let mut audio_unit = audio_unit_from_device(device, false)?; let mut audio_unit = audio_unit_from_device(device, false)?;
@ -776,23 +782,43 @@ impl EventLoop {
streams[stream_id.0] = None; streams[stream_id.0] = None;
} }
pub fn play_stream(&self, stream: StreamId) { pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
let mut streams = self.streams.lock().unwrap(); let mut streams = self.streams.lock().unwrap();
let stream = streams[stream.0].as_mut().unwrap(); let stream = streams[stream.0].as_mut().unwrap();
if !stream.playing { if !stream.playing {
stream.audio_unit.start().unwrap(); if let Err(e) = stream.audio_unit.start() {
let description = format!("{}", std::error::Error::description(&e));
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = true; stream.playing = true;
} }
Ok(())
} }
pub fn pause_stream(&self, stream: StreamId) { pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
let mut streams = self.streams.lock().unwrap(); let mut streams = self.streams.lock().unwrap();
let stream = streams[stream.0].as_mut().unwrap(); let stream = streams[stream.0].as_mut().unwrap();
if stream.playing { if stream.playing {
stream.audio_unit.stop().unwrap(); if let Err(e) = stream.audio_unit.stop() {
let description = format!("{}", std::error::Error::description(&e));
let err = BackendSpecificError { description };
return Err(err.into());
}
stream.playing = false; stream.playing = false;
} }
Ok(())
}
}
fn check_os_status(os_status: OSStatus) -> Result<(), BackendSpecificError> {
match coreaudio::Error::from_os_status(os_status) {
Ok(()) => Ok(()),
Err(err) => {
let description = std::error::Error::description(&err).to_string();
Err(BackendSpecificError { description })
}
} }
} }

View File

@ -8,10 +8,14 @@ use stdweb::unstable::TryInto;
use stdweb::web::TypedArray; use stdweb::web::TypedArray;
use stdweb::web::set_timeout; use stdweb::web::set_timeout;
use CreationError; use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DeviceNameError;
use DevicesError;
use Format; use Format;
use FormatsEnumerationError; use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use StreamData; use StreamData;
use SupportedFormat; use SupportedFormat;
use UnknownTypeOutputBuffer; use UnknownTypeOutputBuffer;
@ -118,12 +122,12 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn build_input_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, CreationError> { pub fn build_input_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, BuildStreamError> {
unimplemented!(); unimplemented!();
} }
#[inline] #[inline]
pub fn build_output_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, CreationError> { pub fn build_output_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, BuildStreamError> {
let stream = js!(return new AudioContext()).into_reference().unwrap(); let stream = js!(return new AudioContext()).into_reference().unwrap();
let mut streams = self.streams.lock().unwrap(); let mut streams = self.streams.lock().unwrap();
@ -145,23 +149,25 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, stream_id: StreamId) { pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
let streams = self.streams.lock().unwrap(); let streams = self.streams.lock().unwrap();
let stream = streams let stream = streams
.get(stream_id.0) .get(stream_id.0)
.and_then(|v| v.as_ref()) .and_then(|v| v.as_ref())
.expect("invalid stream ID"); .expect("invalid stream ID");
js!(@{stream}.resume()); js!(@{stream}.resume());
Ok(())
} }
#[inline] #[inline]
pub fn pause_stream(&self, stream_id: StreamId) { pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
let streams = self.streams.lock().unwrap(); let streams = self.streams.lock().unwrap();
let stream = streams let stream = streams
.get(stream_id.0) .get(stream_id.0)
.and_then(|v| v.as_ref()) .and_then(|v| v.as_ref())
.expect("invalid stream ID"); .expect("invalid stream ID");
js!(@{stream}.suspend()); js!(@{stream}.suspend());
Ok(())
} }
} }
@ -183,6 +189,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.
@ -221,17 +234,17 @@ pub struct Device;
impl Device { impl Device {
#[inline] #[inline]
pub fn name(&self) -> String { pub fn name(&self) -> Result<String, DeviceNameError> {
"Default Device".to_owned() Ok("Default Device".to_owned())
} }
#[inline] #[inline]
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> { pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unimplemented!(); unimplemented!();
} }
#[inline] #[inline]
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> { pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
// TODO: right now cpal's API doesn't allow flexibility here // TODO: right now cpal's API doesn't allow flexibility here
// "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if // "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if
// this ever becomes more flexible, don't forget to change that // this ever becomes more flexible, don't forget to change that

View File

@ -23,7 +23,7 @@
//! `default_*_device()` functions return an `Option` in case no device is available for that //! `default_*_device()` functions return an `Option` in case no device is available for that
//! stream type on the system. //! stream type on the system.
//! //!
//! ``` //! ```no_run
//! let device = cpal::default_output_device().expect("no output device available"); //! let device = cpal::default_output_device().expect("no output device available");
//! ``` //! ```
//! //!
@ -60,10 +60,10 @@
//! //!
//! Now we must start the stream. This is done with the `play_stream()` method on the event loop. //! Now we must start the stream. This is done with the `play_stream()` method on the event loop.
//! //!
//! ``` //! ```no_run
//! # let event_loop: cpal::EventLoop = return; //! # let event_loop: cpal::EventLoop = return;
//! # let stream_id: cpal::StreamId = return; //! # let stream_id: cpal::StreamId = return;
//! event_loop.play_stream(stream_id); //! event_loop.play_stream(stream_id).expect("failed to play_stream");
//! ``` //! ```
//! //!
//! Now everything is ready! We call `run()` on the `event_loop` to begin processing. //! Now everything is ready! We call `run()` on the `event_loop` to begin processing.
@ -114,10 +114,10 @@
#![recursion_limit = "512"] #![recursion_limit = "512"]
extern crate failure;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
// Extern crate declarations with `#[macro_use]` must unfortunately be at crate root. // Extern crate declarations with `#[macro_use]` must unfortunately be at crate root.
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
#[macro_use] #[macro_use]
@ -129,7 +129,7 @@ pub use samples_formats::{Sample, SampleFormat};
target_os = "macos", target_os = "ios", target_os = "emscripten")))] target_os = "macos", target_os = "ios", target_os = "emscripten")))]
use null as cpal_impl; use null as cpal_impl;
use std::error::Error; use failure::Fail;
use std::fmt; use std::fmt;
use std::iter; use std::iter;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -160,7 +160,7 @@ mod cpal_impl;
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Device(cpal_impl::Device); pub struct Device(cpal_impl::Device);
/// Collection of voices managed together. /// Collection of streams managed together.
/// ///
/// Created with the [`new`](struct.EventLoop.html#method.new) method. /// Created with the [`new`](struct.EventLoop.html#method.new) method.
pub struct EventLoop(cpal_impl::EventLoop); pub struct EventLoop(cpal_impl::EventLoop);
@ -279,75 +279,171 @@ 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 {
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
}
/// An error that may occur while attempting to retrieve a device name.
#[derive(Debug, Fail)]
pub enum DeviceNameError {
/// See the `BackendSpecificError` docs for more information about this error variant.
#[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)] #[derive(Debug, Fail)]
pub enum FormatsEnumerationError { pub enum SupportedFormatsError {
/// The device no longer exists. This can happen if the device is disconnected while the /// The device no longer exists. This can happen if the device is disconnected while the
/// program is running. /// program is running.
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
DeviceNotAvailable, DeviceNotAvailable,
/// We called something the C-Layer did not understand /// We called something the C-Layer did not understand
#[fail(display = "Invalid argument passed to the backend. For example, this happens when trying to read capture capabilities when the device does not support it.")]
InvalidArgument, InvalidArgument,
/// The C-Layer returned an error we don't know about /// See the `BackendSpecificError` docs for more information about this error variant.
Unknown #[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
} }
/// May occur when attempting to request the default input or output stream format from a `Device`. /// May occur when attempting to request the default input or output stream format from a `Device`.
#[derive(Debug)] #[derive(Debug, Fail)]
pub enum DefaultFormatError { pub enum DefaultFormatError {
/// The device no longer exists. This can happen if the device is disconnected while the /// The device no longer exists. This can happen if the device is disconnected while the
/// program is running. /// program is running.
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
DeviceNotAvailable, DeviceNotAvailable,
/// Returned if e.g. the default input format was requested on an output-only audio device. /// Returned if e.g. the default input format was requested on an output-only audio device.
#[fail(display = "The requested stream type is not supported by the device.")]
StreamTypeNotSupported, StreamTypeNotSupported,
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
} }
/// Error that can happen when creating a `Voice`. /// Error that can happen when creating a `Stream`.
#[derive(Debug)] #[derive(Debug, Fail)]
pub enum CreationError { pub enum BuildStreamError {
/// The device no longer exists. This can happen if the device is disconnected while the /// The device no longer exists. This can happen if the device is disconnected while the
/// program is running. /// program is running.
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
DeviceNotAvailable, DeviceNotAvailable,
/// The required format is not supported. /// The required format is not supported.
#[fail(display = "The requested stream format is not supported by the device.")]
FormatNotSupported, FormatNotSupported,
/// An ALSA device function was called with a feature it does not support /// We called something the C-Layer did not understand
/// (trying to use capture capabilities on an output only format yields this) ///
/// On ALSA device functions called with a feature they do not support will yield this. E.g.
/// Trying to use capture capabilities on an output only format yields this.
#[fail(display = "The requested device does not support this capability (invalid argument)")]
InvalidArgument, InvalidArgument,
/// The C-Layer returned an error we don't know about /// Occurs if adding a new Stream ID would cause an integer overflow.
Unknown, #[fail(display = "Adding a new stream ID would cause an overflow")]
StreamIdOverflow,
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
}
/// Errors that might occur when calling `play_stream`.
///
/// As of writing this, only macOS may immediately return an error while calling this method. This
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
/// them immediately.
#[derive(Debug, Fail)]
pub enum PlayStreamError {
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
}
/// Errors that might occur when calling `pause_stream`.
///
/// As of writing this, only macOS may immediately return an error while calling this method. This
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
/// them immediately.
#[derive(Debug, Fail)]
pub enum PauseStreamError {
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
} }
/// An iterator yielding all `Device`s currently available to the system. /// An iterator yielding all `Device`s currently available to the system.
/// ///
/// 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.
@ -367,7 +463,7 @@ pub fn default_output_device() -> Option<Device> {
impl Device { impl Device {
/// The human-readable name of the device. /// The human-readable name of the device.
#[inline] #[inline]
pub fn name(&self) -> String { pub fn name(&self) -> Result<String, DeviceNameError> {
self.0.name() self.0.name()
} }
@ -375,7 +471,7 @@ impl Device {
/// ///
/// Can return an error if the device is no longer valid (eg. it has been disconnected). /// Can return an error if the device is no longer valid (eg. it has been disconnected).
#[inline] #[inline]
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> { pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
Ok(SupportedInputFormats(self.0.supported_input_formats()?)) Ok(SupportedInputFormats(self.0.supported_input_formats()?))
} }
@ -383,7 +479,7 @@ impl Device {
/// ///
/// Can return an error if the device is no longer valid (eg. it has been disconnected). /// Can return an error if the device is no longer valid (eg. it has been disconnected).
#[inline] #[inline]
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> { pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
Ok(SupportedOutputFormats(self.0.supported_output_formats()?)) Ok(SupportedOutputFormats(self.0.supported_output_formats()?))
} }
@ -424,7 +520,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
self.0.build_input_stream(&device.0, format).map(StreamId) self.0.build_input_stream(&device.0, format).map(StreamId)
} }
@ -440,7 +536,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
self.0.build_output_stream(&device.0, format).map(StreamId) self.0.build_output_stream(&device.0, format).map(StreamId)
} }
@ -456,7 +552,7 @@ impl EventLoop {
/// If the stream does not exist, this function can either panic or be a no-op. /// If the stream does not exist, this function can either panic or be a no-op.
/// ///
#[inline] #[inline]
pub fn play_stream(&self, stream: StreamId) { pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
self.0.play_stream(stream.0) self.0.play_stream(stream.0)
} }
@ -471,7 +567,7 @@ impl EventLoop {
/// If the stream does not exist, this function can either panic or be a no-op. /// If the stream does not exist, this function can either panic or be a no-op.
/// ///
#[inline] #[inline]
pub fn pause_stream(&self, stream: StreamId) { pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
self.0.pause_stream(stream.0) self.0.pause_stream(stream.0)
} }
@ -693,79 +789,45 @@ impl Iterator for SupportedOutputFormats {
} }
} }
impl fmt::Display for FormatsEnumerationError { impl From<BackendSpecificError> for DevicesError {
#[inline] fn from(err: BackendSpecificError) -> Self {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { DevicesError::BackendSpecific { err }
write!(fmt, "{}", self.description())
} }
} }
impl Error for FormatsEnumerationError { impl From<BackendSpecificError> for DeviceNameError {
#[inline] fn from(err: BackendSpecificError) -> Self {
fn description(&self) -> &str { DeviceNameError::BackendSpecific { err }
match self {
&FormatsEnumerationError::DeviceNotAvailable => {
"The requested device is no longer available (for example, it has been unplugged)."
},
&FormatsEnumerationError::InvalidArgument => {
"Invalid argument passed to the Backend (This happens when trying to read for example capture capabilities but the device does not support it -> dmix on Linux)"
},
&FormatsEnumerationError::Unknown => {
"An unknown error in the Backend occured"
},
}
} }
} }
impl fmt::Display for CreationError { impl From<BackendSpecificError> for SupportedFormatsError {
#[inline] fn from(err: BackendSpecificError) -> Self {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { SupportedFormatsError::BackendSpecific { err }
write!(fmt, "{}", self.description())
} }
} }
impl Error for CreationError { impl From<BackendSpecificError> for DefaultFormatError {
#[inline] fn from(err: BackendSpecificError) -> Self {
fn description(&self) -> &str { DefaultFormatError::BackendSpecific { err }
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."
},
&CreationError::InvalidArgument => {
"The requested device does not support this capability (invalid argument)"
}
&CreationError::Unknown => {
"An unknown error in the Backend occured"
},
}
} }
} }
impl fmt::Display for DefaultFormatError { impl From<BackendSpecificError> for BuildStreamError {
#[inline] fn from(err: BackendSpecificError) -> Self {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { BuildStreamError::BackendSpecific { err }
write!(fmt, "{}", self.description())
} }
} }
impl Error for DefaultFormatError { impl From<BackendSpecificError> for PlayStreamError {
#[inline] fn from(err: BackendSpecificError) -> Self {
fn description(&self) -> &str { PlayStreamError::BackendSpecific { err }
match self {
&DefaultFormatError::DeviceNotAvailable => {
CreationError::DeviceNotAvailable.description()
},
&DefaultFormatError::StreamTypeNotSupported => {
"The requested stream type is not supported by the device."
},
} }
}
impl From<BackendSpecificError> for PauseStreamError {
fn from(err: BackendSpecificError) -> Self {
PauseStreamError::BackendSpecific { err }
} }
} }

View File

@ -2,10 +2,14 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use CreationError; use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use DevicesError;
use DeviceNameError;
use Format; use Format;
use FormatsEnumerationError; use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use StreamData; use StreamData;
use SupportedFormat; use SupportedFormat;
@ -25,13 +29,13 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, CreationError> { pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
Err(CreationError::DeviceNotAvailable) Err(BuildStreamError::DeviceNotAvailable)
} }
#[inline] #[inline]
pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, CreationError> { pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
Err(CreationError::DeviceNotAvailable) Err(BuildStreamError::DeviceNotAvailable)
} }
#[inline] #[inline]
@ -40,12 +44,12 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, _: StreamId) { pub fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> {
panic!() panic!()
} }
#[inline] #[inline]
pub fn pause_stream(&self, _: StreamId) { pub fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> {
panic!() panic!()
} }
} }
@ -56,6 +60,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;
@ -80,12 +90,12 @@ pub struct Device;
impl Device { impl Device {
#[inline] #[inline]
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> { pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unimplemented!() unimplemented!()
} }
#[inline] #[inline]
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> { pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
unimplemented!() unimplemented!()
} }
@ -100,8 +110,8 @@ impl Device {
} }
#[inline] #[inline]
pub fn name(&self) -> String { pub fn name(&self) -> Result<String, DeviceNameError> {
"null".to_owned() Ok("null".to_owned())
} }
} }

View File

@ -9,15 +9,19 @@ 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 DeviceNameError;
use DevicesError;
use Format; use Format;
use FormatsEnumerationError; use SupportedFormatsError;
use SampleFormat; use SampleFormat;
use SampleRate; use SampleRate;
use SupportedFormat; 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;
@ -165,7 +169,7 @@ unsafe fn data_flow_from_immendpoint(endpoint: *const IMMEndpoint) -> EDataFlow
pub unsafe fn is_format_supported( pub unsafe fn is_format_supported(
client: *const IAudioClient, client: *const IAudioClient,
waveformatex_ptr: *const mmreg::WAVEFORMATEX, waveformatex_ptr: *const mmreg::WAVEFORMATEX,
) -> Result<bool, FormatsEnumerationError> ) -> Result<bool, SupportedFormatsError>
{ {
@ -187,7 +191,7 @@ pub unsafe fn is_format_supported(
(_, Err(ref e)) (_, Err(ref e))
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::DeviceNotAvailable); return Err(BuildStreamError::DeviceNotAvailable);
}, },
(_, Err(e)) => { (_, Err(e)) => {
(*audio_client).Release(); (*audio_client).Release();
@ -195,7 +199,7 @@ pub unsafe fn is_format_supported(
}, },
(winerror::S_FALSE, _) => { (winerror::S_FALSE, _) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::FormatNotSupported); return Err(BuildStreamError::FormatNotSupported);
}, },
(_, Ok(())) => (), (_, Ok(())) => (),
}; };
@ -213,7 +217,7 @@ pub unsafe fn is_format_supported(
// has been found, but not an exact match) so we also treat this as unsupported. // has been found, but not an exact match) so we also treat this as unsupported.
match (result, check_result(result)) { match (result, check_result(result)) {
(_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { (_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(FormatsEnumerationError::DeviceNotAvailable); return Err(SupportedFormatsError::DeviceNotAvailable);
}, },
(_, Err(_)) => { (_, Err(_)) => {
Ok(false) Ok(false)
@ -294,7 +298,7 @@ unsafe impl Sync for Device {
} }
impl Device { impl Device {
pub fn name(&self) -> String { pub fn name(&self) -> Result<String, DeviceNameError> {
unsafe { unsafe {
// Open the device's property store. // Open the device's property store.
let mut property_store = ptr::null_mut(); let mut property_store = ptr::null_mut();
@ -302,15 +306,24 @@ impl Device {
// Get the endpoint's friendly-name property. // Get the endpoint's friendly-name property.
let mut property_value = mem::zeroed(); let mut property_value = mem::zeroed();
check_result( if let Err(err) = check_result(
(*property_store).GetValue( (*property_store).GetValue(
&devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _, &devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _,
&mut property_value &mut property_value
) )
).expect("failed to get friendly-name from property store"); ) {
let description = format!("failed to retrieve name from property store: {}", err);
let err = BackendSpecificError { description };
return Err(err.into());
}
// Read the friendly-name from the union data field, expecting a *const u16. // Read the friendly-name from the union data field, expecting a *const u16.
assert_eq!(property_value.vt, wtypes::VT_LPWSTR as _); if property_value.vt != wtypes::VT_LPWSTR as _ {
let description =
format!("property store produced invalid data: {:?}", property_value.vt);
let err = BackendSpecificError { description };
return Err(err.into());
}
let ptr_usize: usize = *(&property_value.data as *const _ as *const usize); let ptr_usize: usize = *(&property_value.data as *const _ as *const usize);
let ptr_utf16 = ptr_usize as *const u16; let ptr_utf16 = ptr_usize as *const u16;
@ -323,12 +336,15 @@ impl Device {
// Create the utf16 slice and covert it into a string. // Create the utf16 slice and covert it into a string.
let name_slice = slice::from_raw_parts(ptr_utf16, len as usize); let name_slice = slice::from_raw_parts(ptr_utf16, len as usize);
let name_os_string: OsString = OsStringExt::from_wide(name_slice); let name_os_string: OsString = OsStringExt::from_wide(name_slice);
let name_string = name_os_string.into_string().unwrap(); let name_string = match name_os_string.into_string() {
Ok(string) => string,
Err(os_string) => os_string.to_string_lossy().into(),
};
// Clean up the property. // Clean up the property.
PropVariantClear(&mut property_value); PropVariantClear(&mut property_value);
name_string Ok(name_string)
} }
} }
@ -386,15 +402,21 @@ impl Device {
// number of channels seems to be supported. Any more or less returns an invalid // number of channels seems to be supported. Any more or less returns an invalid
// parameter error. Thus we just assume that the default number of channels is the only // parameter error. Thus we just assume that the default number of channels is the only
// number supported. // number supported.
fn supported_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> { fn supported_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
// initializing COM because we call `CoTaskMemFree` to release the format. // initializing COM because we call `CoTaskMemFree` to release the format.
com::com_initialized(); com::com_initialized();
// Retrieve the `IAudioClient`. // Retrieve the `IAudioClient`.
let lock = match self.ensure_future_audio_client() { let lock = match self.ensure_future_audio_client() {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => Ok(lock) => lock,
return Err(FormatsEnumerationError::DeviceNotAvailable), Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
e => e.unwrap(), return Err(SupportedFormatsError::DeviceNotAvailable)
}
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
}; };
let client = lock.unwrap().0; let client = lock.unwrap().0;
@ -402,11 +424,15 @@ impl Device {
// Retrieve the pointer to the default WAVEFORMATEX. // Retrieve the pointer to the default WAVEFORMATEX.
let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized()); let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized());
match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) { match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(FormatsEnumerationError::DeviceNotAvailable);
},
Err(e) => panic!("{:?}", e),
Ok(()) => (), Ok(()) => (),
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(SupportedFormatsError::DeviceNotAvailable);
},
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
}; };
// If the default format can't succeed we have no hope of finding other formats. // If the default format can't succeed we have no hope of finding other formats.
@ -450,8 +476,15 @@ impl Device {
// TODO: Test the different sample formats? // TODO: Test the different sample formats?
// Create the supported formats. // Create the supported formats.
let mut format = format_from_waveformatex_ptr(default_waveformatex_ptr.0) let mut format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0) {
.expect("could not create a cpal::Format from a WAVEFORMATEX"); Some(fmt) => fmt,
None => {
let description =
"could not create a `cpal::Format` from a `WAVEFORMATEX`".to_string();
let err = BackendSpecificError { description };
return Err(err.into());
}
};
let mut supported_formats = Vec::with_capacity(supported_sample_rates.len()); let mut supported_formats = Vec::with_capacity(supported_sample_rates.len());
for rate in supported_sample_rates { for rate in supported_sample_rates {
format.sample_rate = SampleRate(rate as _); format.sample_rate = SampleRate(rate as _);
@ -462,7 +495,7 @@ impl Device {
} }
} }
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> { pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
if self.data_flow() == eCapture { if self.data_flow() == eCapture {
self.supported_formats() self.supported_formats()
// If it's an output device, assume no input formats. // If it's an output device, assume no input formats.
@ -471,7 +504,7 @@ impl Device {
} }
} }
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> { pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
if self.data_flow() == eRender { if self.data_flow() == eRender {
self.supported_formats() self.supported_formats()
// If it's an input device, assume no output formats. // If it's an input device, assume no output formats.
@ -489,9 +522,15 @@ impl Device {
com::com_initialized(); com::com_initialized();
let lock = match self.ensure_future_audio_client() { let lock = match self.ensure_future_audio_client() {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => Ok(lock) => lock,
return Err(DefaultFormatError::DeviceNotAvailable), Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
e => e.unwrap(), return Err(DefaultFormatError::DeviceNotAvailable)
}
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
}; };
let client = lock.unwrap().0; let client = lock.unwrap().0;
@ -501,7 +540,11 @@ impl Device {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(DefaultFormatError::DeviceNotAvailable); return Err(DefaultFormatError::DeviceNotAvailable);
}, },
Err(e) => panic!("{:?}", e), Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (), Ok(()) => (),
}; };
@ -688,6 +731,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))?;
Ok(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 +771,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 Err(BackendSpecificError { description });
}
}
}

View File

@ -20,8 +20,11 @@ use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::atomic::AtomicUsize; use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use CreationError; use BackendSpecificError;
use BuildStreamError;
use Format; use Format;
use PauseStreamError;
use PlayStreamError;
use SampleFormat; use SampleFormat;
use StreamData; use StreamData;
use UnknownTypeOutputBuffer; use UnknownTypeOutputBuffer;
@ -114,7 +117,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
unsafe { unsafe {
// Making sure that COM is initialized. // Making sure that COM is initialized.
@ -123,21 +126,26 @@ impl EventLoop {
// Obtaining a `IAudioClient`. // Obtaining a `IAudioClient`.
let audio_client = match device.build_audioclient() { let audio_client = match device.build_audioclient() {
Ok(client) => client,
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
return Err(CreationError::DeviceNotAvailable), return Err(BuildStreamError::DeviceNotAvailable),
e => e.unwrap(), Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
}; };
// Computing the format and initializing the device. // Computing the format and initializing the device.
let waveformatex = { let waveformatex = {
let format_attempt = format_to_waveformatextensible(format) let format_attempt = format_to_waveformatextensible(format)
.ok_or(CreationError::FormatNotSupported)?; .ok_or(BuildStreamError::FormatNotSupported)?;
let share_mode = AUDCLNT_SHAREMODE_SHARED; let share_mode = AUDCLNT_SHAREMODE_SHARED;
// Ensure the format is supported. // Ensure the format is supported.
match super::device::is_format_supported(audio_client, &format_attempt.Format) { match super::device::is_format_supported(audio_client, &format_attempt.Format) {
Ok(false) => return Err(CreationError::FormatNotSupported), Ok(false) => return Err(BuildStreamError::FormatNotSupported),
Err(_) => return Err(CreationError::DeviceNotAvailable), Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
_ => (), _ => (),
} }
@ -154,11 +162,13 @@ impl EventLoop {
Err(ref e) Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::DeviceNotAvailable); return Err(BuildStreamError::DeviceNotAvailable);
}, },
Err(e) => { Err(e) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("{:?}", e); let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}, },
Ok(()) => (), Ok(()) => (),
}; };
@ -175,11 +185,13 @@ impl EventLoop {
Err(ref e) Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::DeviceNotAvailable); return Err(BuildStreamError::DeviceNotAvailable);
}, },
Err(e) => { Err(e) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("{:?}", e); let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}, },
Ok(()) => (), Ok(()) => (),
}; };
@ -192,16 +204,17 @@ impl EventLoop {
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
if event == ptr::null_mut() { if event == ptr::null_mut() {
(*audio_client).Release(); (*audio_client).Release();
panic!("Failed to create event"); let description = format!("failed to create event");
let err = BackendSpecificError { description };
return Err(err.into());
} }
match check_result((*audio_client).SetEventHandle(event)) { if let Err(e) = check_result((*audio_client).SetEventHandle(event)) {
Err(_) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("Failed to call SetEventHandle") let description = format!("failed to call SetEventHandle: {}", e);
}, let err = BackendSpecificError { description };
Ok(_) => (), return Err(err.into());
}; }
event event
}; };
@ -218,11 +231,13 @@ impl EventLoop {
Err(ref e) Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::DeviceNotAvailable); return Err(BuildStreamError::DeviceNotAvailable);
}, },
Err(e) => { Err(e) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("{:?}", e); let description = format!("failed to build capture client: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}, },
Ok(()) => (), Ok(()) => (),
}; };
@ -231,7 +246,9 @@ impl EventLoop {
}; };
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows if new_stream_id.0 == usize::max_value() {
return Err(BuildStreamError::StreamIdOverflow);
}
// Once we built the `StreamInner`, we add a command that will be picked up by the // Once we built the `StreamInner`, we add a command that will be picked up by the
// `run()` method and added to the `RunContext`. // `run()` method and added to the `RunContext`.
@ -261,7 +278,7 @@ impl EventLoop {
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
) -> Result<StreamId, CreationError> ) -> Result<StreamId, BuildStreamError>
{ {
unsafe { unsafe {
// Making sure that COM is initialized. // Making sure that COM is initialized.
@ -270,21 +287,26 @@ impl EventLoop {
// Obtaining a `IAudioClient`. // Obtaining a `IAudioClient`.
let audio_client = match device.build_audioclient() { let audio_client = match device.build_audioclient() {
Ok(client) => client,
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
return Err(CreationError::DeviceNotAvailable), return Err(BuildStreamError::DeviceNotAvailable),
e => e.unwrap(), Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
}; };
// Computing the format and initializing the device. // Computing the format and initializing the device.
let waveformatex = { let waveformatex = {
let format_attempt = format_to_waveformatextensible(format) let format_attempt = format_to_waveformatextensible(format)
.ok_or(CreationError::FormatNotSupported)?; .ok_or(BuildStreamError::FormatNotSupported)?;
let share_mode = AUDCLNT_SHAREMODE_SHARED; let share_mode = AUDCLNT_SHAREMODE_SHARED;
// Ensure the format is supported. // Ensure the format is supported.
match super::device::is_format_supported(audio_client, &format_attempt.Format) { match super::device::is_format_supported(audio_client, &format_attempt.Format) {
Ok(false) => return Err(CreationError::FormatNotSupported), Ok(false) => return Err(BuildStreamError::FormatNotSupported),
Err(_) => return Err(CreationError::DeviceNotAvailable), Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
_ => (), _ => (),
} }
@ -299,11 +321,13 @@ impl EventLoop {
Err(ref e) Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::DeviceNotAvailable); return Err(BuildStreamError::DeviceNotAvailable);
}, },
Err(e) => { Err(e) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("{:?}", e); let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}, },
Ok(()) => (), Ok(()) => (),
}; };
@ -316,13 +340,17 @@ impl EventLoop {
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null()); let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
if event == ptr::null_mut() { if event == ptr::null_mut() {
(*audio_client).Release(); (*audio_client).Release();
panic!("Failed to create event"); let description = format!("failed to create event");
let err = BackendSpecificError { description };
return Err(err.into());
} }
match check_result((*audio_client).SetEventHandle(event)) { match check_result((*audio_client).SetEventHandle(event)) {
Err(_) => { Err(e) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("Failed to call SetEventHandle") let description = format!("failed to call SetEventHandle: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}, },
Ok(_) => (), Ok(_) => (),
}; };
@ -339,11 +367,13 @@ impl EventLoop {
Err(ref e) Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::DeviceNotAvailable); return Err(BuildStreamError::DeviceNotAvailable);
}, },
Err(e) => { Err(e) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("{:?}", e); let description = format!("failed to obtain buffer size: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}, },
Ok(()) => (), Ok(()) => (),
}; };
@ -363,11 +393,13 @@ impl EventLoop {
Err(ref e) Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => { if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release(); (*audio_client).Release();
return Err(CreationError::DeviceNotAvailable); return Err(BuildStreamError::DeviceNotAvailable);
}, },
Err(e) => { Err(e) => {
(*audio_client).Release(); (*audio_client).Release();
panic!("{:?}", e); let description = format!("failed to build render client: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}, },
Ok(()) => (), Ok(()) => (),
}; };
@ -376,7 +408,9 @@ impl EventLoop {
}; };
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed)); let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows if new_stream_id.0 == usize::max_value() {
return Err(BuildStreamError::StreamIdOverflow);
}
// Once we built the `StreamInner`, we add a command that will be picked up by the // Once we built the `StreamInner`, we add a command that will be picked up by the
// `run()` method and added to the `RunContext`. // `run()` method and added to the `RunContext`.
@ -610,13 +644,15 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn play_stream(&self, stream: StreamId) { pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
self.push_command(Command::PlayStream(stream)); self.push_command(Command::PlayStream(stream));
Ok(())
} }
#[inline] #[inline]
pub fn pause_stream(&self, stream: StreamId) { pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
self.push_command(Command::PauseStream(stream)); self.push_command(Command::PauseStream(stream));
Ok(())
} }
#[inline] #[inline]