From e8a05379c279ffe3fde629b7637d16d2bb7d3358 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Sun, 23 Jun 2019 15:49:48 +0200 Subject: [PATCH 1/8] [WIP] Introduce a `Host` API This is an implementation of the API described at #204. Please see that issue for more details on the motivation. ----- A **Host** provides access to the available audio devices on the system. Some platforms have more than one host available, e.g. wasapi/asio/dsound on windows, alsa/pulse/jack on linux and so on. As a result, some audio devices are only available on certain hosts, while others are only available on other hosts. Every platform supported by CPAL has at least one **DefaultHost** that is guaranteed to be available (alsa, wasapi and coreaudio). Currently, the default hosts are the only hosts supported by CPAL, however this will change as of landing #221 (cc @freesig). These changes should also accommodate support for other hosts such as jack #250 (cc @derekdreery) and pulseaudio (cc @knappador) #259. This introduces a suite of traits allowing for both compile time and runtime dispatch of different hosts and their uniquely associated device and event loop types. A new private **host** module has been added containing the individual host implementations, each in their own submodule gated to the platforms on which they are available. A new **platform** module has been added containing platform-specific items, including a dynamically dispatched host type that allows for easily switching between hosts at runtime. The **ALL_HOSTS** slice contains a **HostId** for each host supported on the current platform. The **available_hosts** function produces a **HostId** for each host that is currently *available* on the platform. The **host_from_id** function allows for initialising a host from its associated ID, failing with a **HostUnavailable** error. The **default_host** function returns the default host and should never fail. Please see the examples for a demonstration of the change in usage. For the most part, things look the same at the surface level, however the role of device enumeration and creating the event loop have been moved from global functions to host methods. The enumerate.rs example has been updated to enumerate all devices for each host, not just the default. **TODO** - [x] Add the new **Host** API - [x] Update examples for the new API. - [x] ALSA host - [ ] WASAPI host - [ ] CoreAudio host - [ ] Emscripten host **Follow-up PR** - [ ] ASIO host #221 cc @ishitatsuyuki more to review for you if you're interested, but it might be easier after #288 lands and this gets rebased. --- examples/beep.rs | 7 +- examples/enumerate.rs | 88 +++-- examples/feedback.rs | 9 +- examples/record_wav.rs | 9 +- src/{ => host}/alsa/enumerate.rs | 0 src/{ => host}/alsa/mod.rs | 134 ++++++- src/{ => host}/coreaudio/enumerate.rs | 0 src/{ => host}/coreaudio/mod.rs | 0 src/{ => host}/emscripten/mod.rs | 0 src/host/mod.rs | 10 + src/{ => host}/null/mod.rs | 207 ++++++----- src/{ => host}/wasapi/com.rs | 0 src/{ => host}/wasapi/device.rs | 0 src/{ => host}/wasapi/mod.rs | 0 src/{ => host}/wasapi/stream.rs | 0 src/lib.rs | 515 +++++++++++--------------- src/platform/mod.rs | 457 +++++++++++++++++++++++ 17 files changed, 989 insertions(+), 447 deletions(-) rename src/{ => host}/alsa/enumerate.rs (100%) rename src/{ => host}/alsa/mod.rs (92%) rename src/{ => host}/coreaudio/enumerate.rs (100%) rename src/{ => host}/coreaudio/mod.rs (100%) rename src/{ => host}/emscripten/mod.rs (100%) create mode 100644 src/host/mod.rs rename src/{ => host}/null/mod.rs (50%) rename src/{ => host}/wasapi/com.rs (100%) rename src/{ => host}/wasapi/device.rs (100%) rename src/{ => host}/wasapi/mod.rs (100%) rename src/{ => host}/wasapi/stream.rs (100%) create mode 100644 src/platform/mod.rs diff --git a/examples/beep.rs b/examples/beep.rs index 96b9eaf..629d550 100644 --- a/examples/beep.rs +++ b/examples/beep.rs @@ -1,10 +1,13 @@ extern crate cpal; extern crate failure; +use cpal::{Device, EventLoop, Host}; + fn main() -> Result<(), failure::Error> { - let device = cpal::default_output_device().expect("failed to find a default output device"); + let host = cpal::default_host(); + let device = host.default_output_device().expect("failed to find a default output device"); let format = device.default_output_format()?; - let event_loop = cpal::EventLoop::new(); + let event_loop = host.event_loop(); let stream_id = event_loop.build_output_stream(&device, &format)?; event_loop.play_stream(stream_id.clone())?; diff --git a/examples/enumerate.rs b/examples/enumerate.rs index ba2e5ab..6832b6d 100644 --- a/examples/enumerate.rs +++ b/examples/enumerate.rs @@ -1,50 +1,60 @@ extern crate cpal; extern crate failure; +use cpal::{Device, Host}; + fn main() -> Result<(), failure::Error> { - let default_in = cpal::default_input_device().map(|e| e.name().unwrap()); - let default_out = cpal::default_output_device().map(|e| e.name().unwrap()); - println!("Default Input Device:\n {:?}", default_in); - println!("Default Output Device:\n {:?}", default_out); + println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS); + let available_hosts = cpal::available_hosts(); + println!("Available hosts:\n {:?}", available_hosts); - let devices = cpal::devices()?; - println!("Devices: "); - for (device_index, device) in devices.enumerate() { - println!("{}. \"{}\"", device_index + 1, device.name()?); + for host_id in available_hosts { + println!("{:?}", host_id); + let host = cpal::host_from_id(host_id)?; + let default_in = host.default_input_device().map(|e| e.name().unwrap()); + let default_out = host.default_output_device().map(|e| e.name().unwrap()); + println!(" Default Input Device:\n {:?}", default_in); + println!(" Default Output Device:\n {:?}", default_out); - // Input formats - if let Ok(fmt) = device.default_input_format() { - println!(" Default input stream format:\n {:?}", fmt); - } - let mut input_formats = match device.supported_input_formats() { - Ok(f) => f.peekable(), - Err(e) => { - println!("Error: {:?}", e); - continue; - }, - }; - if input_formats.peek().is_some() { - println!(" All supported input stream formats:"); - for (format_index, format) in input_formats.enumerate() { - println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + let devices = host.devices()?; + println!(" Devices: "); + for (device_index, device) in devices.enumerate() { + println!(" {}. \"{}\"", device_index + 1, device.name()?); + + // Input formats + if let Ok(fmt) = device.default_input_format() { + println!(" Default input stream format:\n {:?}", fmt); + } + let mut input_formats = match device.supported_input_formats() { + Ok(f) => f.peekable(), + Err(e) => { + println!("Error: {:?}", e); + continue; + }, + }; + if input_formats.peek().is_some() { + println!(" All supported input stream formats:"); + for (format_index, format) in input_formats.enumerate() { + println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + } } - } - // Output formats - if let Ok(fmt) = device.default_output_format() { - println!(" Default output stream format:\n {:?}", fmt); - } - let mut output_formats = match device.supported_output_formats() { - Ok(f) => f.peekable(), - Err(e) => { - println!("Error: {:?}", e); - continue; - }, - }; - if output_formats.peek().is_some() { - println!(" All supported output stream formats:"); - for (format_index, format) in output_formats.enumerate() { - println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + // Output formats + if let Ok(fmt) = device.default_output_format() { + println!(" Default output stream format:\n {:?}", fmt); + } + let mut output_formats = match device.supported_output_formats() { + Ok(f) => f.peekable(), + Err(e) => { + println!("Error: {:?}", e); + continue; + }, + }; + if output_formats.peek().is_some() { + println!(" All supported output stream formats:"); + for (format_index, format) in output_formats.enumerate() { + println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); + } } } } diff --git a/examples/feedback.rs b/examples/feedback.rs index 95d634f..4c80d65 100644 --- a/examples/feedback.rs +++ b/examples/feedback.rs @@ -9,14 +9,17 @@ extern crate cpal; extern crate failure; +use cpal::{Device, EventLoop, Host}; + const LATENCY_MS: f32 = 150.0; fn main() -> Result<(), failure::Error> { - let event_loop = cpal::EventLoop::new(); + let host = cpal::default_host(); + let event_loop = host.event_loop(); // Default devices. - let input_device = cpal::default_input_device().expect("failed to get default input device"); - let output_device = cpal::default_output_device().expect("failed to get default output device"); + let input_device = host.default_input_device().expect("failed to get default input device"); + let output_device = host.default_output_device().expect("failed to get default output device"); println!("Using default input device: \"{}\"", input_device.name()?); println!("Using default output device: \"{}\"", output_device.name()?); diff --git a/examples/record_wav.rs b/examples/record_wav.rs index 66002df..8fa4aaa 100644 --- a/examples/record_wav.rs +++ b/examples/record_wav.rs @@ -6,13 +6,18 @@ extern crate cpal; extern crate failure; extern crate hound; +use cpal::{Device, EventLoop, Host}; + fn main() -> Result<(), failure::Error> { + // Use the default host for working with audio devices. + let host = cpal::default_host(); + // 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 = host.default_input_device().expect("Failed to get default input device"); println!("Default input device: {}", device.name()?); let format = device.default_input_format().expect("Failed to get default input format"); println!("Default input format: {:?}", format); - let event_loop = cpal::EventLoop::new(); + let event_loop = host.event_loop(); let stream_id = event_loop.build_input_stream(&device, &format)?; event_loop.play_stream(stream_id)?; diff --git a/src/alsa/enumerate.rs b/src/host/alsa/enumerate.rs similarity index 100% rename from src/alsa/enumerate.rs rename to src/host/alsa/enumerate.rs diff --git a/src/alsa/mod.rs b/src/host/alsa/mod.rs similarity index 92% rename from src/alsa/mod.rs rename to src/host/alsa/mod.rs index 4d276cc..6b8b2f5 100644 --- a/src/alsa/mod.rs +++ b/src/host/alsa/mod.rs @@ -7,16 +7,21 @@ use ChannelCount; use BackendSpecificError; use BuildStreamError; use DefaultFormatError; +use Device as DeviceTrait; use DeviceNameError; +use DevicesError; +use EventLoop as EventLoopTrait; use Format; +use Host as HostTrait; use PauseStreamError; use PlayStreamError; -use SupportedFormatsError; use SampleFormat; use SampleRate; +use SupportedFormatsError; use StreamData; use StreamDataResult; use StreamError; +use StreamId as StreamIdTrait; use SupportedFormat; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; @@ -32,6 +37,109 @@ pub type SupportedOutputFormats = VecIntoIter; mod enumerate; +/// The default linux and freebsd host type. +#[derive(Debug)] +pub struct Host; + +impl Host { + pub fn new() -> Result { + Ok(Host) + } +} + +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + // Assume ALSA is always available on linux/freebsd. + true + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + default_input_device() + } + + fn default_output_device(&self) -> Option { + default_output_device() + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + Device::name(self) + } + + fn supported_input_formats(&self) -> Result { + Device::supported_input_formats(self) + } + + fn supported_output_formats(&self) -> Result { + Device::supported_output_formats(self) + } + + fn default_input_format(&self) -> Result { + Device::default_input_format(self) + } + + fn default_output_format(&self) -> Result { + Device::default_output_format(self) + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_input_stream(self, device, format) + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_output_stream(self, device, format) + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { + EventLoop::play_stream(self, stream) + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { + EventLoop::pause_stream(self, stream) + } + + fn destroy_stream(&self, stream: Self::StreamId) { + EventLoop::destroy_stream(self, stream) + } + + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send, + { + EventLoop::run(self, callback) + } +} + +impl StreamIdTrait for StreamId {} struct Trigger { // [read fd, write fd] @@ -79,7 +187,7 @@ pub struct Device(String); impl Device { #[inline] - pub fn name(&self) -> Result { + fn name(&self) -> Result { Ok(self.0.clone()) } @@ -287,13 +395,13 @@ impl Device { Ok(output.into_iter()) } - pub fn supported_input_formats(&self) -> Result { + fn supported_input_formats(&self) -> Result { unsafe { self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE) } } - pub fn supported_output_formats(&self) -> Result { + fn supported_output_formats(&self) -> Result { unsafe { self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK) } @@ -340,11 +448,11 @@ impl Device { } } - pub fn default_input_format(&self) -> Result { + fn default_input_format(&self) -> Result { self.default_format(alsa::SND_PCM_STREAM_CAPTURE) } - pub fn default_output_format(&self) -> Result { + fn default_output_format(&self) -> Result { self.default_format(alsa::SND_PCM_STREAM_PLAYBACK) } } @@ -434,7 +542,7 @@ enum StreamType { Input, Output } impl EventLoop { #[inline] - pub fn new() -> EventLoop { + fn new() -> EventLoop { let pending_command_trigger = Trigger::new(); let mut initial_descriptors = vec![]; @@ -460,7 +568,7 @@ impl EventLoop { } #[inline] - pub fn run(&self, mut callback: F) -> ! + fn run(&self, mut callback: F) -> ! where F: FnMut(StreamId, StreamDataResult) { self.run_inner(&mut callback) @@ -645,7 +753,7 @@ impl EventLoop { panic!("`cpal::EventLoop::run` API currently disallows returning"); } - pub fn build_input_stream( + fn build_input_stream( &self, device: &Device, format: &Format, @@ -724,7 +832,7 @@ impl EventLoop { } } - pub fn build_output_stream( + fn build_output_stream( &self, device: &Device, format: &Format, @@ -805,18 +913,18 @@ impl EventLoop { } #[inline] - pub fn destroy_stream(&self, stream_id: StreamId) { + fn destroy_stream(&self, stream_id: StreamId) { self.push_command(Command::DestroyStream(stream_id)); } #[inline] - pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { + fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { self.push_command(Command::PlayStream(stream_id)); Ok(()) } #[inline] - pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { + fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { self.push_command(Command::PauseStream(stream_id)); Ok(()) } diff --git a/src/coreaudio/enumerate.rs b/src/host/coreaudio/enumerate.rs similarity index 100% rename from src/coreaudio/enumerate.rs rename to src/host/coreaudio/enumerate.rs diff --git a/src/coreaudio/mod.rs b/src/host/coreaudio/mod.rs similarity index 100% rename from src/coreaudio/mod.rs rename to src/host/coreaudio/mod.rs diff --git a/src/emscripten/mod.rs b/src/host/emscripten/mod.rs similarity index 100% rename from src/emscripten/mod.rs rename to src/host/emscripten/mod.rs diff --git a/src/host/mod.rs b/src/host/mod.rs new file mode 100644 index 0000000..25cfb73 --- /dev/null +++ b/src/host/mod.rs @@ -0,0 +1,10 @@ +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +pub(crate) mod alsa; +#[cfg(any(target_os = "macos", target_os = "ios"))] +mod coreaudio; +//mod dynamic; +#[cfg(target_os = "emscripten")] +mod emscripten; +mod null; +#[cfg(windows)] +mod wasapi; diff --git a/src/null/mod.rs b/src/host/null/mod.rs similarity index 50% rename from src/null/mod.rs rename to src/host/null/mod.rs index ff5213a..45b6b9b 100644 --- a/src/null/mod.rs +++ b/src/host/null/mod.rs @@ -1,64 +1,35 @@ #![allow(dead_code)] -use std::marker::PhantomData; - use BuildStreamError; use DefaultFormatError; +use Device as DeviceTrait; use DevicesError; use DeviceNameError; +use EventLoop as EventLoopTrait; use Format; +use Host as HostTrait; use PauseStreamError; use PlayStreamError; use StreamDataResult; use SupportedFormatsError; +use StreamId as StreamIdTrait; use SupportedFormat; +#[derive(Default)] +pub struct Devices; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Device; + pub struct EventLoop; -impl EventLoop { - #[inline] - pub fn new() -> EventLoop { - EventLoop - } - - #[inline] - pub fn run(&self, _callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) - { - loop { /* TODO: don't spin */ } - } - - #[inline] - pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result { - Err(BuildStreamError::DeviceNotAvailable) - } - - #[inline] - pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result { - Err(BuildStreamError::DeviceNotAvailable) - } - - #[inline] - pub fn destroy_stream(&self, _: StreamId) { - unimplemented!() - } - - #[inline] - pub fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> { - panic!() - } - - #[inline] - pub fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> { - panic!() - } -} +pub struct Host; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct StreamId; -#[derive(Default)] -pub struct Devices; +pub struct SupportedInputFormats; +pub struct SupportedOutputFormats; impl Devices { pub fn new() -> Result { @@ -66,6 +37,107 @@ impl Devices { } } +impl EventLoop { + pub fn new() -> EventLoop { + EventLoop + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + #[inline] + fn name(&self) -> Result { + Ok("null".to_owned()) + } + + #[inline] + fn supported_input_formats(&self) -> Result { + unimplemented!() + } + + #[inline] + fn supported_output_formats(&self) -> Result { + unimplemented!() + } + + #[inline] + fn default_input_format(&self) -> Result { + unimplemented!() + } + + #[inline] + fn default_output_format(&self) -> Result { + unimplemented!() + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + #[inline] + fn run(&self, _callback: F) -> ! + where F: FnMut(StreamId, StreamDataResult) + { + loop { /* TODO: don't spin */ } + } + + #[inline] + fn build_input_stream(&self, _: &Device, _: &Format) -> Result { + Err(BuildStreamError::DeviceNotAvailable) + } + + #[inline] + fn build_output_stream(&self, _: &Device, _: &Format) -> Result { + Err(BuildStreamError::DeviceNotAvailable) + } + + #[inline] + fn destroy_stream(&self, _: StreamId) { + unimplemented!() + } + + #[inline] + fn play_stream(&self, _: StreamId) -> Result<(), PlayStreamError> { + panic!() + } + + #[inline] + fn pause_stream(&self, _: StreamId) -> Result<(), PauseStreamError> { + panic!() + } +} + +impl HostTrait for Host { + type Device = Device; + type Devices = Devices; + type EventLoop = EventLoop; + + fn is_available() -> bool { + false + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + None + } + + fn default_output_device(&self) -> Option { + None + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl StreamIdTrait for StreamId {} + impl Iterator for Devices { type Item = Device; @@ -75,49 +147,6 @@ impl Iterator for Devices { } } -#[inline] -pub fn default_input_device() -> Option { - None -} - -#[inline] -pub fn default_output_device() -> Option { - None -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Device; - -impl Device { - #[inline] - pub fn supported_input_formats(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn supported_output_formats(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn default_input_format(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn default_output_format(&self) -> Result { - unimplemented!() - } - - #[inline] - pub fn name(&self) -> Result { - Ok("null".to_owned()) - } -} - -pub struct SupportedInputFormats; -pub struct SupportedOutputFormats; - impl Iterator for SupportedInputFormats { type Item = SupportedFormat; @@ -135,11 +164,3 @@ impl Iterator for SupportedOutputFormats { None } } - -pub struct InputBuffer<'a, T: 'a> { - marker: PhantomData<&'a T>, -} - -pub struct OutputBuffer<'a, T: 'a> { - marker: PhantomData<&'a mut T>, -} diff --git a/src/wasapi/com.rs b/src/host/wasapi/com.rs similarity index 100% rename from src/wasapi/com.rs rename to src/host/wasapi/com.rs diff --git a/src/wasapi/device.rs b/src/host/wasapi/device.rs similarity index 100% rename from src/wasapi/device.rs rename to src/host/wasapi/device.rs diff --git a/src/wasapi/mod.rs b/src/host/wasapi/mod.rs similarity index 100% rename from src/wasapi/mod.rs rename to src/host/wasapi/mod.rs diff --git a/src/wasapi/stream.rs b/src/host/wasapi/stream.rs similarity index 100% rename from src/wasapi/stream.rs rename to src/host/wasapi/stream.rs diff --git a/src/lib.rs b/src/lib.rs index 0c04b11..b567f27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,19 +2,25 @@ //! //! Here are some concepts cpal exposes: //! -//! - A `Device` is an audio device that may have any number of input and output streams. -//! - A stream is an open audio channel. Input streams allow you to receive audio data, output -//! streams allow you to play audio data. You must choose which `Device` runs your stream before -//! you create one. -//! - An `EventLoop` is a collection of streams being run by one or more `Device`. Each stream must -//! belong to an `EventLoop`, and all the streams that belong to an `EventLoop` are managed -//! together. +//! - A [**Host**](./trait.Host.html) provides access to the available audio devices on the system. +//! Some platforms have more than one host available, but every platform supported by CPAL has at +//! least one [**DefaultHost**](./trait.Host.html) that is guaranteed to be available. +//! - A [**Device**](./trait.Device.html) is an audio device that may have any number of input and +//! output streams. +//! - A stream is an open flow of audio data. Input streams allow you to receive audio data, output +//! streams allow you to play audio data. You must choose which **Device** will run your stream +//! before you can create one. Often, a default device can be retrieved via the **Host**. +//! - An [**EventLoop**](./trait.EventLoop.html) is a collection of streams being run by one or +//! more **Device**s under a single **Host**. Each stream must belong to an **EventLoop**, and +//! all the streams that belong to an **EventLoop** are managed together. //! -//! The first step is to create an `EventLoop`: +//! The first step is to initialise the `Host` (for accessing audio devices) and create an +//! `EventLoop`: //! //! ``` -//! use cpal::EventLoop; -//! let event_loop = EventLoop::new(); +//! use cpal::Host; +//! let host = cpal::default_host(); +//! let event_loop = host.event_loop(); //! ``` //! //! Then choose a `Device`. The easiest way is to use the default input or output `Device` via the @@ -24,7 +30,9 @@ //! stream type on the system. //! //! ```no_run -//! let device = cpal::default_output_device().expect("no output device available"); +//! # use cpal::Host; +//! # let host = cpal::default_host(); +//! let device = host.default_output_device().expect("no output device available"); //! ``` //! //! Before we can create a stream, we must decide what the format of the audio samples is going to @@ -38,7 +46,9 @@ //! > has been disconnected. //! //! ```no_run -//! # let device = cpal::default_output_device().unwrap(); +//! use cpal::{Device, Host}; +//! # let host = cpal::default_host(); +//! # let device = host.default_output_device().unwrap(); //! let mut supported_formats_range = device.supported_output_formats() //! .expect("error while querying formats"); //! let format = supported_formats_range.next() @@ -49,9 +59,11 @@ //! Now that we have everything for the stream, we can create it from our event loop: //! //! ```no_run -//! # let device = cpal::default_output_device().unwrap(); +//! use cpal::{Device, EventLoop, Host}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); +//! # let device = host.default_output_device().unwrap(); //! # let format = device.supported_output_formats().unwrap().next().unwrap().with_max_sample_rate(); -//! # let event_loop = cpal::EventLoop::new(); //! let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); //! ``` //! @@ -61,15 +73,19 @@ //! Now we must start the stream. This is done with the `play_stream()` method on the event loop. //! //! ```no_run -//! # let event_loop: cpal::EventLoop = return; -//! # let stream_id: cpal::StreamId = return; +//! # use cpal::{EventLoop, Host}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); +//! # let stream_id = unimplemented!(); //! 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. //! //! ```no_run -//! # let event_loop = cpal::EventLoop::new(); +//! # use cpal::{EventLoop, Host}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); //! event_loop.run(move |_stream_id, _stream_result| { //! // react to stream events and read or write stream data here //! }); @@ -87,9 +103,9 @@ //! In this example, we simply fill the given output buffer with zeroes. //! //! ```no_run -//! use cpal::{StreamData, UnknownTypeOutputBuffer}; -//! -//! # let event_loop = cpal::EventLoop::new(); +//! use cpal::{EventLoop, Host, StreamData, UnknownTypeOutputBuffer}; +//! # let host = cpal::default_host(); +//! # let event_loop = host.event_loop(); //! event_loop.run(move |stream_id, stream_result| { //! let stream_data = match stream_result { //! Ok(data) => data, @@ -132,51 +148,203 @@ extern crate lazy_static; #[macro_use] extern crate stdweb; +pub use platform::{ALL_HOSTS, DefaultHost, HostId, available_hosts, default_host, host_from_id}; pub use samples_formats::{Sample, SampleFormat}; -#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", - target_os = "macos", target_os = "ios", target_os = "emscripten")))] -use null as cpal_impl; - use failure::Fail; -use std::fmt; -use std::iter; use std::ops::{Deref, DerefMut}; -mod null; +mod host; +pub mod platform; mod samples_formats; -#[cfg(any(target_os = "linux", target_os = "freebsd"))] -#[path = "alsa/mod.rs"] -mod cpal_impl; +/// A **Host** provides access to the available audio devices on the system. +/// +/// Each platform may have a number of available hosts depending on the system, each with their own +/// pros and cons. +/// +/// For example, WASAPI is the standard audio host API that ships with the Windows operating +/// system. However, due to historical limitations with respect to performance and flexibility, +/// Steinberg created the ASIO API providing better audio device support for pro audio and +/// low-latency applications. As a result, it is common for some devices and device capabilities to +/// only be available via ASIO, while others are only available via WASAPI. +/// +/// Another great example is the Linux platform. While the ALSA host API is the lowest-level API +/// available to almost all distributions of Linux, its flexibility is limited as it requires that +/// each process have exclusive access to the devices with which they establish streams. PortAudio +/// is another popular host API that aims to solve this issue by providing user-space mixing, +/// however it has its own limitations w.r.t. low-latency and high-performance audio applications. +/// JACK is yet another host API that is more suitable to pro-audio applications, however it is +/// less readily available by default in many Linux distributions and is known to be tricky to +/// setup. +pub trait Host { + /// The type used for enumerating available devices by the host. + type Devices: Iterator; + /// The `Device` type yielded by the host. + type Device: Device; + /// The event loop type used by the `Host` + type EventLoop: EventLoop; -#[cfg(windows)] -#[path = "wasapi/mod.rs"] -mod cpal_impl; + /// Whether or not the host is available on the system. + fn is_available() -> bool; -#[cfg(any(target_os = "macos", target_os = "ios"))] -#[path = "coreaudio/mod.rs"] -mod cpal_impl; + /// An iterator yielding all `Device`s currently available to the host on the system. + /// + /// Can be empty if the system does not support audio in general. + fn devices(&self) -> Result; -#[cfg(target_os = "emscripten")] -#[path = "emscripten/mod.rs"] -mod cpal_impl; + /// The default input audio device on the system. + /// + /// Returns `None` if no input device is available. + fn default_input_device(&self) -> Option; -/// An opaque type that identifies a device that is capable of either audio input or output. + /// The default output audio device on the system. + /// + /// Returns `None` if no output device is available. + fn default_output_device(&self) -> Option; + + /// Initialise the event loop, ready for managing audio streams. + fn event_loop(&self) -> Self::EventLoop; + + /// An iterator yielding all `Device`s currently available to the system that support one or more + /// input stream formats. + /// + /// Can be empty if the system does not support audio input. + fn input_devices(&self) -> Result, DevicesError> { + fn supports_input(device: &D) -> bool { + device.supported_input_formats() + .map(|mut iter| iter.next().is_some()) + .unwrap_or(false) + } + Ok(self.devices()?.filter(supports_input::)) + } + + /// An iterator yielding all `Device`s currently available to the system that support one or more + /// output stream formats. + /// + /// Can be empty if the system does not support audio output. + fn output_devices(&self) -> Result, DevicesError> { + fn supports_output(device: &D) -> bool { + device.supported_output_formats() + .map(|mut iter| iter.next().is_some()) + .unwrap_or(false) + } + Ok(self.devices()?.filter(supports_output::)) + } +} + +/// A device that is capable of audio input and/or output. /// /// Please note that `Device`s may become invalid if they get disconnected. Therefore all the -/// methods that involve a device return a `Result`. -#[derive(Clone, PartialEq, Eq)] -pub struct Device(cpal_impl::Device); +/// methods that involve a device return a `Result` allowing the user to handle this case. +pub trait Device { + /// The iterator type yielding supported input stream formats. + type SupportedInputFormats: Iterator; + /// The iterator type yielding supported output stream formats. + type SupportedOutputFormats: Iterator; + + /// The human-readable name of the device. + fn name(&self) -> Result; + + /// An iterator yielding formats that are supported by the backend. + /// + /// Can return an error if the device is no longer valid (eg. it has been disconnected). + fn supported_input_formats(&self) -> Result; + + /// An iterator yielding output stream formats that are supported by the device. + /// + /// Can return an error if the device is no longer valid (eg. it has been disconnected). + fn supported_output_formats(&self) -> Result; + + /// The default input stream format for the device. + fn default_input_format(&self) -> Result; + + /// The default output stream format for the device. + fn default_output_format(&self) -> Result; +} /// Collection of streams managed together. /// -/// Created with the [`new`](struct.EventLoop.html#method.new) method. -pub struct EventLoop(cpal_impl::EventLoop); +/// Created with the `Host::event_loop` method. +pub trait EventLoop { + /// The `Device` type yielded by the host. + type Device: Device; + /// The type used to uniquely distinguish between streams. + type StreamId: StreamId; -/// Identifier of a stream within the `EventLoop`. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct StreamId(cpal_impl::StreamId); + /// Creates a new input stream that will run from the given device and with the given format. + /// + /// On success, returns an identifier for the stream. + /// + /// Can return an error if the device is no longer valid, or if the input stream format is not + /// supported by the device. + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result; + + /// Creates a new output stream that will play on the given device and with the given format. + /// + /// On success, returns an identifier for the stream. + /// + /// Can return an error if the device is no longer valid, or if the output stream format is not + /// supported by the device. + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result; + + /// Instructs the audio device that it should start playing the stream with the given ID. + /// + /// Has no effect is the stream was already playing. + /// + /// Only call this after you have submitted some data, otherwise you may hear some glitches. + /// + /// # Panic + /// + /// If the stream does not exist, this function can either panic or be a no-op. + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError>; + + /// Instructs the audio device that it should stop playing the stream with the given ID. + /// + /// Has no effect is the stream was already paused. + /// + /// If you call `play` afterwards, the playback will resume where it was. + /// + /// # Panic + /// + /// If the stream does not exist, this function can either panic or be a no-op. + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError>; + + /// Destroys an existing stream. + /// + /// # Panic + /// + /// If the stream does not exist, this function can either panic or be a no-op. + fn destroy_stream(&self, stream: Self::StreamId); + + /// Takes control of the current thread and begins the stream processing. + /// + /// > **Note**: Since it takes control of the thread, this method is best called on a separate + /// > thread. + /// + /// Whenever a stream needs to be fed some data, the closure passed as parameter is called. + /// You can call the other methods of `EventLoop` without getting a deadlock. + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send; +} + +/// The set of required bounds for host `StreamId` types. +pub trait StreamId: Clone + std::fmt::Debug + PartialEq + Eq {} + +/// A host's device iterator yielding only *input* devices. +pub type InputDevices = std::iter::Filter::Item) -> bool>; + +/// A host's device iterator yielding only *output* devices. +pub type OutputDevices = std::iter::Filter::Item) -> bool>; /// Number of channels. pub type ChannelCount = u16; @@ -271,26 +439,10 @@ pub enum UnknownTypeOutputBuffer<'a> { F32(OutputBuffer<'a, f32>), } -/// An iterator yielding all `Device`s currently available to the system. -/// -/// See [`devices()`](fn.devices.html). -pub struct Devices(cpal_impl::Devices); - -/// A `Devices` yielding only *input* devices. -pub type InputDevices = iter::Filter bool>; - -/// A `Devices` yielding only *output* devices. -pub type OutputDevices = iter::Filter bool>; - -/// An iterator that produces a list of input stream formats supported by the device. -/// -/// See [`Device::supported_input_formats()`](struct.Device.html#method.supported_input_formats). -pub struct SupportedInputFormats(cpal_impl::SupportedInputFormats); - -/// An iterator that produces a list of output stream formats supported by the device. -/// -/// See [`Device::supported_output_formats()`](struct.Device.html#method.supported_output_formats). -pub struct SupportedOutputFormats(cpal_impl::SupportedOutputFormats); +/// The requested host, although supported on this platform, is unavailable. +#[derive(Clone, Debug, Fail)] +#[fail(display = "the requested host is unavailable")] +pub struct HostUnavailable; /// Some error has occurred that is specific to the backend from which it was produced. /// @@ -440,191 +592,6 @@ pub enum StreamError { } } -/// An iterator yielding all `Device`s currently available to the system. -/// -/// Can be empty if the system does not support audio in general. -#[inline] -pub fn devices() -> Result { - Ok(Devices(cpal_impl::Devices::new()?)) -} - -/// An iterator yielding all `Device`s currently available to the system that support one or more -/// input stream formats. -/// -/// Can be empty if the system does not support audio input. -pub fn input_devices() -> Result { - fn supports_input(device: &Device) -> bool { - device.supported_input_formats() - .map(|mut iter| iter.next().is_some()) - .unwrap_or(false) - } - Ok(devices()?.filter(supports_input)) -} - -/// An iterator yielding all `Device`s currently available to the system that support one or more -/// output stream formats. -/// -/// Can be empty if the system does not support audio output. -pub fn output_devices() -> Result { - fn supports_output(device: &Device) -> bool { - device.supported_output_formats() - .map(|mut iter| iter.next().is_some()) - .unwrap_or(false) - } - Ok(devices()?.filter(supports_output)) -} - -/// The default input audio device on the system. -/// -/// Returns `None` if no input device is available. -pub fn default_input_device() -> Option { - cpal_impl::default_input_device().map(Device) -} - -/// The default output audio device on the system. -/// -/// Returns `None` if no output device is available. -pub fn default_output_device() -> Option { - cpal_impl::default_output_device().map(Device) -} - -impl Device { - /// The human-readable name of the device. - #[inline] - pub fn name(&self) -> Result { - self.0.name() - } - - /// An iterator yielding formats that are supported by the backend. - /// - /// Can return an error if the device is no longer valid (eg. it has been disconnected). - #[inline] - pub fn supported_input_formats(&self) -> Result { - Ok(SupportedInputFormats(self.0.supported_input_formats()?)) - } - - /// An iterator yielding output stream formats that are supported by the device. - /// - /// Can return an error if the device is no longer valid (eg. it has been disconnected). - #[inline] - pub fn supported_output_formats(&self) -> Result { - Ok(SupportedOutputFormats(self.0.supported_output_formats()?)) - } - - /// The default input stream format for the device. - #[inline] - pub fn default_input_format(&self) -> Result { - self.0.default_input_format() - } - - /// The default output stream format for the device. - #[inline] - pub fn default_output_format(&self) -> Result { - self.0.default_output_format() - } -} - -impl fmt::Debug for Device { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -impl EventLoop { - /// Initializes a new events loop. - #[inline] - pub fn new() -> EventLoop { - EventLoop(cpal_impl::EventLoop::new()) - } - - /// Creates a new input stream that will run from the given device and with the given format. - /// - /// On success, returns an identifier for the stream. - /// - /// Can return an error if the device is no longer valid, or if the input stream format is not - /// supported by the device. - #[inline] - pub fn build_input_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - self.0.build_input_stream(&device.0, format).map(StreamId) - } - - /// Creates a new output stream that will play on the given device and with the given format. - /// - /// On success, returns an identifier for the stream. - /// - /// Can return an error if the device is no longer valid, or if the output stream format is not - /// supported by the device. - #[inline] - pub fn build_output_stream( - &self, - device: &Device, - format: &Format, - ) -> Result - { - self.0.build_output_stream(&device.0, format).map(StreamId) - } - - /// Instructs the audio device that it should start playing the stream with the given ID. - /// - /// Has no effect is the stream was already playing. - /// - /// Only call this after you have submitted some data, otherwise you may hear some glitches. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - /// - #[inline] - pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { - self.0.play_stream(stream.0) - } - - /// Instructs the audio device that it should stop playing the stream with the given ID. - /// - /// Has no effect is the stream was already paused. - /// - /// If you call `play` afterwards, the playback will resume where it was. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - /// - #[inline] - pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { - self.0.pause_stream(stream.0) - } - - /// Destroys an existing stream. - /// - /// # Panic - /// - /// If the stream does not exist, this function can either panic or be a no-op. - /// - #[inline] - pub fn destroy_stream(&self, stream_id: StreamId) { - self.0.destroy_stream(stream_id.0) - } - - /// Takes control of the current thread and begins the stream processing. - /// - /// > **Note**: Since it takes control of the thread, this method is best called on a separate - /// > thread. - /// - /// Whenever a stream needs to be fed some data, the closure passed as parameter is called. - /// You can call the other methods of `EventLoop` without getting a deadlock. - #[inline] - pub fn run(&self, mut callback: F) -> ! - where F: FnMut(StreamId, StreamDataResult) + Send - { - self.0.run(move |id, data| callback(StreamId(id), data)) - } -} - impl SupportedFormat { /// Turns this `SupportedFormat` into a `Format` corresponding to the maximum samples rate. #[inline] @@ -775,48 +742,6 @@ impl From for SupportedFormat { } } -impl Iterator for Devices { - type Item = Device; - - #[inline] - fn next(&mut self) -> Option { - self.0.next().map(Device) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } -} - -impl Iterator for SupportedInputFormats { - type Item = SupportedFormat; - - #[inline] - fn next(&mut self) -> Option { - self.0.next() - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } -} - -impl Iterator for SupportedOutputFormats { - type Item = SupportedFormat; - - #[inline] - fn next(&mut self) -> Option { - self.0.next() - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } -} - impl From for DevicesError { fn from(err: BackendSpecificError) -> Self { DevicesError::BackendSpecific { err } diff --git a/src/platform/mod.rs b/src/platform/mod.rs new file mode 100644 index 0000000..6ba5324 --- /dev/null +++ b/src/platform/mod.rs @@ -0,0 +1,457 @@ +//! Platform-specific items. +//! +//! This module also contains the implementation of the platform's dynamically dispatched `Host` +//! type and its associated `EventLoop`, `Device`, `StreamId` and other associated types. These +//! types are useful in the case that users require switching between audio host APIs at runtime. + +// A macro to assist with implementing a platform's dynamically dispatched `Host` type. +// +// These dynamically dispatched types are necessary to allow for users to switch between hosts at +// runtime. +// +// For example the invocation `impl_platform_host(Wasapi wasapi, Asio asio)`, this macro should +// expand to: +// +// ``` +// pub enum HostId { +// Wasapi, +// Asio, +// } +// +// pub enum Host { +// Wasapi(crate::host::wasapi::Host), +// Asio(crate::host::asio::Host), +// } +// ``` +// +// And so on for Device, Devices, EventLoop, Host, StreamId, SupportedInputFormats, +// SupportedOutputFormats and all their necessary trait implementations. +// ``` +macro_rules! impl_platform_host { + ($($HostVariant:ident $host_mod:ident),*) => { + /// All hosts supported by CPAL on this platform. + pub const ALL_HOSTS: &'static [HostId] = &[ + $( + HostId::$HostVariant, + )* + ]; + + /// The platform's dynamically dispatched **Host** type. + /// + /// An instance of this **Host** type may represent one of any of the **Host**s available + /// on the platform. + /// + /// Use this type if you require switching between available hosts at runtime. + /// + /// This type may be constructed via the **host_from_id** function. **HostId**s may + /// be acquired via the **ALL_HOSTS** const and the **available_hosts** function. + pub struct Host(HostInner); + + /// The **Device** implementation associated with the platform's dynamically dispatched + /// **Host** type. + pub struct Device(DeviceInner); + + /// The **Devices** iterator associated with the platform's dynamically dispatched **Host** + /// type. + pub struct Devices(DevicesInner); + + /// The **EventLoop** implementation associated with the platform's dynamically dispatched + /// **Host** type. + pub struct EventLoop(EventLoopInner); + + /// The **StreamId** implementation associated with the platform's dynamically dispatched + /// **Host** type. + #[derive(Clone, Debug, Eq, PartialEq)] + pub struct StreamId(StreamIdInner); + + /// The **SupportedInputFormats** iterator associated with the platform's dynamically + /// dispatched **Host** type. + pub struct SupportedInputFormats(SupportedInputFormatsInner); + + /// The **SupportedOutputFormats** iterator associated with the platform's dynamically + /// dispatched **Host** type. + pub struct SupportedOutputFormats(SupportedOutputFormatsInner); + + /// Unique identifier for available hosts on the platform. + #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] + pub enum HostId { + $( + $HostVariant, + )* + } + + enum DeviceInner { + $( + $HostVariant(crate::host::$host_mod::Device), + )* + } + + enum DevicesInner { + $( + $HostVariant(crate::host::$host_mod::Devices), + )* + } + + enum EventLoopInner { + $( + $HostVariant(crate::host::$host_mod::EventLoop), + )* + } + + enum HostInner { + $( + $HostVariant(crate::host::$host_mod::Host), + )* + } + + #[derive(Clone, Debug, Eq, PartialEq)] + enum StreamIdInner { + $( + $HostVariant(crate::host::$host_mod::StreamId), + )* + } + + enum SupportedInputFormatsInner { + $( + $HostVariant(crate::host::$host_mod::SupportedInputFormats), + )* + } + + enum SupportedOutputFormatsInner { + $( + $HostVariant(crate::host::$host_mod::SupportedOutputFormats), + )* + } + + impl Host { + /// The unique identifier associated with this host. + pub fn id(&self) -> HostId { + match self.0 { + $( + HostInner::$HostVariant(_) => HostId::$HostVariant, + )* + } + } + } + + impl Iterator for Devices { + type Item = Device; + + fn next(&mut self) -> Option { + match self.0 { + $( + DevicesInner::$HostVariant(ref mut d) => { + d.next().map(DeviceInner::$HostVariant).map(Device) + } + )* + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + $( + DevicesInner::$HostVariant(ref d) => d.size_hint(), + )* + } + } + } + + impl Iterator for SupportedInputFormats { + type Item = crate::SupportedFormat; + + fn next(&mut self) -> Option { + match self.0 { + $( + SupportedInputFormatsInner::$HostVariant(ref mut s) => s.next(), + )* + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + $( + SupportedInputFormatsInner::$HostVariant(ref d) => d.size_hint(), + )* + } + } + } + + impl Iterator for SupportedOutputFormats { + type Item = crate::SupportedFormat; + + fn next(&mut self) -> Option { + match self.0 { + $( + SupportedOutputFormatsInner::$HostVariant(ref mut s) => s.next(), + )* + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.0 { + $( + SupportedOutputFormatsInner::$HostVariant(ref d) => d.size_hint(), + )* + } + } + } + + impl crate::Device for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.name(), + )* + } + } + + fn supported_input_formats(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => { + d.supported_input_formats() + .map(SupportedInputFormatsInner::$HostVariant) + .map(SupportedInputFormats) + } + )* + } + } + + fn supported_output_formats(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => { + d.supported_output_formats() + .map(SupportedOutputFormatsInner::$HostVariant) + .map(SupportedOutputFormats) + } + )* + } + } + + fn default_input_format(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.default_input_format(), + )* + } + } + + fn default_output_format(&self) -> Result { + match self.0 { + $( + DeviceInner::$HostVariant(ref d) => d.default_output_format(), + )* + } + } + } + + impl crate::EventLoop for EventLoop { + type StreamId = StreamId; + type Device = Device; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &crate::Format, + ) -> Result { + match (&self.0, &device.0) { + $( + (&EventLoopInner::$HostVariant(ref e), &DeviceInner::$HostVariant(ref d)) => { + e.build_input_stream(d, format) + .map(StreamIdInner::$HostVariant) + .map(StreamId) + } + )* + } + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &crate::Format, + ) -> Result { + match (&self.0, &device.0) { + $( + (&EventLoopInner::$HostVariant(ref e), &DeviceInner::$HostVariant(ref d)) => { + e.build_output_stream(d, format) + .map(StreamIdInner::$HostVariant) + .map(StreamId) + } + )* + } + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), crate::PlayStreamError> { + match (&self.0, stream.0) { + $( + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { + e.play_stream(s) + } + )* + } + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), crate::PauseStreamError> { + match (&self.0, stream.0) { + $( + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { + e.pause_stream(s) + } + )* + } + } + + fn destroy_stream(&self, stream: Self::StreamId) { + match (&self.0, stream.0) { + $( + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { + e.destroy_stream(s) + } + )* + } + } + + fn run(&self, mut callback: F) -> ! + where + F: FnMut(Self::StreamId, crate::StreamDataResult) + Send + { + match self.0 { + $( + EventLoopInner::$HostVariant(ref e) => { + e.run(|id, result| { + let result = result; + callback(StreamId(StreamIdInner::$HostVariant(id)), result); + }); + }, + )* + } + } + } + + impl crate::Host for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + $( crate::host::$host_mod::Host::is_available() ||)* false + } + + fn devices(&self) -> Result { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + h.devices().map(DevicesInner::$HostVariant).map(Devices) + } + )* + } + } + + fn default_input_device(&self) -> Option { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + h.default_input_device().map(DeviceInner::$HostVariant).map(Device) + } + )* + } + } + + fn default_output_device(&self) -> Option { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + h.default_output_device().map(DeviceInner::$HostVariant).map(Device) + } + )* + } + } + + fn event_loop(&self) -> Self::EventLoop { + match self.0 { + $( + HostInner::$HostVariant(ref h) => { + EventLoop(EventLoopInner::$HostVariant(h.event_loop())) + } + )* + } + } + } + + impl crate::StreamId for StreamId {} + + /// Produces a list of hosts that are currently available on the system. + pub fn available_hosts() -> Vec { + let mut host_ids = vec![]; + $( + if ::is_available() { + host_ids.push(HostId::$HostVariant); + } + )* + host_ids + } + + /// Given a unique host identifier, initialise and produce the host if it is available. + pub fn host_from_id(id: HostId) -> Result { + match id { + $( + HostId::$HostVariant => { + crate::host::$host_mod::Host::new() + .map(HostInner::$HostVariant) + .map(Host) + } + )* + } + } + }; +} + +// TODO: Add pulseaudio and jack here eventually. +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +impl_platform_host!(Alsa alsa); + +#[cfg(any(target_os = "macos", target_os = "ios"))] +impl_platform_host!(CoreAudio coreaudio); + +#[cfg(target_os = "emscripten")] +impl_platform_host!(Emscripten emscripten); + +// TODO: Add `Asio asio` once #221 lands. +#[cfg(windows)] +impl_platform_host!(Wasapi wasapi); + +#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", + target_os = "ios", target_os = "emscripten")))] +impl_platform_host!(Null null); + +/// The default host for the current compilation target platform. +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +pub type DefaultHost = crate::host::alsa::Host; + +/// The default host for the current compilation target platform. +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub type DefaultHost = crate::host::coreaudio::Host; + +/// The default host for the current compilation target platform. +#[cfg(target_os = "emscripten")] +pub type DefaultHost = crate::host::emscripten::Host; + +#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", + target_os = "ios", target_os = "emscripten")))] +pub type DefaultHost = crate::host::null::Host; + +/// The default host for the current compilation target platform. +#[cfg(windows)] +pub type DefaultHost = crate::host::wasapi::Host; + +/// Retrieve the default host for the system. +/// +/// There should *always* be a default host for each of the supported target platforms, regardless +/// of whether or not there are any available audio devices. +pub fn default_host() -> DefaultHost { + DefaultHost::new().expect("the default host should always be available") +} From 69cd058d28f423ce3d0dbee692d758139b408b8f Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 24 Jun 2019 22:21:19 +0200 Subject: [PATCH 2/8] Implement new `Host` API for WASAPI backend --- src/host/mod.rs | 2 +- src/host/wasapi/mod.rs | 117 ++++++++++++++++++++++++++++++++++++++ src/host/wasapi/stream.rs | 12 ++-- src/platform/mod.rs | 12 ++-- 4 files changed, 130 insertions(+), 13 deletions(-) diff --git a/src/host/mod.rs b/src/host/mod.rs index 25cfb73..1821453 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -7,4 +7,4 @@ mod coreaudio; mod emscripten; mod null; #[cfg(windows)] -mod wasapi; +pub(crate) mod wasapi; diff --git a/src/host/wasapi/mod.rs b/src/host/wasapi/mod.rs index 6fb462b..f890cc0 100644 --- a/src/host/wasapi/mod.rs +++ b/src/host/wasapi/mod.rs @@ -1,6 +1,19 @@ extern crate winapi; use BackendSpecificError; +use BuildStreamError; +use DefaultFormatError; +use Device as DeviceTrait; +use DeviceNameError; +use DevicesError; +use EventLoop as EventLoopTrait; +use Format; +use Host as HostTrait; +use PlayStreamError; +use PauseStreamError; +use StreamDataResult; +use StreamId as StreamIdTrait; +use SupportedFormatsError; use self::winapi::um::winnt::HRESULT; use std::io::Error as IoError; pub use self::device::{Device, Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; @@ -10,6 +23,110 @@ mod com; mod device; mod stream; +/// The WASAPI host, the default windows host type. +#[derive(Debug)] +pub struct Host; + +impl Host { + pub fn new() -> Result { + Ok(Host) + } +} + +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + // Assume WASAPI is always available on windows. + true + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + default_input_device() + } + + fn default_output_device(&self) -> Option { + default_output_device() + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + Device::name(self) + } + + fn supported_input_formats(&self) -> Result { + Device::supported_input_formats(self) + } + + fn supported_output_formats(&self) -> Result { + Device::supported_output_formats(self) + } + + fn default_input_format(&self) -> Result { + Device::default_input_format(self) + } + + fn default_output_format(&self) -> Result { + Device::default_output_format(self) + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_input_stream(self, device, format) + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_output_stream(self, device, format) + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { + EventLoop::play_stream(self, stream) + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { + EventLoop::pause_stream(self, stream) + } + + fn destroy_stream(&self, stream: Self::StreamId) { + EventLoop::destroy_stream(self, stream) + } + + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send, + { + EventLoop::run(self, callback) + } +} + +impl StreamIdTrait for StreamId {} + #[inline] fn check_result(result: HRESULT) -> Result<(), IoError> { if result < 0 { diff --git a/src/host/wasapi/stream.rs b/src/host/wasapi/stream.rs index cad7b81..5621134 100644 --- a/src/host/wasapi/stream.rs +++ b/src/host/wasapi/stream.rs @@ -115,7 +115,7 @@ impl EventLoop { } } - pub fn build_input_stream( + pub(crate) fn build_input_stream( &self, device: &Device, format: &Format, @@ -276,7 +276,7 @@ impl EventLoop { } } - pub fn build_output_stream( + pub(crate) fn build_output_stream( &self, device: &Device, format: &Format, @@ -439,12 +439,12 @@ impl EventLoop { } #[inline] - pub fn destroy_stream(&self, stream_id: StreamId) { + pub(crate) fn destroy_stream(&self, stream_id: StreamId) { self.push_command(Command::DestroyStream(stream_id)); } #[inline] - pub fn run(&self, mut callback: F) -> ! + pub(crate) fn run(&self, mut callback: F) -> ! where F: FnMut(StreamId, StreamDataResult) { self.run_inner(&mut callback); @@ -617,13 +617,13 @@ impl EventLoop { } #[inline] - pub fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { + pub(crate) fn play_stream(&self, stream: StreamId) -> Result<(), PlayStreamError> { self.push_command(Command::PlayStream(stream)); Ok(()) } #[inline] - pub fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { + pub(crate) fn pause_stream(&self, stream: StreamId) -> Result<(), PauseStreamError> { self.push_command(Command::PauseStream(stream)); Ok(()) } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 6ba5324..ef6a635 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -288,8 +288,8 @@ macro_rules! impl_platform_host { fn play_stream(&self, stream: Self::StreamId) -> Result<(), crate::PlayStreamError> { match (&self.0, stream.0) { $( - (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { - e.play_stream(s) + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(ref s)) => { + e.play_stream(s.clone()) } )* } @@ -298,8 +298,8 @@ macro_rules! impl_platform_host { fn pause_stream(&self, stream: Self::StreamId) -> Result<(), crate::PauseStreamError> { match (&self.0, stream.0) { $( - (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { - e.pause_stream(s) + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(ref s)) => { + e.pause_stream(s.clone()) } )* } @@ -308,8 +308,8 @@ macro_rules! impl_platform_host { fn destroy_stream(&self, stream: Self::StreamId) { match (&self.0, stream.0) { $( - (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(s)) => { - e.destroy_stream(s) + (&EventLoopInner::$HostVariant(ref e), StreamIdInner::$HostVariant(ref s)) => { + e.destroy_stream(s.clone()) } )* } From 6b6830ab57f60c0bfaec8c6f099c09689a95b5ad Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 24 Jun 2019 22:38:48 +0200 Subject: [PATCH 3/8] Implement `Host` API for emscripten backend --- src/host/emscripten/mod.rs | 136 +++++++++++++++++++++++++++++++++---- src/host/mod.rs | 6 +- 2 files changed, 125 insertions(+), 17 deletions(-) diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 78ffa96..18b621f 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -10,17 +10,125 @@ use stdweb::web::set_timeout; use BuildStreamError; use DefaultFormatError; +use Device as DeviceTrait; use DeviceNameError; use DevicesError; +use EventLoop as EventLoopTrait; use Format; +use Host as HostTrait; use PauseStreamError; use PlayStreamError; use SupportedFormatsError; use StreamData; use StreamDataResult; +use StreamId as StreamIdTrait; use SupportedFormat; use UnknownTypeOutputBuffer; +/// The default emscripten host type. +#[derive(Debug)] +pub struct Host; + +impl Host { + pub fn new() -> Result { + Ok(Host) + } +} + +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + // Assume ALSA is always available on linux/freebsd. + true + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + default_input_device() + } + + fn default_output_device(&self) -> Option { + default_output_device() + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + Device::name(self) + } + + fn supported_input_formats(&self) -> Result { + Device::supported_input_formats(self) + } + + fn supported_output_formats(&self) -> Result { + Device::supported_output_formats(self) + } + + fn default_input_format(&self) -> Result { + Device::default_input_format(self) + } + + fn default_output_format(&self) -> Result { + Device::default_output_format(self) + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_input_stream(self, device, format) + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_output_stream(self, device, format) + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { + EventLoop::play_stream(self, stream) + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { + EventLoop::pause_stream(self, stream) + } + + fn destroy_stream(&self, stream: Self::StreamId) { + EventLoop::destroy_stream(self, stream) + } + + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send, + { + EventLoop::run(self, callback) + } +} + +impl StreamIdTrait for StreamId {} + // The emscripten backend works by having a global variable named `_cpal_audio_contexts`, which // is an array of `AudioContext` objects. A stream ID corresponds to an entry in this array. // @@ -44,7 +152,7 @@ impl EventLoop { } #[inline] - pub fn run(&self, callback: F) -> ! + fn run(&self, callback: F) -> ! where F: FnMut(StreamId, StreamDataResult), { // The `run` function uses `set_timeout` to invoke a Rust callback repeatidely. The job @@ -124,12 +232,12 @@ impl EventLoop { } #[inline] - pub fn build_input_stream(&self, _: &Device, _format: &Format) -> Result { + fn build_input_stream(&self, _: &Device, _format: &Format) -> Result { unimplemented!(); } #[inline] - pub fn build_output_stream(&self, _: &Device, _format: &Format) -> Result { + fn build_output_stream(&self, _: &Device, _format: &Format) -> Result { let stream = js!(return new AudioContext()).into_reference().unwrap(); let mut streams = self.streams.lock().unwrap(); @@ -146,12 +254,12 @@ impl EventLoop { } #[inline] - pub fn destroy_stream(&self, stream_id: StreamId) { + fn destroy_stream(&self, stream_id: StreamId) { self.streams.lock().unwrap()[stream_id.0] = None; } #[inline] - pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { + fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { let streams = self.streams.lock().unwrap(); let stream = streams .get(stream_id.0) @@ -162,7 +270,7 @@ impl EventLoop { } #[inline] - pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { + fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { let streams = self.streams.lock().unwrap(); let stream = streams .get(stream_id.0) @@ -193,7 +301,7 @@ fn is_webaudio_available() -> bool { pub struct Devices(bool); impl Devices { - pub fn new() -> Result { + fn new() -> Result { Ok(Self::default()) } } @@ -218,12 +326,12 @@ impl Iterator for Devices { } #[inline] -pub fn default_input_device() -> Option { +fn default_input_device() -> Option { unimplemented!(); } #[inline] -pub fn default_output_device() -> Option { +fn default_output_device() -> Option { if is_webaudio_available() { Some(Device) } else { @@ -236,17 +344,17 @@ pub struct Device; impl Device { #[inline] - pub fn name(&self) -> Result { + fn name(&self) -> Result { Ok("Default Device".to_owned()) } #[inline] - pub fn supported_input_formats(&self) -> Result { + fn supported_input_formats(&self) -> Result { unimplemented!(); } #[inline] - pub fn supported_output_formats(&self) -> Result { + fn supported_output_formats(&self) -> Result { // TODO: right now cpal's API doesn't allow flexibility here // "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if // this ever becomes more flexible, don't forget to change that @@ -264,11 +372,11 @@ impl Device { ) } - pub fn default_input_format(&self) -> Result { + fn default_input_format(&self) -> Result { unimplemented!(); } - pub fn default_output_format(&self) -> Result { + fn default_output_format(&self) -> Result { // TODO: because it is hard coded, see supported_output_formats. Ok( Format { diff --git a/src/host/mod.rs b/src/host/mod.rs index 1821453..7862815 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -1,10 +1,10 @@ #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub(crate) mod alsa; #[cfg(any(target_os = "macos", target_os = "ios"))] -mod coreaudio; +pub(crate) mod coreaudio; //mod dynamic; #[cfg(target_os = "emscripten")] -mod emscripten; -mod null; +pub(crate) mod emscripten; +pub(crate) mod null; #[cfg(windows)] pub(crate) mod wasapi; From f7cf0c65b8d632b53526aba3e1c6720eae90f692 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 24 Jun 2019 22:44:57 +0200 Subject: [PATCH 4/8] Implement `Host` API for coreaudio backend --- src/host/coreaudio/mod.rs | 132 ++++++++++++++++++++++++++++++++++---- 1 file changed, 120 insertions(+), 12 deletions(-) diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index 3cd3613..d37de7b 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -5,8 +5,11 @@ use ChannelCount; use BackendSpecificError; use BuildStreamError; use DefaultFormatError; +use Device as DeviceTrait; use DeviceNameError; +use EventLoop as EventLoopTrait; use Format; +use Host as HostTrait; use PauseStreamError; use PlayStreamError; use SupportedFormatsError; @@ -14,6 +17,7 @@ use SampleFormat; use SampleRate; use StreamData; use StreamDataResult; +use StreamId as StreamIdTrait; use SupportedFormat; use UnknownTypeInputBuffer; use UnknownTypeOutputBuffer; @@ -72,13 +76,117 @@ mod enumerate; pub use self::enumerate::{Devices, SupportedInputFormats, SupportedOutputFormats, default_input_device, default_output_device}; +/// Coreaudio host, the default host on macOS and iOS. +#[derive(Debug)] +pub struct Host; + +impl Host { + pub fn new() -> Result { + Ok(Host) + } +} + +impl HostTrait for Host { + type Devices = Devices; + type Device = Device; + type EventLoop = EventLoop; + + fn is_available() -> bool { + // Assume ALSA is always available on linux/freebsd. + true + } + + fn devices(&self) -> Result { + Devices::new() + } + + fn default_input_device(&self) -> Option { + default_input_device() + } + + fn default_output_device(&self) -> Option { + default_output_device() + } + + fn event_loop(&self) -> Self::EventLoop { + EventLoop::new() + } +} + +impl DeviceTrait for Device { + type SupportedInputFormats = SupportedInputFormats; + type SupportedOutputFormats = SupportedOutputFormats; + + fn name(&self) -> Result { + Device::name(self) + } + + fn supported_input_formats(&self) -> Result { + Device::supported_input_formats(self) + } + + fn supported_output_formats(&self) -> Result { + Device::supported_output_formats(self) + } + + fn default_input_format(&self) -> Result { + Device::default_input_format(self) + } + + fn default_output_format(&self) -> Result { + Device::default_output_format(self) + } +} + +impl EventLoopTrait for EventLoop { + type Device = Device; + type StreamId = StreamId; + + fn build_input_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_input_stream(self, device, format) + } + + fn build_output_stream( + &self, + device: &Self::Device, + format: &Format, + ) -> Result { + EventLoop::build_output_stream(self, device, format) + } + + fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> { + EventLoop::play_stream(self, stream) + } + + fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> { + EventLoop::pause_stream(self, stream) + } + + fn destroy_stream(&self, stream: Self::StreamId) { + EventLoop::destroy_stream(self, stream) + } + + fn run(&self, callback: F) -> ! + where + F: FnMut(Self::StreamId, StreamDataResult) + Send, + { + EventLoop::run(self, callback) + } +} + +impl StreamIdTrait for StreamId {} + #[derive(Clone, PartialEq, Eq)] pub struct Device { audio_device_id: AudioDeviceID, } impl Device { - pub fn name(&self) -> Result { + fn name(&self) -> Result { let property_address = AudioObjectPropertyAddress { mSelector: kAudioDevicePropertyDeviceNameCFString, mScope: kAudioDevicePropertyScopeOutput, @@ -212,11 +320,11 @@ impl Device { } } - pub fn supported_input_formats(&self) -> Result { + fn supported_input_formats(&self) -> Result { self.supported_formats(kAudioObjectPropertyScopeInput) } - pub fn supported_output_formats(&self) -> Result { + fn supported_output_formats(&self) -> Result { self.supported_formats(kAudioObjectPropertyScopeOutput) } @@ -296,11 +404,11 @@ impl Device { } } - pub fn default_input_format(&self) -> Result { + fn default_input_format(&self) -> Result { self.default_format(kAudioObjectPropertyScopeInput) } - pub fn default_output_format(&self) -> Result { + fn default_output_format(&self) -> Result { self.default_format(kAudioObjectPropertyScopeOutput) } } @@ -435,7 +543,7 @@ fn audio_unit_from_device(device: &Device, input: bool) -> Result EventLoop { + fn new() -> EventLoop { EventLoop { user_callback: Arc::new(Mutex::new(UserCallback::Inactive)), streams: Mutex::new(Vec::new()), @@ -443,7 +551,7 @@ impl EventLoop { } #[inline] - pub fn run(&self, mut callback: F) -> ! + fn run(&self, mut callback: F) -> ! where F: FnMut(StreamId, StreamDataResult) + Send { { @@ -490,7 +598,7 @@ impl EventLoop { } #[inline] - pub fn build_input_stream( + fn build_input_stream( &self, device: &Device, format: &Format, @@ -712,7 +820,7 @@ impl EventLoop { } #[inline] - pub fn build_output_stream( + fn build_output_stream( &self, device: &Device, format: &Format, @@ -787,14 +895,14 @@ impl EventLoop { Ok(StreamId(stream_id)) } - pub fn destroy_stream(&self, stream_id: StreamId) { + fn destroy_stream(&self, stream_id: StreamId) { { let mut streams = self.streams.lock().unwrap(); streams[stream_id.0] = None; } } - pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { + fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> { let mut streams = self.streams.lock().unwrap(); let stream = streams[stream_id.0].as_mut().unwrap(); @@ -809,7 +917,7 @@ impl EventLoop { Ok(()) } - pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { + fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> { let mut streams = self.streams.lock().unwrap(); let stream = streams[stream_id.0].as_mut().unwrap(); From 6e9b40e225cf8cc67e0448a6e818fc25e9feb31f Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 24 Jun 2019 22:49:18 +0200 Subject: [PATCH 5/8] Minimize compiler flags by using nested platform_impl mods Also addresses some other CI errors: - Add Host::new constructor for null backend - Add missing DevicesError import to coreaudio backend --- src/host/coreaudio/mod.rs | 1 + src/host/null/mod.rs | 6 ++++ src/platform/mod.rs | 65 +++++++++++++++++++++++++-------------- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index d37de7b..85da95e 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -7,6 +7,7 @@ use BuildStreamError; use DefaultFormatError; use Device as DeviceTrait; use DeviceNameError; +use DevicesError; use EventLoop as EventLoopTrait; use Format; use Host as HostTrait; diff --git a/src/host/null/mod.rs b/src/host/null/mod.rs index 45b6b9b..d45200f 100644 --- a/src/host/null/mod.rs +++ b/src/host/null/mod.rs @@ -31,6 +31,12 @@ pub struct StreamId; pub struct SupportedInputFormats; pub struct SupportedOutputFormats; +impl Host { + pub fn new() -> Result { + Ok(Host) + } +} + impl Devices { pub fn new() -> Result { Ok(Devices) diff --git a/src/platform/mod.rs b/src/platform/mod.rs index ef6a635..0327aea 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -4,6 +4,9 @@ //! type and its associated `EventLoop`, `Device`, `StreamId` and other associated types. These //! types are useful in the case that users require switching between audio host APIs at runtime. +#[doc(inline)] +pub use self::platform_impl::*; + // A macro to assist with implementing a platform's dynamically dispatched `Host` type. // // These dynamically dispatched types are necessary to allow for users to switch between hosts at @@ -412,41 +415,57 @@ macro_rules! impl_platform_host { // TODO: Add pulseaudio and jack here eventually. #[cfg(any(target_os = "linux", target_os = "freebsd"))] -impl_platform_host!(Alsa alsa); +mod platform_impl { + pub use crate::host::alsa::Host as AlsaHost; + + /// The default host for the current compilation target platform. + pub type DefaultHost = crate::host::alsa::Host; + + impl_platform_host!(Alsa alsa); +} + #[cfg(any(target_os = "macos", target_os = "ios"))] -impl_platform_host!(CoreAudio coreaudio); +mod platform_impl { + pub use crate::host::coreaudio::Host as CoreAudioHost; + + /// The default host for the current compilation target platform. + pub type DefaultHost = crate::host::coreaudio::Host; + + impl_platform_host!(CoreAudio coreaudio); +} #[cfg(target_os = "emscripten")] -impl_platform_host!(Emscripten emscripten); +mod platform_impl { + pub use crate::host::emscripten::Host as EmscriptenHost; + + /// The default host for the current compilation target platform. + pub type DefaultHost = crate::host::emscripten::Host; + + impl_platform_host!(Emscripten emscripten); +} // TODO: Add `Asio asio` once #221 lands. #[cfg(windows)] -impl_platform_host!(Wasapi wasapi); +mod platform_impl { + pub use crate::host::wasapi::Host as WasapiHost; + + /// The default host for the current compilation target platform. + pub type DefaultHost = crate::host::wasapi::Host; + + impl_platform_host!(Wasapi wasapi); +} #[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", target_os = "ios", target_os = "emscripten")))] -impl_platform_host!(Null null); +mod platform_impl { + pub use crate::host::null::Host as NullHost; -/// The default host for the current compilation target platform. -#[cfg(any(target_os = "linux", target_os = "freebsd"))] -pub type DefaultHost = crate::host::alsa::Host; + /// The default host for the current compilation target platform. + pub type DefaultHost = crate::host::null::Host; -/// The default host for the current compilation target platform. -#[cfg(any(target_os = "macos", target_os = "ios"))] -pub type DefaultHost = crate::host::coreaudio::Host; - -/// The default host for the current compilation target platform. -#[cfg(target_os = "emscripten")] -pub type DefaultHost = crate::host::emscripten::Host; - -#[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", - target_os = "ios", target_os = "emscripten")))] -pub type DefaultHost = crate::host::null::Host; - -/// The default host for the current compilation target platform. -#[cfg(windows)] -pub type DefaultHost = crate::host::wasapi::Host; + impl_platform_host!(Null null); +} /// Retrieve the default host for the system. /// From e131979d22ac07aebffdef87d5bd84ff456631aa Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Mon, 24 Jun 2019 23:27:46 +0200 Subject: [PATCH 6/8] Update README for addition of host enumeration API --- README.md | 1 + src/host/mod.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3017107..705ad39 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Low-level library for audio input and output in pure Rust. This library currently supports the following: +- Enumerate supported audio hosts. - Enumerate all available audio devices. - Get the current default input and output devices. - Enumerate known supported input and output stream formats for a device. diff --git a/src/host/mod.rs b/src/host/mod.rs index 7862815..0c3155e 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod alsa; #[cfg(any(target_os = "macos", target_os = "ios"))] pub(crate) mod coreaudio; -//mod dynamic; #[cfg(target_os = "emscripten")] pub(crate) mod emscripten; pub(crate) mod null; From 283a73054e31aa2bc63b4cfe0cac0c00215beb7c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 25 Jun 2019 16:26:27 +0200 Subject: [PATCH 7/8] Address some nits highlighted by ishitatsuyuki --- src/host/coreaudio/mod.rs | 2 +- src/host/emscripten/mod.rs | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/host/coreaudio/mod.rs b/src/host/coreaudio/mod.rs index 85da95e..ff22983 100644 --- a/src/host/coreaudio/mod.rs +++ b/src/host/coreaudio/mod.rs @@ -93,7 +93,7 @@ impl HostTrait for Host { type EventLoop = EventLoop; fn is_available() -> bool { - // Assume ALSA is always available on linux/freebsd. + // Assume coreaudio is always available on macOS and iOS. true } diff --git a/src/host/emscripten/mod.rs b/src/host/emscripten/mod.rs index 18b621f..15502df 100644 --- a/src/host/emscripten/mod.rs +++ b/src/host/emscripten/mod.rs @@ -41,7 +41,7 @@ impl HostTrait for Host { type EventLoop = EventLoop; fn is_available() -> bool { - // Assume ALSA is always available on linux/freebsd. + // Assume this host is always available on emscripten. true } diff --git a/src/lib.rs b/src/lib.rs index b567f27..285c0d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,7 +171,7 @@ mod samples_formats; /// /// Another great example is the Linux platform. While the ALSA host API is the lowest-level API /// available to almost all distributions of Linux, its flexibility is limited as it requires that -/// each process have exclusive access to the devices with which they establish streams. PortAudio +/// each process have exclusive access to the devices with which they establish streams. PulseAudio /// is another popular host API that aims to solve this issue by providing user-space mixing, /// however it has its own limitations w.r.t. low-latency and high-performance audio applications. /// JACK is yet another host API that is more suitable to pro-audio applications, however it is From 51eba20c449a8da7247abab2f485a356790e9e5c Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 25 Jun 2019 16:54:50 +0200 Subject: [PATCH 8/8] Remove DefaultHost type in favour of determining at runtime Re-exports host-specific types so that they are available within the platform module if necessary (e.g. host::asla::Host as AlsaHost). Allows for converting platform-specific host types (e.g. AlsaHost) into the dynamically dispatched type generated for the target platform (`Host`). --- src/lib.rs | 2 +- src/platform/mod.rs | 138 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 112 insertions(+), 28 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 285c0d8..d4817df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,7 +148,7 @@ extern crate lazy_static; #[macro_use] extern crate stdweb; -pub use platform::{ALL_HOSTS, DefaultHost, HostId, available_hosts, default_host, host_from_id}; +pub use platform::{ALL_HOSTS, HostId, available_hosts, default_host, host_from_id}; pub use samples_formats::{Sample, SampleFormat}; use failure::Fail; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 0327aea..8dcc658 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -387,6 +387,38 @@ macro_rules! impl_platform_host { impl crate::StreamId for StreamId {} + $( + impl From for Device { + fn from(h: crate::host::$host_mod::Device) -> Self { + Device(DeviceInner::$HostVariant(h)) + } + } + + impl From for Devices { + fn from(h: crate::host::$host_mod::Devices) -> Self { + Devices(DevicesInner::$HostVariant(h)) + } + } + + impl From for EventLoop { + fn from(h: crate::host::$host_mod::EventLoop) -> Self { + EventLoop(EventLoopInner::$HostVariant(h)) + } + } + + impl From for Host { + fn from(h: crate::host::$host_mod::Host) -> Self { + Host(HostInner::$HostVariant(h)) + } + } + + impl From for StreamId { + fn from(h: crate::host::$host_mod::StreamId) -> Self { + StreamId(StreamIdInner::$HostVariant(h)) + } + } + )* + /// Produces a list of hosts that are currently available on the system. pub fn available_hosts() -> Vec { let mut host_ids = vec![]; @@ -416,61 +448,113 @@ macro_rules! impl_platform_host { // TODO: Add pulseaudio and jack here eventually. #[cfg(any(target_os = "linux", target_os = "freebsd"))] mod platform_impl { - pub use crate::host::alsa::Host as AlsaHost; - - /// The default host for the current compilation target platform. - pub type DefaultHost = crate::host::alsa::Host; + pub use crate::host::alsa::{ + Device as AlsaDevice, + Devices as AlsaDevices, + EventLoop as AlsaEventLoop, + Host as AlsaHost, + StreamId as AlsaStreamId, + SupportedInputFormats as AlsaSupportedInputFormats, + SupportedOutputFormats as AlsaSupportedOutputFormats, + }; impl_platform_host!(Alsa alsa); + + /// The default host for the current compilation target platform. + pub fn default_host() -> Host { + AlsaHost::new() + .expect("the default host should always be available") + .into() + } } #[cfg(any(target_os = "macos", target_os = "ios"))] mod platform_impl { - pub use crate::host::coreaudio::Host as CoreAudioHost; - - /// The default host for the current compilation target platform. - pub type DefaultHost = crate::host::coreaudio::Host; + pub use crate::host::coreaudio::{ + Device as CoreAudioDevice, + Devices as CoreAudioDevices, + EventLoop as CoreAudioEventLoop, + Host as CoreAudioHost, + StreamId as CoreAudioStreamId, + SupportedInputFormats as CoreAudioSupportedInputFormats, + SupportedOutputFormats as CoreAudioSupportedOutputFormats, + }; impl_platform_host!(CoreAudio coreaudio); + + /// The default host for the current compilation target platform. + pub fn default_host() -> Host { + CoreAudioHost::new() + .expect("the default host should always be available") + .into() + } } #[cfg(target_os = "emscripten")] mod platform_impl { - pub use crate::host::emscripten::Host as EmscriptenHost; - - /// The default host for the current compilation target platform. - pub type DefaultHost = crate::host::emscripten::Host; + pub use crate::host::emscripten::{ + Device as EmscriptenDevice, + Devices as EmscriptenDevices, + EventLoop as EmscriptenEventLoop, + Host as EmscriptenHost, + StreamId as EmscriptenStreamId, + SupportedInputFormats as EmscriptenSupportedInputFormats, + SupportedOutputFormats as EmscriptenSupportedOutputFormats, + }; impl_platform_host!(Emscripten emscripten); + + /// The default host for the current compilation target platform. + pub fn default_host() -> Host { + EmscriptenHost::new() + .expect("the default host should always be available") + .into() + } } // TODO: Add `Asio asio` once #221 lands. #[cfg(windows)] mod platform_impl { - pub use crate::host::wasapi::Host as WasapiHost; - - /// The default host for the current compilation target platform. - pub type DefaultHost = crate::host::wasapi::Host; + pub use crate::host::wasapi::{ + Device as WasapiDevice, + Devices as WasapiDevices, + EventLoop as WasapiEventLoop, + Host as WasapiHost, + StreamId as WasapiStreamId, + SupportedInputFormats as WasapiSupportedInputFormats, + SupportedOutputFormats as WasapiSupportedOutputFormats, + }; impl_platform_host!(Wasapi wasapi); + + /// The default host for the current compilation target platform. + pub fn default_host() -> Host { + WasapiHost::new() + .expect("the default host should always be available") + .into() + } } #[cfg(not(any(windows, target_os = "linux", target_os = "freebsd", target_os = "macos", target_os = "ios", target_os = "emscripten")))] mod platform_impl { - pub use crate::host::null::Host as NullHost; - - /// The default host for the current compilation target platform. - pub type DefaultHost = crate::host::null::Host; + pub use crate::host::null::{ + Device as NullDevice, + Devices as NullDevices, + EventLoop as NullEventLoop, + Host as NullHost, + StreamId as NullStreamId, + SupportedInputFormats as NullSupportedInputFormats, + SupportedOutputFormats as NullSupportedOutputFormats, + }; impl_platform_host!(Null null); -} -/// Retrieve the default host for the system. -/// -/// There should *always* be a default host for each of the supported target platforms, regardless -/// of whether or not there are any available audio devices. -pub fn default_host() -> DefaultHost { - DefaultHost::new().expect("the default host should always be available") + /// The default host for the current compilation target platform. + pub fn default_host() -> Host { + NullHost::new() + .expect("the default host should always be available") + .into() + } }