Improve handling of `BuildStreamError` throughout crate.

This commit is contained in:
mitchmindtree 2019-06-21 02:38:59 +02:00
parent f0e4e312c1
commit ba8d354e93
4 changed files with 160 additions and 73 deletions

View File

@ -690,29 +690,41 @@ impl EventLoop {
) { ) {
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
-22 => return Err(BuildStreamError::InvalidArgument), -22 => return Err(BuildStreamError::InvalidArgument),
e => if check_errors(e).is_err() { e => if let Err(description) = check_errors(e) {
return Err(BuildStreamError::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(),
@ -728,8 +740,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)
@ -754,29 +769,41 @@ impl EventLoop {
) { ) {
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable), -16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
-22 => return Err(BuildStreamError::InvalidArgument), -22 => return Err(BuildStreamError::InvalidArgument),
e => if check_errors(e).is_err() { e => if let Err(description) = check_errors(e) {
return Err(BuildStreamError::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(),
@ -826,12 +853,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") {
@ -851,29 +878,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(())
@ -882,34 +914,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`.
@ -945,8 +985,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

@ -513,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());
} }
} }
} }
@ -618,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));
} }

View File

@ -371,9 +371,15 @@ pub enum BuildStreamError {
/// Trying to use capture capabilities on an output only format yields this. /// Trying to use capture capabilities on an output only format yields this.
#[fail(display = "The requested device does not support this capability (invalid argument)")] #[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.
#[fail(display = "An unknown error in the Backend occured")] #[fail(display = "Adding a new stream ID would cause an overflow")]
Unknown, StreamIdOverflow,
/// 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.
@ -777,6 +783,12 @@ impl From<BackendSpecificError> for DefaultFormatError {
} }
} }
impl From<BackendSpecificError> for BuildStreamError {
fn from(err: BackendSpecificError) -> Self {
BuildStreamError::BackendSpecific { err }
}
}
// If a backend does not provide an API for retrieving supported formats, we query it with a bunch // If a backend does not provide an API for retrieving supported formats, we query it with a bunch
// of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa. // of commonly used rates. This is always the case for wasapi and is sometimes the case for alsa.
// //

View File

@ -20,6 +20,7 @@ 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 BackendSpecificError;
use BuildStreamError; use BuildStreamError;
use Format; use Format;
use SampleFormat; use SampleFormat;
@ -123,9 +124,14 @@ 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(BuildStreamError::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.
@ -158,7 +164,9 @@ impl EventLoop {
}, },
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(()) => (),
}; };
@ -179,7 +187,9 @@ impl EventLoop {
}, },
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 +202,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
}; };
@ -222,7 +233,9 @@ impl EventLoop {
}, },
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 +244,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`.
@ -270,9 +285,14 @@ 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(BuildStreamError::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.
@ -303,7 +323,9 @@ impl EventLoop {
}, },
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 +338,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(_) => (),
}; };
@ -343,7 +369,9 @@ impl EventLoop {
}, },
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(()) => (),
}; };
@ -367,7 +395,9 @@ impl EventLoop {
}, },
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 +406,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`.