Skip to content

Commit

Permalink
[WIP] Introduce a Host API
Browse files Browse the repository at this point in the history
This is an implementation of the API described at RustAudio#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 RustAudio#221 (cc
@freesig). These changes should also accommodate support for other hosts
such as jack RustAudio#250 (cc @derekdreery) and pulseaudio (cc @knappador) RustAudio#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 RustAudio#221

cc @ishitatsuyuki more to review for you if you're interested, but it
might be easier after RustAudio#288 lands and this gets rebased.
  • Loading branch information
mitchmindtree committed Jun 24, 2019
1 parent 26f7e99 commit cb93806
Show file tree
Hide file tree
Showing 17 changed files with 960 additions and 418 deletions.
7 changes: 5 additions & 2 deletions examples/beep.rs
Original file line number Diff line number Diff line change
@@ -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())?;

Expand Down
88 changes: 49 additions & 39 deletions examples/enumerate.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions examples/feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()?);

Expand Down
9 changes: 7 additions & 2 deletions examples/record_wav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;

Expand Down
File renamed without changes.
134 changes: 121 additions & 13 deletions src/alsa/mod.rs → src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 StreamError;
use StreamEvent;
use StreamId as StreamIdTrait;
use SupportedFormat;
use UnknownTypeInputBuffer;
use UnknownTypeOutputBuffer;
Expand All @@ -32,6 +37,109 @@ pub type SupportedOutputFormats = VecIntoIter<SupportedFormat>;

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, StreamEvent) + Send,
{
EventLoop::run(self, callback)
}
}

impl StreamIdTrait for StreamId {}

struct Trigger {
// [read fd, write fd]
Expand Down Expand Up @@ -79,7 +187,7 @@ pub struct Device(String);

impl Device {
#[inline]
pub fn name(&self) -> Result<String, DeviceNameError> {
fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.0.clone())
}

Expand Down Expand Up @@ -287,13 +395,13 @@ impl Device {
Ok(output.into_iter())
}

pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
unsafe {
self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE)
}
}

pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
unsafe {
self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK)
}
Expand Down Expand Up @@ -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)
}

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)
}
}
Expand Down Expand Up @@ -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![];
Expand All @@ -460,7 +568,7 @@ impl EventLoop {
}

#[inline]
pub fn run<F>(&self, mut callback: F) -> !
fn run<F>(&self, mut callback: F) -> !
where F: FnMut(StreamId, StreamEvent)
{
self.run_inner(&mut callback)
Expand Down Expand Up @@ -648,7 +756,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,
Expand Down Expand Up @@ -727,7 +835,7 @@ impl EventLoop {
}
}

pub fn build_output_stream(
fn build_output_stream(
&self,
device: &Device,
format: &Format,
Expand Down Expand Up @@ -808,18 +916,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(())
}
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
10 changes: 10 additions & 0 deletions src/host/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Loading

0 comments on commit cb93806

Please sign in to comment.