2015-03-26 09:03:13 +00:00
|
|
|
extern crate alsa_sys as alsa;
|
2014-12-16 15:45:45 +00:00
|
|
|
extern crate libc;
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
pub use self::enumerate::{Devices, default_input_device, default_output_device};
|
2015-09-01 15:15:49 +00:00
|
|
|
|
2018-02-04 12:02:16 +00:00
|
|
|
use ChannelCount;
|
2019-06-20 22:22:30 +00:00
|
|
|
use BackendSpecificError;
|
2019-06-20 19:31:15 +00:00
|
|
|
use BuildStreamError;
|
2018-02-12 13:10:24 +00:00
|
|
|
use DefaultFormatError;
|
2019-06-20 22:53:11 +00:00
|
|
|
use DeviceNameError;
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
use DevicesError;
|
2015-09-01 15:15:49 +00:00
|
|
|
use Format;
|
2019-06-21 01:03:03 +00:00
|
|
|
use PauseStreamError;
|
|
|
|
use PlayStreamError;
|
2015-09-01 15:15:49 +00:00
|
|
|
use SampleFormat;
|
2018-02-04 12:02:16 +00:00
|
|
|
use SampleRate;
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
use SupportedFormatsError;
|
2018-02-12 13:10:24 +00:00
|
|
|
use StreamData;
|
2019-06-24 18:43:27 +00:00
|
|
|
use StreamDataResult;
|
2019-06-21 22:06:55 +00:00
|
|
|
use StreamError;
|
2017-10-20 19:18:40 +00:00
|
|
|
use SupportedFormat;
|
2018-02-12 13:10:24 +00:00
|
|
|
use UnknownTypeInputBuffer;
|
|
|
|
use UnknownTypeOutputBuffer;
|
2019-06-28 21:42:07 +00:00
|
|
|
use traits::{DeviceTrait, EventLoopTrait, HostTrait, StreamIdTrait};
|
2015-09-01 15:15:49 +00:00
|
|
|
|
2019-08-10 13:08:21 +00:00
|
|
|
use std::{cmp, ffi, ptr};
|
2017-11-02 09:30:15 +00:00
|
|
|
use std::sync::Mutex;
|
2019-06-13 06:31:37 +00:00
|
|
|
use std::sync::mpsc::{channel, Sender, Receiver};
|
2017-11-02 09:30:15 +00:00
|
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
2017-10-11 11:24:49 +00:00
|
|
|
use std::vec::IntoIter as VecIntoIter;
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
pub type SupportedInputFormats = VecIntoIter<SupportedFormat>;
|
|
|
|
pub type SupportedOutputFormats = VecIntoIter<SupportedFormat>;
|
2015-09-01 15:15:49 +00:00
|
|
|
|
|
|
|
mod enumerate;
|
|
|
|
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
/// 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 {}
|
2017-07-13 11:58:01 +00:00
|
|
|
|
|
|
|
struct Trigger {
|
|
|
|
// [read fd, write fd]
|
|
|
|
fds: [libc::c_int; 2],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Trigger {
|
|
|
|
fn new() -> Self {
|
2017-10-11 11:24:49 +00:00
|
|
|
let mut fds = [0, 0];
|
2017-07-13 11:58:01 +00:00
|
|
|
match unsafe { libc::pipe(fds.as_mut_ptr()) } {
|
|
|
|
0 => Trigger { fds: fds },
|
2017-10-11 11:24:49 +00:00
|
|
|
_ => panic!("Could not create pipe"),
|
2017-07-13 11:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
fn read_fd(&self) -> libc::c_int {
|
|
|
|
self.fds[0]
|
|
|
|
}
|
|
|
|
fn write_fd(&self) -> libc::c_int {
|
|
|
|
self.fds[1]
|
|
|
|
}
|
|
|
|
fn wakeup(&self) {
|
|
|
|
let buf = 1u64;
|
|
|
|
let ret = unsafe { libc::write(self.write_fd(), &buf as *const u64 as *const _, 8) };
|
|
|
|
assert!(ret == 8);
|
|
|
|
}
|
|
|
|
fn clear_pipe(&self) {
|
|
|
|
let mut out = 0u64;
|
|
|
|
let ret = unsafe { libc::read(self.read_fd(), &mut out as *mut u64 as *mut _, 8) };
|
|
|
|
assert_eq!(ret, 8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Trigger {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
libc::close(self.fds[0]);
|
|
|
|
libc::close(self.fds[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-09-01 15:15:49 +00:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
2018-02-12 13:10:24 +00:00
|
|
|
pub struct Device(String);
|
2015-09-01 15:15:49 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
impl Device {
|
|
|
|
#[inline]
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn name(&self) -> Result<String, DeviceNameError> {
|
2019-06-20 22:53:11 +00:00
|
|
|
Ok(self.0.clone())
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
2015-09-22 13:46:56 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
unsafe fn supported_formats(
|
|
|
|
&self,
|
|
|
|
stream_t: alsa::snd_pcm_stream_t,
|
2019-06-20 19:16:39 +00:00
|
|
|
) -> Result<VecIntoIter<SupportedFormat>, SupportedFormatsError>
|
2018-02-12 13:10:24 +00:00
|
|
|
{
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut handle = ptr::null_mut();
|
2019-06-20 22:22:30 +00:00
|
|
|
let device_name = match ffi::CString::new(&self.0[..]) {
|
|
|
|
Ok(name) => name,
|
|
|
|
Err(err) => {
|
|
|
|
let description = format!("failed to retrieve device name: {}", err);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
|
|
|
};
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
match alsa::snd_pcm_open(
|
|
|
|
&mut handle,
|
|
|
|
device_name.as_ptr() as *const _,
|
|
|
|
stream_t,
|
|
|
|
alsa::SND_PCM_NONBLOCK,
|
|
|
|
) {
|
|
|
|
-2 |
|
2019-06-20 19:16:39 +00:00
|
|
|
-16 /* determined empirically */ => return Err(SupportedFormatsError::DeviceNotAvailable),
|
|
|
|
-22 => return Err(SupportedFormatsError::InvalidArgument),
|
2019-06-20 22:22:30 +00:00
|
|
|
e => if let Err(description) = check_errors(e) {
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into())
|
2018-11-06 20:01:03 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let hw_params = HwParams::alloc();
|
|
|
|
match check_errors(alsa::snd_pcm_hw_params_any(handle, hw_params.0)) {
|
2019-06-20 22:22:30 +00:00
|
|
|
Err(description) => {
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
Ok(_) => (),
|
|
|
|
};
|
|
|
|
|
|
|
|
// TODO: check endianess
|
|
|
|
const FORMATS: [(SampleFormat, alsa::snd_pcm_format_t); 3] =
|
|
|
|
[
|
|
|
|
//SND_PCM_FORMAT_S8,
|
|
|
|
//SND_PCM_FORMAT_U8,
|
|
|
|
(SampleFormat::I16, alsa::SND_PCM_FORMAT_S16_LE),
|
|
|
|
//SND_PCM_FORMAT_S16_BE,
|
|
|
|
(SampleFormat::U16, alsa::SND_PCM_FORMAT_U16_LE),
|
|
|
|
//SND_PCM_FORMAT_U16_BE,
|
|
|
|
/*SND_PCM_FORMAT_S24_LE,
|
|
|
|
SND_PCM_FORMAT_S24_BE,
|
|
|
|
SND_PCM_FORMAT_U24_LE,
|
|
|
|
SND_PCM_FORMAT_U24_BE,
|
|
|
|
SND_PCM_FORMAT_S32_LE,
|
|
|
|
SND_PCM_FORMAT_S32_BE,
|
|
|
|
SND_PCM_FORMAT_U32_LE,
|
|
|
|
SND_PCM_FORMAT_U32_BE,*/
|
|
|
|
(SampleFormat::F32, alsa::SND_PCM_FORMAT_FLOAT_LE) /*SND_PCM_FORMAT_FLOAT_BE,
|
|
|
|
SND_PCM_FORMAT_FLOAT64_LE,
|
|
|
|
SND_PCM_FORMAT_FLOAT64_BE,
|
|
|
|
SND_PCM_FORMAT_IEC958_SUBFRAME_LE,
|
|
|
|
SND_PCM_FORMAT_IEC958_SUBFRAME_BE,
|
|
|
|
SND_PCM_FORMAT_MU_LAW,
|
|
|
|
SND_PCM_FORMAT_A_LAW,
|
|
|
|
SND_PCM_FORMAT_IMA_ADPCM,
|
|
|
|
SND_PCM_FORMAT_MPEG,
|
|
|
|
SND_PCM_FORMAT_GSM,
|
|
|
|
SND_PCM_FORMAT_SPECIAL,
|
|
|
|
SND_PCM_FORMAT_S24_3LE,
|
|
|
|
SND_PCM_FORMAT_S24_3BE,
|
|
|
|
SND_PCM_FORMAT_U24_3LE,
|
|
|
|
SND_PCM_FORMAT_U24_3BE,
|
|
|
|
SND_PCM_FORMAT_S20_3LE,
|
|
|
|
SND_PCM_FORMAT_S20_3BE,
|
|
|
|
SND_PCM_FORMAT_U20_3LE,
|
|
|
|
SND_PCM_FORMAT_U20_3BE,
|
|
|
|
SND_PCM_FORMAT_S18_3LE,
|
|
|
|
SND_PCM_FORMAT_S18_3BE,
|
|
|
|
SND_PCM_FORMAT_U18_3LE,
|
|
|
|
SND_PCM_FORMAT_U18_3BE,*/,
|
|
|
|
];
|
|
|
|
|
|
|
|
let mut supported_formats = Vec::new();
|
|
|
|
for &(sample_format, alsa_format) in FORMATS.iter() {
|
|
|
|
if alsa::snd_pcm_hw_params_test_format(handle,
|
|
|
|
hw_params.0,
|
|
|
|
alsa_format) == 0
|
2016-01-28 20:27:09 +00:00
|
|
|
{
|
2018-02-12 13:10:24 +00:00
|
|
|
supported_formats.push(sample_format);
|
2015-09-22 13:46:56 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut min_rate = 0;
|
2019-06-20 22:22:30 +00:00
|
|
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_min(
|
|
|
|
hw_params.0,
|
|
|
|
&mut min_rate,
|
|
|
|
ptr::null_mut(),
|
|
|
|
)) {
|
|
|
|
let description = format!("unable to get minimum supported rate: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
|
|
|
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut max_rate = 0;
|
2019-06-20 22:22:30 +00:00
|
|
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_max(
|
|
|
|
hw_params.0,
|
|
|
|
&mut max_rate,
|
|
|
|
ptr::null_mut(),
|
|
|
|
)) {
|
|
|
|
let description = format!("unable to get maximum supported rate: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let sample_rates = if min_rate == max_rate {
|
|
|
|
vec![(min_rate, max_rate)]
|
|
|
|
} else if alsa::snd_pcm_hw_params_test_rate(handle,
|
|
|
|
hw_params.0,
|
|
|
|
min_rate + 1,
|
|
|
|
0) == 0
|
|
|
|
{
|
|
|
|
vec![(min_rate, max_rate)]
|
|
|
|
} else {
|
|
|
|
const RATES: [libc::c_uint; 13] = [
|
|
|
|
5512,
|
|
|
|
8000,
|
|
|
|
11025,
|
|
|
|
16000,
|
|
|
|
22050,
|
|
|
|
32000,
|
|
|
|
44100,
|
|
|
|
48000,
|
|
|
|
64000,
|
|
|
|
88200,
|
|
|
|
96000,
|
|
|
|
176400,
|
|
|
|
192000,
|
|
|
|
];
|
|
|
|
|
|
|
|
let mut rates = Vec::new();
|
|
|
|
for &rate in RATES.iter() {
|
|
|
|
if alsa::snd_pcm_hw_params_test_rate(handle,
|
|
|
|
hw_params.0,
|
|
|
|
rate,
|
|
|
|
0) == 0
|
2017-10-11 11:24:49 +00:00
|
|
|
{
|
2018-02-12 13:10:24 +00:00
|
|
|
rates.push((rate, rate));
|
2015-09-10 17:48:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
if rates.len() == 0 {
|
2017-10-20 19:18:40 +00:00
|
|
|
vec![(min_rate, max_rate)]
|
2018-02-12 13:10:24 +00:00
|
|
|
} else {
|
|
|
|
rates
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut min_channels = 0;
|
2019-06-20 22:22:30 +00:00
|
|
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_min(hw_params.0, &mut min_channels)) {
|
|
|
|
let description = format!("unable to get minimum supported channel count: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
|
|
|
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut max_channels = 0;
|
2019-06-20 22:22:30 +00:00
|
|
|
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_max(hw_params.0, &mut max_channels)) {
|
|
|
|
let description = format!("unable to get maximum supported channel count: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
let max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned
|
|
|
|
let supported_channels = (min_channels .. max_channels + 1)
|
|
|
|
.filter_map(|num| if alsa::snd_pcm_hw_params_test_channels(
|
|
|
|
handle,
|
|
|
|
hw_params.0,
|
|
|
|
num,
|
|
|
|
) == 0
|
2017-10-23 14:41:38 +00:00
|
|
|
{
|
2018-02-12 13:10:24 +00:00
|
|
|
Some(num as ChannelCount)
|
2015-09-10 17:48:39 +00:00
|
|
|
} else {
|
2018-02-12 13:10:24 +00:00
|
|
|
None
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
|
|
|
let mut output = Vec::with_capacity(supported_formats.len() * supported_channels.len() *
|
|
|
|
sample_rates.len());
|
|
|
|
for &data_type in supported_formats.iter() {
|
|
|
|
for channels in supported_channels.iter() {
|
|
|
|
for &(min_rate, max_rate) in sample_rates.iter() {
|
|
|
|
output.push(SupportedFormat {
|
|
|
|
channels: channels.clone(),
|
|
|
|
min_sample_rate: SampleRate(min_rate as u32),
|
|
|
|
max_sample_rate: SampleRate(max_rate as u32),
|
|
|
|
data_type: data_type,
|
|
|
|
});
|
2015-09-10 17:48:39 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
// TODO: RAII
|
|
|
|
alsa::snd_pcm_close(handle);
|
|
|
|
Ok(output.into_iter())
|
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn supported_input_formats(&self) -> Result<SupportedInputFormats, SupportedFormatsError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
unsafe {
|
|
|
|
self.supported_formats(alsa::SND_PCM_STREAM_CAPTURE)
|
|
|
|
}
|
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn supported_output_formats(&self) -> Result<SupportedOutputFormats, SupportedFormatsError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
unsafe {
|
|
|
|
self.supported_formats(alsa::SND_PCM_STREAM_PLAYBACK)
|
2015-09-10 17:48:39 +00:00
|
|
|
}
|
2015-09-01 15:15:49 +00:00
|
|
|
}
|
2015-09-22 12:46:27 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
// ALSA does not offer default stream formats, so instead we compare all supported formats by
|
|
|
|
// the `SupportedFormat::cmp_default_heuristics` order and select the greatest.
|
|
|
|
fn default_format(
|
|
|
|
&self,
|
|
|
|
stream_t: alsa::snd_pcm_stream_t,
|
|
|
|
) -> Result<Format, DefaultFormatError>
|
|
|
|
{
|
|
|
|
let mut formats: Vec<_> = unsafe {
|
|
|
|
match self.supported_formats(stream_t) {
|
2019-06-20 19:16:39 +00:00
|
|
|
Err(SupportedFormatsError::DeviceNotAvailable) => {
|
2018-02-12 13:10:24 +00:00
|
|
|
return Err(DefaultFormatError::DeviceNotAvailable);
|
|
|
|
},
|
2019-06-20 19:16:39 +00:00
|
|
|
Err(SupportedFormatsError::InvalidArgument) => {
|
2018-11-06 20:01:03 +00:00
|
|
|
// this happens sometimes when querying for input and output capabilities but
|
|
|
|
// the device supports only one
|
|
|
|
return Err(DefaultFormatError::StreamTypeNotSupported);
|
|
|
|
}
|
2019-06-20 22:22:30 +00:00
|
|
|
Err(SupportedFormatsError::BackendSpecific { err }) => {
|
2019-06-20 23:34:07 +00:00
|
|
|
return Err(err.into());
|
2018-11-06 20:01:03 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
Ok(fmts) => fmts.collect(),
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
formats.sort_by(|a, b| a.cmp_default_heuristics(b));
|
|
|
|
|
|
|
|
match formats.into_iter().last() {
|
|
|
|
Some(f) => {
|
|
|
|
let min_r = f.min_sample_rate;
|
|
|
|
let max_r = f.max_sample_rate;
|
|
|
|
let mut format = f.with_max_sample_rate();
|
|
|
|
const HZ_44100: SampleRate = SampleRate(44_100);
|
|
|
|
if min_r <= HZ_44100 && HZ_44100 <= max_r {
|
|
|
|
format.sample_rate = HZ_44100;
|
|
|
|
}
|
|
|
|
Ok(format)
|
|
|
|
},
|
|
|
|
None => Err(DefaultFormatError::StreamTypeNotSupported)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn default_input_format(&self) -> Result<Format, DefaultFormatError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
self.default_format(alsa::SND_PCM_STREAM_CAPTURE)
|
|
|
|
}
|
|
|
|
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn default_output_format(&self) -> Result<Format, DefaultFormatError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
self.default_format(alsa::SND_PCM_STREAM_PLAYBACK)
|
2015-09-22 12:46:27 +00:00
|
|
|
}
|
2015-09-01 15:15:49 +00:00
|
|
|
}
|
|
|
|
|
2016-08-02 20:28:37 +00:00
|
|
|
pub struct EventLoop {
|
2018-02-12 13:10:24 +00:00
|
|
|
// Each newly-created stream gets a new ID from this counter. The counter is then incremented.
|
|
|
|
next_stream_id: AtomicUsize, // TODO: use AtomicU64 when stable?
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
// A trigger that uses a `pipe()` as backend. Signalled whenever a new command is ready, so
|
|
|
|
// that `poll()` can wake up and pick the changes.
|
2019-06-21 22:06:55 +00:00
|
|
|
pending_command_trigger: Trigger,
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
// This field is locked by the `run()` method.
|
|
|
|
// The mutex also ensures that only one thread at a time has `run()` running.
|
|
|
|
run_context: Mutex<RunContext>,
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
// Commands processed by the `run()` method that is currently running.
|
2019-06-13 06:31:37 +00:00
|
|
|
commands: Sender<Command>,
|
2017-10-11 11:24:49 +00:00
|
|
|
}
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
unsafe impl Send for EventLoop {
|
2016-08-02 20:28:37 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
unsafe impl Sync for EventLoop {
|
2016-08-02 20:28:37 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
enum Command {
|
2018-02-12 13:10:24 +00:00
|
|
|
NewStream(StreamInner),
|
|
|
|
PlayStream(StreamId),
|
|
|
|
PauseStream(StreamId),
|
|
|
|
DestroyStream(StreamId),
|
2016-10-02 11:18:27 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
struct RunContext {
|
2019-06-21 22:06:55 +00:00
|
|
|
// Descriptors to wait for. Always contains `pending_command_trigger.read_fd()` as first element.
|
2017-10-18 18:24:05 +00:00
|
|
|
descriptors: Vec<libc::pollfd>,
|
2018-02-12 13:10:24 +00:00
|
|
|
// List of streams that are written in `descriptors`.
|
|
|
|
streams: Vec<StreamInner>,
|
2019-06-13 06:31:37 +00:00
|
|
|
|
|
|
|
commands: Receiver<Command>,
|
2016-10-02 11:18:27 +00:00
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
struct StreamInner {
|
|
|
|
// The id of the stream.
|
|
|
|
id: StreamId,
|
2016-08-02 20:28:37 +00:00
|
|
|
|
|
|
|
// The ALSA channel.
|
2017-10-18 18:24:05 +00:00
|
|
|
channel: *mut alsa::snd_pcm_t,
|
2016-08-02 20:28:37 +00:00
|
|
|
|
|
|
|
// When converting between file descriptors and `snd_pcm_t`, this is the number of
|
|
|
|
// file descriptors that this `snd_pcm_t` uses.
|
|
|
|
num_descriptors: usize,
|
|
|
|
|
|
|
|
// Format of the samples.
|
|
|
|
sample_format: SampleFormat,
|
|
|
|
|
|
|
|
// Number of channels, ie. number of samples per frame.
|
2014-12-16 15:45:45 +00:00
|
|
|
num_channels: u16,
|
2016-08-02 20:28:37 +00:00
|
|
|
|
|
|
|
// Number of samples that can fit in the buffer.
|
2016-09-30 16:18:28 +00:00
|
|
|
buffer_len: usize,
|
2016-08-02 20:28:37 +00:00
|
|
|
|
|
|
|
// Minimum number of samples to put in the buffer.
|
|
|
|
period_len: usize,
|
|
|
|
|
2017-11-02 09:30:15 +00:00
|
|
|
// Whether or not the hardware supports pausing the stream.
|
|
|
|
can_pause: bool,
|
|
|
|
|
|
|
|
// Whether or not the sample stream is currently paused.
|
|
|
|
is_paused: bool,
|
2016-10-02 11:18:27 +00:00
|
|
|
|
|
|
|
// A file descriptor opened with `eventfd`.
|
|
|
|
// It is used to wait for resume signal.
|
2017-07-13 11:58:01 +00:00
|
|
|
resume_trigger: Trigger,
|
2019-04-30 06:43:47 +00:00
|
|
|
|
|
|
|
// Lazily allocated buffer that is reused inside the loop.
|
|
|
|
// Zero-allocate a new buffer (the fastest way to have zeroed memory) at the first time this is
|
|
|
|
// used.
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: Vec<u8>,
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
|
2018-02-12 13:10:24 +00:00
|
|
|
pub struct StreamId(usize);
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
enum StreamType { Input, Output }
|
|
|
|
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
impl EventLoop {
|
2016-09-30 16:18:28 +00:00
|
|
|
#[inline]
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn new() -> EventLoop {
|
2019-06-21 22:06:55 +00:00
|
|
|
let pending_command_trigger = Trigger::new();
|
2016-09-30 16:18:28 +00:00
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
let mut initial_descriptors = vec![];
|
|
|
|
reset_descriptors_with_pending_command_trigger(
|
|
|
|
&mut initial_descriptors,
|
|
|
|
&pending_command_trigger,
|
|
|
|
);
|
2017-11-01 09:15:17 +00:00
|
|
|
|
2019-06-13 06:31:37 +00:00
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
let run_context = Mutex::new(RunContext {
|
2017-11-01 09:15:17 +00:00
|
|
|
descriptors: initial_descriptors,
|
2018-02-12 13:10:24 +00:00
|
|
|
streams: Vec::new(),
|
2019-06-13 06:31:37 +00:00
|
|
|
commands: rx,
|
2017-10-23 14:41:38 +00:00
|
|
|
});
|
2016-09-30 16:18:28 +00:00
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
EventLoop {
|
2018-02-12 13:10:24 +00:00
|
|
|
next_stream_id: AtomicUsize::new(0),
|
2019-06-21 22:06:55 +00:00
|
|
|
pending_command_trigger: pending_command_trigger,
|
2017-10-18 18:24:05 +00:00
|
|
|
run_context,
|
2019-06-13 06:31:37 +00:00
|
|
|
commands: tx,
|
2016-09-30 16:18:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
#[inline]
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn run<F>(&self, mut callback: F) -> !
|
2019-06-24 18:43:27 +00:00
|
|
|
where F: FnMut(StreamId, StreamDataResult)
|
2017-10-18 18:24:05 +00:00
|
|
|
{
|
|
|
|
self.run_inner(&mut callback)
|
|
|
|
}
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2019-06-24 18:43:27 +00:00
|
|
|
fn run_inner(&self, callback: &mut dyn FnMut(StreamId, StreamDataResult)) -> ! {
|
2017-10-18 18:24:05 +00:00
|
|
|
unsafe {
|
|
|
|
let mut run_context = self.run_context.lock().unwrap();
|
|
|
|
let run_context = &mut *run_context;
|
2016-10-02 11:18:27 +00:00
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
'stream_loop: loop {
|
2019-06-23 17:04:24 +00:00
|
|
|
process_commands(run_context);
|
2019-06-21 22:06:55 +00:00
|
|
|
|
|
|
|
reset_descriptors_with_pending_command_trigger(
|
|
|
|
&mut run_context.descriptors,
|
|
|
|
&self.pending_command_trigger,
|
|
|
|
);
|
|
|
|
append_stream_poll_descriptors(run_context);
|
|
|
|
|
|
|
|
// At this point, this should include the command `pending_commands_trigger` along
|
|
|
|
// with the poll descriptors for each stream.
|
|
|
|
match poll_all_descriptors(&mut run_context.descriptors) {
|
|
|
|
Ok(true) => (),
|
|
|
|
Ok(false) => continue,
|
|
|
|
Err(err) => {
|
|
|
|
for stream in run_context.streams.iter() {
|
2019-06-24 18:43:27 +00:00
|
|
|
let result = Err(err.clone().into());
|
|
|
|
callback(stream.id, result);
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2019-06-21 22:06:55 +00:00
|
|
|
run_context.streams.clear();
|
|
|
|
break 'stream_loop;
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
// If the `pending_command_trigger` was signaled, we need to process the comands.
|
2017-10-18 18:24:05 +00:00
|
|
|
if run_context.descriptors[0].revents != 0 {
|
|
|
|
run_context.descriptors[0].revents = 0;
|
2019-06-21 22:06:55 +00:00
|
|
|
self.pending_command_trigger.clear_pipe();
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
// The set of streams that error within the following loop and should be removed.
|
|
|
|
let mut streams_to_remove: Vec<(StreamId, StreamError)> = vec![];
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
// Iterate over each individual stream/descriptor.
|
|
|
|
let mut i_stream = 0;
|
2017-10-18 18:24:05 +00:00
|
|
|
let mut i_descriptor = 1;
|
|
|
|
while (i_descriptor as usize) < run_context.descriptors.len() {
|
2019-06-21 22:06:55 +00:00
|
|
|
let stream = &mut run_context.streams[i_stream];
|
|
|
|
let stream_descriptor_ptr = run_context.descriptors.as_mut_ptr().offset(i_descriptor);
|
|
|
|
|
|
|
|
// Only go on if this event was a pollout or pollin event.
|
|
|
|
let stream_type = match check_for_pollout_or_pollin(stream, stream_descriptor_ptr) {
|
|
|
|
Ok(Some(ty)) => ty,
|
|
|
|
Ok(None) => {
|
|
|
|
i_descriptor += stream.num_descriptors as isize;
|
|
|
|
i_stream += 1;
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
Err(err) => {
|
|
|
|
streams_to_remove.push((stream.id, err.into()));
|
|
|
|
i_descriptor += stream.num_descriptors as isize;
|
2018-02-12 13:10:24 +00:00
|
|
|
i_stream += 1;
|
2017-10-18 18:24:05 +00:00
|
|
|
continue;
|
|
|
|
}
|
2019-06-21 22:06:55 +00:00
|
|
|
};
|
2017-10-18 18:24:05 +00:00
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
// Get the number of available samples for reading/writing.
|
|
|
|
let available_samples = match get_available_samples(stream) {
|
|
|
|
Ok(n) => n,
|
|
|
|
Err(err) => {
|
|
|
|
streams_to_remove.push((stream.id, err.into()));
|
|
|
|
i_descriptor += stream.num_descriptors as isize;
|
|
|
|
i_stream += 1;
|
|
|
|
continue;
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
// Only go on if there is at least `stream.period_len` samples.
|
|
|
|
if available_samples < stream.period_len {
|
|
|
|
i_descriptor += stream.num_descriptors as isize;
|
2018-02-12 13:10:24 +00:00
|
|
|
i_stream += 1;
|
2017-10-18 18:24:05 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
// Prepare the data buffer.
|
|
|
|
let buffer_size = stream.sample_format.sample_size() * available_samples;
|
|
|
|
stream.buffer.resize(buffer_size, 0u8);
|
|
|
|
let available_frames = available_samples / stream.num_channels as usize;
|
2019-04-30 06:43:47 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
match stream_type {
|
|
|
|
StreamType::Input => {
|
2019-06-21 22:06:55 +00:00
|
|
|
let result = alsa::snd_pcm_readi(
|
|
|
|
stream.channel,
|
|
|
|
stream.buffer.as_mut_ptr() as *mut _,
|
2019-04-30 06:43:47 +00:00
|
|
|
available_frames as alsa::snd_pcm_uframes_t,
|
|
|
|
);
|
2019-06-21 22:06:55 +00:00
|
|
|
if let Err(err) = check_errors(result as _) {
|
|
|
|
let description = format!("`snd_pcm_readi` failed: {}", err);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
streams_to_remove.push((stream.id, err.into()));
|
|
|
|
continue;
|
|
|
|
}
|
2019-04-30 06:43:47 +00:00
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
let input_buffer = match stream.sample_format {
|
2019-04-30 06:43:47 +00:00
|
|
|
SampleFormat::I16 => UnknownTypeInputBuffer::I16(::InputBuffer {
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: cast_input_buffer(&mut stream.buffer),
|
2019-04-30 06:43:47 +00:00
|
|
|
}),
|
|
|
|
SampleFormat::U16 => UnknownTypeInputBuffer::U16(::InputBuffer {
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: cast_input_buffer(&mut stream.buffer),
|
2019-04-30 06:43:47 +00:00
|
|
|
}),
|
|
|
|
SampleFormat::F32 => UnknownTypeInputBuffer::F32(::InputBuffer {
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: cast_input_buffer(&mut stream.buffer),
|
2019-04-30 06:43:47 +00:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
let stream_data = StreamData::Input {
|
|
|
|
buffer: input_buffer,
|
2017-10-18 18:24:05 +00:00
|
|
|
};
|
2019-06-24 18:43:27 +00:00
|
|
|
callback(stream.id, Ok(stream_data));
|
2017-10-18 18:24:05 +00:00
|
|
|
},
|
2019-04-30 06:43:47 +00:00
|
|
|
StreamType::Output => {
|
|
|
|
{
|
|
|
|
// We're now sure that we're ready to write data.
|
2019-06-21 22:06:55 +00:00
|
|
|
let output_buffer = match stream.sample_format {
|
2019-04-30 06:43:47 +00:00
|
|
|
SampleFormat::I16 => UnknownTypeOutputBuffer::I16(::OutputBuffer {
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: cast_output_buffer(&mut stream.buffer),
|
2019-04-30 06:43:47 +00:00
|
|
|
}),
|
|
|
|
SampleFormat::U16 => UnknownTypeOutputBuffer::U16(::OutputBuffer {
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: cast_output_buffer(&mut stream.buffer),
|
2019-04-30 06:43:47 +00:00
|
|
|
}),
|
|
|
|
SampleFormat::F32 => UnknownTypeOutputBuffer::F32(::OutputBuffer {
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: cast_output_buffer(&mut stream.buffer),
|
2019-04-30 06:43:47 +00:00
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
let stream_data = StreamData::Output {
|
|
|
|
buffer: output_buffer,
|
|
|
|
};
|
2019-06-24 18:43:27 +00:00
|
|
|
callback(stream.id, Ok(stream_data));
|
2019-04-30 06:43:47 +00:00
|
|
|
}
|
|
|
|
loop {
|
|
|
|
let result = alsa::snd_pcm_writei(
|
2019-06-21 22:06:55 +00:00
|
|
|
stream.channel,
|
|
|
|
stream.buffer.as_ptr() as *const _,
|
2019-04-30 06:43:47 +00:00
|
|
|
available_frames as alsa::snd_pcm_uframes_t,
|
|
|
|
);
|
|
|
|
|
2019-07-07 03:45:41 +00:00
|
|
|
if result as i32 == -libc::EPIPE {
|
2019-04-30 06:43:47 +00:00
|
|
|
// buffer underrun
|
2019-06-21 22:06:55 +00:00
|
|
|
// TODO: Notify the user of this.
|
2019-07-07 03:45:41 +00:00
|
|
|
alsa::snd_pcm_recover(stream.channel, result as i32, 0);
|
2019-06-21 22:06:55 +00:00
|
|
|
} else if let Err(err) = check_errors(result as _) {
|
|
|
|
let description = format!("`snd_pcm_writei` failed: {}", err);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
streams_to_remove.push((stream.id, err.into()));
|
|
|
|
continue;
|
|
|
|
} else if result as usize != available_frames {
|
|
|
|
let description = format!(
|
|
|
|
"unexpected number of frames written: expected {}, \
|
|
|
|
result {} (this should never happen)",
|
|
|
|
available_frames,
|
|
|
|
result,
|
|
|
|
);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
streams_to_remove.push((stream.id, err.into()));
|
|
|
|
continue;
|
2019-04-30 06:43:47 +00:00
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2019-06-21 22:06:55 +00:00
|
|
|
|
|
|
|
// Remove any streams that have errored and notify the user.
|
|
|
|
for (stream_id, err) in streams_to_remove {
|
|
|
|
run_context.streams.retain(|s| s.id != stream_id);
|
2019-06-24 18:43:27 +00:00
|
|
|
callback(stream_id, Err(err.into()));
|
2019-06-21 22:06:55 +00:00
|
|
|
}
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
}
|
2019-06-21 22:06:55 +00:00
|
|
|
|
|
|
|
panic!("`cpal::EventLoop::run` API currently disallows returning");
|
2015-09-10 17:48:39 +00:00
|
|
|
}
|
|
|
|
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn build_input_stream(
|
2018-02-12 13:10:24 +00:00
|
|
|
&self,
|
|
|
|
device: &Device,
|
|
|
|
format: &Format,
|
2019-06-20 19:31:15 +00:00
|
|
|
) -> Result<StreamId, BuildStreamError>
|
2018-02-12 13:10:24 +00:00
|
|
|
{
|
2014-12-16 15:45:45 +00:00
|
|
|
unsafe {
|
2018-02-12 13:10:24 +00:00
|
|
|
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
|
|
|
|
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut capture_handle = ptr::null_mut();
|
2018-02-12 13:10:24 +00:00
|
|
|
match alsa::snd_pcm_open(
|
|
|
|
&mut capture_handle,
|
|
|
|
name.as_ptr(),
|
|
|
|
alsa::SND_PCM_STREAM_CAPTURE,
|
|
|
|
alsa::SND_PCM_NONBLOCK,
|
|
|
|
) {
|
2019-06-20 19:31:15 +00:00
|
|
|
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
|
|
|
|
-22 => return Err(BuildStreamError::InvalidArgument),
|
2019-06-21 00:38:59 +00:00
|
|
|
e => if let Err(description) = check_errors(e) {
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
2018-11-06 20:01:03 +00:00
|
|
|
}
|
2015-09-22 13:46:56 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
let hw_params = HwParams::alloc();
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
set_hw_params_from_format(capture_handle, &hw_params, format)
|
|
|
|
.map_err(|description| BackendSpecificError { description })?;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
|
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
let (buffer_len, period_len) = set_sw_params_from_format(capture_handle, format)
|
|
|
|
.map_err(|description| BackendSpecificError { description })?;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
if let Err(desc) = check_errors(alsa::snd_pcm_prepare(capture_handle)) {
|
|
|
|
let description = format!("could not get capture handle: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let num_descriptors = {
|
|
|
|
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(capture_handle);
|
2019-06-21 00:38:59 +00:00
|
|
|
if num_descriptors == 0 {
|
|
|
|
let description = "poll descriptor count for capture stream was 0".to_string();
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
num_descriptors as usize
|
|
|
|
};
|
|
|
|
|
|
|
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
2019-06-21 00:38:59 +00:00
|
|
|
if new_stream_id.0 == usize::max_value() {
|
2019-08-04 10:49:15 +00:00
|
|
|
panic!("number of streams used has overflowed usize");
|
2019-06-21 00:38:59 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let stream_inner = StreamInner {
|
|
|
|
id: new_stream_id.clone(),
|
|
|
|
channel: capture_handle,
|
|
|
|
sample_format: format.data_type,
|
|
|
|
num_descriptors: num_descriptors,
|
|
|
|
num_channels: format.channels as u16,
|
|
|
|
buffer_len: buffer_len,
|
|
|
|
period_len: period_len,
|
|
|
|
can_pause: can_pause,
|
|
|
|
is_paused: false,
|
|
|
|
resume_trigger: Trigger::new(),
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: vec![],
|
2015-09-22 13:20:11 +00:00
|
|
|
};
|
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
if let Err(desc) = check_errors(alsa::snd_pcm_start(capture_handle)) {
|
|
|
|
let description = format!("could not start capture stream: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
self.push_command(Command::NewStream(stream_inner));
|
|
|
|
Ok(new_stream_id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn build_output_stream(
|
2018-02-12 13:10:24 +00:00
|
|
|
&self,
|
|
|
|
device: &Device,
|
|
|
|
format: &Format,
|
2019-06-20 19:31:15 +00:00
|
|
|
) -> Result<StreamId, BuildStreamError>
|
2018-02-12 13:10:24 +00:00
|
|
|
{
|
|
|
|
unsafe {
|
|
|
|
let name = ffi::CString::new(device.0.clone()).expect("unable to clone device");
|
|
|
|
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut playback_handle = ptr::null_mut();
|
2018-02-12 13:10:24 +00:00
|
|
|
match alsa::snd_pcm_open(
|
|
|
|
&mut playback_handle,
|
|
|
|
name.as_ptr(),
|
|
|
|
alsa::SND_PCM_STREAM_PLAYBACK,
|
|
|
|
alsa::SND_PCM_NONBLOCK,
|
|
|
|
) {
|
2019-06-20 19:31:15 +00:00
|
|
|
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
|
|
|
|
-22 => return Err(BuildStreamError::InvalidArgument),
|
2019-06-21 00:38:59 +00:00
|
|
|
e => if let Err(description) = check_errors(e) {
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into())
|
2018-11-06 20:01:03 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
let hw_params = HwParams::alloc();
|
2018-02-12 13:10:24 +00:00
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
set_hw_params_from_format(playback_handle, &hw_params, format)
|
|
|
|
.map_err(|description| BackendSpecificError { description })?;
|
2017-10-11 11:24:49 +00:00
|
|
|
|
2017-11-02 09:30:15 +00:00
|
|
|
let can_pause = alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1;
|
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
let (buffer_len, period_len) = set_sw_params_from_format(playback_handle, format)
|
|
|
|
.map_err(|description| BackendSpecificError { description })?;
|
2015-09-22 15:52:35 +00:00
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
if let Err(desc) = check_errors(alsa::snd_pcm_prepare(playback_handle)) {
|
|
|
|
let description = format!("could not get playback handle: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2017-01-24 00:10:44 +00:00
|
|
|
|
2016-08-02 20:28:37 +00:00
|
|
|
let num_descriptors = {
|
|
|
|
let num_descriptors = alsa::snd_pcm_poll_descriptors_count(playback_handle);
|
2019-06-21 00:38:59 +00:00
|
|
|
if num_descriptors == 0 {
|
|
|
|
let description = "poll descriptor count for playback stream was 0".to_string();
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2016-08-02 20:28:37 +00:00
|
|
|
num_descriptors as usize
|
|
|
|
};
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
let new_stream_id = StreamId(self.next_stream_id.fetch_add(1, Ordering::Relaxed));
|
2019-06-21 00:38:59 +00:00
|
|
|
if new_stream_id.0 == usize::max_value() {
|
|
|
|
return Err(BuildStreamError::StreamIdOverflow);
|
|
|
|
}
|
2017-10-18 18:24:05 +00:00
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
let stream_inner = StreamInner {
|
|
|
|
id: new_stream_id.clone(),
|
2017-10-18 18:24:05 +00:00
|
|
|
channel: playback_handle,
|
|
|
|
sample_format: format.data_type,
|
|
|
|
num_descriptors: num_descriptors,
|
2018-02-04 09:38:06 +00:00
|
|
|
num_channels: format.channels as u16,
|
2017-10-18 18:24:05 +00:00
|
|
|
buffer_len: buffer_len,
|
|
|
|
period_len: period_len,
|
2017-11-02 09:30:15 +00:00
|
|
|
can_pause: can_pause,
|
|
|
|
is_paused: false,
|
2017-10-18 18:24:05 +00:00
|
|
|
resume_trigger: Trigger::new(),
|
2019-06-21 22:06:55 +00:00
|
|
|
buffer: vec![],
|
2017-10-18 18:24:05 +00:00
|
|
|
};
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
self.push_command(Command::NewStream(stream_inner));
|
|
|
|
Ok(new_stream_id)
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-22 13:16:47 +00:00
|
|
|
|
2015-09-11 08:55:29 +00:00
|
|
|
#[inline]
|
2017-11-02 09:30:15 +00:00
|
|
|
fn push_command(&self, command: Command) {
|
2019-06-13 06:31:37 +00:00
|
|
|
// Safe to unwrap: sender outlives receiver.
|
|
|
|
self.commands.send(command).unwrap();
|
2019-06-21 22:06:55 +00:00
|
|
|
self.pending_command_trigger.wakeup();
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn destroy_stream(&self, stream_id: StreamId) {
|
2018-02-12 13:10:24 +00:00
|
|
|
self.push_command(Command::DestroyStream(stream_id));
|
2017-11-02 09:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn play_stream(&self, stream_id: StreamId) -> Result<(), PlayStreamError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
self.push_command(Command::PlayStream(stream_id));
|
2019-06-21 01:03:03 +00:00
|
|
|
Ok(())
|
2014-12-22 13:16:47 +00:00
|
|
|
}
|
|
|
|
|
2015-09-11 08:55:29 +00:00
|
|
|
#[inline]
|
[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.
2019-06-23 13:49:48 +00:00
|
|
|
fn pause_stream(&self, stream_id: StreamId) -> Result<(), PauseStreamError> {
|
2018-02-12 13:10:24 +00:00
|
|
|
self.push_command(Command::PauseStream(stream_id));
|
2019-06-21 01:03:03 +00:00
|
|
|
Ok(())
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-21 22:06:55 +00:00
|
|
|
// Process any pending `Command`s within the `RunContext`'s queue.
|
2019-06-23 17:04:24 +00:00
|
|
|
fn process_commands(run_context: &mut RunContext) {
|
2019-06-21 22:06:55 +00:00
|
|
|
for command in run_context.commands.try_iter() {
|
|
|
|
match command {
|
|
|
|
Command::DestroyStream(stream_id) => {
|
|
|
|
run_context.streams.retain(|s| s.id != stream_id);
|
|
|
|
},
|
|
|
|
Command::PlayStream(stream_id) => {
|
|
|
|
if let Some(stream) = run_context.streams.iter_mut()
|
|
|
|
.find(|stream| stream.can_pause && stream.id == stream_id)
|
|
|
|
{
|
|
|
|
unsafe {
|
|
|
|
alsa::snd_pcm_pause(stream.channel, 0);
|
|
|
|
}
|
|
|
|
stream.is_paused = false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Command::PauseStream(stream_id) => {
|
|
|
|
if let Some(stream) = run_context.streams.iter_mut()
|
|
|
|
.find(|stream| stream.can_pause && stream.id == stream_id)
|
|
|
|
{
|
|
|
|
unsafe {
|
|
|
|
alsa::snd_pcm_pause(stream.channel, 1);
|
|
|
|
}
|
|
|
|
stream.is_paused = true;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Command::NewStream(stream_inner) => {
|
|
|
|
run_context.streams.push(stream_inner);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Resets the descriptors so that only `pending_command_trigger.read_fd()` is contained.
|
|
|
|
fn reset_descriptors_with_pending_command_trigger(
|
|
|
|
descriptors: &mut Vec<libc::pollfd>,
|
|
|
|
pending_command_trigger: &Trigger,
|
|
|
|
) {
|
|
|
|
descriptors.clear();
|
|
|
|
descriptors.push(libc::pollfd {
|
|
|
|
fd: pending_command_trigger.read_fd(),
|
|
|
|
events: libc::POLLIN,
|
|
|
|
revents: 0,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Appends the `poll` descriptors for each stream onto the `RunContext`'s descriptor slice, ready
|
|
|
|
// for a call to `libc::poll`.
|
|
|
|
fn append_stream_poll_descriptors(run_context: &mut RunContext) {
|
|
|
|
for stream in run_context.streams.iter() {
|
|
|
|
run_context.descriptors.reserve(stream.num_descriptors);
|
|
|
|
let len = run_context.descriptors.len();
|
|
|
|
let filled = unsafe {
|
|
|
|
alsa::snd_pcm_poll_descriptors(
|
|
|
|
stream.channel,
|
|
|
|
run_context.descriptors.as_mut_ptr().offset(len as isize),
|
|
|
|
stream.num_descriptors as libc::c_uint,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
debug_assert_eq!(filled, stream.num_descriptors as libc::c_int);
|
|
|
|
unsafe {
|
|
|
|
run_context.descriptors.set_len(len + stream.num_descriptors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Poll all descriptors within the given set.
|
|
|
|
//
|
|
|
|
// Returns `Ok(true)` if some event has occurred or `Ok(false)` if no events have
|
|
|
|
// occurred.
|
|
|
|
//
|
|
|
|
// Returns an `Err` if `libc::poll` returns a negative value for some reason.
|
|
|
|
fn poll_all_descriptors(descriptors: &mut [libc::pollfd]) -> Result<bool, BackendSpecificError> {
|
|
|
|
let res = unsafe {
|
|
|
|
// Don't timeout, wait forever.
|
|
|
|
libc::poll(descriptors.as_mut_ptr(), descriptors.len() as libc::nfds_t, -1)
|
|
|
|
};
|
|
|
|
if res < 0 {
|
|
|
|
let description = format!("`libc::poll()` failed: {}", res);
|
|
|
|
Err(BackendSpecificError { description })
|
|
|
|
} else if res == 0 {
|
|
|
|
Ok(false)
|
|
|
|
} else {
|
|
|
|
Ok(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether the event is `POLLOUT` or `POLLIN`.
|
|
|
|
//
|
|
|
|
// If so, return the stream type associated with the event.
|
|
|
|
//
|
|
|
|
// Otherwise, returns `Ok(None)`.
|
|
|
|
//
|
|
|
|
// Returns an `Err` if the `snd_pcm_poll_descriptors_revents` call fails.
|
|
|
|
fn check_for_pollout_or_pollin(
|
|
|
|
stream: &StreamInner,
|
|
|
|
stream_descriptor_ptr: *mut libc::pollfd,
|
|
|
|
) -> Result<Option<StreamType>, BackendSpecificError> {
|
|
|
|
let (revent, res) = unsafe {
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut revent = 0;
|
2019-06-21 22:06:55 +00:00
|
|
|
let res = alsa::snd_pcm_poll_descriptors_revents(
|
|
|
|
stream.channel,
|
|
|
|
stream_descriptor_ptr,
|
|
|
|
stream.num_descriptors as libc::c_uint,
|
|
|
|
&mut revent,
|
|
|
|
);
|
|
|
|
(revent, res)
|
|
|
|
};
|
|
|
|
if let Err(desc) = check_errors(res) {
|
|
|
|
let description =
|
|
|
|
format!("`snd_pcm_poll_descriptors_revents` failed: {}",desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if revent as i16 == libc::POLLOUT {
|
|
|
|
Ok(Some(StreamType::Output))
|
|
|
|
} else if revent as i16 == libc::POLLIN {
|
|
|
|
Ok(Some(StreamType::Input))
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the number of samples that are available to read/write.
|
|
|
|
fn get_available_samples(stream: &StreamInner) -> Result<usize, BackendSpecificError> {
|
|
|
|
let available = unsafe {
|
2019-08-24 03:32:19 +00:00
|
|
|
alsa::snd_pcm_avail_update(stream.channel)
|
2019-06-21 22:06:55 +00:00
|
|
|
};
|
|
|
|
if available == -32 {
|
|
|
|
// buffer underrun
|
|
|
|
// TODO: Notify the user some how.
|
|
|
|
Ok(stream.buffer_len)
|
|
|
|
} else if let Err(desc) = check_errors(available as libc::c_int) {
|
|
|
|
let description = format!("failed to get available samples: {}", desc);
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
Err(err)
|
|
|
|
} else {
|
|
|
|
Ok((available * stream.num_channels as alsa::snd_pcm_sframes_t) as usize)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
unsafe fn set_hw_params_from_format(
|
|
|
|
pcm_handle: *mut alsa::snd_pcm_t,
|
|
|
|
hw_params: &HwParams,
|
|
|
|
format: &Format,
|
2019-05-31 20:08:39 +00:00
|
|
|
) -> Result<(), String> {
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) {
|
2019-06-21 00:38:59 +00:00
|
|
|
return Err(format!("errors on pcm handle: {}", e));
|
2019-05-31 20:08:39 +00:00
|
|
|
}
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_access(pcm_handle,
|
2018-02-12 13:10:24 +00:00
|
|
|
hw_params.0,
|
2019-05-31 20:08:39 +00:00
|
|
|
alsa::SND_PCM_ACCESS_RW_INTERLEAVED)) {
|
2019-06-21 00:38:59 +00:00
|
|
|
return Err(format!("handle not acessible: {}", e));
|
2019-05-31 20:08:39 +00:00
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let data_type = if cfg!(target_endian = "big") {
|
|
|
|
match format.data_type {
|
|
|
|
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_BE,
|
|
|
|
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_BE,
|
|
|
|
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_BE,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
match format.data_type {
|
|
|
|
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_LE,
|
|
|
|
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_LE,
|
|
|
|
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_LE,
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-05-31 20:08:39 +00:00
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format(pcm_handle,
|
2018-02-12 13:10:24 +00:00
|
|
|
hw_params.0,
|
2019-05-31 20:08:39 +00:00
|
|
|
data_type)) {
|
2019-06-21 00:38:59 +00:00
|
|
|
return Err(format!("format could not be set: {}", e));
|
2019-05-31 20:08:39 +00:00
|
|
|
}
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(pcm_handle,
|
2018-02-12 13:10:24 +00:00
|
|
|
hw_params.0,
|
|
|
|
format.sample_rate.0 as libc::c_uint,
|
2019-05-31 20:08:39 +00:00
|
|
|
0)) {
|
2019-06-21 00:38:59 +00:00
|
|
|
return Err(format!("sample rate could not be set: {}", e));
|
2019-05-31 20:08:39 +00:00
|
|
|
}
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(pcm_handle,
|
2018-02-12 13:10:24 +00:00
|
|
|
hw_params.0,
|
|
|
|
format.channels as
|
2019-05-31 20:08:39 +00:00
|
|
|
libc::c_uint)) {
|
2019-06-21 00:38:59 +00:00
|
|
|
return Err(format!("channel count could not be set: {}", e));
|
2019-05-31 20:08:39 +00:00
|
|
|
}
|
2019-06-21 00:38:59 +00:00
|
|
|
|
2019-08-31 12:33:57 +00:00
|
|
|
// If this isn't set manually a overlarge buffer may be used causing audio delay
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_buffer_time_near(
|
|
|
|
pcm_handle,
|
|
|
|
hw_params.0,
|
|
|
|
&mut 100_000,
|
|
|
|
&mut 0,
|
|
|
|
)) {
|
|
|
|
return Err(format!("buffer time could not be set: {}", e));
|
|
|
|
}
|
|
|
|
|
2019-05-31 20:08:39 +00:00
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) {
|
2019-06-21 00:38:59 +00:00
|
|
|
return Err(format!("hardware params could not be set: {}", e));
|
2019-05-31 20:08:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn set_sw_params_from_format(
|
|
|
|
pcm_handle: *mut alsa::snd_pcm_t,
|
|
|
|
format: &Format,
|
2019-06-21 00:38:59 +00:00
|
|
|
) -> Result<(usize, usize), String>
|
2018-02-12 13:10:24 +00:00
|
|
|
{
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut sw_params = ptr::null_mut(); // TODO: RAII
|
2019-06-21 00:38:59 +00:00
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) {
|
|
|
|
return Err(format!("snd_pcm_sw_params_malloc failed: {}", e));
|
|
|
|
}
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_current(pcm_handle, sw_params)) {
|
|
|
|
return Err(format!("snd_pcm_sw_params_current failed: {}", e));
|
|
|
|
}
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params, 0)) {
|
|
|
|
return Err(format!("snd_pcm_sw_params_set_start_threshold failed: {}", e));
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let (buffer_len, period_len) = {
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut buffer = 0;
|
|
|
|
let mut period = 0;
|
2019-06-21 00:38:59 +00:00
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_get_params(pcm_handle, &mut buffer, &mut period)) {
|
|
|
|
return Err(format!("failed to initialize buffer: {}", e));
|
|
|
|
}
|
|
|
|
if buffer == 0 {
|
|
|
|
return Err(format!("initialization resulted in a null buffer"));
|
|
|
|
}
|
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_set_avail_min(pcm_handle, sw_params, period)) {
|
|
|
|
return Err(format!("snd_pcm_sw_params_set_avail_min failed: {}", e));
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
let buffer = buffer as usize * format.channels as usize;
|
|
|
|
let period = period as usize * format.channels as usize;
|
|
|
|
(buffer, period)
|
|
|
|
};
|
|
|
|
|
2019-06-21 00:38:59 +00:00
|
|
|
if let Err(e) = check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)) {
|
|
|
|
return Err(format!("snd_pcm_sw_params failed: {}", e));
|
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
alsa::snd_pcm_sw_params_free(sw_params);
|
2019-06-21 00:38:59 +00:00
|
|
|
Ok((buffer_len, period_len))
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
/// Wrapper around `hw_params`.
|
|
|
|
struct HwParams(*mut alsa::snd_pcm_hw_params_t);
|
|
|
|
|
|
|
|
impl HwParams {
|
|
|
|
pub fn alloc() -> HwParams {
|
|
|
|
unsafe {
|
2019-08-10 13:08:21 +00:00
|
|
|
let mut hw_params = ptr::null_mut();
|
2017-10-18 18:24:05 +00:00
|
|
|
check_errors(alsa::snd_pcm_hw_params_malloc(&mut hw_params))
|
|
|
|
.expect("unable to get hardware parameters");
|
|
|
|
HwParams(hw_params)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for HwParams {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
alsa::snd_pcm_hw_params_free(self.0);
|
|
|
|
}
|
2014-12-22 13:16:47 +00:00
|
|
|
}
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
impl Drop for StreamInner {
|
2015-09-11 08:55:29 +00:00
|
|
|
#[inline]
|
2014-12-16 15:45:45 +00:00
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
2017-10-18 18:24:05 +00:00
|
|
|
alsa::snd_pcm_close(self.channel);
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-11 08:55:29 +00:00
|
|
|
#[inline]
|
2014-12-16 15:45:45 +00:00
|
|
|
fn check_errors(err: libc::c_int) -> Result<(), String> {
|
|
|
|
if err < 0 {
|
|
|
|
unsafe {
|
2017-10-11 11:24:49 +00:00
|
|
|
let s = ffi::CStr::from_ptr(alsa::snd_strerror(err))
|
|
|
|
.to_bytes()
|
|
|
|
.to_vec();
|
2016-01-15 20:16:34 +00:00
|
|
|
let s = String::from_utf8(s).expect("Streaming error occured");
|
2015-04-04 07:06:46 +00:00
|
|
|
return Err(s);
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-04-30 06:43:47 +00:00
|
|
|
|
|
|
|
/// Cast a byte slice into a (immutable) slice of desired type.
|
|
|
|
/// Safety: it's up to the caller to ensure that the input slice has valid bit representations.
|
|
|
|
unsafe fn cast_input_buffer<T>(v: &[u8]) -> &[T] {
|
|
|
|
debug_assert!(v.len() % std::mem::size_of::<T>() == 0);
|
|
|
|
std::slice::from_raw_parts(v.as_ptr() as *const T, v.len() / std::mem::size_of::<T>())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Cast a byte slice into a mutable slice of desired type.
|
|
|
|
/// Safety: it's up to the caller to ensure that the input slice has valid bit representations.
|
|
|
|
unsafe fn cast_output_buffer<T>(v: &mut [u8]) -> &mut [T] {
|
|
|
|
debug_assert!(v.len() % std::mem::size_of::<T>() == 0);
|
|
|
|
std::slice::from_raw_parts_mut(v.as_mut_ptr() as *mut T, v.len() / std::mem::size_of::<T>())
|
2019-06-20 19:16:39 +00:00
|
|
|
}
|