[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.
This commit is contained in:
mitchmindtree 2019-06-23 15:49:48 +02:00
parent 2b9e2e0b2c
commit e8a05379c2
17 changed files with 989 additions and 447 deletions

View File

@ -1,10 +1,13 @@
extern crate cpal; extern crate cpal;
extern crate failure; extern crate failure;
use cpal::{Device, EventLoop, Host};
fn main() -> Result<(), failure::Error> { 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 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)?; let stream_id = event_loop.build_output_stream(&device, &format)?;
event_loop.play_stream(stream_id.clone())?; event_loop.play_stream(stream_id.clone())?;

View File

@ -1,50 +1,60 @@
extern crate cpal; extern crate cpal;
extern crate failure; extern crate failure;
use cpal::{Device, Host};
fn main() -> Result<(), failure::Error> { fn main() -> Result<(), failure::Error> {
let default_in = cpal::default_input_device().map(|e| e.name().unwrap()); println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS);
let default_out = cpal::default_output_device().map(|e| e.name().unwrap()); let available_hosts = cpal::available_hosts();
println!("Default Input Device:\n {:?}", default_in); println!("Available hosts:\n {:?}", available_hosts);
println!("Default Output Device:\n {:?}", default_out);
let devices = cpal::devices()?; for host_id in available_hosts {
println!("Devices: "); println!("{:?}", host_id);
for (device_index, device) in devices.enumerate() { let host = cpal::host_from_id(host_id)?;
println!("{}. \"{}\"", device_index + 1, device.name()?); 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 let devices = host.devices()?;
if let Ok(fmt) = device.default_input_format() { println!(" Devices: ");
println!(" Default input stream format:\n {:?}", fmt); for (device_index, device) in devices.enumerate() {
} println!(" {}. \"{}\"", device_index + 1, device.name()?);
let mut input_formats = match device.supported_input_formats() {
Ok(f) => f.peekable(), // Input formats
Err(e) => { if let Ok(fmt) = device.default_input_format() {
println!("Error: {:?}", e); println!(" Default input stream format:\n {:?}", fmt);
continue; }
}, let mut input_formats = match device.supported_input_formats() {
}; Ok(f) => f.peekable(),
if input_formats.peek().is_some() { Err(e) => {
println!(" All supported input stream formats:"); println!("Error: {:?}", e);
for (format_index, format) in input_formats.enumerate() { continue;
println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); },
};
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 // Output formats
if let Ok(fmt) = device.default_output_format() { if let Ok(fmt) = device.default_output_format() {
println!(" Default output stream format:\n {:?}", fmt); println!(" Default output stream format:\n {:?}", fmt);
} }
let mut output_formats = match device.supported_output_formats() { let mut output_formats = match device.supported_output_formats() {
Ok(f) => f.peekable(), Ok(f) => f.peekable(),
Err(e) => { Err(e) => {
println!("Error: {:?}", e); println!("Error: {:?}", e);
continue; continue;
}, },
}; };
if output_formats.peek().is_some() { if output_formats.peek().is_some() {
println!(" All supported output stream formats:"); println!(" All supported output stream formats:");
for (format_index, format) in output_formats.enumerate() { for (format_index, format) in output_formats.enumerate() {
println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format); println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format);
}
} }
} }
} }

View File

@ -9,14 +9,17 @@
extern crate cpal; extern crate cpal;
extern crate failure; extern crate failure;
use cpal::{Device, EventLoop, Host};
const LATENCY_MS: f32 = 150.0; const LATENCY_MS: f32 = 150.0;
fn main() -> Result<(), failure::Error> { fn main() -> Result<(), failure::Error> {
let event_loop = cpal::EventLoop::new(); let host = cpal::default_host();
let event_loop = host.event_loop();
// Default devices. // Default devices.
let input_device = cpal::default_input_device().expect("failed to get default input device"); let input_device = host.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 = host.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()?);

View File

@ -6,13 +6,18 @@ extern crate cpal;
extern crate failure; extern crate failure;
extern crate hound; extern crate hound;
use cpal::{Device, EventLoop, Host};
fn main() -> Result<(), failure::Error> { 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. // 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()?); 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 = host.event_loop();
let stream_id = event_loop.build_input_stream(&device, &format)?; let stream_id = event_loop.build_input_stream(&device, &format)?;
event_loop.play_stream(stream_id)?; event_loop.play_stream(stream_id)?;

View File

@ -7,16 +7,21 @@ use ChannelCount;
use BackendSpecificError; use BackendSpecificError;
use BuildStreamError; use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use Device as DeviceTrait;
use DeviceNameError; use DeviceNameError;
use DevicesError;
use EventLoop as EventLoopTrait;
use Format; use Format;
use Host as HostTrait;
use PauseStreamError; use PauseStreamError;
use PlayStreamError; use PlayStreamError;
use SupportedFormatsError;
use SampleFormat; use SampleFormat;
use SampleRate; use SampleRate;
use SupportedFormatsError;
use StreamData; use StreamData;
use StreamDataResult; use StreamDataResult;
use StreamError; use StreamError;
use StreamId as StreamIdTrait;
use SupportedFormat; use SupportedFormat;
use UnknownTypeInputBuffer; use UnknownTypeInputBuffer;
use UnknownTypeOutputBuffer; use UnknownTypeOutputBuffer;
@ -32,6 +37,109 @@ pub type SupportedOutputFormats = VecIntoIter<SupportedFormat>;
mod enumerate; mod enumerate;
/// The default linux and freebsd host type.
#[derive(Debug)]
pub struct Host;
impl Host {
pub fn new() -> Result<Self, crate::HostUnavailable> {
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<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Self::Device> {
default_input_device()
}
fn default_output_device(&self) -> Option<Self::Device> {
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<String, DeviceNameError> {
Device::name(self)
}
fn supported_input_formats(&self) -> Result<Self::SupportedInputFormats, SupportedFormatsError> {
Device::supported_input_formats(self)
}
fn supported_output_formats(&self) -> Result<Self::SupportedOutputFormats, SupportedFormatsError> {
Device::supported_output_formats(self)
}
fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
Device::default_input_format(self)
}
fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
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<Self::StreamId, BuildStreamError> {
EventLoop::build_input_stream(self, device, format)
}
fn build_output_stream(
&self,
device: &Self::Device,
format: &Format,
) -> Result<Self::StreamId, BuildStreamError> {
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<F>(&self, callback: F) -> !
where
F: FnMut(Self::StreamId, StreamDataResult) + Send,
{
EventLoop::run(self, callback)
}
}
impl StreamIdTrait for StreamId {}
struct Trigger { struct Trigger {
// [read fd, write fd] // [read fd, write fd]
@ -79,7 +187,7 @@ pub struct Device(String);
impl Device { impl Device {
#[inline] #[inline]
pub fn name(&self) -> Result<String, DeviceNameError> { fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.0.clone()) Ok(self.0.clone())
} }
@ -287,13 +395,13 @@ impl Device {
Ok(output.into_iter()) Ok(output.into_iter())
} }
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> { 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, SupportedFormatsError> { 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)
} }
@ -340,11 +448,11 @@ impl Device {
} }
} }
pub fn default_input_format(&self) -> Result<Format, DefaultFormatError> { fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
self.default_format(alsa::SND_PCM_STREAM_CAPTURE) self.default_format(alsa::SND_PCM_STREAM_CAPTURE)
} }
pub fn default_output_format(&self) -> Result<Format, DefaultFormatError> { fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
self.default_format(alsa::SND_PCM_STREAM_PLAYBACK) self.default_format(alsa::SND_PCM_STREAM_PLAYBACK)
} }
} }
@ -434,7 +542,7 @@ enum StreamType { Input, Output }
impl EventLoop { impl EventLoop {
#[inline] #[inline]
pub fn new() -> EventLoop { fn new() -> EventLoop {
let pending_command_trigger = Trigger::new(); let pending_command_trigger = Trigger::new();
let mut initial_descriptors = vec![]; let mut initial_descriptors = vec![];
@ -460,7 +568,7 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn run<F>(&self, mut callback: F) -> ! fn run<F>(&self, mut callback: F) -> !
where F: FnMut(StreamId, StreamDataResult) where F: FnMut(StreamId, StreamDataResult)
{ {
self.run_inner(&mut callback) self.run_inner(&mut callback)
@ -645,7 +753,7 @@ impl EventLoop {
panic!("`cpal::EventLoop::run` API currently disallows returning"); panic!("`cpal::EventLoop::run` API currently disallows returning");
} }
pub fn build_input_stream( fn build_input_stream(
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
@ -724,7 +832,7 @@ impl EventLoop {
} }
} }
pub fn build_output_stream( fn build_output_stream(
&self, &self,
device: &Device, device: &Device,
format: &Format, format: &Format,
@ -805,18 +913,18 @@ impl EventLoop {
} }
#[inline] #[inline]
pub fn destroy_stream(&self, stream_id: StreamId) { fn destroy_stream(&self, stream_id: StreamId) {
self.push_command(Command::DestroyStream(stream_id)); self.push_command(Command::DestroyStream(stream_id));
} }
#[inline] #[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)); self.push_command(Command::PlayStream(stream_id));
Ok(()) Ok(())
} }
#[inline] #[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)); self.push_command(Command::PauseStream(stream_id));
Ok(()) Ok(())
} }

10
src/host/mod.rs Normal file
View File

@ -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;

View File

@ -1,64 +1,35 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::marker::PhantomData;
use BuildStreamError; use BuildStreamError;
use DefaultFormatError; use DefaultFormatError;
use Device as DeviceTrait;
use DevicesError; use DevicesError;
use DeviceNameError; use DeviceNameError;
use EventLoop as EventLoopTrait;
use Format; use Format;
use Host as HostTrait;
use PauseStreamError; use PauseStreamError;
use PlayStreamError; use PlayStreamError;
use StreamDataResult; use StreamDataResult;
use SupportedFormatsError; use SupportedFormatsError;
use StreamId as StreamIdTrait;
use SupportedFormat; use SupportedFormat;
#[derive(Default)]
pub struct Devices;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device;
pub struct EventLoop; pub struct EventLoop;
impl EventLoop { pub struct Host;
#[inline]
pub fn new() -> EventLoop {
EventLoop
}
#[inline]
pub fn run<F>(&self, _callback: F) -> !
where F: FnMut(StreamId, StreamDataResult)
{
loop { /* TODO: don't spin */ }
}
#[inline]
pub fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
Err(BuildStreamError::DeviceNotAvailable)
}
#[inline]
pub fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
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!()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StreamId; pub struct StreamId;
#[derive(Default)] pub struct SupportedInputFormats;
pub struct Devices; pub struct SupportedOutputFormats;
impl Devices { impl Devices {
pub fn new() -> Result<Self, DevicesError> { pub fn new() -> Result<Self, DevicesError> {
@ -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<String, DeviceNameError> {
Ok("null".to_owned())
}
#[inline]
fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unimplemented!()
}
#[inline]
fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
unimplemented!()
}
#[inline]
fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
unimplemented!()
}
#[inline]
fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
unimplemented!()
}
}
impl EventLoopTrait for EventLoop {
type Device = Device;
type StreamId = StreamId;
#[inline]
fn run<F>(&self, _callback: F) -> !
where F: FnMut(StreamId, StreamDataResult)
{
loop { /* TODO: don't spin */ }
}
#[inline]
fn build_input_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
Err(BuildStreamError::DeviceNotAvailable)
}
#[inline]
fn build_output_stream(&self, _: &Device, _: &Format) -> Result<StreamId, BuildStreamError> {
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<Self::Devices, DevicesError> {
Devices::new()
}
fn default_input_device(&self) -> Option<Device> {
None
}
fn default_output_device(&self) -> Option<Device> {
None
}
fn event_loop(&self) -> Self::EventLoop {
EventLoop::new()
}
}
impl StreamIdTrait for StreamId {}
impl Iterator for Devices { impl Iterator for Devices {
type Item = Device; type Item = Device;
@ -75,49 +147,6 @@ impl Iterator for Devices {
} }
} }
#[inline]
pub fn default_input_device() -> Option<Device> {
None
}
#[inline]
pub fn default_output_device() -> Option<Device> {
None
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device;
impl Device {
#[inline]
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unimplemented!()
}
#[inline]
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
unimplemented!()
}
#[inline]
pub fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
unimplemented!()
}
#[inline]
pub fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
unimplemented!()
}
#[inline]
pub fn name(&self) -> Result<String, DeviceNameError> {
Ok("null".to_owned())
}
}
pub struct SupportedInputFormats;
pub struct SupportedOutputFormats;
impl Iterator for SupportedInputFormats { impl Iterator for SupportedInputFormats {
type Item = SupportedFormat; type Item = SupportedFormat;
@ -135,11 +164,3 @@ impl Iterator for SupportedOutputFormats {
None None
} }
} }
pub struct InputBuffer<'a, T: 'a> {
marker: PhantomData<&'a T>,
}
pub struct OutputBuffer<'a, T: 'a> {
marker: PhantomData<&'a mut T>,
}

