2020-04-03 16:14:52 +00:00
|
|
|
extern crate alsa;
|
2014-12-16 15:45:45 +00:00
|
|
|
extern crate libc;
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
use self::alsa::poll::Descriptors;
|
2020-01-13 14:27:41 +00:00
|
|
|
use crate::{
|
2020-01-27 20:28:07 +00:00
|
|
|
BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError,
|
2020-04-16 12:50:36 +00:00
|
|
|
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
|
|
|
|
PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedStreamConfig,
|
|
|
|
SupportedStreamConfigRange, SupportedStreamConfigsError,
|
2020-01-13 14:27:41 +00:00
|
|
|
};
|
2020-04-27 16:00:40 +00:00
|
|
|
use std::convert::TryInto;
|
2019-07-09 06:47:33 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
use std::thread::{self, JoinHandle};
|
|
|
|
use std::vec::IntoIter as VecIntoIter;
|
2020-04-15 17:42:43 +00:00
|
|
|
use std::{cmp, mem};
|
2019-07-09 06:47:33 +00:00
|
|
|
use traits::{DeviceTrait, HostTrait, StreamTrait};
|
2015-09-01 15:15:49 +00:00
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
pub use self::enumerate::{default_input_device, default_output_device, Devices};
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
pub type SupportedInputConfigs = VecIntoIter<SupportedStreamConfigRange>;
|
|
|
|
pub type SupportedOutputConfigs = VecIntoIter<SupportedStreamConfigRange>;
|
2015-09-01 15:15:49 +00:00
|
|
|
|
|
|
|
mod enumerate;
|
|
|
|
|
2020-01-07 16:18:49 +00:00
|
|
|
/// The default linux, dragonfly and freebsd host type.
|
[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
|
|
|
#[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;
|
|
|
|
|
|
|
|
fn is_available() -> bool {
|
2020-01-07 16:18:49 +00:00
|
|
|
// Assume ALSA is always available on linux/dragonfly/freebsd.
|
[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
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DeviceTrait for Device {
|
2020-01-27 20:28:07 +00:00
|
|
|
type SupportedInputConfigs = SupportedInputConfigs;
|
|
|
|
type SupportedOutputConfigs = SupportedOutputConfigs;
|
2019-07-09 06:47:33 +00:00
|
|
|
type Stream = Stream;
|
[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> {
|
|
|
|
Device::name(self)
|
|
|
|
}
|
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn supported_input_configs(
|
2020-01-20 19:35:23 +00:00
|
|
|
&self,
|
2020-01-27 20:28:07 +00:00
|
|
|
) -> Result<Self::SupportedInputConfigs, SupportedStreamConfigsError> {
|
|
|
|
Device::supported_input_configs(self)
|
[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
|
|
|
}
|
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn supported_output_configs(
|
2020-01-20 19:35:23 +00:00
|
|
|
&self,
|
2020-01-27 20:28:07 +00:00
|
|
|
) -> Result<Self::SupportedOutputConfigs, SupportedStreamConfigsError> {
|
|
|
|
Device::supported_output_configs(self)
|
[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
|
|
|
}
|
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
|
|
|
Device::default_input_config(self)
|
[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
|
|
|
}
|
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
|
|
|
Device::default_output_config(self)
|
[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
|
|
|
}
|
|
|
|
|
2020-01-22 05:52:18 +00:00
|
|
|
fn build_input_stream_raw<D, E>(
|
2020-01-13 14:27:41 +00:00
|
|
|
&self,
|
2020-02-02 17:28:38 +00:00
|
|
|
conf: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-01-13 14:27:41 +00:00
|
|
|
data_callback: D,
|
|
|
|
error_callback: E,
|
|
|
|
) -> Result<Self::Stream, BuildStreamError>
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
|
2020-01-13 14:27:41 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
2020-02-02 17:28:38 +00:00
|
|
|
let stream_inner =
|
2020-04-03 16:14:52 +00:00
|
|
|
self.build_stream_inner(conf, sample_format, alsa::Direction::Capture)?;
|
2020-01-13 14:27:41 +00:00
|
|
|
let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback);
|
|
|
|
Ok(stream)
|
[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
|
|
|
}
|
|
|
|
|
2020-01-22 05:52:18 +00:00
|
|
|
fn build_output_stream_raw<D, E>(
|
2020-01-13 14:27:41 +00:00
|
|
|
&self,
|
2020-02-02 17:28:38 +00:00
|
|
|
conf: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-01-13 14:27:41 +00:00
|
|
|
data_callback: D,
|
|
|
|
error_callback: E,
|
|
|
|
) -> Result<Self::Stream, BuildStreamError>
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
|
2020-01-13 14:27:41 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
2020-02-02 17:28:38 +00:00
|
|
|
let stream_inner =
|
2020-04-03 16:14:52 +00:00
|
|
|
self.build_stream_inner(conf, sample_format, alsa::Direction::Playback)?;
|
2020-01-13 14:27:41 +00:00
|
|
|
let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback);
|
|
|
|
Ok(stream)
|
[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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
struct TriggerSender(libc::c_int);
|
2017-07-13 11:58:01 +00:00
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
struct TriggerReceiver(libc::c_int);
|
|
|
|
|
|
|
|
impl TriggerSender {
|
2017-07-13 11:58:01 +00:00
|
|
|
fn wakeup(&self) {
|
|
|
|
let buf = 1u64;
|
2019-07-09 06:47:33 +00:00
|
|
|
let ret = unsafe { libc::write(self.0, &buf as *const u64 as *const _, 8) };
|
2017-07-13 11:58:01 +00:00
|
|
|
assert!(ret == 8);
|
|
|
|
}
|
2019-07-09 06:47:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TriggerReceiver {
|
2017-07-13 11:58:01 +00:00
|
|
|
fn clear_pipe(&self) {
|
|
|
|
let mut out = 0u64;
|
2019-07-09 06:47:33 +00:00
|
|
|
let ret = unsafe { libc::read(self.0, &mut out as *mut u64 as *mut _, 8) };
|
2017-07-13 11:58:01 +00:00
|
|
|
assert_eq!(ret, 8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
fn trigger() -> (TriggerSender, TriggerReceiver) {
|
|
|
|
let mut fds = [0, 0];
|
|
|
|
match unsafe { libc::pipe(fds.as_mut_ptr()) } {
|
|
|
|
0 => (TriggerSender(fds[1]), TriggerReceiver(fds[0])),
|
|
|
|
_ => panic!("Could not create pipe"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for TriggerSender {
|
2017-07-13 11:58:01 +00:00
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
2019-07-09 06:47:33 +00:00
|
|
|
libc::close(self.0);
|
2017-07-13 11:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
impl Drop for TriggerReceiver {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
libc::close(self.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-13 11:58:01 +00:00
|
|
|
|
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 {
|
2020-01-13 14:27:41 +00:00
|
|
|
fn build_stream_inner(
|
|
|
|
&self,
|
2020-02-02 17:28:38 +00:00
|
|
|
conf: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-04-03 16:14:52 +00:00
|
|
|
stream_type: alsa::Direction,
|
2020-01-13 14:27:41 +00:00
|
|
|
) -> Result<StreamInner, BuildStreamError> {
|
2020-04-03 16:14:52 +00:00
|
|
|
let name = &self.0;
|
2019-07-09 06:47:33 +00:00
|
|
|
|
2020-04-12 17:11:35 +00:00
|
|
|
let handle = match alsa::pcm::PCM::new(name, stream_type, true).map_err(|e| (e, e.errno()))
|
|
|
|
{
|
2020-04-03 16:14:52 +00:00
|
|
|
Err((_, Some(nix::errno::Errno::EBUSY))) => {
|
|
|
|
return Err(BuildStreamError::DeviceNotAvailable)
|
2019-07-09 06:47:33 +00:00
|
|
|
}
|
2020-04-03 16:14:52 +00:00
|
|
|
Err((_, Some(nix::errno::Errno::EINVAL))) => {
|
|
|
|
return Err(BuildStreamError::InvalidArgument)
|
|
|
|
}
|
|
|
|
Err((e, _)) => return Err(e.into()),
|
|
|
|
Ok(handle) => handle,
|
2019-07-09 06:47:33 +00:00
|
|
|
};
|
2020-04-03 16:14:52 +00:00
|
|
|
let can_pause = {
|
|
|
|
let hw_params = set_hw_params_from_format(&handle, conf, sample_format)?;
|
|
|
|
hw_params.can_pause()
|
2019-07-09 06:47:33 +00:00
|
|
|
};
|
2020-04-03 16:14:52 +00:00
|
|
|
let (buffer_len, period_len) = set_sw_params_from_format(&handle, conf)?;
|
2019-07-09 06:47:33 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
handle.prepare()?;
|
2019-07-09 06:47:33 +00:00
|
|
|
|
|
|
|
let num_descriptors = {
|
2020-04-03 16:14:52 +00:00
|
|
|
let num_descriptors = handle.count();
|
2019-07-09 06:47:33 +00:00
|
|
|
if num_descriptors == 0 {
|
|
|
|
let description = "poll descriptor count for stream was 0".to_string();
|
|
|
|
let err = BackendSpecificError { description };
|
|
|
|
return Err(err.into());
|
|
|
|
}
|
2020-04-03 16:14:52 +00:00
|
|
|
num_descriptors
|
2019-07-09 06:47:33 +00:00
|
|
|
};
|
|
|
|
|
2020-04-28 14:38:57 +00:00
|
|
|
// Check to see if we can retrieve valid timestamps from the device.
|
2020-04-28 15:43:40 +00:00
|
|
|
// Related: https://bugs.freedesktop.org/show_bug.cgi?id=88503
|
2020-04-28 14:38:57 +00:00
|
|
|
let ts = handle.status()?.get_htstamp();
|
|
|
|
let creation_instant = match (ts.tv_sec, ts.tv_nsec) {
|
|
|
|
(0, 0) => Some(std::time::Instant::now()),
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
handle.start()?;
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
let stream_inner = StreamInner {
|
|
|
|
channel: handle,
|
2020-02-02 17:28:38 +00:00
|
|
|
sample_format,
|
2019-07-09 06:47:33 +00:00
|
|
|
num_descriptors,
|
2020-04-27 16:00:40 +00:00
|
|
|
conf: conf.clone(),
|
2019-07-09 06:47:33 +00:00
|
|
|
buffer_len,
|
|
|
|
period_len,
|
|
|
|
can_pause,
|
2020-04-28 14:38:57 +00:00
|
|
|
creation_instant,
|
2019-07-09 06:47:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(stream_inner)
|
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +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 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
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
fn supported_configs(
|
2018-02-12 13:10:24 +00:00
|
|
|
&self,
|
2020-04-03 16:14:52 +00:00
|
|
|
stream_t: alsa::Direction,
|
2020-01-27 20:28:07 +00:00
|
|
|
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
|
2020-04-03 16:14:52 +00:00
|
|
|
let name = &self.0;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
let handle = match alsa::pcm::PCM::new(name, stream_t, true).map_err(|e| (e, e.errno())) {
|
|
|
|
Err((_, Some(nix::errno::Errno::ENOENT)))
|
|
|
|
| Err((_, Some(nix::errno::Errno::EBUSY))) => {
|
|
|
|
return Err(SupportedStreamConfigsError::DeviceNotAvailable)
|
2018-11-06 20:01:03 +00:00
|
|
|
}
|
2020-04-03 16:14:52 +00:00
|
|
|
Err((_, Some(nix::errno::Errno::EINVAL))) => {
|
|
|
|
return Err(SupportedStreamConfigsError::InvalidArgument)
|
2019-06-20 22:22:30 +00:00
|
|
|
}
|
2020-04-03 16:14:52 +00:00
|
|
|
Err((e, _)) => return Err(e.into()),
|
|
|
|
Ok(handle) => handle,
|
2018-02-12 13:10:24 +00:00
|
|
|
};
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
let hw_params = alsa::pcm::HwParams::any(&handle)?;
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
// TODO: check endianess
|
2020-04-03 16:14:52 +00:00
|
|
|
const FORMATS: [(SampleFormat, alsa::pcm::Format); 3] = [
|
2020-01-20 19:35:23 +00:00
|
|
|
//SND_PCM_FORMAT_S8,
|
|
|
|
//SND_PCM_FORMAT_U8,
|
2020-04-03 16:14:52 +00:00
|
|
|
(SampleFormat::I16, alsa::pcm::Format::S16LE),
|
2020-01-20 19:35:23 +00:00
|
|
|
//SND_PCM_FORMAT_S16_BE,
|
2020-04-03 16:14:52 +00:00
|
|
|
(SampleFormat::U16, alsa::pcm::Format::U16LE),
|
2020-01-20 19:35:23 +00:00
|
|
|
//SND_PCM_FORMAT_U16_BE,
|
2020-01-20 23:15:52 +00:00
|
|
|
//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,
|
2020-04-03 16:14:52 +00:00
|
|
|
(SampleFormat::F32, alsa::pcm::Format::FloatLE),
|
2020-01-20 23:15:52 +00:00
|
|
|
//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,
|
2020-01-20 19:35:23 +00:00
|
|
|
];
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let mut supported_formats = Vec::new();
|
|
|
|
for &(sample_format, alsa_format) in FORMATS.iter() {
|
2020-04-03 16:14:52 +00:00
|
|
|
if hw_params.test_format(alsa_format).is_ok() {
|
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
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
let min_rate = hw_params.get_rate_min()?;
|
|
|
|
let max_rate = hw_params.get_rate_max()?;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
let sample_rates = if min_rate == max_rate || hw_params.test_rate(min_rate + 1).is_ok() {
|
2018-02-12 13:10:24 +00:00
|
|
|
vec![(min_rate, max_rate)]
|
|
|
|
} else {
|
|
|
|
const RATES: [libc::c_uint; 13] = [
|
2020-01-20 19:35:23 +00:00
|
|
|
5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400,
|
2018-02-12 13:10:24 +00:00
|
|
|
192000,
|
|
|
|
];
|
|
|
|
|
|
|
|
let mut rates = Vec::new();
|
|
|
|
for &rate in RATES.iter() {
|
2020-04-03 16:14:52 +00:00
|
|
|
if hw_params.test_rate(rate).is_ok() {
|
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
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
let min_channels = hw_params.get_channels_min()?;
|
|
|
|
let max_channels = hw_params.get_channels_max()?;
|
2019-06-20 22:22:30 +00:00
|
|
|
|
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
|
2020-01-20 19:35:23 +00:00
|
|
|
let supported_channels = (min_channels..max_channels + 1)
|
|
|
|
.filter_map(|num| {
|
2020-04-03 16:14:52 +00:00
|
|
|
if hw_params.test_channels(num).is_ok() {
|
2020-01-20 19:35:23 +00:00
|
|
|
Some(num as ChannelCount)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2018-02-12 13:10:24 +00:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
|
2020-01-20 19:35:23 +00:00
|
|
|
let mut output = Vec::with_capacity(
|
|
|
|
supported_formats.len() * supported_channels.len() * sample_rates.len(),
|
|
|
|
);
|
2020-01-27 20:28:07 +00:00
|
|
|
for &sample_format in supported_formats.iter() {
|
2018-02-12 13:10:24 +00:00
|
|
|
for channels in supported_channels.iter() {
|
|
|
|
for &(min_rate, max_rate) in sample_rates.iter() {
|
2020-01-27 20:28:07 +00:00
|
|
|
output.push(SupportedStreamConfigRange {
|
2020-01-20 19:35:23 +00:00
|
|
|
channels: channels.clone(),
|
|
|
|
min_sample_rate: SampleRate(min_rate as u32),
|
|
|
|
max_sample_rate: SampleRate(max_rate as u32),
|
2020-01-27 20:28:07 +00:00
|
|
|
sample_format: sample_format,
|
2020-01-20 19:35:23 +00:00
|
|
|
});
|
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
|
|
|
Ok(output.into_iter())
|
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn supported_input_configs(
|
|
|
|
&self,
|
|
|
|
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
|
2020-04-03 16:14:52 +00:00
|
|
|
self.supported_configs(alsa::Direction::Capture)
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn supported_output_configs(
|
|
|
|
&self,
|
|
|
|
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
|
2020-04-03 16:14:52 +00:00
|
|
|
self.supported_configs(alsa::Direction::Playback)
|
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
|
2020-01-27 20:28:07 +00:00
|
|
|
// the `SupportedStreamConfigRange::cmp_default_heuristics` order and select the greatest.
|
|
|
|
fn default_config(
|
2018-02-12 13:10:24 +00:00
|
|
|
&self,
|
2020-04-03 16:14:52 +00:00
|
|
|
stream_t: alsa::Direction,
|
2020-01-27 20:28:07 +00:00
|
|
|
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
2020-04-03 16:14:52 +00:00
|
|
|
let mut formats: Vec<_> = {
|
2020-01-27 20:28:07 +00:00
|
|
|
match self.supported_configs(stream_t) {
|
|
|
|
Err(SupportedStreamConfigsError::DeviceNotAvailable) => {
|
|
|
|
return Err(DefaultStreamConfigError::DeviceNotAvailable);
|
2020-01-20 19:35:23 +00:00
|
|
|
}
|
2020-01-27 20:28:07 +00:00
|
|
|
Err(SupportedStreamConfigsError::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
|
2020-01-27 20:28:07 +00:00
|
|
|
return Err(DefaultStreamConfigError::StreamTypeNotSupported);
|
2018-11-06 20:01:03 +00:00
|
|
|
}
|
2020-01-27 20:28:07 +00:00
|
|
|
Err(SupportedStreamConfigsError::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)
|
2020-01-20 19:35:23 +00:00
|
|
|
}
|
2020-01-27 20:28:07 +00:00
|
|
|
None => Err(DefaultStreamConfigError::StreamTypeNotSupported),
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
2020-04-03 16:14:52 +00:00
|
|
|
self.default_config(alsa::Direction::Capture)
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
2020-04-03 16:14:52 +00:00
|
|
|
self.default_config(alsa::Direction::Playback)
|
2015-09-22 12:46:27 +00:00
|
|
|
}
|
2015-09-01 15:15:49 +00:00
|
|
|
}
|
|
|
|
|
2018-02-12 13:10:24 +00:00
|
|
|
struct StreamInner {
|
2016-08-02 20:28:37 +00:00
|
|
|
// The ALSA channel.
|
2020-04-03 16:14:52 +00:00
|
|
|
channel: alsa::pcm::PCM,
|
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,
|
|
|
|
|
2020-04-27 16:00:40 +00:00
|
|
|
// The configuration used to open this stream.
|
|
|
|
conf: StreamConfig,
|
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,
|
2020-04-28 14:38:57 +00:00
|
|
|
|
|
|
|
// In the case that the device does not return valid timestamps via `get_htstamp`, this field
|
|
|
|
// will be `Some` and will contain an `Instant` representing the moment the stream was created.
|
|
|
|
//
|
|
|
|
// If this field is `Some`, then the stream will use the duration since this instant as a
|
|
|
|
// source for timestamps.
|
|
|
|
//
|
|
|
|
// If this field is `None` then the elapsed duration between `get_trigger_htstamp` and
|
|
|
|
// `get_htstamp` is used.
|
|
|
|
creation_instant: Option<std::time::Instant>,
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
// Assume that the ALSA library is built with thread safe option.
|
|
|
|
unsafe impl Sync for StreamInner {}
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2020-01-13 14:27:41 +00:00
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
2020-01-20 19:35:23 +00:00
|
|
|
enum StreamType {
|
|
|
|
Input,
|
|
|
|
Output,
|
|
|
|
}
|
2019-06-21 22:06:55 +00:00
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
pub struct Stream {
|
|
|
|
/// The high-priority audio processing thread calling callbacks.
|
|
|
|
/// Option used for moving out in destructor.
|
|
|
|
thread: Option<JoinHandle<()>>,
|
2019-06-21 22:06:55 +00:00
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
/// Handle to the underlying stream for playback controls.
|
|
|
|
inner: Arc<StreamInner>,
|
2016-09-30 16:18:28 +00:00
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
/// Used to signal to stop processing.
|
|
|
|
trigger: TriggerSender,
|
|
|
|
}
|
2017-11-01 09:15:17 +00:00
|
|
|
|
2020-01-13 14:27:41 +00:00
|
|
|
#[derive(Default)]
|
|
|
|
struct StreamWorkerContext {
|
|
|
|
descriptors: Vec<libc::pollfd>,
|
|
|
|
buffer: Vec<u8>,
|
|
|
|
}
|
|
|
|
|
2020-01-19 14:06:19 +00:00
|
|
|
fn input_stream_worker(
|
2020-01-13 14:27:41 +00:00
|
|
|
rx: TriggerReceiver,
|
|
|
|
stream: &StreamInner,
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback: &mut (dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static),
|
2020-01-13 14:27:41 +00:00
|
|
|
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
|
2020-01-19 14:06:19 +00:00
|
|
|
) {
|
2020-01-13 14:27:41 +00:00
|
|
|
let mut ctxt = StreamWorkerContext::default();
|
2019-07-09 06:47:33 +00:00
|
|
|
loop {
|
2020-04-03 16:14:52 +00:00
|
|
|
let flow = report_error(
|
|
|
|
poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt),
|
|
|
|
error_callback,
|
|
|
|
)
|
|
|
|
.unwrap_or(PollDescriptorsFlow::Continue);
|
|
|
|
|
|
|
|
match flow {
|
2020-01-13 14:27:41 +00:00
|
|
|
PollDescriptorsFlow::Continue => continue,
|
|
|
|
PollDescriptorsFlow::Return => return,
|
2020-01-20 19:35:23 +00:00
|
|
|
PollDescriptorsFlow::Ready {
|
2020-04-27 16:00:40 +00:00
|
|
|
avail_frames: _,
|
|
|
|
delay_frames,
|
2020-01-20 19:35:23 +00:00
|
|
|
stream_type,
|
|
|
|
} => {
|
2020-01-13 14:27:41 +00:00
|
|
|
assert_eq!(
|
|
|
|
stream_type,
|
|
|
|
StreamType::Input,
|
|
|
|
"expected input stream, but polling descriptors indicated output",
|
|
|
|
);
|
2020-04-27 16:00:40 +00:00
|
|
|
let res = process_input(stream, &mut ctxt.buffer, delay_frames, data_callback);
|
|
|
|
report_error(res, error_callback);
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-09 06:47:33 +00:00
|
|
|
|
2020-01-19 14:06:19 +00:00
|
|
|
fn output_stream_worker(
|
2020-01-13 14:27:41 +00:00
|
|
|
rx: TriggerReceiver,
|
|
|
|
stream: &StreamInner,
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback: &mut (dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static),
|
2020-01-13 14:27:41 +00:00
|
|
|
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
|
2020-01-19 14:06:19 +00:00
|
|
|
) {
|
2020-01-13 14:27:41 +00:00
|
|
|
let mut ctxt = StreamWorkerContext::default();
|
|
|
|
loop {
|
2020-04-03 16:14:52 +00:00
|
|
|
let flow = report_error(
|
|
|
|
poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt),
|
|
|
|
error_callback,
|
|
|
|
)
|
|
|
|
.unwrap_or(PollDescriptorsFlow::Continue);
|
|
|
|
|
|
|
|
match flow {
|
2020-01-13 14:27:41 +00:00
|
|
|
PollDescriptorsFlow::Continue => continue,
|
|
|
|
PollDescriptorsFlow::Return => return,
|
2020-01-20 19:35:23 +00:00
|
|
|
PollDescriptorsFlow::Ready {
|
2020-04-27 16:00:40 +00:00
|
|
|
avail_frames,
|
|
|
|
delay_frames,
|
2020-01-20 19:35:23 +00:00
|
|
|
stream_type,
|
|
|
|
} => {
|
2020-01-13 14:27:41 +00:00
|
|
|
assert_eq!(
|
|
|
|
stream_type,
|
|
|
|
StreamType::Output,
|
|
|
|
"expected output stream, but polling descriptors indicated input",
|
|
|
|
);
|
2020-04-27 16:00:40 +00:00
|
|
|
let res = process_output(
|
2020-01-13 14:27:41 +00:00
|
|
|
stream,
|
|
|
|
&mut ctxt.buffer,
|
2020-04-27 16:00:40 +00:00
|
|
|
avail_frames,
|
|
|
|
delay_frames,
|
2020-01-13 14:27:41 +00:00
|
|
|
data_callback,
|
|
|
|
error_callback,
|
|
|
|
);
|
2020-04-27 16:00:40 +00:00
|
|
|
report_error(res, error_callback);
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
2019-07-09 06:47:33 +00:00
|
|
|
}
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
|
|
|
}
|
2019-06-13 06:31:37 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
fn report_error<T, E>(
|
|
|
|
result: Result<T, E>,
|
|
|
|
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
|
|
|
|
) -> Option<T>
|
|
|
|
where
|
|
|
|
E: Into<StreamError>,
|
|
|
|
{
|
|
|
|
match result {
|
|
|
|
Ok(val) => Some(val),
|
|
|
|
Err(err) => {
|
|
|
|
error_callback(err.into());
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-13 14:27:41 +00:00
|
|
|
enum PollDescriptorsFlow {
|
|
|
|
Continue,
|
|
|
|
Return,
|
|
|
|
Ready {
|
|
|
|
stream_type: StreamType,
|
2020-04-27 16:00:40 +00:00
|
|
|
avail_frames: usize,
|
|
|
|
delay_frames: usize,
|
2020-01-20 19:35:23 +00:00
|
|
|
},
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This block is shared between both input and output stream worker functions.
|
|
|
|
fn poll_descriptors_and_prepare_buffer(
|
|
|
|
rx: &TriggerReceiver,
|
|
|
|
stream: &StreamInner,
|
|
|
|
ctxt: &mut StreamWorkerContext,
|
2020-04-03 16:14:52 +00:00
|
|
|
) -> Result<PollDescriptorsFlow, BackendSpecificError> {
|
2020-01-13 14:27:41 +00:00
|
|
|
let StreamWorkerContext {
|
|
|
|
ref mut descriptors,
|
|
|
|
ref mut buffer,
|
|
|
|
} = *ctxt;
|
|
|
|
|
|
|
|
descriptors.clear();
|
|
|
|
|
|
|
|
// Add the self-pipe for signaling termination.
|
|
|
|
descriptors.push(libc::pollfd {
|
|
|
|
fd: rx.0,
|
|
|
|
events: libc::POLLIN,
|
|
|
|
revents: 0,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Add ALSA polling fds.
|
|
|
|
let len = descriptors.len();
|
2020-04-03 16:14:52 +00:00
|
|
|
descriptors.resize(
|
|
|
|
stream.num_descriptors + len,
|
|
|
|
libc::pollfd {
|
|
|
|
fd: 0,
|
|
|
|
events: 0,
|
|
|
|
revents: 0,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
let filled = stream.channel.fill(&mut descriptors[len..])?;
|
|
|
|
debug_assert_eq!(filled, stream.num_descriptors);
|
|
|
|
|
|
|
|
// Don't timeout, wait forever.
|
|
|
|
let res = alsa::poll::poll(descriptors, -1)?;
|
|
|
|
if res == 0 {
|
|
|
|
let description = String::from("`alsa::poll()` spuriously returned");
|
|
|
|
return Err(BackendSpecificError { description });
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if descriptors[0].revents != 0 {
|
|
|
|
// The stream has been requested to be destroyed.
|
|
|
|
rx.clear_pipe();
|
2020-04-03 16:14:52 +00:00
|
|
|
return Ok(PollDescriptorsFlow::Return);
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
let stream_type = match stream.channel.revents(&descriptors[1..])? {
|
|
|
|
alsa::poll::Flags::OUT => StreamType::Output,
|
|
|
|
alsa::poll::Flags::IN => StreamType::Input,
|
|
|
|
_ => {
|
2020-01-13 14:27:41 +00:00
|
|
|
// Nothing to process, poll again
|
2020-04-03 16:14:52 +00:00
|
|
|
return Ok(PollDescriptorsFlow::Continue);
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
// Get the number of available samples for reading/writing.
|
2020-04-27 16:00:40 +00:00
|
|
|
let (avail_frames, delay_frames) = get_avail_delay(stream)?;
|
|
|
|
let available_samples = avail_frames * stream.conf.channels as usize;
|
2016-09-30 16:18:28 +00:00
|
|
|
|
2020-01-13 14:27:41 +00:00
|
|
|
// Only go on if there is at least `stream.period_len` samples.
|
|
|
|
if available_samples < stream.period_len {
|
2020-04-03 16:14:52 +00:00
|
|
|
return Ok(PollDescriptorsFlow::Continue);
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
2016-09-30 16:18:28 +00:00
|
|
|
|
2020-01-13 14:27:41 +00:00
|
|
|
// Prepare the data buffer.
|
|
|
|
let buffer_size = stream.sample_format.sample_size() * available_samples;
|
|
|
|
buffer.resize(buffer_size, 0u8);
|
2016-08-02 20:28:37 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
Ok(PollDescriptorsFlow::Ready {
|
2020-01-13 14:27:41 +00:00
|
|
|
stream_type,
|
2020-04-27 16:00:40 +00:00
|
|
|
avail_frames,
|
|
|
|
delay_frames,
|
2020-04-03 16:14:52 +00:00
|
|
|
})
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2020-01-13 14:27:41 +00:00
|
|
|
// Read input data from ALSA and deliver it to the user.
|
2020-01-19 14:06:19 +00:00
|
|
|
fn process_input(
|
2020-01-13 14:27:41 +00:00
|
|
|
stream: &StreamInner,
|
|
|
|
buffer: &mut [u8],
|
2020-04-27 16:00:40 +00:00
|
|
|
delay_frames: usize,
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback: &mut (dyn FnMut(&Data, &InputCallbackInfo) + Send + 'static),
|
2020-04-03 16:14:52 +00:00
|
|
|
) -> Result<(), BackendSpecificError> {
|
|
|
|
stream.channel.io().readi(buffer)?;
|
2020-01-19 14:06:19 +00:00
|
|
|
let sample_format = stream.sample_format;
|
|
|
|
let data = buffer.as_mut_ptr() as *mut ();
|
|
|
|
let len = buffer.len() / sample_format.sample_size();
|
2020-01-20 19:35:23 +00:00
|
|
|
let data = unsafe { Data::from_parts(data, len, sample_format) };
|
2020-04-27 16:00:40 +00:00
|
|
|
let callback = stream_timestamp(stream)?;
|
|
|
|
let delay_duration = frames_to_duration(delay_frames, stream.conf.sample_rate);
|
|
|
|
let capture = callback
|
|
|
|
.sub(delay_duration)
|
2020-04-28 14:38:57 +00:00
|
|
|
.expect("`capture` is earlier than representation supported by `StreamInstant`");
|
2020-04-27 16:00:40 +00:00
|
|
|
let timestamp = crate::InputStreamTimestamp { callback, capture };
|
|
|
|
let info = crate::InputCallbackInfo { timestamp };
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback(&data, &info);
|
2020-04-03 16:14:52 +00:00
|
|
|
|
|
|
|
Ok(())
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
2015-09-10 17:48:39 +00:00
|
|
|
|
2020-01-13 14:27:41 +00:00
|
|
|
// Request data from the user's function and write it via ALSA.
|
|
|
|
//
|
|
|
|
// Returns `true`
|
2020-01-19 14:06:19 +00:00
|
|
|
fn process_output(
|
2020-01-13 14:27:41 +00:00
|
|
|
stream: &StreamInner,
|
|
|
|
buffer: &mut [u8],
|
|
|
|
available_frames: usize,
|
2020-04-27 16:00:40 +00:00
|
|
|
delay_frames: usize,
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback: &mut (dyn FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static),
|
2020-01-13 14:27:41 +00:00
|
|
|
error_callback: &mut dyn FnMut(StreamError),
|
2020-04-27 16:00:40 +00:00
|
|
|
) -> Result<(), BackendSpecificError> {
|
2020-01-13 14:27:41 +00:00
|
|
|
{
|
|
|
|
// We're now sure that we're ready to write data.
|
2020-01-19 14:06:19 +00:00
|
|
|
let sample_format = stream.sample_format;
|
|
|
|
let data = buffer.as_mut_ptr() as *mut ();
|
|
|
|
let len = buffer.len() / sample_format.sample_size();
|
2020-01-20 19:35:23 +00:00
|
|
|
let mut data = unsafe { Data::from_parts(data, len, sample_format) };
|
2020-04-27 16:00:40 +00:00
|
|
|
let callback = stream_timestamp(stream)?;
|
|
|
|
let delay_duration = frames_to_duration(delay_frames, stream.conf.sample_rate);
|
|
|
|
let playback = callback
|
|
|
|
.add(delay_duration)
|
|
|
|
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
|
|
|
|
let timestamp = crate::OutputStreamTimestamp { callback, playback };
|
|
|
|
let info = crate::OutputCallbackInfo { timestamp };
|
2020-04-16 12:50:36 +00:00
|
|
|
data_callback(&mut data, &info);
|
2020-01-13 14:27:41 +00:00
|
|
|
}
|
|
|
|
loop {
|
2020-04-03 16:14:52 +00:00
|
|
|
match stream.channel.io().writei(buffer) {
|
|
|
|
Err(err) if err.errno() == Some(nix::errno::Errno::EPIPE) => {
|
|
|
|
// buffer underrun
|
|
|
|
// TODO: Notify the user of this.
|
|
|
|
let _ = stream.channel.try_recover(err, false);
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
error_callback(err.into());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Ok(result) if result != available_frames => {
|
|
|
|
let description = format!(
|
|
|
|
"unexpected number of frames written: expected {}, \
|
|
|
|
result {} (this should never happen)",
|
|
|
|
available_frames, result,
|
|
|
|
);
|
|
|
|
error_callback(BackendSpecificError { description }.into());
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
break;
|
|
|
|
}
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-27 16:00:40 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-04-28 14:38:57 +00:00
|
|
|
// Use the elapsed duration since the start of the stream.
|
2020-04-27 16:00:40 +00:00
|
|
|
//
|
|
|
|
// This ensures positive values that are compatible with our `StreamInstant` representation.
|
|
|
|
fn stream_timestamp(stream: &StreamInner) -> Result<crate::StreamInstant, BackendSpecificError> {
|
2020-04-28 14:38:57 +00:00
|
|
|
match stream.creation_instant {
|
|
|
|
None => {
|
|
|
|
let status = stream.channel.status()?;
|
|
|
|
let trigger_ts = status.get_trigger_htstamp();
|
|
|
|
let ts = status.get_htstamp();
|
|
|
|
let nanos = timespec_diff_nanos(ts, trigger_ts)
|
|
|
|
.try_into()
|
|
|
|
.unwrap_or_else(|_| {
|
|
|
|
panic!(
|
|
|
|
"get_htstamp `{:?}` was earlier than get_trigger_htstamp `{:?}`",
|
|
|
|
ts, trigger_ts
|
|
|
|
);
|
|
|
|
});
|
|
|
|
Ok(crate::StreamInstant::from_nanos(nanos))
|
|
|
|
}
|
|
|
|
Some(creation) => {
|
|
|
|
let now = std::time::Instant::now();
|
|
|
|
let duration = now.duration_since(creation);
|
|
|
|
let instant = crate::StreamInstant::from_nanos_i128(duration.as_nanos() as i128)
|
|
|
|
.expect("stream duration has exceeded `StreamInstant` representation");
|
|
|
|
Ok(instant)
|
|
|
|
}
|
|
|
|
}
|
2020-04-27 16:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Adapted from `timestamp2ns` here:
|
|
|
|
// https://fossies.org/linux/alsa-lib/test/audio_time.c
|
|
|
|
fn timespec_to_nanos(ts: libc::timespec) -> i64 {
|
|
|
|
ts.tv_sec as i64 * 1_000_000_000 + ts.tv_nsec
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adapted from `timediff` here:
|
|
|
|
// https://fossies.org/linux/alsa-lib/test/audio_time.c
|
|
|
|
fn timespec_diff_nanos(a: libc::timespec, b: libc::timespec) -> i64 {
|
|
|
|
timespec_to_nanos(a) - timespec_to_nanos(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
|
|
|
|
fn frames_to_duration(frames: usize, rate: crate::SampleRate) -> std::time::Duration {
|
|
|
|
let secsf = frames as f64 / rate.0 as f64;
|
|
|
|
let secs = secsf as u64;
|
|
|
|
let nanos = ((secsf - secs as f64) * 1_000_000_000.0) as u32;
|
|
|
|
std::time::Duration::new(secs, nanos)
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
impl Stream {
|
2020-01-19 14:06:19 +00:00
|
|
|
fn new_input<D, E>(
|
2020-01-13 14:27:41 +00:00
|
|
|
inner: Arc<StreamInner>,
|
|
|
|
mut data_callback: D,
|
|
|
|
mut error_callback: E,
|
|
|
|
) -> Stream
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&Data, &InputCallbackInfo) + Send + 'static,
|
2020-01-13 14:27:41 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
|
|
|
let (tx, rx) = trigger();
|
|
|
|
// Clone the handle for passing into worker thread.
|
|
|
|
let stream = inner.clone();
|
|
|
|
let thread = thread::spawn(move || {
|
2020-01-19 14:06:19 +00:00
|
|
|
input_stream_worker(rx, &*stream, &mut data_callback, &mut error_callback);
|
2020-01-13 14:27:41 +00:00
|
|
|
});
|
|
|
|
Stream {
|
|
|
|
thread: Some(thread),
|
|
|
|
inner,
|
|
|
|
trigger: tx,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-19 14:06:19 +00:00
|
|
|
fn new_output<D, E>(
|
2020-01-13 14:27:41 +00:00
|
|
|
inner: Arc<StreamInner>,
|
|
|
|
mut data_callback: D,
|
|
|
|
mut error_callback: E,
|
|
|
|
) -> Stream
|
|
|
|
where
|
2020-04-16 12:50:36 +00:00
|
|
|
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
|
2020-01-13 14:27:41 +00:00
|
|
|
E: FnMut(StreamError) + Send + 'static,
|
|
|
|
{
|
2019-07-09 06:47:33 +00:00
|
|
|
let (tx, rx) = trigger();
|
|
|
|
// Clone the handle for passing into worker thread.
|
|
|
|
let stream = inner.clone();
|
|
|
|
let thread = thread::spawn(move || {
|
2020-01-19 14:06:19 +00:00
|
|
|
output_stream_worker(rx, &*stream, &mut data_callback, &mut error_callback);
|
2019-07-09 06:47:33 +00:00
|
|
|
});
|
|
|
|
Stream {
|
|
|
|
thread: Some(thread),
|
|
|
|
inner,
|
|
|
|
trigger: tx,
|
2019-06-21 22:06:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
impl Drop for Stream {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
self.trigger.wakeup();
|
|
|
|
self.thread.take().unwrap().join().unwrap();
|
|
|
|
}
|
2019-06-21 22:06:55 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 06:47:33 +00:00
|
|
|
impl StreamTrait for Stream {
|
|
|
|
fn play(&self) -> Result<(), PlayStreamError> {
|
2020-04-12 17:11:35 +00:00
|
|
|
self.inner.channel.pause(false).ok();
|
2019-07-09 06:47:33 +00:00
|
|
|
Ok(())
|
2019-06-21 22:06:55 +00:00
|
|
|
}
|
2020-01-20 19:35:23 +00:00
|
|
|
fn pause(&self) -> Result<(), PauseStreamError> {
|
2020-04-12 17:11:35 +00:00
|
|
|
self.inner.channel.pause(true).ok();
|
2019-07-09 06:47:33 +00:00
|
|
|
Ok(())
|
2019-06-21 22:06:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-27 16:00:40 +00:00
|
|
|
// Determine the number of frames that are available to read/write along with the latency.
|
|
|
|
fn get_avail_delay(stream: &StreamInner) -> Result<(usize, usize), BackendSpecificError> {
|
|
|
|
match stream.channel.avail_delay() {
|
2020-04-03 16:14:52 +00:00
|
|
|
Err(err) if err.errno() == Some(nix::errno::Errno::EPIPE) => {
|
|
|
|
// buffer underrun
|
|
|
|
// TODO: Notify the user some how.
|
2020-04-27 16:00:40 +00:00
|
|
|
Ok((stream.buffer_len, 0))
|
2020-04-03 16:14:52 +00:00
|
|
|
}
|
|
|
|
Err(err) => Err(err.into()),
|
2020-04-27 16:00:40 +00:00
|
|
|
Ok((avail, delay)) => Ok((avail as usize, delay as usize)),
|
2019-06-21 22:06:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
fn set_hw_params_from_format<'a>(
|
|
|
|
pcm_handle: &'a alsa::pcm::PCM,
|
2020-02-02 17:28:38 +00:00
|
|
|
config: &StreamConfig,
|
|
|
|
sample_format: SampleFormat,
|
2020-04-03 16:14:52 +00:00
|
|
|
) -> Result<alsa::pcm::HwParams<'a>, BackendSpecificError> {
|
2020-04-15 17:41:40 +00:00
|
|
|
let mut hw_params = alsa::pcm::HwParams::any(pcm_handle)?;
|
2020-04-03 16:14:52 +00:00
|
|
|
hw_params.set_access(alsa::pcm::Access::RWInterleaved)?;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
2020-01-27 20:28:07 +00:00
|
|
|
let sample_format = if cfg!(target_endian = "big") {
|
2020-02-02 17:28:38 +00:00
|
|
|
match sample_format {
|
2020-04-03 16:14:52 +00:00
|
|
|
SampleFormat::I16 => alsa::pcm::Format::S16BE,
|
|
|
|
SampleFormat::U16 => alsa::pcm::Format::U16BE,
|
|
|
|
SampleFormat::F32 => alsa::pcm::Format::FloatBE,
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-02-02 17:28:38 +00:00
|
|
|
match sample_format {
|
2020-04-03 16:14:52 +00:00
|
|
|
SampleFormat::I16 => alsa::pcm::Format::S16LE,
|
|
|
|
SampleFormat::U16 => alsa::pcm::Format::U16LE,
|
|
|
|
SampleFormat::F32 => alsa::pcm::Format::FloatLE,
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
hw_params.set_format(sample_format)?;
|
|
|
|
hw_params.set_rate(config.sample_rate.0, alsa::ValueOr::Nearest)?;
|
|
|
|
hw_params.set_channels(config.channels as u32)?;
|
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
|
2020-04-15 17:41:40 +00:00
|
|
|
let mut hw_params_copy = hw_params.clone();
|
|
|
|
if let Err(_) = hw_params.set_buffer_time_near(100_000, alsa::ValueOr::Nearest) {
|
2020-04-15 17:46:54 +00:00
|
|
|
// Swap out the params with errors for a snapshot taken before the error was introduced.
|
2020-04-15 17:41:40 +00:00
|
|
|
mem::swap(&mut hw_params_copy, &mut hw_params);
|
|
|
|
}
|
2019-08-31 12:33:57 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
pcm_handle.hw_params(&hw_params)?;
|
2019-05-31 20:08:39 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
Ok(hw_params)
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
fn set_sw_params_from_format(
|
|
|
|
pcm_handle: &alsa::pcm::PCM,
|
2020-02-02 17:28:38 +00:00
|
|
|
config: &StreamConfig,
|
2020-04-03 16:14:52 +00:00
|
|
|
) -> Result<(usize, usize), BackendSpecificError> {
|
|
|
|
let sw_params = pcm_handle.sw_params_current()?;
|
|
|
|
sw_params.set_start_threshold(0)?;
|
2018-02-12 13:10:24 +00:00
|
|
|
|
|
|
|
let (buffer_len, period_len) = {
|
2020-04-03 16:14:52 +00:00
|
|
|
let (buffer, period) = pcm_handle.get_params()?;
|
2019-06-21 00:38:59 +00:00
|
|
|
if buffer == 0 {
|
2020-04-03 16:14:52 +00:00
|
|
|
return Err(BackendSpecificError {
|
|
|
|
description: "initialization resulted in a null buffer".to_string(),
|
|
|
|
});
|
2019-06-21 00:38:59 +00:00
|
|
|
}
|
2020-04-03 16:14:52 +00:00
|
|
|
sw_params.set_avail_min(period as alsa::pcm::Frames)?;
|
2020-02-02 17:28:38 +00:00
|
|
|
let buffer = buffer as usize * config.channels as usize;
|
|
|
|
let period = period as usize * config.channels as usize;
|
2018-02-12 13:10:24 +00:00
|
|
|
(buffer, period)
|
|
|
|
};
|
|
|
|
|
2020-04-27 16:00:40 +00:00
|
|
|
sw_params.set_tstamp_mode(true)?;
|
2020-04-28 14:38:57 +00:00
|
|
|
// TODO:
|
|
|
|
// Pending new version of alsa-sys and alsa-rs getting published.
|
|
|
|
// sw_params.set_tstamp_type(alsa::pcm::TstampType::MonotonicRaw)?;
|
2020-04-27 16:00:40 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
pcm_handle.sw_params(&sw_params)?;
|
2019-06-21 00:38:59 +00:00
|
|
|
|
|
|
|
Ok((buffer_len, period_len))
|
2018-02-12 13:10:24 +00:00
|
|
|
}
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
impl From<alsa::Error> for BackendSpecificError {
|
|
|
|
fn from(err: alsa::Error) -> Self {
|
|
|
|
BackendSpecificError {
|
|
|
|
description: err.to_string(),
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
impl From<alsa::Error> for BuildStreamError {
|
|
|
|
fn from(err: alsa::Error) -> Self {
|
|
|
|
let err: BackendSpecificError = err.into();
|
|
|
|
err.into()
|
2014-12-22 13:16:47 +00:00
|
|
|
}
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
impl From<alsa::Error> for SupportedStreamConfigsError {
|
|
|
|
fn from(err: alsa::Error) -> Self {
|
|
|
|
let err: BackendSpecificError = err.into();
|
|
|
|
err.into()
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
impl From<alsa::Error> for PlayStreamError {
|
|
|
|
fn from(err: alsa::Error) -> Self {
|
|
|
|
let err: BackendSpecificError = err.into();
|
|
|
|
err.into()
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|
2020-04-03 16:14:52 +00:00
|
|
|
}
|
2014-12-16 15:45:45 +00:00
|
|
|
|
2020-04-03 16:14:52 +00:00
|
|
|
impl From<alsa::Error> for PauseStreamError {
|
|
|
|
fn from(err: alsa::Error) -> Self {
|
|
|
|
let err: BackendSpecificError = err.into();
|
|
|
|
err.into()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<alsa::Error> for StreamError {
|
|
|
|
fn from(err: alsa::Error) -> Self {
|
|
|
|
let err: BackendSpecificError = err.into();
|
|
|
|
err.into()
|
|
|
|
}
|
2014-12-16 15:45:45 +00:00
|
|
|
}
|