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"]
[dependencies]
failure = "0.1.5"
lazy_static = "1.3"
[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:
- ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-nightly-i686-pc-windows-gnu.exe'
- ps: Start-FileDownload 'https://static.rust-lang.org/cargo-dist/cargo-nightly-i686-pc-windows-gnu.tar.gz'
- rust-nightly-i686-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
- 7z e cargo-nightly-i686-pc-windows-gnu.tar.gz
- 7z x cargo-nightly-i686-pc-windows-gnu.tar
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
- SET PATH=%PATH%;%CD%\cargo-nightly-i686-pc-windows-gnu\bin
- rustc -V
- cargo -V
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
- rustup-init -yv --default-toolchain %channel% --default-host %target%
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
- rustc -vV
- cargo -vV
build: false
test_script:
- cargo build --verbose
# - cargo test --verbose
- cargo test --verbose %cargoflags%

View File

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

View File

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

View File

@ -7,26 +7,27 @@
//! precisely synchronised.
extern crate cpal;
extern crate failure;
const LATENCY_MS: f32 = 150.0;
fn main() {
fn main() -> Result<(), failure::Error> {
let event_loop = cpal::EventLoop::new();
// Default devices.
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");
println!("Using default input device: \"{}\"", input_device.name());
println!("Using default output device: \"{}\"", output_device.name());
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");
println!("Using default input device: \"{}\"", input_device.name()?);
println!("Using default output device: \"{}\"", output_device.name()?);
// 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;
// Build streams.
println!("Attempting to build both streams with `{:?}`.", format);
let input_stream_id = event_loop.build_input_stream(&input_device, &format).unwrap();
let output_stream_id = event_loop.build_output_stream(&output_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)?;
println!("Successfully built streams.");
// 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.
for _ in 0..latency_samples {
tx.send(0.0).unwrap();
tx.send(0.0)?;
}
// Play the streams.
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(output_stream_id.clone());
event_loop.play_stream(input_stream_id.clone())?;
event_loop.play_stream(output_stream_id.clone())?;
// Run the event loop on a separate thread.
std::thread::spawn(move || {
@ -87,4 +88,5 @@ fn main() {
println!("Playing for 3 seconds... ");
std::thread::sleep(std::time::Duration::from_secs(3));
println!("Done!");
Ok(())
}

View File

@ -3,23 +3,23 @@
//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav".
extern crate cpal;
extern crate failure;
extern crate hound;
fn main() {
fn main() -> Result<(), failure::Error> {
// 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");
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");
println!("Default input format: {:?}", format);
let event_loop = cpal::EventLoop::new();
let stream_id = event_loop.build_input_stream(&device, &format)
.expect("Failed to build input stream");
event_loop.play_stream(stream_id);
let stream_id = event_loop.build_input_stream(&device, &format)?;
event_loop.play_stream(stream_id)?;
// The WAV file we're recording to.
const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");
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)));
// A flag to indicate that recording is in progress.
@ -73,8 +73,9 @@ fn main() {
// Let recording go for roughly three seconds.
std::thread::sleep(std::time::Duration::from_secs(3));
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);
Ok(())
}
fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {

View File

@ -1,3 +1,4 @@
use {BackendSpecificError, DevicesError};
use super::Device;
use super::alsa;
use super::check_errors;
@ -13,6 +14,28 @@ pub struct Devices {
next_str: *const *const u8,
}
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
unsafe {
// TODO: check in which situation this can fail.
let card = -1; // -1 means all cards.
let iface = b"pcm\0"; // Interface identification.
let mut hints = mem::uninitialized(); // Array of device name hints.
let res = alsa::snd_device_name_hint(card, iface.as_ptr() as *const _, &mut hints);
if let Err(description) = check_errors(res) {
let err = BackendSpecificError { description };
return Err(err.into());
}
let hints = hints as *const *const u8;
let devices = Devices {
global_list: hints,
next_str: hints,
};
Ok(devices)
}
}
}
unsafe impl Send for Devices {
}
unsafe impl Sync for Devices {
@ -27,24 +50,6 @@ impl Drop for Devices {
}
}
impl Default for Devices {
fn default() -> Devices {
unsafe {
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 {
type Item = Device;

View File

@ -4,10 +4,14 @@ extern crate libc;
pub use self::enumerate::{Devices, default_input_device, default_output_device};
use ChannelCount;
use CreationError;
use BackendSpecificError;
use BuildStreamError;
use DefaultFormatError;
use DeviceNameError;
use Format;
use FormatsEnumerationError;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use SampleFormat;
use SampleRate;
use StreamData;
@ -73,17 +77,24 @@ pub struct Device(String);
impl Device {
#[inline]
pub fn name(&self) -> String {
self.0.clone()
pub fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.0.clone())
}
unsafe fn supported_formats(
&self,
stream_t: alsa::snd_pcm_stream_t,
) -> Result<VecIntoIter<SupportedFormat>, FormatsEnumerationError>
) -> Result<VecIntoIter<SupportedFormat>, SupportedFormatsError>
{
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(
&mut handle,
@ -92,16 +103,20 @@ impl Device {
alsa::SND_PCM_NONBLOCK,
) {
-2 |
-16 /* determined empirically */ => return Err(FormatsEnumerationError::DeviceNotAvailable),
-22 => return Err(FormatsEnumerationError::InvalidArgument),
e => if check_errors(e).is_err() {
return Err(FormatsEnumerationError::Unknown)
-16 /* determined empirically */ => return Err(SupportedFormatsError::DeviceNotAvailable),
-22 => return Err(SupportedFormatsError::InvalidArgument),
e => if let Err(description) = check_errors(e) {
let err = BackendSpecificError { description };
return Err(err.into())
}
}
let hw_params = HwParams::alloc();
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(_) => (),
};
@ -158,15 +173,26 @@ impl Device {
}
let mut min_rate = mem::uninitialized();
check_errors(alsa::snd_pcm_hw_params_get_rate_min(hw_params.0,
&mut min_rate,
ptr::null_mut()))
.expect("unable to get minimum supported rete");
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_min(
hw_params.0,
&mut min_rate,
ptr::null_mut(),
)) {
let description = format!("unable to get minimum supported rate: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let mut max_rate = mem::uninitialized();
check_errors(alsa::snd_pcm_hw_params_get_rate_max(hw_params.0,
&mut max_rate,
ptr::null_mut()))
.expect("unable to get maximum supported rate");
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_max(
hw_params.0,
&mut max_rate,
ptr::null_mut(),
)) {
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 {
vec![(min_rate, max_rate)]
@ -212,11 +238,19 @@ impl Device {
};
let mut min_channels = mem::uninitialized();
check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels))
.expect("unable to get minimum supported channel count");
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels)) {
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();
check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels))
.expect("unable to get maximum supported channel count");
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels)) {
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 supported_channels = (min_channels .. max_channels + 1)
.filter_map(|num| if alsa::snd_pcm_hw_params_test_channels(
@ -251,13 +285,13 @@ impl Device {
Ok(output.into_iter())
}
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unsafe {
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 {
self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK)
}
@ -272,16 +306,16 @@ impl Device {
{
let mut formats: Vec<_> = unsafe {
match self.supported_formats(stream_t) {
Err(FormatsEnumerationError::DeviceNotAvailable) => {
Err(SupportedFormatsError::DeviceNotAvailable) => {
return Err(DefaultFormatError::DeviceNotAvailable);
},
Err(FormatsEnumerationError::InvalidArgument) => {
Err(SupportedFormatsError::InvalidArgument) => {
// this happens sometimes when querying for input and output capabilities but
// the device supports only one
return Err(DefaultFormatError::StreamTypeNotSupported);
}
Err(FormatsEnumerationError::Unknown) => {
return Err(DefaultFormatError::DeviceNotAvailable);
Err(SupportedFormatsError::BackendSpecific { err }) => {
return Err(err.into());
}
Ok(fmts) => fmts.collect(),
}
@ -644,7 +678,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
unsafe {
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_NONBLOCK,
) {
-16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable),
-22 => return Err(CreationError::InvalidArgument),
e => if check_errors(e).is_err() {
return Err(CreationError::Unknown);
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
-22 => return Err(BuildStreamError::InvalidArgument),
e => if let Err(description) = check_errors(e) {
let err = BackendSpecificError { description };
return Err(err.into());
}
}
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 (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))
.expect("could not get playback handle");
if let Err(desc) = check_errors(alsa::snd_pcm_prepare(capture_handle)) {
let description = format!("could not get capture handle: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
let num_descriptors = {
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
};
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 {
id: new_stream_id.clone(),
@ -696,8 +742,11 @@ impl EventLoop {
buffer: None,
};
check_errors(alsa::snd_pcm_start(capture_handle))
.expect("could not start capture stream");
if let Err(desc) = check_errors(alsa::snd_pcm_start(capture_handle)) {
let description = format!("could not start capture stream: {}", desc);
let err = BackendSpecificError { description };
return Err(err.into());
}
self.push_command(Command::NewStream(stream_inner));
Ok(new_stream_id)
@ -708,7 +757,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
unsafe {
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_NONBLOCK,
) {
-16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable),
-22 => return Err(CreationError::InvalidArgument),
e => if check_errors(e).is_err() {
return Err(CreationError::Unknown);
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
-22 => return Err(BuildStreamError::InvalidArgument),
e => if let Err(description) = check_errors(e) {
let err = BackendSpecificError { description };
return Err(err.into())
}
}
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 (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))
.expect("could not get playback handle");
if let Err(desc) = check_errors(alsa::snd_pcm_prepare(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 = 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
};
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 {
id: new_stream_id.clone(),
@ -778,13 +839,15 @@ impl EventLoop {
}
#[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));
Ok(())
}
#[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));
Ok(())
}
}
@ -794,12 +857,12 @@ unsafe fn set_hw_params_from_format(
format: &Format,
) -> Result<(), String> {
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,
hw_params.0,
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") {
@ -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,
hw_params.0,
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,
hw_params.0,
format.sample_rate.0 as libc::c_uint,
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,
hw_params.0,
format.channels as
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 /
format.channels as alsa::snd_pcm_uframes_t /
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,
&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)) {
return Err("Hardware params could not be set.".to_string());
return Err(format!("hardware params could not be set: {}", e));
}
Ok(())
@ -850,34 +918,42 @@ unsafe fn set_hw_params_from_format(
unsafe fn set_sw_params_from_format(
pcm_handle: *mut alsa::snd_pcm_t,
format: &Format,
) -> (usize, usize)
) -> Result<(usize, usize), String>
{
let mut sw_params = mem::uninitialized(); // TODO: RAII
check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)).unwrap();
check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)).unwrap();
check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle,
sw_params,
0))
.unwrap();
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) {
return Err(format!("snd_pcm_sw_params_malloc failed: {}", e));
}
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)) {
return Err(format!("snd_pcm_sw_params_current failed: {}", e));
}
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 mut buffer = mem::uninitialized();
let mut period = mem::uninitialized();
check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period))
.expect("could not initialize buffer");
assert!(buffer != 0);
check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle,
sw_params,
period))
.unwrap();
if let Err(e) = check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period)) {
return Err(format!("failed to initialize buffer: {}", e));
}
if buffer == 0 {
return Err(format!("initialization resulted in a null buffer"));
}
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 period = period as usize * format.channels as usize;
(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);
(buffer_len, period_len)
Ok((buffer_len, period_len))
}
/// Wrapper around `hw_params`.
@ -913,8 +989,6 @@ impl Drop for StreamInner {
#[inline]
fn check_errors(err: libc::c_int) -> Result<(), String> {
use std::ffi;
if err < 0 {
unsafe {
let s = ffi::CStr::from_ptr(alsa::snd_strerror(err))
@ -940,4 +1014,4 @@ unsafe fn cast_input_buffer<T>(v: &[u8]) -> &[T] {
unsafe fn cast_output_buffer<T>(v: &mut [u8]) -> &mut [T] {
debug_assert!(v.len() % std::mem::size_of::<T>() == 0);
std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::<T>())
}
}

View File

@ -1,4 +1,4 @@
use SupportedFormat;
use {BackendSpecificError, DevicesError, SupportedFormat};
use std::mem;
use std::ptr::null;
use std::vec::IntoIter as VecIntoIter;
@ -64,20 +64,27 @@ unsafe fn audio_devices() -> Result<Vec<AudioDeviceID>, OSStatus> {
pub struct Devices(VecIntoIter<AudioDeviceID>);
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
let devices = unsafe {
match audio_devices() {
Ok(devices) => devices,
Err(os_status) => {
let description = format!("{}", os_status);
let err = BackendSpecificError { description };
return Err(err.into());
}
}
};
Ok(Devices(devices.into_iter()))
}
}
unsafe impl Send for Devices {
}
unsafe impl Sync for Devices {
}
impl Default for Devices {
fn default() -> Self {
let devices = unsafe {
audio_devices().expect("failed to get audio output devices")
};
Devices(devices.into_iter())
}
}
impl Iterator for Devices {
type Item = Device;
fn next(&mut self) -> Option<Device> {

View File

@ -2,11 +2,14 @@ extern crate coreaudio;
extern crate core_foundation_sys;
use ChannelCount;
use CreationError;
use BackendSpecificError;
use BuildStreamError;
use DefaultFormatError;
use DeviceNameError;
use Format;
use FormatsEnumerationError;
use Sample;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use SampleFormat;
use SampleRate;
use StreamData;
@ -51,7 +54,6 @@ use self::coreaudio::sys::{
kAudioFormatFlagIsFloat,
kAudioFormatFlagIsPacked,
kAudioFormatLinearPCM,
kAudioHardwareNoError,
kAudioObjectPropertyElementMaster,
kAudioObjectPropertyScopeOutput,
kAudioOutputUnitProperty_CurrentDevice,
@ -75,7 +77,7 @@ pub struct Device {
}
impl Device {
pub fn name(&self) -> String {
pub fn name(&self) -> Result<String, DeviceNameError> {
let property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyDeviceNameCFString,
mScope: kAudioDevicePropertyScopeOutput,
@ -92,23 +94,24 @@ impl Device {
&data_size as *const _ as *mut _,
&device_name as *const _ as *mut _,
);
if status != kAudioHardwareNoError as i32 {
return format!("<OSStatus: {:?}>", status);
}
check_os_status(status)?;
let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8);
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 _)
};
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`.
fn supported_formats(
&self,
scope: AudioObjectPropertyScope,
) -> Result<SupportedOutputFormats, FormatsEnumerationError>
) -> Result<SupportedOutputFormats, SupportedFormatsError>
{
let mut property_address = AudioObjectPropertyAddress {
mSelector: kAudioDevicePropertyStreamConfiguration,
@ -126,9 +129,8 @@ impl Device {
null(),
&data_size as *const _ as *mut _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
check_os_status(status)?;
let mut audio_buffer_list: Vec<u8> = vec![];
audio_buffer_list.reserve_exact(data_size as usize);
let status = AudioObjectGetPropertyData(
@ -139,9 +141,8 @@ impl Device {
&data_size as *const _ as *mut _,
audio_buffer_list.as_mut_ptr() as *mut _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
check_os_status(status)?;
let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList;
// If there's no buffers, skip.
@ -176,9 +177,8 @@ impl Device {
null(),
&data_size as *const _ as *mut _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
check_os_status(status)?;
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
let mut ranges: Vec<u8> = vec![];
ranges.reserve_exact(data_size as usize);
@ -190,9 +190,8 @@ impl Device {
&data_size as *const _ as *mut _,
ranges.as_mut_ptr() as *mut _,
);
if status != kAudioHardwareNoError as i32 {
unimplemented!();
}
check_os_status(status)?;
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
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)
}
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
self.supported_formats(kAudioObjectPropertyScopeOutput)
}
@ -225,18 +224,25 @@ impl Device {
scope: AudioObjectPropertyScope,
) -> 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) {
Err(err) => err,
Ok(_) => return None,
Ok(_) => return Ok(()),
};
match err {
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat |
coreaudio::Error::NoKnownSubtype |
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
coreaudio::Error::AudioCodec(_) |
coreaudio::Error::AudioFormat(_) => Some(DefaultFormatError::StreamTypeNotSupported),
_ => Some(DefaultFormatError::DeviceNotAvailable),
coreaudio::Error::AudioFormat(_) => {
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 _,
&asbd as *const _ as *mut _,
);
if status != kAudioHardwareNoError as i32 {
let err = default_format_error_from_os_status(status)
.expect("no known error for OSStatus");
return Err(err);
}
default_format_error_from_os_status(status)?;
let sample_format = {
let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag(
@ -338,15 +339,15 @@ struct StreamInner {
}
// TODO need stronger error identification
impl From<coreaudio::Error> for CreationError {
fn from(err: coreaudio::Error) -> CreationError {
impl From<coreaudio::Error> for BuildStreamError {
fn from(err: coreaudio::Error) -> BuildStreamError {
match err {
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat |
coreaudio::Error::NoKnownSubtype |
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
coreaudio::Error::AudioCodec(_) |
coreaudio::Error::AudioFormat(_) => CreationError::FormatNotSupported,
_ => CreationError::DeviceNotAvailable,
coreaudio::Error::AudioFormat(_) => BuildStreamError::FormatNotSupported,
_ => BuildStreamError::DeviceNotAvailable,
}
}
}
@ -483,7 +484,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
// The scope and element for working with a device's input stream.
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 sample_rate as u32 != format.sample_rate.0 {
// In order to avoid breaking existing input streams we `panic!` if there is already an
// active input stream for this device with the actual sample rate.
// In order to avoid breaking existing input streams we return an error if there is
// already an active input stream for this device with the actual sample rate.
for stream in &*self.streams.lock().unwrap() {
if let Some(stream) = stream.as_ref() {
if stream.device_id == device.audio_device_id {
panic!("cannot change device sample rate for stream as an existing stream \
is already running at the current sample rate.");
let description = "cannot change device sample rate for stream as an \
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()
.position(|r| r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate);
let range_index = match maybe_index {
None => return Err(CreationError::FormatNotSupported),
None => return Err(BuildStreamError::FormatNotSupported),
Some(i) => i,
};
@ -617,7 +621,9 @@ impl EventLoop {
let timer = ::std::time::Instant::now();
while sample_rate != reported_rate {
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));
}
@ -700,7 +706,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
let mut audio_unit = audio_unit_from_device(device, false)?;
@ -776,23 +782,43 @@ impl EventLoop {
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 stream = streams[stream.0].as_mut().unwrap();
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;
}
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 stream = streams[stream.0].as_mut().unwrap();
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;
}
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::set_timeout;
use CreationError;
use BuildStreamError;
use DefaultFormatError;
use DeviceNameError;
use DevicesError;
use Format;
use FormatsEnumerationError;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use StreamData;
use SupportedFormat;
use UnknownTypeOutputBuffer;
@ -118,12 +122,12 @@ impl EventLoop {
}
#[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!();
}
#[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 mut streams = self.streams.lock().unwrap();
@ -145,23 +149,25 @@ impl EventLoop {
}
#[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 stream = streams
.get(stream_id.0)
.and_then(|v| v.as_ref())
.expect("invalid stream ID");
js!(@{stream}.resume());
Ok(())
}
#[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 stream = streams
.get(stream_id.0)
.and_then(|v| v.as_ref())
.expect("invalid stream ID");
js!(@{stream}.suspend());
Ok(())
}
}
@ -183,6 +189,13 @@ fn is_webaudio_available() -> bool {
// Content is false if the iterator is empty.
pub struct Devices(bool);
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Self::default())
}
}
impl Default for Devices {
fn default() -> Devices {
// We produce an empty iterator if the WebAudio API isn't available.
@ -221,17 +234,17 @@ pub struct Device;
impl Device {
#[inline]
pub fn name(&self) -> String {
"Default Device".to_owned()
pub fn name(&self) -> Result<String, DeviceNameError> {
Ok("Default Device".to_owned())
}
#[inline]
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unimplemented!();
}
#[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
// "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

View File

@ -23,7 +23,7 @@
//! `default_*_device()` functions return an `Option` in case no device is available for that
//! stream type on the system.
//!
//! ```
//! ```no_run
//! 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.
//!
//! ```
//! ```no_run
//! # let event_loop: cpal::EventLoop = 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.
@ -114,10 +114,10 @@
#![recursion_limit = "512"]
extern crate failure;
#[cfg(target_os = "windows")]
#[macro_use]
extern crate lazy_static;
// Extern crate declarations with `#[macro_use]` must unfortunately be at crate root.
#[cfg(target_os = "emscripten")]
#[macro_use]
@ -129,7 +129,7 @@ pub use samples_formats::{Sample, SampleFormat};
target_os = "macos", target_os = "ios", target_os = "emscripten")))]
use null as cpal_impl;
use std::error::Error;
use failure::Fail;
use std::fmt;
use std::iter;
use std::ops::{Deref, DerefMut};
@ -160,7 +160,7 @@ mod cpal_impl;
#[derive(Clone, PartialEq, Eq)]
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.
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).
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.
#[derive(Debug)]
pub enum FormatsEnumerationError {
#[derive(Debug, Fail)]
pub enum SupportedFormatsError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
DeviceNotAvailable,
/// 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,
/// The C-Layer returned an error we don't know about
Unknown
/// See the `BackendSpecificError` docs for more information about this error variant.
#[fail(display = "{}", err)]
BackendSpecific {
#[fail(cause)]
err: BackendSpecificError,
}
}
/// May occur when attempting to request the default input or output stream format from a `Device`.
#[derive(Debug)]
#[derive(Debug, Fail)]
pub enum DefaultFormatError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
DeviceNotAvailable,
/// 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,
/// 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`.
#[derive(Debug)]
pub enum CreationError {
/// Error that can happen when creating a `Stream`.
#[derive(Debug, Fail)]
pub enum BuildStreamError {
/// The device no longer exists. This can happen if the device is disconnected while the
/// program is running.
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
DeviceNotAvailable,
/// The required format is not supported.
#[fail(display = "The requested stream format is not supported by the device.")]
FormatNotSupported,
/// An ALSA device function was called with a feature it does not support
/// (trying to use capture capabilities on an output only format yields this)
/// We called something the C-Layer did not understand
///
/// 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,
/// The C-Layer returned an error we don't know about
Unknown,
/// Occurs if adding a new Stream ID would cause an integer overflow.
#[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.
///
/// Can be empty if the system does not support audio in general.
#[inline]
pub fn devices() -> Devices {
Devices(Default::default())
pub fn devices() -> Result<Devices, DevicesError> {
Ok(Devices(cpal_impl::Devices::new()?))
}
/// An iterator yielding all `Device`s currently available to the system that support one or more
/// input stream formats.
///
/// Can be empty if the system does not support audio input.
pub fn input_devices() -> InputDevices {
pub fn input_devices() -> Result<InputDevices, DevicesError> {
fn supports_input(device: &Device) -> bool {
device.supported_input_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
devices().filter(supports_input)
Ok(devices()?.filter(supports_input))
}
/// An iterator yielding all `Device`s currently available to the system that support one or more
/// output stream formats.
///
/// Can be empty if the system does not support audio output.
pub fn output_devices() -> OutputDevices {
pub fn output_devices() -> Result<OutputDevices, DevicesError> {
fn supports_output(device: &Device) -> bool {
device.supported_output_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
devices().filter(supports_output)
Ok(devices()?.filter(supports_output))
}
/// The default input audio device on the system.
@ -367,7 +463,7 @@ pub fn default_output_device() -> Option<Device> {
impl Device {
/// The human-readable name of the device.
#[inline]
pub fn name(&self) -> String {
pub fn name(&self) -> Result<String, DeviceNameError> {
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).
#[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()?))
}
@ -383,7 +479,7 @@ impl Device {
///
/// Can return an error if the device is no longer valid (eg. it has been disconnected).
#[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()?))
}
@ -424,7 +520,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
self.0.build_input_stream(&device.0, format).map(StreamId)
}
@ -440,7 +536,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
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.
///
#[inline]
pub fn play_stream(&self, stream: StreamId) {
pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
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.
///
#[inline]
pub fn pause_stream(&self, stream: StreamId) {
pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
self.0.pause_stream(stream.0)
}
@ -693,79 +789,45 @@ impl Iterator for SupportedOutputFormats {
}
}
impl fmt::Display for FormatsEnumerationError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", self.description())
impl From<BackendSpecificError> for DevicesError {
fn from(err: BackendSpecificError) -> Self {
DevicesError::BackendSpecific { err }
}
}
impl Error for FormatsEnumerationError {
#[inline]
fn description(&self) -> &str {
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 From<BackendSpecificError> for DeviceNameError {
fn from(err: BackendSpecificError) -> Self {
DeviceNameError::BackendSpecific { err }
}
}
impl fmt::Display for CreationError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", self.description())
impl From<BackendSpecificError> for SupportedFormatsError {
fn from(err: BackendSpecificError) -> Self {
SupportedFormatsError::BackendSpecific { err }
}
}
impl Error for CreationError {
#[inline]
fn description(&self) -> &str {
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 From<BackendSpecificError> for DefaultFormatError {
fn from(err: BackendSpecificError) -> Self {
DefaultFormatError::BackendSpecific { err }
}
}
impl fmt::Display for DefaultFormatError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", self.description())
impl From<BackendSpecificError> for BuildStreamError {
fn from(err: BackendSpecificError) -> Self {
BuildStreamError::BackendSpecific { err }
}
}
impl Error for DefaultFormatError {
#[inline]
fn description(&self) -> &str {
match self {
&DefaultFormatError::DeviceNotAvailable => {
CreationError::DeviceNotAvailable.description()
},
impl From<BackendSpecificError> for PlayStreamError {
fn from(err: BackendSpecificError) -> Self {
PlayStreamError::BackendSpecific { err }
}
}
&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 CreationError;
use BuildStreamError;
use DefaultFormatError;
use DevicesError;
use DeviceNameError;
use Format;
use FormatsEnumerationError;
use PauseStreamError;
use PlayStreamError;
use SupportedFormatsError;
use StreamData;
use SupportedFormat;
@ -25,13 +29,13 @@ impl EventLoop {
}
#[inline]
pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, CreationError> {
Err(CreationError::DeviceNotAvailable)
pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
Err(BuildStreamError::DeviceNotAvailable)
}
#[inline]
pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, CreationError> {
Err(CreationError::DeviceNotAvailable)
pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
Err(BuildStreamError::DeviceNotAvailable)
}
#[inline]
@ -40,12 +44,12 @@ impl EventLoop {
}
#[inline]
pub fn play_stream(&self, _: StreamId) {
pub fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> {
panic!()
}
#[inline]
pub fn pause_stream(&self, _: StreamId) {
pub fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> {
panic!()
}
}
@ -56,6 +60,12 @@ pub struct StreamId;
#[derive(Default)]
pub struct Devices;
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Devices)
}
}
impl Iterator for Devices {
type Item = Device;
@ -80,12 +90,12 @@ pub struct Device;
impl Device {
#[inline]
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unimplemented!()
}
#[inline]
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
unimplemented!()
}
@ -100,8 +110,8 @@ impl Device {
}
#[inline]
pub fn name(&self) -> String {
"null".to_owned()
pub fn name(&self) -> Result<String, DeviceNameError> {
Ok("null".to_owned())
}
}
@ -132,4 +142,4 @@ pub struct InputBuffer<'a, T: 'a> {
pub struct OutputBuffer<'a, T: 'a> {
marker: PhantomData<&'a mut T>,
}
}

View File

@ -9,15 +9,19 @@ use std::ptr;
use std::slice;
use std::sync::{Arc, Mutex, MutexGuard};
use BackendSpecificError;
use DefaultFormatError;
use DeviceNameError;
use DevicesError;
use Format;
use FormatsEnumerationError;
use SupportedFormatsError;
use SampleFormat;
use SampleRate;
use SupportedFormat;
use COMMON_SAMPLE_RATES;
use super::check_result;
use super::check_result_backend_specific;
use super::com;
use super::winapi::Interface;
use super::winapi::ctypes::c_void;
@ -165,7 +169,7 @@ unsafe fn data_flow_from_immendpoint(endpoint: *const IMMEndpoint) -> EDataFlow
pub unsafe fn is_format_supported(
client: *const IAudioClient,
waveformatex_ptr: *const mmreg::WAVEFORMATEX,
) -> Result<bool, FormatsEnumerationError>
) -> Result<bool, SupportedFormatsError>
{
@ -187,7 +191,7 @@ pub unsafe fn is_format_supported(
(_, Err(ref e))
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(CreationError::DeviceNotAvailable);
return Err(BuildStreamError::DeviceNotAvailable);
},
(_, Err(e)) => {
(*audio_client).Release();
@ -195,7 +199,7 @@ pub unsafe fn is_format_supported(
},
(winerror::S_FALSE, _) => {
(*audio_client).Release();
return Err(CreationError::FormatNotSupported);
return Err(BuildStreamError::FormatNotSupported);
},
(_, 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.
match (result, check_result(result)) {
(_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(FormatsEnumerationError::DeviceNotAvailable);
return Err(SupportedFormatsError::DeviceNotAvailable);
},
(_, Err(_)) => {
Ok(false)
@ -294,7 +298,7 @@ unsafe impl Sync for Device {
}
impl Device {
pub fn name(&self) -> String {
pub fn name(&self) -> Result<String, DeviceNameError> {
unsafe {
// Open the device's property store.
let mut property_store = ptr::null_mut();
@ -302,15 +306,24 @@ impl Device {
// Get the endpoint's friendly-name property.
let mut property_value = mem::zeroed();
check_result(
if let Err(err) = check_result(
(*property_store).GetValue(
&devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _,
&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.
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_utf16 = ptr_usize as *const u16;
@ -323,12 +336,15 @@ impl Device {
// Create the utf16 slice and covert it into a string.
let name_slice = slice::from_raw_parts(ptr_utf16, len as usize);
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.
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
// parameter error. Thus we just assume that the default number of channels is the only
// 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.
com::com_initialized();
// Retrieve the `IAudioClient`.
let lock = match self.ensure_future_audio_client() {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
return Err(FormatsEnumerationError::DeviceNotAvailable),
e => e.unwrap(),
Ok(lock) => lock,
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());
},
};
let client = lock.unwrap().0;
@ -402,11 +424,15 @@ impl Device {
// Retrieve the pointer to the default WAVEFORMATEX.
let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized());
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(()) => (),
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.
@ -450,8 +476,15 @@ impl Device {
// TODO: Test the different sample formats?
// Create the supported formats.
let mut format = format_from_waveformatex_ptr(default_waveformatex_ptr.0)
.expect("could not create a cpal::Format from a WAVEFORMATEX");
let mut format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0) {
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());
for rate in supported_sample_rates {
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 {
self.supported_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 {
self.supported_formats()
// If it's an input device, assume no output formats.
@ -489,9 +522,15 @@ impl Device {
com::com_initialized();
let lock = match self.ensure_future_audio_client() {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
return Err(DefaultFormatError::DeviceNotAvailable),
e => e.unwrap(),
Ok(lock) => lock,
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(DefaultFormatError::DeviceNotAvailable)
}
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
};
let client = lock.unwrap().0;
@ -501,7 +540,11 @@ impl Device {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
return Err(DefaultFormatError::DeviceNotAvailable);
},
Err(e) => panic!("{:?}", e),
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
@ -688,6 +731,32 @@ pub struct Devices {
next_item: u32,
}
impl Devices {
pub fn new() -> Result<Self, DevicesError> {
unsafe {
let mut collection: *mut IMMDeviceCollection = mem::uninitialized();
// can fail because of wrong parameters (should never happen) or out of memory
check_result_backend_specific(
(*ENUMERATOR.0).EnumAudioEndpoints(
eAll,
DEVICE_STATE_ACTIVE,
&mut collection,
)
)?;
let mut count = mem::uninitialized();
// can fail if the parameter is null, which should never happen
check_result_backend_specific((*collection).GetCount(&mut count))?;
Ok(Devices {
collection: collection,
total_count: count,
next_item: 0,
})
}
}
}
unsafe impl Send 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 {
type Item = Device;

View File

@ -1,10 +1,10 @@
extern crate winapi;
use BackendSpecificError;
use self::winapi::um::winnt::HRESULT;
use std::io::Error as IoError;
pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device};
pub use self::stream::{EventLoop, StreamId};
use self::winapi::um::winnt::HRESULT;
mod com;
mod device;
@ -18,3 +18,13 @@ fn check_result(result: HRESULT) -> Result<(), IoError> {
Ok(())
}
}
fn check_result_backend_specific(result: HRESULT) -> Result<(), BackendSpecificError> {
match check_result(result) {
Ok(()) => Ok(()),
Err(err) => {
let description = format!("{}", err);
return 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::Ordering;
use CreationError;
use BackendSpecificError;
use BuildStreamError;
use Format;
use PauseStreamError;
use PlayStreamError;
use SampleFormat;
use StreamData;
use UnknownTypeOutputBuffer;
@ -114,7 +117,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
unsafe {
// Making sure that COM is initialized.
@ -123,21 +126,26 @@ impl EventLoop {
// Obtaining a `IAudioClient`.
let audio_client = match device.build_audioclient() {
Ok(client) => client,
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
return Err(CreationError::DeviceNotAvailable),
e => e.unwrap(),
return Err(BuildStreamError::DeviceNotAvailable),
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
};
// Computing the format and initializing the device.
let waveformatex = {
let format_attempt = format_to_waveformatextensible(format)
.ok_or(CreationError::FormatNotSupported)?;
.ok_or(BuildStreamError::FormatNotSupported)?;
let share_mode = AUDCLNT_SHAREMODE_SHARED;
// Ensure the format is supported.
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
Ok(false) => return Err(CreationError::FormatNotSupported),
Err(_) => return Err(CreationError::DeviceNotAvailable),
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
_ => (),
}
@ -154,11 +162,13 @@ impl EventLoop {
Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(CreationError::DeviceNotAvailable);
return Err(BuildStreamError::DeviceNotAvailable);
},
Err(e) => {
(*audio_client).Release();
panic!("{:?}", e);
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
@ -175,11 +185,13 @@ impl EventLoop {
Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(CreationError::DeviceNotAvailable);
return Err(BuildStreamError::DeviceNotAvailable);
},
Err(e) => {
(*audio_client).Release();
panic!("{:?}", e);
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
@ -192,16 +204,17 @@ impl EventLoop {
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
if event == ptr::null_mut() {
(*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)) {
Err(_) => {
(*audio_client).Release();
panic!("Failed to call SetEventHandle")
},
Ok(_) => (),
};
if let Err(e) = check_result((*audio_client).SetEventHandle(event)) {
(*audio_client).Release();
let description = format!("failed to call SetEventHandle: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
event
};
@ -218,11 +231,13 @@ impl EventLoop {
Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(CreationError::DeviceNotAvailable);
return Err(BuildStreamError::DeviceNotAvailable);
},
Err(e) => {
(*audio_client).Release();
panic!("{:?}", e);
let description = format!("failed to build capture client: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
@ -231,7 +246,9 @@ impl EventLoop {
};
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
// `run()` method and added to the `RunContext`.
@ -261,7 +278,7 @@ impl EventLoop {
&self,
device: &Device,
format: &Format,
) -> Result<StreamId, CreationError>
) -> Result<StreamId, BuildStreamError>
{
unsafe {
// Making sure that COM is initialized.
@ -270,21 +287,26 @@ impl EventLoop {
// Obtaining a `IAudioClient`.
let audio_client = match device.build_audioclient() {
Ok(client) => client,
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
return Err(CreationError::DeviceNotAvailable),
e => e.unwrap(),
return Err(BuildStreamError::DeviceNotAvailable),
Err(e) => {
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
}
};
// Computing the format and initializing the device.
let waveformatex = {
let format_attempt = format_to_waveformatextensible(format)
.ok_or(CreationError::FormatNotSupported)?;
.ok_or(BuildStreamError::FormatNotSupported)?;
let share_mode = AUDCLNT_SHAREMODE_SHARED;
// Ensure the format is supported.
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
Ok(false) => return Err(CreationError::FormatNotSupported),
Err(_) => return Err(CreationError::DeviceNotAvailable),
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
_ => (),
}
@ -299,11 +321,13 @@ impl EventLoop {
Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(CreationError::DeviceNotAvailable);
return Err(BuildStreamError::DeviceNotAvailable);
},
Err(e) => {
(*audio_client).Release();
panic!("{:?}", e);
let description = format!("{}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
@ -316,13 +340,17 @@ impl EventLoop {
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
if event == ptr::null_mut() {
(*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)) {
Err(_) => {
Err(e) => {
(*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(_) => (),
};
@ -339,11 +367,13 @@ impl EventLoop {
Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(CreationError::DeviceNotAvailable);
return Err(BuildStreamError::DeviceNotAvailable);
},
Err(e) => {
(*audio_client).Release();
panic!("{:?}", e);
let description = format!("failed to obtain buffer size: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
@ -363,11 +393,13 @@ impl EventLoop {
Err(ref e)
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
(*audio_client).Release();
return Err(CreationError::DeviceNotAvailable);
return Err(BuildStreamError::DeviceNotAvailable);
},
Err(e) => {
(*audio_client).Release();
panic!("{:?}", e);
let description = format!("failed to build render client: {}", e);
let err = BackendSpecificError { description };
return Err(err.into());
},
Ok(()) => (),
};
@ -376,7 +408,9 @@ impl EventLoop {
};
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
// `run()` method and added to the `RunContext`.
@ -529,7 +563,7 @@ impl EventLoop {
if hresult == AUDCLNT_S_BUFFER_EMPTY { continue; }
debug_assert!(!buffer.is_null());
let buffer_len = frames_available as usize
let buffer_len = frames_available as usize
* stream.bytes_per_frame as usize / sample_size;
// Simplify the capture callback sample format branches.
@ -568,9 +602,9 @@ impl EventLoop {
&mut buffer as *mut *mut _,
);
// FIXME: can return `AUDCLNT_E_DEVICE_INVALIDATED`
check_result(hresult).unwrap();
check_result(hresult).unwrap();
debug_assert!(!buffer.is_null());
let buffer_len = frames_available as usize
let buffer_len = frames_available as usize
* stream.bytes_per_frame as usize / sample_size;
// Simplify the render callback sample format branches.
@ -594,7 +628,7 @@ impl EventLoop {
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => (),
e => e.unwrap(),
};
}}
}}
}
match stream.sample_format {
@ -610,13 +644,15 @@ impl EventLoop {
}
#[inline]
pub fn play_stream(&self, stream: StreamId) {
pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
self.push_command(Command::PlayStream(stream));
Ok(())
}
#[inline]
pub fn pause_stream(&self, stream: StreamId) {
pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
self.push_command(Command::PauseStream(stream));
Ok(())
}
#[inline]