Skip to content

Commit 4f1bde3

Browse files
committed
[WIP] Introduce a Host API
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.
1 parent 26f7e99 commit 4f1bde3

File tree

17 files changed

+960
-418
lines changed

17 files changed

+960
-418
lines changed

examples/beep.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
extern crate cpal;
22
extern crate failure;
33

4+
use cpal::{Device, EventLoop, Host};
5+
46
fn main() -> Result<(), failure::Error> {
5-
let device = cpal::default_output_device().expect("failed to find a default output device");
7+
let host = cpal::default_host();
8+
let device = host.default_output_device().expect("failed to find a default output device");
69
let format = device.default_output_format()?;
7-
let event_loop = cpal::EventLoop::new();
10+
let event_loop = host.event_loop();
811
let stream_id = event_loop.build_output_stream(&device, &format)?;
912
event_loop.play_stream(stream_id.clone())?;
1013

examples/enumerate.rs

+49-39
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,60 @@
11
extern crate cpal;
22
extern crate failure;
33

4+
use cpal::{Device, Host};
5+
46
fn main() -> Result<(), failure::Error> {
5-
let default_in = cpal::default_input_device().map(|e| e.name().unwrap());
6-
let default_out = cpal::default_output_device().map(|e| e.name().unwrap());
7-
println!("Default Input Device:\n {:?}", default_in);
8-
println!("Default Output Device:\n {:?}", default_out);
7+
println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS);
8+
let available_hosts = cpal::available_hosts();
9+
println!("Available hosts:\n {:?}", available_hosts);
910

10-
let devices = cpal::devices()?;
11-
println!("Devices: ");
12-
for (device_index, device) in devices.enumerate() {
13-
println!("{}. \"{}\"", device_index + 1, device.name()?);
11+
for host_id in available_hosts {
12+
println!("{:?}", host_id);
13+
let host = cpal::host_from_id(host_id)?;
14+
let default_in = host.default_input_device().map(|e| e.name().unwrap());
15+
let default_out = host.default_output_device().map(|e| e.name().unwrap());
16+
println!(" Default Input Device:\n {:?}", default_in);
17+
println!(" Default Output Device:\n {:?}", default_out);
1418

15-
// Input formats
16-
if let Ok(fmt) = device.default_input_format() {
17-
println!(" Default input stream format:\n {:?}", fmt);
18-
}
19-
let mut input_formats = match device.supported_input_formats() {
20-
Ok(f) => f.peekable(),
21-
Err(e) => {
22-
println!("Error: {:?}", e);
23-
continue;
24-
},
25-
};
26-
if input_formats.peek().is_some() {
27-
println!(" All supported input stream formats:");
28-
for (format_index, format) in input_formats.enumerate() {
29-
println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format);
19+
let devices = host.devices()?;
20+
println!(" Devices: ");
21+
for (device_index, device) in devices.enumerate() {
22+
println!(" {}. \"{}\"", device_index + 1, device.name()?);
23+
24+
// Input formats
25+
if let Ok(fmt) = device.default_input_format() {
26+
println!(" Default input stream format:\n {:?}", fmt);
27+
}
28+
let mut input_formats = match device.supported_input_formats() {
29+
Ok(f) => f.peekable(),
30+
Err(e) => {
31+
println!("Error: {:?}", e);
32+
continue;
33+
},
34+
};
35+
if input_formats.peek().is_some() {
36+
println!(" All supported input stream formats:");
37+
for (format_index, format) in input_formats.enumerate() {
38+
println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format);
39+
}
3040
}
31-
}
3241

33-
// Output formats
34-
if let Ok(fmt) = device.default_output_format() {
35-
println!(" Default output stream format:\n {:?}", fmt);
36-
}
37-
let mut output_formats = match device.supported_output_formats() {
38-
Ok(f) => f.peekable(),
39-
Err(e) => {
40-
println!("Error: {:?}", e);
41-
continue;
42-
},
43-
};
44-
if output_formats.peek().is_some() {
45-
println!(" All supported output stream formats:");
46-
for (format_index, format) in output_formats.enumerate() {
47-
println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format);
42+
// Output formats
43+
if let Ok(fmt) = device.default_output_format() {
44+
println!(" Default output stream format:\n {:?}", fmt);
45+
}
46+
let mut output_formats = match device.supported_output_formats() {
47+
Ok(f) => f.peekable(),
48+
Err(e) => {
49+
println!("Error: {:?}", e);
50+
continue;
51+
},
52+
};
53+
if output_formats.peek().is_some() {
54+
println!(" All supported output stream formats:");
55+
for (format_index, format) in output_formats.enumerate() {
56+
println!(" {}.{}. {:?}", device_index + 1, format_index + 1, format);
57+
}
4858
}
4959
}
5060
}

examples/feedback.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
extern crate cpal;
1010
extern crate failure;
1111

12+
use cpal::{Device, EventLoop, Host};
13+
1214
const LATENCY_MS: f32 = 150.0;
1315

1416
fn main() -> Result<(), failure::Error> {
15-
let event_loop = cpal::EventLoop::new();
17+
let host = cpal::default_host();
18+
let event_loop = host.event_loop();
1619

1720
// Default devices.
18-
let input_device = cpal::default_input_device().expect("failed to get default input device");
19-
let output_device = cpal::default_output_device().expect("failed to get default output device");
21+
let input_device = host.default_input_device().expect("failed to get default input device");
22+
let output_device = host.default_output_device().expect("failed to get default output device");
2023
println!("Using default input device: \"{}\"", input_device.name()?);
2124
println!("Using default output device: \"{}\"", output_device.name()?);
2225

examples/record_wav.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@ extern crate cpal;
66
extern crate failure;
77
extern crate hound;
88

9+
use cpal::{Device, EventLoop, Host};
10+
911
fn main() -> Result<(), failure::Error> {
12+
// Use the default host for working with audio devices.
13+
let host = cpal::default_host();
14+
1015
// Setup the default input device and stream with the default input format.
11-
let device = cpal::default_input_device().expect("Failed to get default input device");
16+
let device = host.default_input_device().expect("Failed to get default input device");
1217
println!("Default input device: {}", device.name()?);
1318
let format = device.default_input_format().expect("Failed to get default input format");
1419
println!("Default input format: {:?}", format);
15-
let event_loop = cpal::EventLoop::new();
20+
let event_loop = host.event_loop();
1621
let stream_id = event_loop.build_input_stream(&device, &format)?;
1722
event_loop.play_stream(stream_id)?;
1823

File renamed without changes.

src/alsa/mod.rs src/host/alsa/mod.rs

+121-13
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ use ChannelCount;
77
use BackendSpecificError;
88
use BuildStreamError;
99
use DefaultFormatError;
10+
use Device as DeviceTrait;
1011
use DeviceNameError;
12+
use DevicesError;
13+
use EventLoop as EventLoopTrait;
1114
use Format;
15+
use Host as HostTrait;
1216
use PauseStreamError;
1317
use PlayStreamError;
14-
use SupportedFormatsError;
1518
use SampleFormat;
1619
use SampleRate;
20+
use SupportedFormatsError;
1721
use StreamData;
1822
use StreamError;
1923
use StreamEvent;
24+
use StreamId as StreamIdTrait;
2025
use SupportedFormat;
2126
use UnknownTypeInputBuffer;
2227
use UnknownTypeOutputBuffer;
@@ -32,6 +37,109 @@ pub type SupportedOutputFormats = VecIntoIter<SupportedFormat>;
3237

3338
mod enumerate;
3439

40+
/// The default linux and freebsd host type.
41+
#[derive(Debug)]
42+
pub struct Host;
43+
44+
impl Host {
45+
pub fn new() -> Result<Self, crate::HostUnavailable> {
46+
Ok(Host)
47+
}
48+
}
49+
50+
impl HostTrait for Host {
51+
type Devices = Devices;
52+
type Device = Device;
53+
type EventLoop = EventLoop;
54+
55+
fn is_available() -> bool {
56+
// Assume ALSA is always available on linux/freebsd.
57+
true
58+
}
59+
60+
fn devices(&self) -> Result<Self::Devices, DevicesError> {
61+
Devices::new()
62+
}
63+
64+
fn default_input_device(&self) -> Option<Self::Device> {
65+
default_input_device()
66+
}
67+
68+
fn default_output_device(&self) -> Option<Self::Device> {
69+
default_output_device()
70+
}
71+
72+
fn event_loop(&self) -> Self::EventLoop {
73+
EventLoop::new()
74+
}
75+
}
76+
77+
impl DeviceTrait for Device {
78+
type SupportedInputFormats = SupportedInputFormats;
79+
type SupportedOutputFormats = SupportedOutputFormats;
80+
81+
fn name(&self) -> Result<String, DeviceNameError> {
82+
Device::name(self)
83+
}
84+
85+
fn supported_input_formats(&self) -> Result<Self::SupportedInputFormats, SupportedFormatsError> {
86+
Device::supported_input_formats(self)
87+
}
88+
89+
fn supported_output_formats(&self) -> Result<Self::SupportedOutputFormats, SupportedFormatsError> {
90+
Device::supported_output_formats(self)
91+
}
92+
93+
fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
94+
Device::default_input_format(self)
95+
}
96+
97+
fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
98+
Device::default_output_format(self)
99+
}
100+
}
101+
102+
impl EventLoopTrait for EventLoop {
103+
type Device = Device;
104+
type StreamId = StreamId;
105+
106+
fn build_input_stream(
107+
&self,
108+
device: &Self::Device,
109+
format: &Format,
110+
) -> Result<Self::StreamId, BuildStreamError> {
111+
EventLoop::build_input_stream(self, device, format)
112+
}
113+
114+
fn build_output_stream(
115+
&self,
116+
device: &Self::Device,
117+
format: &Format,
118+
) -> Result<Self::StreamId, BuildStreamError> {
119+
EventLoop::build_output_stream(self, device, format)
120+
}
121+
122+
fn play_stream(&self, stream: Self::StreamId) -> Result<(), PlayStreamError> {
123+
EventLoop::play_stream(self, stream)
124+
}
125+
126+
fn pause_stream(&self, stream: Self::StreamId) -> Result<(), PauseStreamError> {
127+
EventLoop::pause_stream(self, stream)
128+
}
129+
130+
fn destroy_stream(&self, stream: Self::StreamId) {
131+
EventLoop::destroy_stream(self, stream)
132+
}
133+
134+
fn run<F>(&self, callback: F) -> !
135+
where
136+
F: FnMut(Self::StreamId, StreamEvent) + Send,
137+
{
138+
EventLoop::run(self, callback)
139+
}
140+
}
141+
142+
impl StreamIdTrait for StreamId {}
35143

36144
struct Trigger {
37145
// [read fd, write fd]
@@ -79,7 +187,7 @@ pub struct Device(String);
79187

80188
impl Device {
81189
#[inline]
82-
pub fn name(&self) -> Result<String, DeviceNameError> {
190+
fn name(&self) -> Result<String, DeviceNameError> {
83191
Ok(self.0.clone())
84192
}
85193

@@ -287,13 +395,13 @@ impl Device {
287395
Ok(output.into_iter())
288396
}
289397

290-
pub fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
398+
fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
291399
unsafe {
292400
self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE)
293401
}
294402
}
295403

296-
pub fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
404+
fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
297405
unsafe {
298406
self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK)
299407
}
@@ -340,11 +448,11 @@ impl Device {
340448
}
341449
}
342450

343-
pub fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
451+
fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
344452
self.default_format(alsa::SND_PCM_STREAM_CAPTURE)
345453
}
346454

