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"]
|
keywords = ["audio", "sound"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
failure = "0.1.5"
|
||||||
lazy_static = "1.3"
|
lazy_static = "1.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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:
|
install:
|
||||||
- ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-nightly-i686-pc-windows-gnu.exe'
|
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||||
- ps: Start-FileDownload 'https://static.rust-lang.org/cargo-dist/cargo-nightly-i686-pc-windows-gnu.tar.gz'
|
- rustup-init -yv --default-toolchain %channel% --default-host %target%
|
||||||
- rust-nightly-i686-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin
|
||||||
- 7z e cargo-nightly-i686-pc-windows-gnu.tar.gz
|
- rustc -vV
|
||||||
- 7z x cargo-nightly-i686-pc-windows-gnu.tar
|
- cargo -vV
|
||||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
|
||||||
- SET PATH=%PATH%;%CD%\cargo-nightly-i686-pc-windows-gnu\bin
|
|
||||||
- rustc -V
|
|
||||||
- cargo -V
|
|
||||||
|
|
||||||
build: false
|
build: false
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cargo build --verbose
|
- cargo test --verbose %cargoflags%
|
||||||
# - cargo test --verbose
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
extern crate cpal;
|
extern crate cpal;
|
||||||
|
extern crate failure;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), failure::Error> {
|
||||||
let device = cpal::default_output_device().expect("Failed to get default output device");
|
let device = cpal::default_output_device().expect("failed to find a default output device");
|
||||||
let format = device.default_output_format().expect("Failed to get default output format");
|
let format = device.default_output_format()?;
|
||||||
let event_loop = cpal::EventLoop::new();
|
let event_loop = cpal::EventLoop::new();
|
||||||
let stream_id = event_loop.build_output_stream(&device, &format).unwrap();
|
let stream_id = event_loop.build_output_stream(&device, &format)?;
|
||||||
event_loop.play_stream(stream_id.clone());
|
event_loop.play_stream(stream_id.clone())?;
|
||||||
|
|
||||||
let sample_rate = format.sample_rate.0 as f32;
|
let sample_rate = format.sample_rate.0 as f32;
|
||||||
let mut sample_clock = 0f32;
|
let mut sample_clock = 0f32;
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
extern crate cpal;
|
extern crate cpal;
|
||||||
|
extern crate failure;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), failure::Error> {
|
||||||
println!("Default Input Device:\n {:?}", cpal::default_input_device().map(|e| e.name()));
|
let default_in = cpal::default_input_device().map(|e| e.name().unwrap());
|
||||||
println!("Default Output Device:\n {:?}", cpal::default_output_device().map(|e| e.name()));
|
let default_out = cpal::default_output_device().map(|e| e.name().unwrap());
|
||||||
|
println!("Default Input Device:\n {:?}", default_in);
|
||||||
|
println!("Default Output Device:\n {:?}", default_out);
|
||||||
|
|
||||||
let devices = cpal::devices();
|
let devices = cpal::devices()?;
|
||||||
println!("Devices: ");
|
println!("Devices: ");
|
||||||
for (device_index, device) in devices.enumerate() {
|
for (device_index, device) in devices.enumerate() {
|
||||||
println!("{}. \"{}\"",
|
println!("{}. \"{}\"", device_index + 1, device.name()?);
|
||||||
device_index + 1,
|
|
||||||
device.name());
|
|
||||||
|
|
||||||
// Input formats
|
// Input formats
|
||||||
if let Ok(fmt) = device.default_input_format() {
|
if let Ok(fmt) = device.default_input_format() {
|
||||||
|
@ -47,4 +48,6 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,26 +7,27 @@
|
||||||
//! precisely synchronised.
|
//! precisely synchronised.
|
||||||
|
|
||||||
extern crate cpal;
|
extern crate cpal;
|
||||||
|
extern crate failure;
|
||||||
|
|
||||||
const LATENCY_MS: f32 = 150.0;
|
const LATENCY_MS: f32 = 150.0;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), failure::Error> {
|
||||||
let event_loop = cpal::EventLoop::new();
|
let event_loop = cpal::EventLoop::new();
|
||||||
|
|
||||||
// Default devices.
|
// Default devices.
|
||||||
let input_device = cpal::default_input_device().expect("Failed to get default input device");
|
let input_device = cpal::default_input_device().expect("failed to get default input device");
|
||||||
let output_device = cpal::default_output_device().expect("Failed to get default output device");
|
let output_device = cpal::default_output_device().expect("failed to get default output device");
|
||||||
println!("Using default input device: \"{}\"", input_device.name());
|
println!("Using default input device: \"{}\"", input_device.name()?);
|
||||||
println!("Using default output device: \"{}\"", output_device.name());
|
println!("Using default output device: \"{}\"", output_device.name()?);
|
||||||
|
|
||||||
// We'll try and use the same format between streams to keep it simple
|
// We'll try and use the same format between streams to keep it simple
|
||||||
let mut format = input_device.default_input_format().expect("Failed to get default format");
|
let mut format = input_device.default_input_format()?;
|
||||||
format.data_type = cpal::SampleFormat::F32;
|
format.data_type = cpal::SampleFormat::F32;
|
||||||
|
|
||||||
// Build streams.
|
// Build streams.
|
||||||
println!("Attempting to build both streams with `{:?}`.", format);
|
println!("Attempting to build both streams with `{:?}`.", format);
|
||||||
let input_stream_id = event_loop.build_input_stream(&input_device, &format).unwrap();
|
let input_stream_id = event_loop.build_input_stream(&input_device, &format)?;
|
||||||
let output_stream_id = event_loop.build_output_stream(&output_device, &format).unwrap();
|
let output_stream_id = event_loop.build_output_stream(&output_device, &format)?;
|
||||||
println!("Successfully built streams.");
|
println!("Successfully built streams.");
|
||||||
|
|
||||||
// Create a delay in case the input and output devices aren't synced.
|
// Create a delay in case the input and output devices aren't synced.
|
||||||
|
@ -38,13 +39,13 @@ fn main() {
|
||||||
|
|
||||||
// Fill the samples with 0.0 equal to the length of the delay.
|
// Fill the samples with 0.0 equal to the length of the delay.
|
||||||
for _ in 0..latency_samples {
|
for _ in 0..latency_samples {
|
||||||
tx.send(0.0).unwrap();
|
tx.send(0.0)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play the streams.
|
// Play the streams.
|
||||||
println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS);
|
println!("Starting the input and output streams with `{}` milliseconds of latency.", LATENCY_MS);
|
||||||
event_loop.play_stream(input_stream_id.clone());
|
event_loop.play_stream(input_stream_id.clone())?;
|
||||||
event_loop.play_stream(output_stream_id.clone());
|
event_loop.play_stream(output_stream_id.clone())?;
|
||||||
|
|
||||||
// Run the event loop on a separate thread.
|
// Run the event loop on a separate thread.
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
@ -87,4 +88,5 @@ fn main() {
|
||||||
println!("Playing for 3 seconds... ");
|
println!("Playing for 3 seconds... ");
|
||||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||||
println!("Done!");
|
println!("Done!");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,23 +3,23 @@
|
||||||
//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav".
|
//! The input data is recorded to "$CARGO_MANIFEST_DIR/recorded.wav".
|
||||||
|
|
||||||
extern crate cpal;
|
extern crate cpal;
|
||||||
|
extern crate failure;
|
||||||
extern crate hound;
|
extern crate hound;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), failure::Error> {
|
||||||
// Setup the default input device and stream with the default input format.
|
// Setup the default input device and stream with the default input format.
|
||||||
let device = cpal::default_input_device().expect("Failed to get default input device");
|
let device = cpal::default_input_device().expect("Failed to get default input device");
|
||||||
println!("Default input device: {}", device.name());
|
println!("Default input device: {}", device.name()?);
|
||||||
let format = device.default_input_format().expect("Failed to get default input format");
|
let format = device.default_input_format().expect("Failed to get default input format");
|
||||||
println!("Default input format: {:?}", format);
|
println!("Default input format: {:?}", format);
|
||||||
let event_loop = cpal::EventLoop::new();
|
let event_loop = cpal::EventLoop::new();
|
||||||
let stream_id = event_loop.build_input_stream(&device, &format)
|
let stream_id = event_loop.build_input_stream(&device, &format)?;
|
||||||
.expect("Failed to build input stream");
|
event_loop.play_stream(stream_id)?;
|
||||||
event_loop.play_stream(stream_id);
|
|
||||||
|
|
||||||
// The WAV file we're recording to.
|
// The WAV file we're recording to.
|
||||||
const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");
|
const PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/recorded.wav");
|
||||||
let spec = wav_spec_from_format(&format);
|
let spec = wav_spec_from_format(&format);
|
||||||
let writer = hound::WavWriter::create(PATH, spec).unwrap();
|
let writer = hound::WavWriter::create(PATH, spec)?;
|
||||||
let writer = std::sync::Arc::new(std::sync::Mutex::new(Some(writer)));
|
let writer = std::sync::Arc::new(std::sync::Mutex::new(Some(writer)));
|
||||||
|
|
||||||
// A flag to indicate that recording is in progress.
|
// A flag to indicate that recording is in progress.
|
||||||
|
@ -73,8 +73,9 @@ fn main() {
|
||||||
// Let recording go for roughly three seconds.
|
// Let recording go for roughly three seconds.
|
||||||
std::thread::sleep(std::time::Duration::from_secs(3));
|
std::thread::sleep(std::time::Duration::from_secs(3));
|
||||||
recording.store(false, std::sync::atomic::Ordering::Relaxed);
|
recording.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
writer.lock().unwrap().take().unwrap().finalize().unwrap();
|
writer.lock().unwrap().take().unwrap().finalize()?;
|
||||||
println!("Recording {} complete!", PATH);
|
println!("Recording {} complete!", PATH);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {
|
fn sample_format(format: cpal::SampleFormat) -> hound::SampleFormat {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use {BackendSpecificError, DevicesError};
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use super::alsa;
|
use super::alsa;
|
||||||
use super::check_errors;
|
use super::check_errors;
|
||||||
|
@ -13,6 +14,28 @@ pub struct Devices {
|
||||||
next_str: *const *const u8,
|
next_str: *const *const u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Devices {
|
||||||
|
pub fn new() -> Result<Self, DevicesError> {
|
||||||
|
unsafe {
|
||||||
|
// TODO: check in which situation this can fail.
|
||||||
|
let card = -1; // -1 means all cards.
|
||||||
|
let iface = b"pcm\0"; // Interface identification.
|
||||||
|
let mut hints = mem::uninitialized(); // Array of device name hints.
|
||||||
|
let res = alsa::snd_device_name_hint(card, iface.as_ptr() as *const _, &mut hints);
|
||||||
|
if let Err(description) = check_errors(res) {
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
let hints = hints as *const *const u8;
|
||||||
|
let devices = Devices {
|
||||||
|
global_list: hints,
|
||||||
|
next_str: hints,
|
||||||
|
};
|
||||||
|
Ok(devices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl Send for Devices {
|
unsafe impl Send for Devices {
|
||||||
}
|
}
|
||||||
unsafe impl Sync for Devices {
|
unsafe impl Sync for Devices {
|
||||||
|
@ -27,24 +50,6 @@ impl Drop for Devices {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Devices {
|
|
||||||
fn default() -> Devices {
|
|
||||||
unsafe {
|
|
||||||
let mut hints = mem::uninitialized();
|
|
||||||
// TODO: check in which situation this can fail
|
|
||||||
check_errors(alsa::snd_device_name_hint(-1, b"pcm\0".as_ptr() as *const _, &mut hints))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let hints = hints as *const *const u8;
|
|
||||||
|
|
||||||
Devices {
|
|
||||||
global_list: hints,
|
|
||||||
next_str: hints,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Devices {
|
impl Iterator for Devices {
|
||||||
type Item = Device;
|
type Item = Device;
|
||||||
|
|
||||||
|
|
238
src/alsa/mod.rs
238
src/alsa/mod.rs
|
@ -4,10 +4,14 @@ extern crate libc;
|
||||||
pub use self::enumerate::{Devices, default_input_device, default_output_device};
|
pub use self::enumerate::{Devices, default_input_device, default_output_device};
|
||||||
|
|
||||||
use ChannelCount;
|
use ChannelCount;
|
||||||
use CreationError;
|
use BackendSpecificError;
|
||||||
|
use BuildStreamError;
|
||||||
use DefaultFormatError;
|
use DefaultFormatError;
|
||||||
|
use DeviceNameError;
|
||||||
use Format;
|
use Format;
|
||||||
use FormatsEnumerationError;
|
use PauseStreamError;
|
||||||
|
use PlayStreamError;
|
||||||
|
use SupportedFormatsError;
|
||||||
use SampleFormat;
|
use SampleFormat;
|
||||||
use SampleRate;
|
use SampleRate;
|
||||||
use StreamData;
|
use StreamData;
|
||||||
|
@ -73,17 +77,24 @@ pub struct Device(String);
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> Result<String, DeviceNameError> {
|
||||||
self.0.clone()
|
Ok(self.0.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn supported_formats(
|
unsafe fn supported_formats(
|
||||||
&self,
|
&self,
|
||||||
stream_t: alsa::snd_pcm_stream_t,
|
stream_t: alsa::snd_pcm_stream_t,
|
||||||
) -> Result<VecIntoIter<SupportedFormat>, FormatsEnumerationError>
|
) -> Result<VecIntoIter<SupportedFormat>, SupportedFormatsError>
|
||||||
{
|
{
|
||||||
let mut handle = mem::uninitialized();
|
let mut handle = mem::uninitialized();
|
||||||
let device_name = ffi::CString::new(&self.0[..]).expect("Unable to get device name");
|
let device_name = match ffi::CString::new(&self.0[..]) {
|
||||||
|
Ok(name) => name,
|
||||||
|
Err(err) => {
|
||||||
|
let description = format!("failed to retrieve device name: {}", err);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
match alsa::snd_pcm_open(
|
match alsa::snd_pcm_open(
|
||||||
&mut handle,
|
&mut handle,
|
||||||
|
@ -92,16 +103,20 @@ impl Device {
|
||||||
alsa::SND_PCM_NONBLOCK,
|
alsa::SND_PCM_NONBLOCK,
|
||||||
) {
|
) {
|
||||||
-2 |
|
-2 |
|
||||||
-16 /* determined empirically */ => return Err(FormatsEnumerationError::DeviceNotAvailable),
|
-16 /* determined empirically */ => return Err(SupportedFormatsError::DeviceNotAvailable),
|
||||||
-22 => return Err(FormatsEnumerationError::InvalidArgument),
|
-22 => return Err(SupportedFormatsError::InvalidArgument),
|
||||||
e => if check_errors(e).is_err() {
|
e => if let Err(description) = check_errors(e) {
|
||||||
return Err(FormatsEnumerationError::Unknown)
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let hw_params = HwParams::alloc();
|
let hw_params = HwParams::alloc();
|
||||||
match check_errors(alsa::snd_pcm_hw_params_any(handle, hw_params.0)) {
|
match check_errors(alsa::snd_pcm_hw_params_any(handle, hw_params.0)) {
|
||||||
Err(_) => return Ok(Vec::new().into_iter()),
|
Err(description) => {
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -158,15 +173,26 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut min_rate = mem::uninitialized();
|
let mut min_rate = mem::uninitialized();
|
||||||
check_errors(alsa::snd_pcm_hw_params_get_rate_min(hw_params.0,
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_min(
|
||||||
&mut min_rate,
|
hw_params.0,
|
||||||
ptr::null_mut()))
|
&mut min_rate,
|
||||||
.expect("unable to get minimum supported rete");
|
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();
|
let mut max_rate = mem::uninitialized();
|
||||||
check_errors(alsa::snd_pcm_hw_params_get_rate_max(hw_params.0,
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_max(
|
||||||
&mut max_rate,
|
hw_params.0,
|
||||||
ptr::null_mut()))
|
&mut max_rate,
|
||||||
.expect("unable to get maximum supported 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 {
|
let sample_rates = if min_rate == max_rate {
|
||||||
vec![(min_rate, max_rate)]
|
vec![(min_rate, max_rate)]
|
||||||
|
@ -212,11 +238,19 @@ impl Device {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut min_channels = mem::uninitialized();
|
let mut min_channels = mem::uninitialized();
|
||||||
check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels))
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels)) {
|
||||||
.expect("unable to get minimum supported channel count");
|
let description = format!("unable to get minimum supported channel count: {}", desc);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
let mut max_channels = mem::uninitialized();
|
let mut max_channels = mem::uninitialized();
|
||||||
check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels))
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels)) {
|
||||||
.expect("unable to get maximum supported channel count");
|
let description = format!("unable to get maximum supported channel count: {}", desc);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
let max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned
|
let max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned
|
||||||
let supported_channels = (min_channels .. max_channels + 1)
|
let supported_channels = (min_channels .. max_channels + 1)
|
||||||
.filter_map(|num| if alsa::snd_pcm_hw_params_test_channels(
|
.filter_map(|num| if alsa::snd_pcm_hw_params_test_channels(
|
||||||
|
@ -251,13 +285,13 @@ impl Device {
|
||||||
Ok(output.into_iter())
|
Ok(output.into_iter())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
|
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE)
|
self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
|
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK)
|
self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK)
|
||||||
}
|
}
|
||||||
|
@ -272,16 +306,16 @@ impl Device {
|
||||||
{
|
{
|
||||||
let mut formats: Vec<_> = unsafe {
|
let mut formats: Vec<_> = unsafe {
|
||||||
match self.supported_formats(stream_t) {
|
match self.supported_formats(stream_t) {
|
||||||
Err(FormatsEnumerationError::DeviceNotAvailable) => {
|
Err(SupportedFormatsError::DeviceNotAvailable) => {
|
||||||
return Err(DefaultFormatError::DeviceNotAvailable);
|
return Err(DefaultFormatError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(FormatsEnumerationError::InvalidArgument) => {
|
Err(SupportedFormatsError::InvalidArgument) => {
|
||||||
// this happens sometimes when querying for input and output capabilities but
|
// this happens sometimes when querying for input and output capabilities but
|
||||||
// the device supports only one
|
// the device supports only one
|
||||||
return Err(DefaultFormatError::StreamTypeNotSupported);
|
return Err(DefaultFormatError::StreamTypeNotSupported);
|
||||||
}
|
}
|
||||||
Err(FormatsEnumerationError::Unknown) => {
|
Err(SupportedFormatsError::BackendSpecific { err }) => {
|
||||||
return Err(DefaultFormatError::DeviceNotAvailable);
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
Ok(fmts) => fmts.collect(),
|
Ok(fmts) => fmts.collect(),
|
||||||
}
|
}
|
||||||
|
@ -644,7 +678,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
|
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
|
||||||
|
@ -656,31 +690,43 @@ impl EventLoop {
|
||||||
alsa::SND_PCM_STREAM_CAPTURE,
|
alsa::SND_PCM_STREAM_CAPTURE,
|
||||||
alsa::SND_PCM_NONBLOCK,
|
alsa::SND_PCM_NONBLOCK,
|
||||||
) {
|
) {
|
||||||
-16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable),
|
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
-22 => return Err(CreationError::InvalidArgument),
|
-22 => return Err(BuildStreamError::InvalidArgument),
|
||||||
e => if check_errors(e).is_err() {
|
e => if let Err(description) = check_errors(e) {
|
||||||
return Err(CreationError::Unknown);
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let hw_params = HwParams::alloc();
|
let hw_params = HwParams::alloc();
|
||||||
|
|
||||||
set_hw_params_from_format(capture_handle, &hw_params, format);
|
set_hw_params_from_format(capture_handle, &hw_params, format)
|
||||||
|
.map_err(|description| BackendSpecificError { description })?;
|
||||||
|
|
||||||
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
|
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
|
||||||
|
|
||||||
let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format);
|
let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format)
|
||||||
|
.map_err(|description| BackendSpecificError { description })?;
|
||||||
|
|
||||||
check_errors(alsa::snd_pcm_prepare(capture_handle))
|
if let Err(desc) = check_errors(alsa::snd_pcm_prepare(capture_handle)) {
|
||||||
.expect("could not get playback handle");
|
let description = format!("could not get capture handle: {}", desc);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
let num_descriptors = {
|
let num_descriptors = {
|
||||||
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(capture_handle);
|
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(capture_handle);
|
||||||
debug_assert!(num_descriptors >= 1);
|
if num_descriptors == 0 {
|
||||||
|
let description = "poll descriptor count for capture stream was 0".to_string();
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
num_descriptors as usize
|
num_descriptors as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
||||||
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows
|
if new_stream_id.0 == usize::max_value() {
|
||||||
|
return Err(BuildStreamError::StreamIdOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
let stream_inner = StreamInner {
|
let stream_inner = StreamInner {
|
||||||
id: new_stream_id.clone(),
|
id: new_stream_id.clone(),
|
||||||
|
@ -696,8 +742,11 @@ impl EventLoop {
|
||||||
buffer: None,
|
buffer: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
check_errors(alsa::snd_pcm_start(capture_handle))
|
if let Err(desc) = check_errors(alsa::snd_pcm_start(capture_handle)) {
|
||||||
.expect("could not start capture stream");
|
let description = format!("could not start capture stream: {}", desc);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
self.push_command(Command::NewStream(stream_inner));
|
self.push_command(Command::NewStream(stream_inner));
|
||||||
Ok(new_stream_id)
|
Ok(new_stream_id)
|
||||||
|
@ -708,7 +757,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
|
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
|
||||||
|
@ -720,31 +769,43 @@ impl EventLoop {
|
||||||
alsa::SND_PCM_STREAM_PLAYBACK,
|
alsa::SND_PCM_STREAM_PLAYBACK,
|
||||||
alsa::SND_PCM_NONBLOCK,
|
alsa::SND_PCM_NONBLOCK,
|
||||||
) {
|
) {
|
||||||
-16 /* determined empirically */ => return Err(CreationError::DeviceNotAvailable),
|
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
-22 => return Err(CreationError::InvalidArgument),
|
-22 => return Err(BuildStreamError::InvalidArgument),
|
||||||
e => if check_errors(e).is_err() {
|
e => if let Err(description) = check_errors(e) {
|
||||||
return Err(CreationError::Unknown);
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let hw_params = HwParams::alloc();
|
let hw_params = HwParams::alloc();
|
||||||
|
|
||||||
set_hw_params_from_format(playback_handle, &hw_params, format);
|
set_hw_params_from_format(playback_handle, &hw_params, format)
|
||||||
|
.map_err(|description| BackendSpecificError { description })?;
|
||||||
|
|
||||||
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
|
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
|
||||||
|
|
||||||
let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format);
|
let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format)
|
||||||
|
.map_err(|description| BackendSpecificError { description })?;
|
||||||
|
|
||||||
check_errors(alsa::snd_pcm_prepare(playback_handle))
|
if let Err(desc) = check_errors(alsa::snd_pcm_prepare(playback_handle)) {
|
||||||
.expect("could not get playback handle");
|
let description = format!("could not get playback handle: {}", desc);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
let num_descriptors = {
|
let num_descriptors = {
|
||||||
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(playback_handle);
|
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(playback_handle);
|
||||||
debug_assert!(num_descriptors >= 1);
|
if num_descriptors == 0 {
|
||||||
|
let description = "poll descriptor count for playback stream was 0".to_string();
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
num_descriptors as usize
|
num_descriptors as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
||||||
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows
|
if new_stream_id.0 == usize::max_value() {
|
||||||
|
return Err(BuildStreamError::StreamIdOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
let stream_inner = StreamInner {
|
let stream_inner = StreamInner {
|
||||||
id: new_stream_id.clone(),
|
id: new_stream_id.clone(),
|
||||||
|
@ -778,13 +839,15 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn play_stream(&self, stream_id: StreamId) {
|
pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
|
||||||
self.push_command(Command::PlayStream(stream_id));
|
self.push_command(Command::PlayStream(stream_id));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pause_stream(&self, stream_id: StreamId) {
|
pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
|
||||||
self.push_command(Command::PauseStream(stream_id));
|
self.push_command(Command::PauseStream(stream_id));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -794,12 +857,12 @@ unsafe fn set_hw_params_from_format(
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) {
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) {
|
||||||
return Err("Errors on pcm handle".to_string());
|
return Err(format!("errors on pcm handle: {}", e));
|
||||||
}
|
}
|
||||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_access(pcm_handle,
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_access(pcm_handle,
|
||||||
hw_params.0,
|
hw_params.0,
|
||||||
alsa::SND_PCM_ACCESS_RW_INTERLEAVED)) {
|
alsa::SND_PCM_ACCESS_RW_INTERLEAVED)) {
|
||||||
return Err("Handle not acessible".to_string());
|
return Err(format!("handle not acessible: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_type = if cfg!(target_endian = "big") {
|
let data_type = if cfg!(target_endian = "big") {
|
||||||
|
@ -819,29 +882,34 @@ unsafe fn set_hw_params_from_format(
|
||||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format(pcm_handle,
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format(pcm_handle,
|
||||||
hw_params.0,
|
hw_params.0,
|
||||||
data_type)) {
|
data_type)) {
|
||||||
return Err("Format could not be set".to_string());
|
return Err(format!("format could not be set: {}", e));
|
||||||
}
|
}
|
||||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(pcm_handle,
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(pcm_handle,
|
||||||
hw_params.0,
|
hw_params.0,
|
||||||
format.sample_rate.0 as libc::c_uint,
|
format.sample_rate.0 as libc::c_uint,
|
||||||
0)) {
|
0)) {
|
||||||
return Err("Sample rate could not be set".to_string());
|
return Err(format!("sample rate could not be set: {}", e));
|
||||||
}
|
}
|
||||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(pcm_handle,
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(pcm_handle,
|
||||||
hw_params.0,
|
hw_params.0,
|
||||||
format.channels as
|
format.channels as
|
||||||
libc::c_uint)) {
|
libc::c_uint)) {
|
||||||
return Err("Channel count could not be set".to_string());
|
return Err(format!("channel count could not be set: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Review this. 200ms seems arbitrary...
|
||||||
let mut max_buffer_size = format.sample_rate.0 as alsa::snd_pcm_uframes_t /
|
let mut max_buffer_size = format.sample_rate.0 as alsa::snd_pcm_uframes_t /
|
||||||
format.channels as alsa::snd_pcm_uframes_t /
|
format.channels as alsa::snd_pcm_uframes_t /
|
||||||
5; // 200ms of buffer
|
5; // 200ms of buffer
|
||||||
check_errors(alsa::snd_pcm_hw_params_set_buffer_size_max(pcm_handle,
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_buffer_size_max(pcm_handle,
|
||||||
hw_params.0,
|
hw_params.0,
|
||||||
&mut max_buffer_size))
|
&mut max_buffer_size))
|
||||||
.unwrap();
|
{
|
||||||
|
return Err(format!("max buffer size could not be set: {}", e));
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) {
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) {
|
||||||
return Err("Hardware params could not be set.".to_string());
|
return Err(format!("hardware params could not be set: {}", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -850,34 +918,42 @@ unsafe fn set_hw_params_from_format(
|
||||||
unsafe fn set_sw_params_from_format(
|
unsafe fn set_sw_params_from_format(
|
||||||
pcm_handle: *mut alsa::snd_pcm_t,
|
pcm_handle: *mut alsa::snd_pcm_t,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> (usize, usize)
|
) -> Result<(usize, usize), String>
|
||||||
{
|
{
|
||||||
let mut sw_params = mem::uninitialized(); // TODO: RAII
|
let mut sw_params = mem::uninitialized(); // TODO: RAII
|
||||||
check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)).unwrap();
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) {
|
||||||
check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)).unwrap();
|
return Err(format!("snd_pcm_sw_params_malloc failed: {}", e));
|
||||||
check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle,
|
}
|
||||||
sw_params,
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)) {
|
||||||
0))
|
return Err(format!("snd_pcm_sw_params_current failed: {}", e));
|
||||||
.unwrap();
|
}
|
||||||
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, 0)) {
|
||||||
|
return Err(format!("snd_pcm_sw_params_set_start_threshold failed: {}", e));
|
||||||
|
}
|
||||||
|
|
||||||
let (buffer_len, period_len) = {
|
let (buffer_len, period_len) = {
|
||||||
let mut buffer = mem::uninitialized();
|
let mut buffer = mem::uninitialized();
|
||||||
let mut period = mem::uninitialized();
|
let mut period = mem::uninitialized();
|
||||||
check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period))
|
if let Err(e) = check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period)) {
|
||||||
.expect("could not initialize buffer");
|
return Err(format!("failed to initialize buffer: {}", e));
|
||||||
assert!(buffer != 0);
|
}
|
||||||
check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle,
|
if buffer == 0 {
|
||||||
sw_params,
|
return Err(format!("initialization resulted in a null buffer"));
|
||||||
period))
|
}
|
||||||
.unwrap();
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle, sw_params, period)) {
|
||||||
|
return Err(format!("snd_pcm_sw_params_set_avail_min failed: {}", e));
|
||||||
|
}
|
||||||
let buffer = buffer as usize * format.channels as usize;
|
let buffer = buffer as usize * format.channels as usize;
|
||||||
let period = period as usize * format.channels as usize;
|
let period = period as usize * format.channels as usize;
|
||||||
(buffer, period)
|
(buffer, period)
|
||||||
};
|
};
|
||||||
|
|
||||||
check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)).unwrap();
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)) {
|
||||||
|
return Err(format!("snd_pcm_sw_params failed: {}", e));
|
||||||
|
}
|
||||||
|
|
||||||
alsa::snd_pcm_sw_params_free(sw_params);
|
alsa::snd_pcm_sw_params_free(sw_params);
|
||||||
(buffer_len, period_len)
|
Ok((buffer_len, period_len))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper around `hw_params`.
|
/// Wrapper around `hw_params`.
|
||||||
|
@ -913,8 +989,6 @@ impl Drop for StreamInner {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn check_errors(err: libc::c_int) -> Result<(), String> {
|
fn check_errors(err: libc::c_int) -> Result<(), String> {
|
||||||
use std::ffi;
|
|
||||||
|
|
||||||
if err < 0 {
|
if err < 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
let s = ffi::CStr::from_ptr(alsa::snd_strerror(err))
|
let s = ffi::CStr::from_ptr(alsa::snd_strerror(err))
|
||||||
|
@ -940,4 +1014,4 @@ unsafe fn cast_input_buffer<T>(v: &[u8]) -> &[T] {
|
||||||
unsafe fn cast_output_buffer<T>(v: &mut [u8]) -> &mut [T] {
|
unsafe fn cast_output_buffer<T>(v: &mut [u8]) -> &mut [T] {
|
||||||
debug_assert!(v.len() % std::mem::size_of::<T>() == 0);
|
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>())
|
std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::<T>())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use SupportedFormat;
|
use {BackendSpecificError, DevicesError, SupportedFormat};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ptr::null;
|
use std::ptr::null;
|
||||||
use std::vec::IntoIter as VecIntoIter;
|
use std::vec::IntoIter as VecIntoIter;
|
||||||
|
@ -64,20 +64,27 @@ unsafe fn audio_devices() -> Result<Vec<AudioDeviceID>, OSStatus> {
|
||||||
|
|
||||||
pub struct Devices(VecIntoIter<AudioDeviceID>);
|
pub struct Devices(VecIntoIter<AudioDeviceID>);
|
||||||
|
|
||||||
|
impl Devices {
|
||||||
|
pub fn new() -> Result<Self, DevicesError> {
|
||||||
|
let devices = unsafe {
|
||||||
|
match audio_devices() {
|
||||||
|
Ok(devices) => devices,
|
||||||
|
Err(os_status) => {
|
||||||
|
let description = format!("{}", os_status);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Devices(devices.into_iter()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl Send for Devices {
|
unsafe impl Send for Devices {
|
||||||
}
|
}
|
||||||
unsafe impl Sync for Devices {
|
unsafe impl Sync for Devices {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Devices {
|
|
||||||
fn default() -> Self {
|
|
||||||
let devices = unsafe {
|
|
||||||
audio_devices().expect("failed to get audio output devices")
|
|
||||||
};
|
|
||||||
Devices(devices.into_iter())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Devices {
|
impl Iterator for Devices {
|
||||||
type Item = Device;
|
type Item = Device;
|
||||||
fn next(&mut self) -> Option<Device> {
|
fn next(&mut self) -> Option<Device> {
|
||||||
|
|
|
@ -2,11 +2,14 @@ extern crate coreaudio;
|
||||||
extern crate core_foundation_sys;
|
extern crate core_foundation_sys;
|
||||||
|
|
||||||
use ChannelCount;
|
use ChannelCount;
|
||||||
use CreationError;
|
use BackendSpecificError;
|
||||||
|
use BuildStreamError;
|
||||||
use DefaultFormatError;
|
use DefaultFormatError;
|
||||||
|
use DeviceNameError;
|
||||||
use Format;
|
use Format;
|
||||||
use FormatsEnumerationError;
|
use PauseStreamError;
|
||||||
use Sample;
|
use PlayStreamError;
|
||||||
|
use SupportedFormatsError;
|
||||||
use SampleFormat;
|
use SampleFormat;
|
||||||
use SampleRate;
|
use SampleRate;
|
||||||
use StreamData;
|
use StreamData;
|
||||||
|
@ -51,7 +54,6 @@ use self::coreaudio::sys::{
|
||||||
kAudioFormatFlagIsFloat,
|
kAudioFormatFlagIsFloat,
|
||||||
kAudioFormatFlagIsPacked,
|
kAudioFormatFlagIsPacked,
|
||||||
kAudioFormatLinearPCM,
|
kAudioFormatLinearPCM,
|
||||||
kAudioHardwareNoError,
|
|
||||||
kAudioObjectPropertyElementMaster,
|
kAudioObjectPropertyElementMaster,
|
||||||
kAudioObjectPropertyScopeOutput,
|
kAudioObjectPropertyScopeOutput,
|
||||||
kAudioOutputUnitProperty_CurrentDevice,
|
kAudioOutputUnitProperty_CurrentDevice,
|
||||||
|
@ -75,7 +77,7 @@ pub struct Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> Result<String, DeviceNameError> {
|
||||||
let property_address = AudioObjectPropertyAddress {
|
let property_address = AudioObjectPropertyAddress {
|
||||||
mSelector: kAudioDevicePropertyDeviceNameCFString,
|
mSelector: kAudioDevicePropertyDeviceNameCFString,
|
||||||
mScope: kAudioDevicePropertyScopeOutput,
|
mScope: kAudioDevicePropertyScopeOutput,
|
||||||
|
@ -92,23 +94,24 @@ impl Device {
|
||||||
&data_size as *const _ as *mut _,
|
&data_size as *const _ as *mut _,
|
||||||
&device_name as *const _ as *mut _,
|
&device_name as *const _ as *mut _,
|
||||||
);
|
);
|
||||||
if status != kAudioHardwareNoError as i32 {
|
check_os_status(status)?;
|
||||||
return format!("<OSStatus: {:?}>", status);
|
|
||||||
}
|
|
||||||
let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8);
|
let c_string: *const c_char = CFStringGetCStringPtr(device_name, kCFStringEncodingUTF8);
|
||||||
if c_string == null() {
|
if c_string == null() {
|
||||||
return "<null>".into();
|
let description = "core foundation unexpectedly returned null string".to_string();
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
CStr::from_ptr(c_string as *mut _)
|
CStr::from_ptr(c_string as *mut _)
|
||||||
};
|
};
|
||||||
c_str.to_string_lossy().into_owned()
|
Ok(c_str.to_string_lossy().into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logic re-used between `supported_input_formats` and `supported_output_formats`.
|
// Logic re-used between `supported_input_formats` and `supported_output_formats`.
|
||||||
fn supported_formats(
|
fn supported_formats(
|
||||||
&self,
|
&self,
|
||||||
scope: AudioObjectPropertyScope,
|
scope: AudioObjectPropertyScope,
|
||||||
) -> Result<SupportedOutputFormats, FormatsEnumerationError>
|
) -> Result<SupportedOutputFormats, SupportedFormatsError>
|
||||||
{
|
{
|
||||||
let mut property_address = AudioObjectPropertyAddress {
|
let mut property_address = AudioObjectPropertyAddress {
|
||||||
mSelector: kAudioDevicePropertyStreamConfiguration,
|
mSelector: kAudioDevicePropertyStreamConfiguration,
|
||||||
|
@ -126,9 +129,8 @@ impl Device {
|
||||||
null(),
|
null(),
|
||||||
&data_size as *const _ as *mut _,
|
&data_size as *const _ as *mut _,
|
||||||
);
|
);
|
||||||
if status != kAudioHardwareNoError as i32 {
|
check_os_status(status)?;
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
let mut audio_buffer_list: Vec<u8> = vec![];
|
let mut audio_buffer_list: Vec<u8> = vec![];
|
||||||
audio_buffer_list.reserve_exact(data_size as usize);
|
audio_buffer_list.reserve_exact(data_size as usize);
|
||||||
let status = AudioObjectGetPropertyData(
|
let status = AudioObjectGetPropertyData(
|
||||||
|
@ -139,9 +141,8 @@ impl Device {
|
||||||
&data_size as *const _ as *mut _,
|
&data_size as *const _ as *mut _,
|
||||||
audio_buffer_list.as_mut_ptr() as *mut _,
|
audio_buffer_list.as_mut_ptr() as *mut _,
|
||||||
);
|
);
|
||||||
if status != kAudioHardwareNoError as i32 {
|
check_os_status(status)?;
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList;
|
let audio_buffer_list = audio_buffer_list.as_mut_ptr() as *mut AudioBufferList;
|
||||||
|
|
||||||
// If there's no buffers, skip.
|
// If there's no buffers, skip.
|
||||||
|
@ -176,9 +177,8 @@ impl Device {
|
||||||
null(),
|
null(),
|
||||||
&data_size as *const _ as *mut _,
|
&data_size as *const _ as *mut _,
|
||||||
);
|
);
|
||||||
if status != kAudioHardwareNoError as i32 {
|
check_os_status(status)?;
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
|
let n_ranges = data_size as usize / mem::size_of::<AudioValueRange>();
|
||||||
let mut ranges: Vec<u8> = vec![];
|
let mut ranges: Vec<u8> = vec![];
|
||||||
ranges.reserve_exact(data_size as usize);
|
ranges.reserve_exact(data_size as usize);
|
||||||
|
@ -190,9 +190,8 @@ impl Device {
|
||||||
&data_size as *const _ as *mut _,
|
&data_size as *const _ as *mut _,
|
||||||
ranges.as_mut_ptr() as *mut _,
|
ranges.as_mut_ptr() as *mut _,
|
||||||
);
|
);
|
||||||
if status != kAudioHardwareNoError as i32 {
|
check_os_status(status)?;
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
|
let ranges: *mut AudioValueRange = ranges.as_mut_ptr() as *mut _;
|
||||||
let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges);
|
let ranges: &'static [AudioValueRange] = slice::from_raw_parts(ranges, n_ranges);
|
||||||
|
|
||||||
|
@ -212,11 +211,11 @@ impl Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_input_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
|
pub fn supported_input_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
||||||
self.supported_formats(kAudioObjectPropertyScopeInput)
|
self.supported_formats(kAudioObjectPropertyScopeInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
|
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
||||||
self.supported_formats(kAudioObjectPropertyScopeOutput)
|
self.supported_formats(kAudioObjectPropertyScopeOutput)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,18 +224,25 @@ impl Device {
|
||||||
scope: AudioObjectPropertyScope,
|
scope: AudioObjectPropertyScope,
|
||||||
) -> Result<Format, DefaultFormatError>
|
) -> Result<Format, DefaultFormatError>
|
||||||
{
|
{
|
||||||
fn default_format_error_from_os_status(status: OSStatus) -> Option<DefaultFormatError> {
|
fn default_format_error_from_os_status(status: OSStatus) -> Result<(), DefaultFormatError> {
|
||||||
let err = match coreaudio::Error::from_os_status(status) {
|
let err = match coreaudio::Error::from_os_status(status) {
|
||||||
Err(err) => err,
|
Err(err) => err,
|
||||||
Ok(_) => return None,
|
Ok(_) => return Ok(()),
|
||||||
};
|
};
|
||||||
match err {
|
match err {
|
||||||
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat |
|
|
||||||
coreaudio::Error::NoKnownSubtype |
|
|
||||||
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
|
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
|
||||||
coreaudio::Error::AudioCodec(_) |
|
coreaudio::Error::AudioCodec(_) |
|
||||||
coreaudio::Error::AudioFormat(_) => Some(DefaultFormatError::StreamTypeNotSupported),
|
coreaudio::Error::AudioFormat(_) => {
|
||||||
_ => Some(DefaultFormatError::DeviceNotAvailable),
|
Err(DefaultFormatError::StreamTypeNotSupported)
|
||||||
|
}
|
||||||
|
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::NoConnection) => {
|
||||||
|
Err(DefaultFormatError::DeviceNotAvailable)
|
||||||
|
}
|
||||||
|
err => {
|
||||||
|
let description = format!("{}", std::error::Error::description(&err));
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
Err(err.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,12 +263,7 @@ impl Device {
|
||||||
&data_size as *const _ as *mut _,
|
&data_size as *const _ as *mut _,
|
||||||
&asbd as *const _ as *mut _,
|
&asbd as *const _ as *mut _,
|
||||||
);
|
);
|
||||||
|
default_format_error_from_os_status(status)?;
|
||||||
if status != kAudioHardwareNoError as i32 {
|
|
||||||
let err = default_format_error_from_os_status(status)
|
|
||||||
.expect("no known error for OSStatus");
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sample_format = {
|
let sample_format = {
|
||||||
let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag(
|
let audio_format = coreaudio::audio_unit::AudioFormat::from_format_and_flag(
|
||||||
|
@ -338,15 +339,15 @@ struct StreamInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO need stronger error identification
|
// TODO need stronger error identification
|
||||||
impl From<coreaudio::Error> for CreationError {
|
impl From<coreaudio::Error> for BuildStreamError {
|
||||||
fn from(err: coreaudio::Error) -> CreationError {
|
fn from(err: coreaudio::Error) -> BuildStreamError {
|
||||||
match err {
|
match err {
|
||||||
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat |
|
coreaudio::Error::RenderCallbackBufferFormatDoesNotMatchAudioUnitStreamFormat |
|
||||||
coreaudio::Error::NoKnownSubtype |
|
coreaudio::Error::NoKnownSubtype |
|
||||||
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
|
coreaudio::Error::AudioUnit(coreaudio::error::AudioUnitError::FormatNotSupported) |
|
||||||
coreaudio::Error::AudioCodec(_) |
|
coreaudio::Error::AudioCodec(_) |
|
||||||
coreaudio::Error::AudioFormat(_) => CreationError::FormatNotSupported,
|
coreaudio::Error::AudioFormat(_) => BuildStreamError::FormatNotSupported,
|
||||||
_ => CreationError::DeviceNotAvailable,
|
_ => BuildStreamError::DeviceNotAvailable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -483,7 +484,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
// The scope and element for working with a device's input stream.
|
// The scope and element for working with a device's input stream.
|
||||||
let scope = Scope::Output;
|
let scope = Scope::Output;
|
||||||
|
@ -512,13 +513,16 @@ impl EventLoop {
|
||||||
// If the requested sample rate is different to the device sample rate, update the device.
|
// If the requested sample rate is different to the device sample rate, update the device.
|
||||||
if sample_rate as u32 != format.sample_rate.0 {
|
if sample_rate as u32 != format.sample_rate.0 {
|
||||||
|
|
||||||
// In order to avoid breaking existing input streams we `panic!` if there is already an
|
// In order to avoid breaking existing input streams we return an error if there is
|
||||||
// active input stream for this device with the actual sample rate.
|
// already an active input stream for this device with the actual sample rate.
|
||||||
for stream in &*self.streams.lock().unwrap() {
|
for stream in &*self.streams.lock().unwrap() {
|
||||||
if let Some(stream) = stream.as_ref() {
|
if let Some(stream) = stream.as_ref() {
|
||||||
if stream.device_id == device.audio_device_id {
|
if stream.device_id == device.audio_device_id {
|
||||||
panic!("cannot change device sample rate for stream as an existing stream \
|
let description = "cannot change device sample rate for stream as an \
|
||||||
is already running at the current sample rate.");
|
existing stream is already running at the current sample rate"
|
||||||
|
.into();
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -555,7 +559,7 @@ impl EventLoop {
|
||||||
.iter()
|
.iter()
|
||||||
.position(|r| r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate);
|
.position(|r| r.mMinimum as u32 == sample_rate && r.mMaximum as u32 == sample_rate);
|
||||||
let range_index = match maybe_index {
|
let range_index = match maybe_index {
|
||||||
None => return Err(CreationError::FormatNotSupported),
|
None => return Err(BuildStreamError::FormatNotSupported),
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -617,7 +621,9 @@ impl EventLoop {
|
||||||
let timer = ::std::time::Instant::now();
|
let timer = ::std::time::Instant::now();
|
||||||
while sample_rate != reported_rate {
|
while sample_rate != reported_rate {
|
||||||
if timer.elapsed() > ::std::time::Duration::from_secs(1) {
|
if timer.elapsed() > ::std::time::Duration::from_secs(1) {
|
||||||
panic!("timeout waiting for sample rate update for device");
|
let description = "timeout waiting for sample rate update for device".into();
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
::std::thread::sleep(::std::time::Duration::from_millis(5));
|
::std::thread::sleep(::std::time::Duration::from_millis(5));
|
||||||
}
|
}
|
||||||
|
@ -700,7 +706,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
let mut audio_unit = audio_unit_from_device(device, false)?;
|
let mut audio_unit = audio_unit_from_device(device, false)?;
|
||||||
|
|
||||||
|
@ -776,23 +782,43 @@ impl EventLoop {
|
||||||
streams[stream_id.0] = None;
|
streams[stream_id.0] = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_stream(&self, stream: StreamId) {
|
pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
|
||||||
let mut streams = self.streams.lock().unwrap();
|
let mut streams = self.streams.lock().unwrap();
|
||||||
let stream = streams[stream.0].as_mut().unwrap();
|
let stream = streams[stream.0].as_mut().unwrap();
|
||||||
|
|
||||||
if !stream.playing {
|
if !stream.playing {
|
||||||
stream.audio_unit.start().unwrap();
|
if let Err(e) = stream.audio_unit.start() {
|
||||||
|
let description = format!("{}", std::error::Error::description(&e));
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
stream.playing = true;
|
stream.playing = true;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pause_stream(&self, stream: StreamId) {
|
pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
|
||||||
let mut streams = self.streams.lock().unwrap();
|
let mut streams = self.streams.lock().unwrap();
|
||||||
let stream = streams[stream.0].as_mut().unwrap();
|
let stream = streams[stream.0].as_mut().unwrap();
|
||||||
|
|
||||||
if stream.playing {
|
if stream.playing {
|
||||||
stream.audio_unit.stop().unwrap();
|
if let Err(e) = stream.audio_unit.stop() {
|
||||||
|
let description = format!("{}", std::error::Error::description(&e));
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
stream.playing = false;
|
stream.playing = false;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_os_status(os_status: OSStatus) -> Result<(), BackendSpecificError> {
|
||||||
|
match coreaudio::Error::from_os_status(os_status) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
let description = std::error::Error::description(&err).to_string();
|
||||||
|
Err(BackendSpecificError { description })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,14 @@ use stdweb::unstable::TryInto;
|
||||||
use stdweb::web::TypedArray;
|
use stdweb::web::TypedArray;
|
||||||
use stdweb::web::set_timeout;
|
use stdweb::web::set_timeout;
|
||||||
|
|
||||||
use CreationError;
|
use BuildStreamError;
|
||||||
use DefaultFormatError;
|
use DefaultFormatError;
|
||||||
|
use DeviceNameError;
|
||||||
|
use DevicesError;
|
||||||
use Format;
|
use Format;
|
||||||
use FormatsEnumerationError;
|
use PauseStreamError;
|
||||||
|
use PlayStreamError;
|
||||||
|
use SupportedFormatsError;
|
||||||
use StreamData;
|
use StreamData;
|
||||||
use SupportedFormat;
|
use SupportedFormat;
|
||||||
use UnknownTypeOutputBuffer;
|
use UnknownTypeOutputBuffer;
|
||||||
|
@ -118,12 +122,12 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build_input_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, CreationError> {
|
pub fn build_input_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, BuildStreamError> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build_output_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, CreationError> {
|
pub fn build_output_stream(&self, _: &Device, _format: &Format) -> Result<StreamId, BuildStreamError> {
|
||||||
let stream = js!(return new AudioContext()).into_reference().unwrap();
|
let stream = js!(return new AudioContext()).into_reference().unwrap();
|
||||||
|
|
||||||
let mut streams = self.streams.lock().unwrap();
|
let mut streams = self.streams.lock().unwrap();
|
||||||
|
@ -145,23 +149,25 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn play_stream(&self, stream_id: StreamId) {
|
pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
|
||||||
let streams = self.streams.lock().unwrap();
|
let streams = self.streams.lock().unwrap();
|
||||||
let stream = streams
|
let stream = streams
|
||||||
.get(stream_id.0)
|
.get(stream_id.0)
|
||||||
.and_then(|v| v.as_ref())
|
.and_then(|v| v.as_ref())
|
||||||
.expect("invalid stream ID");
|
.expect("invalid stream ID");
|
||||||
js!(@{stream}.resume());
|
js!(@{stream}.resume());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pause_stream(&self, stream_id: StreamId) {
|
pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
|
||||||
let streams = self.streams.lock().unwrap();
|
let streams = self.streams.lock().unwrap();
|
||||||
let stream = streams
|
let stream = streams
|
||||||
.get(stream_id.0)
|
.get(stream_id.0)
|
||||||
.and_then(|v| v.as_ref())
|
.and_then(|v| v.as_ref())
|
||||||
.expect("invalid stream ID");
|
.expect("invalid stream ID");
|
||||||
js!(@{stream}.suspend());
|
js!(@{stream}.suspend());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +189,13 @@ fn is_webaudio_available() -> bool {
|
||||||
|
|
||||||
// Content is false if the iterator is empty.
|
// Content is false if the iterator is empty.
|
||||||
pub struct Devices(bool);
|
pub struct Devices(bool);
|
||||||
|
|
||||||
|
impl Devices {
|
||||||
|
pub fn new() -> Result<Self, DevicesError> {
|
||||||
|
Ok(Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for Devices {
|
impl Default for Devices {
|
||||||
fn default() -> Devices {
|
fn default() -> Devices {
|
||||||
// We produce an empty iterator if the WebAudio API isn't available.
|
// We produce an empty iterator if the WebAudio API isn't available.
|
||||||
|
@ -221,17 +234,17 @@ pub struct Device;
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> Result<String, DeviceNameError> {
|
||||||
"Default Device".to_owned()
|
Ok("Default Device".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
|
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
|
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
||||||
// TODO: right now cpal's API doesn't allow flexibility here
|
// TODO: right now cpal's API doesn't allow flexibility here
|
||||||
// "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if
|
// "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if
|
||||||
// this ever becomes more flexible, don't forget to change that
|
// this ever becomes more flexible, don't forget to change that
|
||||||
|
|
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
|
//! `default_*_device()` functions return an `Option` in case no device is available for that
|
||||||
//! stream type on the system.
|
//! stream type on the system.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```no_run
|
||||||
//! let device = cpal::default_output_device().expect("no output device available");
|
//! let device = cpal::default_output_device().expect("no output device available");
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -60,10 +60,10 @@
|
||||||
//!
|
//!
|
||||||
//! Now we must start the stream. This is done with the `play_stream()` method on the event loop.
|
//! Now we must start the stream. This is done with the `play_stream()` method on the event loop.
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```no_run
|
||||||
//! # let event_loop: cpal::EventLoop = return;
|
//! # let event_loop: cpal::EventLoop = return;
|
||||||
//! # let stream_id: cpal::StreamId = return;
|
//! # let stream_id: cpal::StreamId = return;
|
||||||
//! event_loop.play_stream(stream_id);
|
//! event_loop.play_stream(stream_id).expect("failed to play_stream");
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Now everything is ready! We call `run()` on the `event_loop` to begin processing.
|
//! Now everything is ready! We call `run()` on the `event_loop` to begin processing.
|
||||||
|
@ -114,10 +114,10 @@
|
||||||
|
|
||||||
#![recursion_limit = "512"]
|
#![recursion_limit = "512"]
|
||||||
|
|
||||||
|
extern crate failure;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
// Extern crate declarations with `#[macro_use]` must unfortunately be at crate root.
|
// Extern crate declarations with `#[macro_use]` must unfortunately be at crate root.
|
||||||
#[cfg(target_os = "emscripten")]
|
#[cfg(target_os = "emscripten")]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -129,7 +129,7 @@ pub use samples_formats::{Sample, SampleFormat};
|
||||||
target_os = "macos", target_os = "ios", target_os = "emscripten")))]
|
target_os = "macos", target_os = "ios", target_os = "emscripten")))]
|
||||||
use null as cpal_impl;
|
use null as cpal_impl;
|
||||||
|
|
||||||
use std::error::Error;
|
use failure::Fail;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
@ -160,7 +160,7 @@ mod cpal_impl;
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
pub struct Device(cpal_impl::Device);
|
pub struct Device(cpal_impl::Device);
|
||||||
|
|
||||||
/// Collection of voices managed together.
|
/// Collection of streams managed together.
|
||||||
///
|
///
|
||||||
/// Created with the [`new`](struct.EventLoop.html#method.new) method.
|
/// Created with the [`new`](struct.EventLoop.html#method.new) method.
|
||||||
pub struct EventLoop(cpal_impl::EventLoop);
|
pub struct EventLoop(cpal_impl::EventLoop);
|
||||||
|
@ -279,75 +279,171 @@ pub struct SupportedInputFormats(cpal_impl::SupportedInputFormats);
|
||||||
/// See [`Device::supported_output_formats()`](struct.Device.html#method.supported_output_formats).
|
/// See [`Device::supported_output_formats()`](struct.Device.html#method.supported_output_formats).
|
||||||
pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats);
|
pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats);
|
||||||
|
|
||||||
|
/// Some error has occurred that is specific to the backend from which it was produced.
|
||||||
|
///
|
||||||
|
/// This error is often used as a catch-all in cases where:
|
||||||
|
///
|
||||||
|
/// - It is unclear exactly what error might be produced by the backend API.
|
||||||
|
/// - It does not make sense to add a variant to the enclosing error type.
|
||||||
|
/// - No error was expected to occur at all, but we return an error to avoid the possibility of a
|
||||||
|
/// `panic!` caused by some unforseen or unknown reason.
|
||||||
|
///
|
||||||
|
/// **Note:** If you notice a `BackendSpecificError` that you believe could be better handled in a
|
||||||
|
/// cross-platform manner, please create an issue or submit a pull request with a patch that adds
|
||||||
|
/// the necessary error variant to the appropriate error enum.
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
#[fail(display = "A backend-specific error has occurred: {}", description)]
|
||||||
|
pub struct BackendSpecificError {
|
||||||
|
pub description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that might occur while attempting to enumerate the available devices on a system.
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum DevicesError {
|
||||||
|
/// See the `BackendSpecificError` docs for more information about this error variant.
|
||||||
|
#[fail(display = "{}", err)]
|
||||||
|
BackendSpecific {
|
||||||
|
#[fail(cause)]
|
||||||
|
err: BackendSpecificError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that may occur while attempting to retrieve a device name.
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum DeviceNameError {
|
||||||
|
/// See the `BackendSpecificError` docs for more information about this error variant.
|
||||||
|
#[fail(display = "{}", err)]
|
||||||
|
BackendSpecific {
|
||||||
|
#[fail(cause)]
|
||||||
|
err: BackendSpecificError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Error that can happen when enumerating the list of supported formats.
|
/// Error that can happen when enumerating the list of supported formats.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Fail)]
|
||||||
pub enum FormatsEnumerationError {
|
pub enum SupportedFormatsError {
|
||||||
/// The device no longer exists. This can happen if the device is disconnected while the
|
/// The device no longer exists. This can happen if the device is disconnected while the
|
||||||
/// program is running.
|
/// program is running.
|
||||||
|
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
|
||||||
DeviceNotAvailable,
|
DeviceNotAvailable,
|
||||||
/// We called something the C-Layer did not understand
|
/// We called something the C-Layer did not understand
|
||||||
|
#[fail(display = "Invalid argument passed to the backend. For example, this happens when trying to read capture capabilities when the device does not support it.")]
|
||||||
InvalidArgument,
|
InvalidArgument,
|
||||||
/// The C-Layer returned an error we don't know about
|
/// See the `BackendSpecificError` docs for more information about this error variant.
|
||||||
Unknown
|
#[fail(display = "{}", err)]
|
||||||
|
BackendSpecific {
|
||||||
|
#[fail(cause)]
|
||||||
|
err: BackendSpecificError,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// May occur when attempting to request the default input or output stream format from a `Device`.
|
/// May occur when attempting to request the default input or output stream format from a `Device`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Fail)]
|
||||||
pub enum DefaultFormatError {
|
pub enum DefaultFormatError {
|
||||||
/// The device no longer exists. This can happen if the device is disconnected while the
|
/// The device no longer exists. This can happen if the device is disconnected while the
|
||||||
/// program is running.
|
/// program is running.
|
||||||
|
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
|
||||||
DeviceNotAvailable,
|
DeviceNotAvailable,
|
||||||
/// Returned if e.g. the default input format was requested on an output-only audio device.
|
/// Returned if e.g. the default input format was requested on an output-only audio device.
|
||||||
|
#[fail(display = "The requested stream type is not supported by the device.")]
|
||||||
StreamTypeNotSupported,
|
StreamTypeNotSupported,
|
||||||
|
/// See the `BackendSpecificError` docs for more information about this error variant.
|
||||||
|
#[fail(display = "{}", err)]
|
||||||
|
BackendSpecific {
|
||||||
|
#[fail(cause)]
|
||||||
|
err: BackendSpecificError,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error that can happen when creating a `Voice`.
|
/// Error that can happen when creating a `Stream`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Fail)]
|
||||||
pub enum CreationError {
|
pub enum BuildStreamError {
|
||||||
/// The device no longer exists. This can happen if the device is disconnected while the
|
/// The device no longer exists. This can happen if the device is disconnected while the
|
||||||
/// program is running.
|
/// program is running.
|
||||||
|
#[fail(display = "The requested device is no longer available. For example, it has been unplugged.")]
|
||||||
DeviceNotAvailable,
|
DeviceNotAvailable,
|
||||||
/// The required format is not supported.
|
/// The required format is not supported.
|
||||||
|
#[fail(display = "The requested stream format is not supported by the device.")]
|
||||||
FormatNotSupported,
|
FormatNotSupported,
|
||||||
/// An ALSA device function was called with a feature it does not support
|
/// We called something the C-Layer did not understand
|
||||||
/// (trying to use capture capabilities on an output only format yields this)
|
///
|
||||||
|
/// On ALSA device functions called with a feature they do not support will yield this. E.g.
|
||||||
|
/// Trying to use capture capabilities on an output only format yields this.
|
||||||
|
#[fail(display = "The requested device does not support this capability (invalid argument)")]
|
||||||
InvalidArgument,
|
InvalidArgument,
|
||||||
/// The C-Layer returned an error we don't know about
|
/// Occurs if adding a new Stream ID would cause an integer overflow.
|
||||||
Unknown,
|
#[fail(display = "Adding a new stream ID would cause an overflow")]
|
||||||
|
StreamIdOverflow,
|
||||||
|
/// See the `BackendSpecificError` docs for more information about this error variant.
|
||||||
|
#[fail(display = "{}", err)]
|
||||||
|
BackendSpecific {
|
||||||
|
#[fail(cause)]
|
||||||
|
err: BackendSpecificError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors that might occur when calling `play_stream`.
|
||||||
|
///
|
||||||
|
/// As of writing this, only macOS may immediately return an error while calling this method. This
|
||||||
|
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
|
||||||
|
/// them immediately.
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum PlayStreamError {
|
||||||
|
/// See the `BackendSpecificError` docs for more information about this error variant.
|
||||||
|
#[fail(display = "{}", err)]
|
||||||
|
BackendSpecific {
|
||||||
|
#[fail(cause)]
|
||||||
|
err: BackendSpecificError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors that might occur when calling `pause_stream`.
|
||||||
|
///
|
||||||
|
/// As of writing this, only macOS may immediately return an error while calling this method. This
|
||||||
|
/// is because both the alsa and wasapi backends only enqueue these commands and do not process
|
||||||
|
/// them immediately.
|
||||||
|
#[derive(Debug, Fail)]
|
||||||
|
pub enum PauseStreamError {
|
||||||
|
/// See the `BackendSpecificError` docs for more information about this error variant.
|
||||||
|
#[fail(display = "{}", err)]
|
||||||
|
BackendSpecific {
|
||||||
|
#[fail(cause)]
|
||||||
|
err: BackendSpecificError,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator yielding all `Device`s currently available to the system.
|
/// An iterator yielding all `Device`s currently available to the system.
|
||||||
///
|
///
|
||||||
/// Can be empty if the system does not support audio in general.
|
/// Can be empty if the system does not support audio in general.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn devices() -> Devices {
|
pub fn devices() -> Result<Devices, DevicesError> {
|
||||||
Devices(Default::default())
|
Ok(Devices(cpal_impl::Devices::new()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator yielding all `Device`s currently available to the system that support one or more
|
/// An iterator yielding all `Device`s currently available to the system that support one or more
|
||||||
/// input stream formats.
|
/// input stream formats.
|
||||||
///
|
///
|
||||||
/// Can be empty if the system does not support audio input.
|
/// Can be empty if the system does not support audio input.
|
||||||
pub fn input_devices() -> InputDevices {
|
pub fn input_devices() -> Result<InputDevices, DevicesError> {
|
||||||
fn supports_input(device: &Device) -> bool {
|
fn supports_input(device: &Device) -> bool {
|
||||||
device.supported_input_formats()
|
device.supported_input_formats()
|
||||||
.map(|mut iter| iter.next().is_some())
|
.map(|mut iter| iter.next().is_some())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
devices().filter(supports_input)
|
Ok(devices()?.filter(supports_input))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator yielding all `Device`s currently available to the system that support one or more
|
/// An iterator yielding all `Device`s currently available to the system that support one or more
|
||||||
/// output stream formats.
|
/// output stream formats.
|
||||||
///
|
///
|
||||||
/// Can be empty if the system does not support audio output.
|
/// Can be empty if the system does not support audio output.
|
||||||
pub fn output_devices() -> OutputDevices {
|
pub fn output_devices() -> Result<OutputDevices, DevicesError> {
|
||||||
fn supports_output(device: &Device) -> bool {
|
fn supports_output(device: &Device) -> bool {
|
||||||
device.supported_output_formats()
|
device.supported_output_formats()
|
||||||
.map(|mut iter| iter.next().is_some())
|
.map(|mut iter| iter.next().is_some())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
devices().filter(supports_output)
|
Ok(devices()?.filter(supports_output))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default input audio device on the system.
|
/// The default input audio device on the system.
|
||||||
|
@ -367,7 +463,7 @@ pub fn default_output_device() -> Option<Device> {
|
||||||
impl Device {
|
impl Device {
|
||||||
/// The human-readable name of the device.
|
/// The human-readable name of the device.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> Result<String, DeviceNameError> {
|
||||||
self.0.name()
|
self.0.name()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,7 +471,7 @@ impl Device {
|
||||||
///
|
///
|
||||||
/// Can return an error if the device is no longer valid (eg. it has been disconnected).
|
/// Can return an error if the device is no longer valid (eg. it has been disconnected).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
|
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
|
||||||
Ok(SupportedInputFormats(self.0.supported_input_formats()?))
|
Ok(SupportedInputFormats(self.0.supported_input_formats()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +479,7 @@ impl Device {
|
||||||
///
|
///
|
||||||
/// Can return an error if the device is no longer valid (eg. it has been disconnected).
|
/// Can return an error if the device is no longer valid (eg. it has been disconnected).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
|
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
||||||
Ok(SupportedOutputFormats(self.0.supported_output_formats()?))
|
Ok(SupportedOutputFormats(self.0.supported_output_formats()?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,7 +520,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
self.0.build_input_stream(&device.0, format).map(StreamId)
|
self.0.build_input_stream(&device.0, format).map(StreamId)
|
||||||
}
|
}
|
||||||
|
@ -440,7 +536,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
self.0.build_output_stream(&device.0, format).map(StreamId)
|
self.0.build_output_stream(&device.0, format).map(StreamId)
|
||||||
}
|
}
|
||||||
|
@ -456,7 +552,7 @@ impl EventLoop {
|
||||||
/// If the stream does not exist, this function can either panic or be a no-op.
|
/// If the stream does not exist, this function can either panic or be a no-op.
|
||||||
///
|
///
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn play_stream(&self, stream: StreamId) {
|
pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
|
||||||
self.0.play_stream(stream.0)
|
self.0.play_stream(stream.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,7 +567,7 @@ impl EventLoop {
|
||||||
/// If the stream does not exist, this function can either panic or be a no-op.
|
/// If the stream does not exist, this function can either panic or be a no-op.
|
||||||
///
|
///
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pause_stream(&self, stream: StreamId) {
|
pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
|
||||||
self.0.pause_stream(stream.0)
|
self.0.pause_stream(stream.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -693,79 +789,45 @@ impl Iterator for SupportedOutputFormats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FormatsEnumerationError {
|
impl From<BackendSpecificError> for DevicesError {
|
||||||
#[inline]
|
fn from(err: BackendSpecificError) -> Self {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
DevicesError::BackendSpecific { err }
|
||||||
write!(fmt, "{}", self.description())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for FormatsEnumerationError {
|
impl From<BackendSpecificError> for DeviceNameError {
|
||||||
#[inline]
|
fn from(err: BackendSpecificError) -> Self {
|
||||||
fn description(&self) -> &str {
|
DeviceNameError::BackendSpecific { err }
|
||||||
match self {
|
|
||||||
&FormatsEnumerationError::DeviceNotAvailable => {
|
|
||||||
"The requested device is no longer available (for example, it has been unplugged)."
|
|
||||||
},
|
|
||||||
&FormatsEnumerationError::InvalidArgument => {
|
|
||||||
"Invalid argument passed to the Backend (This happens when trying to read for example capture capabilities but the device does not support it -> dmix on Linux)"
|
|
||||||
},
|
|
||||||
&FormatsEnumerationError::Unknown => {
|
|
||||||
"An unknown error in the Backend occured"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CreationError {
|
impl From<BackendSpecificError> for SupportedFormatsError {
|
||||||
#[inline]
|
fn from(err: BackendSpecificError) -> Self {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
SupportedFormatsError::BackendSpecific { err }
|
||||||
write!(fmt, "{}", self.description())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for CreationError {
|
impl From<BackendSpecificError> for DefaultFormatError {
|
||||||
#[inline]
|
fn from(err: BackendSpecificError) -> Self {
|
||||||
fn description(&self) -> &str {
|
DefaultFormatError::BackendSpecific { err }
|
||||||
match self {
|
|
||||||
&CreationError::DeviceNotAvailable => {
|
|
||||||
"The requested device is no longer available (for example, it has been unplugged)."
|
|
||||||
},
|
|
||||||
|
|
||||||
&CreationError::FormatNotSupported => {
|
|
||||||
"The requested samples format is not supported by the device."
|
|
||||||
},
|
|
||||||
|
|
||||||
&CreationError::InvalidArgument => {
|
|
||||||
"The requested device does not support this capability (invalid argument)"
|
|
||||||
}
|
|
||||||
|
|
||||||
&CreationError::Unknown => {
|
|
||||||
"An unknown error in the Backend occured"
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DefaultFormatError {
|
impl From<BackendSpecificError> for BuildStreamError {
|
||||||
#[inline]
|
fn from(err: BackendSpecificError) -> Self {
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
BuildStreamError::BackendSpecific { err }
|
||||||
write!(fmt, "{}", self.description())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error for DefaultFormatError {
|
impl From<BackendSpecificError> for PlayStreamError {
|
||||||
#[inline]
|
fn from(err: BackendSpecificError) -> Self {
|
||||||
fn description(&self) -> &str {
|
PlayStreamError::BackendSpecific { err }
|
||||||
match self {
|
}
|
||||||
&DefaultFormatError::DeviceNotAvailable => {
|
}
|
||||||
CreationError::DeviceNotAvailable.description()
|
|
||||||
},
|
|
||||||
|
|
||||||
&DefaultFormatError::StreamTypeNotSupported => {
|
impl From<BackendSpecificError> for PauseStreamError {
|
||||||
"The requested stream type is not supported by the device."
|
fn from(err: BackendSpecificError) -> Self {
|
||||||
},
|
PauseStreamError::BackendSpecific { err }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,14 @@
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use CreationError;
|
use BuildStreamError;
|
||||||
use DefaultFormatError;
|
use DefaultFormatError;
|
||||||
|
use DevicesError;
|
||||||
|
use DeviceNameError;
|
||||||
use Format;
|
use Format;
|
||||||
use FormatsEnumerationError;
|
use PauseStreamError;
|
||||||
|
use PlayStreamError;
|
||||||
|
use SupportedFormatsError;
|
||||||
use StreamData;
|
use StreamData;
|
||||||
use SupportedFormat;
|
use SupportedFormat;
|
||||||
|
|
||||||
|
@ -25,13 +29,13 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, CreationError> {
|
pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
|
||||||
Err(CreationError::DeviceNotAvailable)
|
Err(BuildStreamError::DeviceNotAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, CreationError> {
|
pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
|
||||||
Err(CreationError::DeviceNotAvailable)
|
Err(BuildStreamError::DeviceNotAvailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -40,12 +44,12 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn play_stream(&self, _: StreamId) {
|
pub fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> {
|
||||||
panic!()
|
panic!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pause_stream(&self, _: StreamId) {
|
pub fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> {
|
||||||
panic!()
|
panic!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +60,12 @@ pub struct StreamId;
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Devices;
|
pub struct Devices;
|
||||||
|
|
||||||
|
impl Devices {
|
||||||
|
pub fn new() -> Result<Self, DevicesError> {
|
||||||
|
Ok(Devices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Iterator for Devices {
|
impl Iterator for Devices {
|
||||||
type Item = Device;
|
type Item = Device;
|
||||||
|
|
||||||
|
@ -80,12 +90,12 @@ pub struct Device;
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
|
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
|
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,8 +110,8 @@ impl Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> Result<String, DeviceNameError> {
|
||||||
"null".to_owned()
|
Ok("null".to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,4 +142,4 @@ pub struct InputBuffer<'a, T: 'a> {
|
||||||
|
|
||||||
pub struct OutputBuffer<'a, T: 'a> {
|
pub struct OutputBuffer<'a, T: 'a> {
|
||||||
marker: PhantomData<&'a mut T>,
|
marker: PhantomData<&'a mut T>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,19 @@ use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
|
use BackendSpecificError;
|
||||||
use DefaultFormatError;
|
use DefaultFormatError;
|
||||||
|
use DeviceNameError;
|
||||||
|
use DevicesError;
|
||||||
use Format;
|
use Format;
|
||||||
use FormatsEnumerationError;
|
use SupportedFormatsError;
|
||||||
use SampleFormat;
|
use SampleFormat;
|
||||||
use SampleRate;
|
use SampleRate;
|
||||||
use SupportedFormat;
|
use SupportedFormat;
|
||||||
use COMMON_SAMPLE_RATES;
|
use COMMON_SAMPLE_RATES;
|
||||||
|
|
||||||
use super::check_result;
|
use super::check_result;
|
||||||
|
use super::check_result_backend_specific;
|
||||||
use super::com;
|
use super::com;
|
||||||
use super::winapi::Interface;
|
use super::winapi::Interface;
|
||||||
use super::winapi::ctypes::c_void;
|
use super::winapi::ctypes::c_void;
|
||||||
|
@ -165,7 +169,7 @@ unsafe fn data_flow_from_immendpoint(endpoint: *const IMMEndpoint) -> EDataFlow
|
||||||
pub unsafe fn is_format_supported(
|
pub unsafe fn is_format_supported(
|
||||||
client: *const IAudioClient,
|
client: *const IAudioClient,
|
||||||
waveformatex_ptr: *const mmreg::WAVEFORMATEX,
|
waveformatex_ptr: *const mmreg::WAVEFORMATEX,
|
||||||
) -> Result<bool, FormatsEnumerationError>
|
) -> Result<bool, SupportedFormatsError>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
@ -187,7 +191,7 @@ pub unsafe fn is_format_supported(
|
||||||
(_, Err(ref e))
|
(_, Err(ref e))
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::DeviceNotAvailable);
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
(_, Err(e)) => {
|
(_, Err(e)) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
|
@ -195,7 +199,7 @@ pub unsafe fn is_format_supported(
|
||||||
},
|
},
|
||||||
(winerror::S_FALSE, _) => {
|
(winerror::S_FALSE, _) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::FormatNotSupported);
|
return Err(BuildStreamError::FormatNotSupported);
|
||||||
},
|
},
|
||||||
(_, Ok(())) => (),
|
(_, Ok(())) => (),
|
||||||
};
|
};
|
||||||
|
@ -213,7 +217,7 @@ pub unsafe fn is_format_supported(
|
||||||
// has been found, but not an exact match) so we also treat this as unsupported.
|
// has been found, but not an exact match) so we also treat this as unsupported.
|
||||||
match (result, check_result(result)) {
|
match (result, check_result(result)) {
|
||||||
(_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
(_, Err(ref e)) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
return Err(FormatsEnumerationError::DeviceNotAvailable);
|
return Err(SupportedFormatsError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
(_, Err(_)) => {
|
(_, Err(_)) => {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
|
@ -294,7 +298,7 @@ unsafe impl Sync for Device {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
pub fn name(&self) -> String {
|
pub fn name(&self) -> Result<String, DeviceNameError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Open the device's property store.
|
// Open the device's property store.
|
||||||
let mut property_store = ptr::null_mut();
|
let mut property_store = ptr::null_mut();
|
||||||
|
@ -302,15 +306,24 @@ impl Device {
|
||||||
|
|
||||||
// Get the endpoint's friendly-name property.
|
// Get the endpoint's friendly-name property.
|
||||||
let mut property_value = mem::zeroed();
|
let mut property_value = mem::zeroed();
|
||||||
check_result(
|
if let Err(err) = check_result(
|
||||||
(*property_store).GetValue(
|
(*property_store).GetValue(
|
||||||
&devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _,
|
&devpkey::DEVPKEY_Device_FriendlyName as *const _ as *const _,
|
||||||
&mut property_value
|
&mut property_value
|
||||||
)
|
)
|
||||||
).expect("failed to get friendly-name from property store");
|
) {
|
||||||
|
let description = format!("failed to retrieve name from property store: {}", err);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
|
||||||
// Read the friendly-name from the union data field, expecting a *const u16.
|
// Read the friendly-name from the union data field, expecting a *const u16.
|
||||||
assert_eq!(property_value.vt, wtypes::VT_LPWSTR as _);
|
if property_value.vt != wtypes::VT_LPWSTR as _ {
|
||||||
|
let description =
|
||||||
|
format!("property store produced invalid data: {:?}", property_value.vt);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
let ptr_usize: usize = *(&property_value.data as *const _ as *const usize);
|
let ptr_usize: usize = *(&property_value.data as *const _ as *const usize);
|
||||||
let ptr_utf16 = ptr_usize as *const u16;
|
let ptr_utf16 = ptr_usize as *const u16;
|
||||||
|
|
||||||
|
@ -323,12 +336,15 @@ impl Device {
|
||||||
// Create the utf16 slice and covert it into a string.
|
// Create the utf16 slice and covert it into a string.
|
||||||
let name_slice = slice::from_raw_parts(ptr_utf16, len as usize);
|
let name_slice = slice::from_raw_parts(ptr_utf16, len as usize);
|
||||||
let name_os_string: OsString = OsStringExt::from_wide(name_slice);
|
let name_os_string: OsString = OsStringExt::from_wide(name_slice);
|
||||||
let name_string = name_os_string.into_string().unwrap();
|
let name_string = match name_os_string.into_string() {
|
||||||
|
Ok(string) => string,
|
||||||
|
Err(os_string) => os_string.to_string_lossy().into(),
|
||||||
|
};
|
||||||
|
|
||||||
// Clean up the property.
|
// Clean up the property.
|
||||||
PropVariantClear(&mut property_value);
|
PropVariantClear(&mut property_value);
|
||||||
|
|
||||||
name_string
|
Ok(name_string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,15 +402,21 @@ impl Device {
|
||||||
// number of channels seems to be supported. Any more or less returns an invalid
|
// number of channels seems to be supported. Any more or less returns an invalid
|
||||||
// parameter error. Thus we just assume that the default number of channels is the only
|
// parameter error. Thus we just assume that the default number of channels is the only
|
||||||
// number supported.
|
// number supported.
|
||||||
fn supported_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
|
fn supported_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
|
||||||
// initializing COM because we call `CoTaskMemFree` to release the format.
|
// initializing COM because we call `CoTaskMemFree` to release the format.
|
||||||
com::com_initialized();
|
com::com_initialized();
|
||||||
|
|
||||||
// Retrieve the `IAudioClient`.
|
// Retrieve the `IAudioClient`.
|
||||||
let lock = match self.ensure_future_audio_client() {
|
let lock = match self.ensure_future_audio_client() {
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
Ok(lock) => lock,
|
||||||
return Err(FormatsEnumerationError::DeviceNotAvailable),
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
e => e.unwrap(),
|
return Err(SupportedFormatsError::DeviceNotAvailable)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let client = lock.unwrap().0;
|
let client = lock.unwrap().0;
|
||||||
|
|
||||||
|
@ -402,11 +424,15 @@ impl Device {
|
||||||
// Retrieve the pointer to the default WAVEFORMATEX.
|
// Retrieve the pointer to the default WAVEFORMATEX.
|
||||||
let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized());
|
let mut default_waveformatex_ptr = WaveFormatExPtr(mem::uninitialized());
|
||||||
match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) {
|
match check_result((*client).GetMixFormat(&mut default_waveformatex_ptr.0)) {
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
|
||||||
return Err(FormatsEnumerationError::DeviceNotAvailable);
|
|
||||||
},
|
|
||||||
Err(e) => panic!("{:?}", e),
|
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
|
return Err(SupportedFormatsError::DeviceNotAvailable);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// If the default format can't succeed we have no hope of finding other formats.
|
// If the default format can't succeed we have no hope of finding other formats.
|
||||||
|
@ -450,8 +476,15 @@ impl Device {
|
||||||
// TODO: Test the different sample formats?
|
// TODO: Test the different sample formats?
|
||||||
|
|
||||||
// Create the supported formats.
|
// Create the supported formats.
|
||||||
let mut format = format_from_waveformatex_ptr(default_waveformatex_ptr.0)
|
let mut format = match format_from_waveformatex_ptr(default_waveformatex_ptr.0) {
|
||||||
.expect("could not create a cpal::Format from a WAVEFORMATEX");
|
Some(fmt) => fmt,
|
||||||
|
None => {
|
||||||
|
let description =
|
||||||
|
"could not create a `cpal::Format` from a `WAVEFORMATEX`".to_string();
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
let mut supported_formats = Vec::with_capacity(supported_sample_rates.len());
|
let mut supported_formats = Vec::with_capacity(supported_sample_rates.len());
|
||||||
for rate in supported_sample_rates {
|
for rate in supported_sample_rates {
|
||||||
format.sample_rate = SampleRate(rate as _);
|
format.sample_rate = SampleRate(rate as _);
|
||||||
|
@ -462,7 +495,7 @@ impl Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, FormatsEnumerationError> {
|
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
|
||||||
if self.data_flow() == eCapture {
|
if self.data_flow() == eCapture {
|
||||||
self.supported_formats()
|
self.supported_formats()
|
||||||
// If it's an output device, assume no input formats.
|
// If it's an output device, assume no input formats.
|
||||||
|
@ -471,7 +504,7 @@ impl Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, FormatsEnumerationError> {
|
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
||||||
if self.data_flow() == eRender {
|
if self.data_flow() == eRender {
|
||||||
self.supported_formats()
|
self.supported_formats()
|
||||||
// If it's an input device, assume no output formats.
|
// If it's an input device, assume no output formats.
|
||||||
|
@ -489,9 +522,15 @@ impl Device {
|
||||||
com::com_initialized();
|
com::com_initialized();
|
||||||
|
|
||||||
let lock = match self.ensure_future_audio_client() {
|
let lock = match self.ensure_future_audio_client() {
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
Ok(lock) => lock,
|
||||||
return Err(DefaultFormatError::DeviceNotAvailable),
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
e => e.unwrap(),
|
return Err(DefaultFormatError::DeviceNotAvailable)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let client = lock.unwrap().0;
|
let client = lock.unwrap().0;
|
||||||
|
|
||||||
|
@ -501,7 +540,11 @@ impl Device {
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
return Err(DefaultFormatError::DeviceNotAvailable);
|
return Err(DefaultFormatError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(e) => panic!("{:?}", e),
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
},
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -688,6 +731,32 @@ pub struct Devices {
|
||||||
next_item: u32,
|
next_item: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Devices {
|
||||||
|
pub fn new() -> Result<Self, DevicesError> {
|
||||||
|
unsafe {
|
||||||
|
let mut collection: *mut IMMDeviceCollection = mem::uninitialized();
|
||||||
|
// can fail because of wrong parameters (should never happen) or out of memory
|
||||||
|
check_result_backend_specific(
|
||||||
|
(*ENUMERATOR.0).EnumAudioEndpoints(
|
||||||
|
eAll,
|
||||||
|
DEVICE_STATE_ACTIVE,
|
||||||
|
&mut collection,
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut count = mem::uninitialized();
|
||||||
|
// can fail if the parameter is null, which should never happen
|
||||||
|
check_result_backend_specific((*collection).GetCount(&mut count))?;
|
||||||
|
|
||||||
|
Ok(Devices {
|
||||||
|
collection: collection,
|
||||||
|
total_count: count,
|
||||||
|
next_item: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe impl Send for Devices {
|
unsafe impl Send for Devices {
|
||||||
}
|
}
|
||||||
unsafe impl Sync for Devices {
|
unsafe impl Sync for Devices {
|
||||||
|
@ -702,32 +771,6 @@ impl Drop for Devices {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Devices {
|
|
||||||
fn default() -> Devices {
|
|
||||||
unsafe {
|
|
||||||
let mut collection: *mut IMMDeviceCollection = mem::uninitialized();
|
|
||||||
// can fail because of wrong parameters (should never happen) or out of memory
|
|
||||||
check_result(
|
|
||||||
(*ENUMERATOR.0).EnumAudioEndpoints(
|
|
||||||
eAll,
|
|
||||||
DEVICE_STATE_ACTIVE,
|
|
||||||
&mut collection,
|
|
||||||
)
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let mut count = mem::uninitialized();
|
|
||||||
// can fail if the parameter is null, which should never happen
|
|
||||||
check_result((*collection).GetCount(&mut count)).unwrap();
|
|
||||||
|
|
||||||
Devices {
|
|
||||||
collection: collection,
|
|
||||||
total_count: count,
|
|
||||||
next_item: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Devices {
|
impl Iterator for Devices {
|
||||||
type Item = Device;
|
type Item = Device;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
extern crate winapi;
|
extern crate winapi;
|
||||||
|
|
||||||
|
use BackendSpecificError;
|
||||||
|
use self::winapi::um::winnt::HRESULT;
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
|
|
||||||
pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device};
|
pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device};
|
||||||
pub use self::stream::{EventLoop, StreamId};
|
pub use self::stream::{EventLoop, StreamId};
|
||||||
use self::winapi::um::winnt::HRESULT;
|
|
||||||
|
|
||||||
mod com;
|
mod com;
|
||||||
mod device;
|
mod device;
|
||||||
|
@ -18,3 +18,13 @@ fn check_result(result: HRESULT) -> Result<(), IoError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_result_backend_specific(result: HRESULT) -> Result<(), BackendSpecificError> {
|
||||||
|
match check_result(result) {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(err) => {
|
||||||
|
let description = format!("{}", err);
|
||||||
|
return Err(BackendSpecificError { description });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,8 +20,11 @@ use std::sync::mpsc::{channel, Sender, Receiver};
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use CreationError;
|
use BackendSpecificError;
|
||||||
|
use BuildStreamError;
|
||||||
use Format;
|
use Format;
|
||||||
|
use PauseStreamError;
|
||||||
|
use PlayStreamError;
|
||||||
use SampleFormat;
|
use SampleFormat;
|
||||||
use StreamData;
|
use StreamData;
|
||||||
use UnknownTypeOutputBuffer;
|
use UnknownTypeOutputBuffer;
|
||||||
|
@ -114,7 +117,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
// Making sure that COM is initialized.
|
// Making sure that COM is initialized.
|
||||||
|
@ -123,21 +126,26 @@ impl EventLoop {
|
||||||
|
|
||||||
// Obtaining a `IAudioClient`.
|
// Obtaining a `IAudioClient`.
|
||||||
let audio_client = match device.build_audioclient() {
|
let audio_client = match device.build_audioclient() {
|
||||||
|
Ok(client) => client,
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
return Err(CreationError::DeviceNotAvailable),
|
return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
e => e.unwrap(),
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Computing the format and initializing the device.
|
// Computing the format and initializing the device.
|
||||||
let waveformatex = {
|
let waveformatex = {
|
||||||
let format_attempt = format_to_waveformatextensible(format)
|
let format_attempt = format_to_waveformatextensible(format)
|
||||||
.ok_or(CreationError::FormatNotSupported)?;
|
.ok_or(BuildStreamError::FormatNotSupported)?;
|
||||||
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
||||||
|
|
||||||
// Ensure the format is supported.
|
// Ensure the format is supported.
|
||||||
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
||||||
Ok(false) => return Err(CreationError::FormatNotSupported),
|
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
|
||||||
Err(_) => return Err(CreationError::DeviceNotAvailable),
|
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,11 +162,13 @@ impl EventLoop {
|
||||||
Err(ref e)
|
Err(ref e)
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::DeviceNotAvailable);
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("{:?}", e);
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
},
|
},
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
};
|
};
|
||||||
|
@ -175,11 +185,13 @@ impl EventLoop {
|
||||||
Err(ref e)
|
Err(ref e)
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::DeviceNotAvailable);
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("{:?}", e);
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
},
|
},
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
};
|
};
|
||||||
|
@ -192,16 +204,17 @@ impl EventLoop {
|
||||||
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
||||||
if event == ptr::null_mut() {
|
if event == ptr::null_mut() {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("Failed to create event");
|
let description = format!("failed to create event");
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
match check_result((*audio_client).SetEventHandle(event)) {
|
if let Err(e) = check_result((*audio_client).SetEventHandle(event)) {
|
||||||
Err(_) => {
|
(*audio_client).Release();
|
||||||
(*audio_client).Release();
|
let description = format!("failed to call SetEventHandle: {}", e);
|
||||||
panic!("Failed to call SetEventHandle")
|
let err = BackendSpecificError { description };
|
||||||
},
|
return Err(err.into());
|
||||||
Ok(_) => (),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
event
|
event
|
||||||
};
|
};
|
||||||
|
@ -218,11 +231,13 @@ impl EventLoop {
|
||||||
Err(ref e)
|
Err(ref e)
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::DeviceNotAvailable);
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("{:?}", e);
|
let description = format!("failed to build capture client: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
},
|
},
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
};
|
};
|
||||||
|
@ -231,7 +246,9 @@ impl EventLoop {
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
||||||
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows
|
if new_stream_id.0 == usize::max_value() {
|
||||||
|
return Err(BuildStreamError::StreamIdOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
||||||
// `run()` method and added to the `RunContext`.
|
// `run()` method and added to the `RunContext`.
|
||||||
|
@ -261,7 +278,7 @@ impl EventLoop {
|
||||||
&self,
|
&self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
format: &Format,
|
format: &Format,
|
||||||
) -> Result<StreamId, CreationError>
|
) -> Result<StreamId, BuildStreamError>
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
// Making sure that COM is initialized.
|
// Making sure that COM is initialized.
|
||||||
|
@ -270,21 +287,26 @@ impl EventLoop {
|
||||||
|
|
||||||
// Obtaining a `IAudioClient`.
|
// Obtaining a `IAudioClient`.
|
||||||
let audio_client = match device.build_audioclient() {
|
let audio_client = match device.build_audioclient() {
|
||||||
|
Ok(client) => client,
|
||||||
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) =>
|
||||||
return Err(CreationError::DeviceNotAvailable),
|
return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
e => e.unwrap(),
|
Err(e) => {
|
||||||
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Computing the format and initializing the device.
|
// Computing the format and initializing the device.
|
||||||
let waveformatex = {
|
let waveformatex = {
|
||||||
let format_attempt = format_to_waveformatextensible(format)
|
let format_attempt = format_to_waveformatextensible(format)
|
||||||
.ok_or(CreationError::FormatNotSupported)?;
|
.ok_or(BuildStreamError::FormatNotSupported)?;
|
||||||
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
let share_mode = AUDCLNT_SHAREMODE_SHARED;
|
||||||
|
|
||||||
// Ensure the format is supported.
|
// Ensure the format is supported.
|
||||||
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
match super::device::is_format_supported(audio_client, &format_attempt.Format) {
|
||||||
Ok(false) => return Err(CreationError::FormatNotSupported),
|
Ok(false) => return Err(BuildStreamError::FormatNotSupported),
|
||||||
Err(_) => return Err(CreationError::DeviceNotAvailable),
|
Err(_) => return Err(BuildStreamError::DeviceNotAvailable),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,11 +321,13 @@ impl EventLoop {
|
||||||
Err(ref e)
|
Err(ref e)
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::DeviceNotAvailable);
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("{:?}", e);
|
let description = format!("{}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
},
|
},
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
};
|
};
|
||||||
|
@ -316,13 +340,17 @@ impl EventLoop {
|
||||||
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
let event = synchapi::CreateEventA(ptr::null_mut(), 0, 0, ptr::null());
|
||||||
if event == ptr::null_mut() {
|
if event == ptr::null_mut() {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("Failed to create event");
|
let description = format!("failed to create event");
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
match check_result((*audio_client).SetEventHandle(event)) {
|
match check_result((*audio_client).SetEventHandle(event)) {
|
||||||
Err(_) => {
|
Err(e) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("Failed to call SetEventHandle")
|
let description = format!("failed to call SetEventHandle: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
},
|
},
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
};
|
};
|
||||||
|
@ -339,11 +367,13 @@ impl EventLoop {
|
||||||
Err(ref e)
|
Err(ref e)
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::DeviceNotAvailable);
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("{:?}", e);
|
let description = format!("failed to obtain buffer size: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
},
|
},
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
};
|
};
|
||||||
|
@ -363,11 +393,13 @@ impl EventLoop {
|
||||||
Err(ref e)
|
Err(ref e)
|
||||||
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
return Err(CreationError::DeviceNotAvailable);
|
return Err(BuildStreamError::DeviceNotAvailable);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
(*audio_client).Release();
|
(*audio_client).Release();
|
||||||
panic!("{:?}", e);
|
let description = format!("failed to build render client: {}", e);
|
||||||
|
let err = BackendSpecificError { description };
|
||||||
|
return Err(err.into());
|
||||||
},
|
},
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
};
|
};
|
||||||
|
@ -376,7 +408,9 @@ impl EventLoop {
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
||||||
assert_ne!(new_stream_id.0, usize::max_value()); // check for overflows
|
if new_stream_id.0 == usize::max_value() {
|
||||||
|
return Err(BuildStreamError::StreamIdOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
// Once we built the `StreamInner`, we add a command that will be picked up by the
|
||||||
// `run()` method and added to the `RunContext`.
|
// `run()` method and added to the `RunContext`.
|
||||||
|
@ -529,7 +563,7 @@ impl EventLoop {
|
||||||
if hresult == AUDCLNT_S_BUFFER_EMPTY { continue; }
|
if hresult == AUDCLNT_S_BUFFER_EMPTY { continue; }
|
||||||
|
|
||||||
debug_assert!(!buffer.is_null());
|
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;
|
* stream.bytes_per_frame as usize / sample_size;
|
||||||
|
|
||||||
// Simplify the capture callback sample format branches.
|
// Simplify the capture callback sample format branches.
|
||||||
|
@ -568,9 +602,9 @@ impl EventLoop {
|
||||||
&mut buffer as *mut *mut _,
|
&mut buffer as *mut *mut _,
|
||||||
);
|
);
|
||||||
// FIXME: can return `AUDCLNT_E_DEVICE_INVALIDATED`
|
// FIXME: can return `AUDCLNT_E_DEVICE_INVALIDATED`
|
||||||
check_result(hresult).unwrap();
|
check_result(hresult).unwrap();
|
||||||
debug_assert!(!buffer.is_null());
|
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;
|
* stream.bytes_per_frame as usize / sample_size;
|
||||||
|
|
||||||
// Simplify the render callback sample format branches.
|
// 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) => (),
|
Err(ref e) if e.raw_os_error() == Some(AUDCLNT_E_DEVICE_INVALIDATED) => (),
|
||||||
e => e.unwrap(),
|
e => e.unwrap(),
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
match stream.sample_format {
|
match stream.sample_format {
|
||||||
|
@ -610,13 +644,15 @@ impl EventLoop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn play_stream(&self, stream: StreamId) {
|
pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> {
|
||||||
self.push_command(Command::PlayStream(stream));
|
self.push_command(Command::PlayStream(stream));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn pause_stream(&self, stream: StreamId) {
|
pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> {
|
||||||
self.push_command(Command::PauseStream(stream));
|
self.push_command(Command::PauseStream(stream));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
Loading…
Reference in New Issue