Merge pull request #286 from mitchmindtree/error_handling
Error Handling Overhaul
This commit is contained in:
commit
9cc5df8805
|
@ -9,6 +9,7 @@ license = "Apache-2.0"
|
|||
keywords = ["audio", "sound"]
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1.5"
|
||||
lazy_static = "1.3"
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
31
appveyor.yml
31
appveyor.yml
|
@ -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%
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
236
src/alsa/mod.rs
236
src/alsa/mod.rs
|
@ -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))
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
238
src/lib.rs
238
src/lib.rs
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`.
|
||||
|
@ -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]
|
||||
|
|
Loading…
Reference in New Issue