View File

@ -2,19 +2,25 @@
//! //!
//! Here are some concepts cpal exposes: //! Here are some concepts cpal exposes:
//! //!
//! - A `Device` is an audio device that may have any number of input and output streams. //! - A [**Host**](./trait.Host.html) provides access to the available audio devices on the system.
//! - A stream is an open audio channel. Input streams allow you to receive audio data, output //! Some platforms have more than one host available, but every platform supported by CPAL has at
//! streams allow you to play audio data. You must choose which `Device` runs your stream before //! least one [**DefaultHost**](./trait.Host.html) that is guaranteed to be available.
//! you create one. //! - A [**Device**](./trait.Device.html) is an audio device that may have any number of input and
//! - An `EventLoop` is a collection of streams being run by one or more `Device`. Each stream must //! output streams.
//! belong to an `EventLoop`, and all the streams that belong to an `EventLoop` are managed //! - A stream is an open flow of audio data. Input streams allow you to receive audio data, output
//! together. //! 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; //! use cpal::Host;
//! let event_loop = EventLoop::new(); //! 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 //! 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. //! stream type on the system.
//! //!
//! ```no_run //! ```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 //! 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. //! > has been disconnected.
//! //!
//! ```no_run //! ```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() //! let mut supported_formats_range = device.supported_output_formats()
//! .expect("error while querying formats"); //! .expect("error while querying formats");
//! let format = supported_formats_range.next() //! 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: //! Now that we have everything for the stream, we can create it from our event loop:
//! //!
//! ```no_run //! ```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 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(); //! 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. //! Now we must start the stream. This is done with the `play_stream()` method on the event loop.
//! //!
//! ```no_run //! ```no_run
//! # let event_loop: cpal::EventLoop = return; //! # use cpal::{EventLoop, Host};
//! # let stream_id: cpal::StreamId = return; //! # 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"); //! 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.
//! //!
//! ```no_run //! ```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| { //! event_loop.run(move |_stream_id, _stream_result| {
//! // react to stream events and read or write stream data here //! // 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. //! In this example, we simply fill the given output buffer with zeroes.
//! //!
//! ```no_run //! ```no_run
//! use cpal::{StreamData, UnknownTypeOutputBuffer}; //! use cpal::{EventLoop, Host, StreamData, UnknownTypeOutputBuffer};
//! //! # let host = cpal::default_host();
//! # let event_loop = cpal::EventLoop::new(); //! # let event_loop = host.event_loop();
//! event_loop.run(move |stream_id, stream_result| { //! event_loop.run(move |stream_id, stream_result| {
//! let stream_data = match stream_result { //! let stream_data = match stream_result {
//! Ok(data) => data, //! Ok(data) => data,
@ -132,51 +148,203 @@ extern crate lazy_static;
#[macro_use] #[macro_use]
extern crate stdweb; extern crate stdweb;
pub use platform::{ALL_HOSTS, DefaultHost, HostId, available_hosts, default_host, host_from_id};
pub use samples_formats::{Sample, SampleFormat}; 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 failure::Fail;
use std::fmt;
use std::iter;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
mod null; mod host;
pub mod platform;
mod samples_formats; mod samples_formats;
#[cfg(any(target_os = "linux", target_os = "freebsd"))] /// A **Host** provides access to the available audio devices on the system.
#[path = "alsa/mod.rs"] ///
mod cpal_impl; /// 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<Item = Self::Device>;
/// The `Device` type yielded by the host.
type Device: Device;
/// The event loop type used by the `Host`
type EventLoop: EventLoop<Device = Self::Device>;
#[cfg(windows)] /// Whether or not the host is available on the system.
#[path = "wasapi/mod.rs"] fn is_available() -> bool;
mod cpal_impl;
#[cfg(any(target_os = "macos", target_os = "ios"))] /// An iterator yielding all `Device`s currently available to the host on the system.
#[path = "coreaudio/mod.rs"] ///
mod cpal_impl; /// Can be empty if the system does not support audio in general.
fn devices(&self) -> Result<Self::Devices, DevicesError>;
#[cfg(target_os = "emscripten")] /// The default input audio device on the system.
#[path = "emscripten/mod.rs"] ///
mod cpal_impl; /// Returns `None` if no input device is available.
fn default_input_device(&self) -> Option<Self::Device>;
/// 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<Self::Device>;
/// 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<InputDevices<Self::Devices>, DevicesError> {
fn supports_input<D: Device>(device: &D) -> bool {
device.supported_input_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_input::<Self::Device>))
}
/// 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<OutputDevices<Self::Devices>, DevicesError> {
fn supports_output<D: Device>(device: &D) -> bool {
device.supported_output_formats()
.map(|mut iter| iter.next().is_some())
.unwrap_or(false)
}
Ok(self.devices()?.filter(supports_output::<Self::Device>))
}
}
/// 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 /// Please note that `Device`s may become invalid if they get disconnected. Therefore all the
/// methods that involve a device return a `Result`. /// methods that involve a device return a `Result` allowing the user to handle this case.
#[derive(Clone, PartialEq, Eq)] pub trait Device {
pub struct Device(cpal_impl::Device); /// The iterator type yielding supported input stream formats.
type SupportedInputFormats: Iterator<Item = SupportedFormat>;
/// The iterator type yielding supported output stream formats.
type SupportedOutputFormats: Iterator<Item = SupportedFormat>;
/// The human-readable name of the device.
fn name(&self) -> Result<String, DeviceNameError>;
/// 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<Self::SupportedInputFormats, SupportedFormatsError>;
/// 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<Self::SupportedOutputFormats, SupportedFormatsError>;
/// The default input stream format for the device.
fn default_input_format(&self) -> Result<Format, DefaultFormatError>;
/// The default output stream format for the device.
fn default_output_format(&self) -> Result<Format, DefaultFormatError>;
}
/// Collection of streams managed together. /// Collection of streams managed together.
/// ///
/// Created with the [`new`](struct.EventLoop.html#method.new) method. /// Created with the `Host::event_loop` method.
pub struct EventLoop(cpal_impl::EventLoop); 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`. /// Creates a new input stream that will run from the given device and with the given format.
#[derive(Debug, Clone, PartialEq, Eq, Hash)] ///
pub struct StreamId(cpal_impl::StreamId); /// 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<Self::StreamId, BuildStreamError>;
/// 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<Self::StreamId, BuildStreamError>;
/// 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<F>(&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<I> = std::iter::Filter<I, fn(&<I as Iterator>::Item) -> bool>;
/// A host's device iterator yielding only *output* devices.
pub type OutputDevices<I> = std::iter::Filter<I, fn(&<I as Iterator>::Item) -> bool>;
/// Number of channels. /// Number of channels.
pub type ChannelCount = u16; pub type ChannelCount = u16;
@ -271,26 +439,10 @@ pub enum UnknownTypeOutputBuffer<'a> {
F32(OutputBuffer<'a, f32>), F32(OutputBuffer<'a, f32>),
} }
/// An iterator yielding all `Device`s currently available to the system. /// The requested host, although supported on this platform, is unavailable.
/// #[derive(Clone, Debug, Fail)]
/// See [`devices()`](fn.devices.html). #[fail(display = "the requested host is unavailable")]
pub struct Devices(cpal_impl::Devices); pub struct HostUnavailable;
/// A `Devices` yielding only *input* devices.
pub type InputDevices = iter::Filter<Devices, fn(&Device) -> bool>;
/// A `Devices` yielding only *output* devices.
pub type OutputDevices = iter::Filter<Devices, fn(&Device) -> 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);
/// Some error has occurred that is specific to the backend from which it was produced. /// 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<Devices, DevicesError> {
Ok(Devices(cpal_impl::Devices::new()?))
}
/// An iterator yielding all `Device`s currently available to the system that support one or more
/// input stream formats.
///
/// Can be empty if the system does not support audio input.
pub fn input_devices() -> Result<InputDevices, DevicesError> {
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<OutputDevices, DevicesError> {
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<Device> {
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<Device> {
cpal_impl::default_output_device().map(Device)
}
impl Device {
/// The human-readable name of the device.
#[inline]
pub fn name(&self) -> Result<String, DeviceNameError> {
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<SupportedInputFormats, SupportedFormatsError> {
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<SupportedOutputFormats, SupportedFormatsError> {
Ok(SupportedOutputFormats(self.0.supported_output_formats()?))
}
/// The default input stream format for the device.
#[inline]
pub fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
self.0.default_input_format()
}
/// The default output stream format for the device.
#[inline]
pub fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
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<StreamId, BuildStreamError>
{
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<StreamId, BuildStreamError>
{
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<F>(&self, mut callback: F) -> !
where F: FnMut(StreamId, StreamDataResult) + Send
{
self.0.run(move |id, data| callback(StreamId(id), data))
}
}
impl SupportedFormat { impl SupportedFormat {
/// Turns this `SupportedFormat` into a `Format` corresponding to the maximum samples rate. /// Turns this `SupportedFormat` into a `Format` corresponding to the maximum samples rate.
#[inline] #[inline]
@ -775,48 +742,6 @@ impl From<Format> for SupportedFormat {
} }
} }
impl Iterator for Devices {
type Item = Device;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(Device)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl Iterator for SupportedInputFormats {
type Item = SupportedFormat;
#[inline]
fn next(&mut self) -> Option<SupportedFormat> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl Iterator for SupportedOutputFormats {
type Item = SupportedFormat;
#[inline]
fn next(&mut self) -> Option<SupportedFormat> {
self.0.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl From<BackendSpecificError> for DevicesError { impl From<BackendSpecificError> for DevicesError {
fn from(err: BackendSpecificError) -> Self { fn from(err: BackendSpecificError) -> Self {
DevicesError::BackendSpecific { err } DevicesError::BackendSpecific { err }

457
src/platform/mod.rs Normal file
View File

@ -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<Self::Item> {
match self.0 {
$(
DevicesInner::$HostVariant(ref mut d) => {
d.next().map(DeviceInner::$HostVariant).map(Device)
}
)*
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.0 {
$(
DevicesInner::$HostVariant(ref d) => d.size_hint(),
)*
}
}
}
impl Iterator for SupportedInputFormats {
type Item = crate::SupportedFormat;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
$(
SupportedInputFormatsInner::$HostVariant(ref mut s) => s.next(),
)*
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.0 {
$(
SupportedInputFormatsInner::$HostVariant(ref d) => d.size_hint(),
)*
}
}
}
impl Iterator for SupportedOutputFormats {
type Item = crate::SupportedFormat;
fn next(&mut self) -> Option<Self::Item> {
match self.0 {
$(
SupportedOutputFormatsInner::$HostVariant(ref mut s) => s.next(),
)*
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
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<String, crate::DeviceNameError> {
match self.0 {
$(
DeviceInner::$HostVariant(ref d) => d.name(),
)*
}
}
fn supported_input_formats(&self) -> Result<Self::SupportedInputFormats, crate::SupportedFormatsError> {
match self.0 {
$(
DeviceInner::$HostVariant(ref d) => {
d.supported_input_formats()
.map(SupportedInputFormatsInner::$HostVariant)
.map(SupportedInputFormats)
}
)*
}
}
fn supported_output_formats(&self) -> Result<Self::SupportedOutputFormats, crate::SupportedFormatsError> {
match self.0 {
$(
DeviceInner::$HostVariant(ref d) => {
d.supported_output_formats()
.map(SupportedOutputFormatsInner::$HostVariant)
.map(SupportedOutputFormats)
}
)*
}
}
fn default_input_format(&self) -> Result<crate::Format, crate::DefaultFormatError> {
match self.0 {
$(
DeviceInner::$HostVariant(ref d) => d.default_input_format(),
)*
}
}
fn default_output_format(&self) -> Result<crate::Format, crate::DefaultFormatError> {
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<Self::StreamId, crate::BuildStreamError> {
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<Self::StreamId, crate::BuildStreamError> {
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<F>(&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<Self::Devices, crate::DevicesError> {
match self.0 {
$(
HostInner::$HostVariant(ref h) => {
h.devices().map(DevicesInner::$HostVariant).map(Devices)
}
)*
}
}
fn default_input_device(&self) -> Option<Self::Device> {
match self.0 {
$(
HostInner::$HostVariant(ref h) => {
h.default_input_device().map(DeviceInner::$HostVariant).map(Device)
}
)*
}
}
fn default_output_device(&self) -> Option<Self::Device> {
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<HostId> {
let mut host_ids = vec![];
$(
if <crate::host::$host_mod::Host as crate::Host>::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<Host, crate::HostUnavailable> {
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")
}