347-
pub fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
455+
fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
348456
self.default_format(alsa::SND_PCM_STREAM_PLAYBACK)
349457
}
350458
}
@@ -434,7 +542,7 @@ enum StreamType { Input, Output }
434542

435543
impl EventLoop {
436544
#[inline]
437-
pub fn new() -> EventLoop {
545+
fn new() -> EventLoop {
438546
let pending_command_trigger = Trigger::new();
439547

440548
let mut initial_descriptors = vec![];
@@ -460,7 +568,7 @@ impl EventLoop {
460568
}
461569

462570
#[inline]
463-
pub fn run<F>(&self, mut callback: F) -> !
571+
fn run<F>(&self, mut callback: F) -> !
464572
where F: FnMut(StreamId, StreamEvent)
465573
{
466574
self.run_inner(&mut callback)
@@ -648,7 +756,7 @@ impl EventLoop {
648756
panic!("`cpal::EventLoop::run` API currently disallows returning");
649757
}
650758

651-
pub fn build_input_stream(
759+
fn build_input_stream(
652760
&self,
653761
device: &Device,
654762
format: &Format,
@@ -727,7 +835,7 @@ impl EventLoop {
727835
}
728836
}
729837

730-
pub fn build_output_stream(
838+
fn build_output_stream(
731839
&self,
732840
device: &Device,
733841
format: &Format,
@@ -808,18 +916,18 @@ impl EventLoop {
808916
}
809917

810918
#[inline]
811-
pub fn destroy_stream(&self, stream_id: StreamId) {
919+
fn destroy_stream(&self, stream_id: StreamId) {
812920
self.push_command(Command::DestroyStream(stream_id));
813921
}
814922

815923
#[inline]
816-
pub fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
924+
fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
817925
self.push_command(Command::PlayStream(stream_id));
818926
Ok(())
819927
}
820928

821929
#[inline]
822-
pub fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
930+
fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
823931
self.push_command(Command::PauseStream(stream_id));
824932
Ok(())
825933
}
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/host/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
2+
pub(crate) mod alsa;
3+
#[cfg(any(target_os = "macos", target_os = "ios"))]
4+
mod coreaudio;
5+
//mod dynamic;
6+
#[cfg(target_os = "emscripten")]
7+
mod emscripten;
8+
mod null;
9+
#[cfg(windows)]
10+
mod wasapi;

0 commit comments

Comments
 (0)