Merge pull request #386 from alexmoon/alsa
Switch from vendored alsa-sys to alsa crate
This commit is contained in:
commit
7df5b4e973
|
@ -27,8 +27,9 @@ asio-sys = { version = "0.1", path = "asio-sys", optional = true }
|
|||
parking_lot = "0.9"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies]
|
||||
alsa-sys = { version = "0.1", path = "alsa-sys" }
|
||||
libc = "0.2"
|
||||
alsa = "0.4.1"
|
||||
nix = "0.15.0"
|
||||
libc = "0.2.65"
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
|
||||
coreaudio-rs = { version = "0.9.1", default-features = false, features = ["audio_unit", "core_audio"] }
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
.cargo/
|
|
@ -1,14 +0,0 @@
|
|||
[package]
|
||||
name = "alsa-sys"
|
||||
version = "0.1.1"
|
||||
authors = ["Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
build = "build.rs"
|
||||
description = "Bindings for the ALSA project (Advanced Linux Sound Architecture)"
|
||||
license = "MIT"
|
||||
links = "alsa"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.7"
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3"
|
|
@ -1,5 +0,0 @@
|
|||
extern crate pkg_config;
|
||||
|
||||
fn main() {
|
||||
pkg_config::find_library("alsa").unwrap();
|
||||
}
|
3777
alsa-sys/src/lib.rs
3777
alsa-sys/src/lib.rs
File diff suppressed because it is too large
Load Diff
|
@ -1,95 +1,37 @@
|
|||
use super::alsa;
|
||||
use super::check_errors;
|
||||
use super::Device;
|
||||
use std::ffi::CString;
|
||||
use std::ptr;
|
||||
use {BackendSpecificError, DevicesError};
|
||||
|
||||
/// ALSA implementation for `Devices`.
|
||||
pub struct Devices {
|
||||
// we keep the original list so that we can pass it to the free function
|
||||
global_list: *const *const u8,
|
||||
|
||||
// pointer to the next string ; contained within `global_list`
|
||||
next_str: *const *const u8,
|
||||
hint_iter: alsa::device_name::HintIter,
|
||||
}
|
||||
|
||||
impl Devices {
|
||||
pub fn new() -> Result<Self, DevicesError> {
|
||||
unsafe {
|
||||
// TODO: check in which situation this can fail.
|
||||
let card = -1; // -1 means all cards.
|
||||
let iface = b"pcm\0"; // Interface identification.
|
||||
let mut hints = ptr::null_mut(); // Array of device name hints.
|
||||
let res = alsa::snd_device_name_hint(card, iface.as_ptr() as *const _, &mut hints);
|
||||
if let Err(description) = check_errors(res) {
|
||||
let err = BackendSpecificError { description };
|
||||
return Err(err.into());
|
||||
}
|
||||
let hints = hints as *const *const u8;
|
||||
let devices = Devices {
|
||||
global_list: hints,
|
||||
next_str: hints,
|
||||
};
|
||||
Ok(devices)
|
||||
}
|
||||
Ok(Devices {
|
||||
hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Devices {}
|
||||
unsafe impl Sync for Devices {}
|
||||
|
||||
impl Drop for Devices {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
alsa::snd_device_name_free_hint(self.global_list as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Devices {
|
||||
type Item = Device;
|
||||
|
||||
fn next(&mut self) -> Option<Device> {
|
||||
loop {
|
||||
unsafe {
|
||||
if (*self.next_str).is_null() {
|
||||
return None;
|
||||
}
|
||||
match self.hint_iter.next() {
|
||||
None => return None,
|
||||
Some(hint) => {
|
||||
let name = hint.name;
|
||||
|
||||
let name = {
|
||||
let n_ptr = alsa::snd_device_name_get_hint(
|
||||
*self.next_str as *const _,
|
||||
b"NAME\0".as_ptr() as *const _,
|
||||
);
|
||||
if !n_ptr.is_null() {
|
||||
let bytes = CString::from_raw(n_ptr).into_bytes();
|
||||
let string = String::from_utf8(bytes).unwrap();
|
||||
Some(string)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let io = {
|
||||
let n_ptr = alsa::snd_device_name_get_hint(
|
||||
*self.next_str as *const _,
|
||||
b"IOID\0".as_ptr() as *const _,
|
||||
);
|
||||
if !n_ptr.is_null() {
|
||||
let bytes = CString::from_raw(n_ptr).into_bytes();
|
||||
let string = String::from_utf8(bytes).unwrap();
|
||||
Some(string)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
self.next_str = self.next_str.offset(1);
|
||||
let io = hint.direction;
|
||||
|
||||
if let Some(io) = io {
|
||||
if io != "Output" {
|
||||
if io != alsa::Direction::Playback {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -105,32 +47,19 @@ impl Iterator for Devices {
|
|||
_ => continue,
|
||||
};
|
||||
|
||||
// trying to open the PCM device to see if it can be opened
|
||||
let name_zeroed = CString::new(&name[..]).unwrap();
|
||||
|
||||
// See if the device has an available output stream.
|
||||
let mut playback_handle = ptr::null_mut();
|
||||
let has_available_output = alsa::snd_pcm_open(
|
||||
&mut playback_handle,
|
||||
name_zeroed.as_ptr() as *const _,
|
||||
alsa::SND_PCM_STREAM_PLAYBACK,
|
||||
alsa::SND_PCM_NONBLOCK,
|
||||
) == 0;
|
||||
if has_available_output {
|
||||
alsa::snd_pcm_close(playback_handle);
|
||||
}
|
||||
let has_available_output = {
|
||||
let playback_handle =
|
||||
alsa::pcm::PCM::new(&name, alsa::Direction::Playback, true);
|
||||
playback_handle.is_ok()
|
||||
};
|
||||
|
||||
// See if the device has an available input stream.
|
||||
let mut capture_handle = ptr::null_mut();
|
||||
let has_available_input = alsa::snd_pcm_open(
|
||||
&mut capture_handle,
|
||||
name_zeroed.as_ptr() as *const _,
|
||||
alsa::SND_PCM_STREAM_CAPTURE,
|
||||
alsa::SND_PCM_NONBLOCK,
|
||||
) == 0;
|
||||
if has_available_input {
|
||||
alsa::snd_pcm_close(capture_handle);
|
||||
}
|
||||
let has_available_input = {
|
||||
let capture_handle =
|
||||
alsa::pcm::PCM::new(&name, alsa::Direction::Capture, true);
|
||||
capture_handle.is_ok()
|
||||
};
|
||||
|
||||
if has_available_output || has_available_input {
|
||||
return Some(Device(name));
|
||||
|
@ -139,6 +68,7 @@ impl Iterator for Devices {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn default_input_device() -> Option<Device> {
|
||||
|
@ -149,3 +79,10 @@ pub fn default_input_device() -> Option<Device> {
|
|||
pub fn default_output_device() -> Option<Device> {
|
||||
Some(Device("default".to_owned()))
|
||||
}
|
||||
|
||||
impl From<alsa::Error> for DevicesError {
|
||||
fn from(err: alsa::Error) -> Self {
|
||||
let err: BackendSpecificError = err.into();
|
||||
err.into()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
extern crate alsa_sys as alsa;
|
||||
extern crate alsa;
|
||||
extern crate libc;
|
||||
|
||||
use self::libc::c_long;
|
||||
use self::alsa::poll::Descriptors;
|
||||
use crate::{
|
||||
BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError,
|
||||
DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate,
|
||||
StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange,
|
||||
SupportedStreamConfigsError,
|
||||
};
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use std::thread::{self, JoinHandle};
|
||||
use std::vec::IntoIter as VecIntoIter;
|
||||
use std::{cmp, ffi, io, ptr};
|
||||
use traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
|
||||
pub use self::enumerate::{default_input_device, default_output_device, Devices};
|
||||
|
@ -94,7 +94,7 @@ impl DeviceTrait for Device {
|
|||
E: FnMut(StreamError) + Send + 'static,
|
||||
{
|
||||
let stream_inner =
|
||||
self.build_stream_inner(conf, sample_format, alsa::SND_PCM_STREAM_CAPTURE)?;
|
||||
self.build_stream_inner(conf, sample_format, alsa::Direction::Capture)?;
|
||||
let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback);
|
||||
Ok(stream)
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ impl DeviceTrait for Device {
|
|||
E: FnMut(StreamError) + Send + 'static,
|
||||
{
|
||||
let stream_inner =
|
||||
self.build_stream_inner(conf, sample_format, alsa::SND_PCM_STREAM_PLAYBACK)?;
|
||||
self.build_stream_inner(conf, sample_format, alsa::Direction::Playback)?;
|
||||
let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback);
|
||||
Ok(stream)
|
||||
}
|
||||
|
@ -169,55 +169,41 @@ impl Device {
|
|||
&self,
|
||||
conf: &StreamConfig,
|
||||
sample_format: SampleFormat,
|
||||
stream_type: alsa::snd_pcm_stream_t,
|
||||
stream_type: alsa::Direction,
|
||||
) -> Result<StreamInner, BuildStreamError> {
|
||||
let name = ffi::CString::new(self.0.clone()).expect("unable to clone device");
|
||||
let name = &self.0;
|
||||
|
||||
let handle = unsafe {
|
||||
let mut handle = ptr::null_mut();
|
||||
match alsa::snd_pcm_open(
|
||||
&mut handle,
|
||||
name.as_ptr(),
|
||||
stream_type,
|
||||
alsa::SND_PCM_NONBLOCK,
|
||||
) {
|
||||
-16 /* determined empirically */ => return Err(BuildStreamError::DeviceNotAvailable),
|
||||
-22 => return Err(BuildStreamError::InvalidArgument),
|
||||
e => if let Err(description) = check_errors(e) {
|
||||
let err = BackendSpecificError { description };
|
||||
return Err(err.into());
|
||||
let handle = match alsa::pcm::PCM::new(name, stream_type, true).map_err(|e| (e, e.errno()))
|
||||
{
|
||||
Err((_, Some(nix::errno::Errno::EBUSY))) => {
|
||||
return Err(BuildStreamError::DeviceNotAvailable)
|
||||
}
|
||||
Err((_, Some(nix::errno::Errno::EINVAL))) => {
|
||||
return Err(BuildStreamError::InvalidArgument)
|
||||
}
|
||||
handle
|
||||
Err((e, _)) => return Err(e.into()),
|
||||
Ok(handle) => handle,
|
||||
};
|
||||
let can_pause = unsafe {
|
||||
let hw_params = HwParams::alloc();
|
||||
set_hw_params_from_format(handle, &hw_params, conf, sample_format)
|
||||
.map_err(|description| BackendSpecificError { description })?;
|
||||
let can_pause = {
|
||||
let hw_params = set_hw_params_from_format(&handle, conf, sample_format)?;
|
||||
hw_params.can_pause()
|
||||
};
|
||||
let (buffer_len, period_len) = set_sw_params_from_format(&handle, conf)?;
|
||||
|
||||
alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1
|
||||
};
|
||||
let (buffer_len, period_len) = unsafe {
|
||||
set_sw_params_from_format(handle, conf)
|
||||
.map_err(|description| BackendSpecificError { description })?
|
||||
};
|
||||
|
||||
if let Err(desc) = check_errors(unsafe { alsa::snd_pcm_prepare(handle) }) {
|
||||
let description = format!("could not get handle: {}", desc);
|
||||
let err = BackendSpecificError { description };
|
||||
return Err(err.into());
|
||||
}
|
||||
handle.prepare()?;
|
||||
|
||||
let num_descriptors = {
|
||||
let num_descriptors = unsafe { alsa::snd_pcm_poll_descriptors_count(handle) };
|
||||
let num_descriptors = handle.count();
|
||||
if num_descriptors == 0 {
|
||||
let description = "poll descriptor count for stream was 0".to_string();
|
||||
let err = BackendSpecificError { description };
|
||||
return Err(err.into());
|
||||
}
|
||||
num_descriptors as usize
|
||||
num_descriptors
|
||||
};
|
||||
|
||||
handle.start()?;
|
||||
|
||||
let stream_inner = StreamInner {
|
||||
channel: handle,
|
||||
sample_format,
|
||||
|
@ -228,12 +214,6 @@ impl Device {
|
|||
can_pause,
|
||||
};
|
||||
|
||||
if let Err(desc) = check_errors(unsafe { alsa::snd_pcm_start(handle) }) {
|
||||
let description = format!("could not start stream: {}", desc);
|
||||
let err = BackendSpecificError { description };
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
Ok(stream_inner)
|
||||
}
|
||||
|
||||
|
@ -242,51 +222,33 @@ impl Device {
|
|||
Ok(self.0.clone())
|
||||
}
|
||||
|
||||
unsafe fn supported_configs(
|
||||
fn supported_configs(
|
||||
&self,
|
||||
stream_t: alsa::snd_pcm_stream_t,
|
||||
stream_t: alsa::Direction,
|
||||
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
|
||||
let mut handle = ptr::null_mut();
|
||||
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());
|
||||
let name = &self.0;
|
||||
|
||||
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)
|
||||
}
|
||||
Err((_, Some(nix::errno::Errno::EINVAL))) => {
|
||||
return Err(SupportedStreamConfigsError::InvalidArgument)
|
||||
}
|
||||
Err((e, _)) => return Err(e.into()),
|
||||
Ok(handle) => handle,
|
||||
};
|
||||
|
||||
match alsa::snd_pcm_open(
|
||||
&mut handle,
|
||||
device_name.as_ptr() as *const _,
|
||||
stream_t,
|
||||
alsa::SND_PCM_NONBLOCK,
|
||||
) {
|
||||
-2 |
|
||||
-16 /* determined empirically */ => return Err(SupportedStreamConfigsError::DeviceNotAvailable),
|
||||
-22 => return Err(SupportedStreamConfigsError::InvalidArgument),
|
||||
e => if let Err(description) = check_errors(e) {
|
||||
let err = BackendSpecificError { description };
|
||||
return Err(err.into())
|
||||
}
|
||||
}
|
||||
|
||||
let hw_params = HwParams::alloc();
|
||||
match check_errors(alsa::snd_pcm_hw_params_any(handle, hw_params.0)) {
|
||||
Err(description) => {
|
||||
let err = BackendSpecificError { description };
|
||||
return Err(err.into());
|
||||
}
|
||||
Ok(_) => (),
|
||||
};
|
||||
let hw_params = alsa::pcm::HwParams::any(&handle)?;
|
||||
|
||||
// TODO: check endianess
|
||||
const FORMATS: [(SampleFormat, alsa::snd_pcm_format_t); 3] = [
|
||||
const FORMATS: [(SampleFormat, alsa::pcm::Format); 3] = [
|
||||
//SND_PCM_FORMAT_S8,
|
||||
//SND_PCM_FORMAT_U8,
|
||||
(SampleFormat::I16, alsa::SND_PCM_FORMAT_S16_LE),
|
||||
(SampleFormat::I16, alsa::pcm::Format::S16LE),
|
||||
//SND_PCM_FORMAT_S16_BE,
|
||||
(SampleFormat::U16, alsa::SND_PCM_FORMAT_U16_LE),
|
||||
(SampleFormat::U16, alsa::pcm::Format::U16LE),
|
||||
//SND_PCM_FORMAT_U16_BE,
|
||||
//SND_PCM_FORMAT_S24_LE,
|
||||
//SND_PCM_FORMAT_S24_BE,
|
||||
|
@ -296,7 +258,7 @@ impl Device {
|
|||
//SND_PCM_FORMAT_S32_BE,
|
||||
//SND_PCM_FORMAT_U32_LE,
|
||||
//SND_PCM_FORMAT_U32_BE,
|
||||
(SampleFormat::F32, alsa::SND_PCM_FORMAT_FLOAT_LE),
|
||||
(SampleFormat::F32, alsa::pcm::Format::FloatLE),
|
||||
//SND_PCM_FORMAT_FLOAT_BE,
|
||||
//SND_PCM_FORMAT_FLOAT64_LE,
|
||||
//SND_PCM_FORMAT_FLOAT64_BE,
|
||||
|
@ -324,36 +286,15 @@ impl Device {
|
|||
|
||||
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 {
|
||||
if hw_params.test_format(alsa_format).is_ok() {
|
||||
supported_formats.push(sample_format);
|
||||
}
|
||||
}
|
||||
|
||||
let mut min_rate = 0;
|
||||
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());
|
||||
}
|
||||
let min_rate = hw_params.get_rate_min()?;
|
||||
let max_rate = hw_params.get_rate_max()?;
|
||||
|
||||
let mut max_rate = 0;
|
||||
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());
|
||||
}
|
||||
|
||||
let sample_rates = if min_rate == max_rate
|
||||
|| alsa::snd_pcm_hw_params_test_rate(handle, hw_params.0, min_rate + 1, 0) == 0
|
||||
{
|
||||
let sample_rates = if min_rate == max_rate || hw_params.test_rate(min_rate + 1).is_ok() {
|
||||
vec![(min_rate, max_rate)]
|
||||
} else {
|
||||
const RATES: [libc::c_uint; 13] = [
|
||||
|
@ -363,7 +304,7 @@ impl Device {
|
|||
|
||||
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 {
|
||||
if hw_params.test_rate(rate).is_ok() {
|
||||
rates.push((rate, rate));
|
||||
}
|
||||
}
|
||||
|
@ -375,30 +316,13 @@ impl Device {
|
|||
}
|
||||
};
|
||||
|
||||
let mut min_channels = 0;
|
||||
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());
|
||||
}
|
||||
|
||||
let mut max_channels = 0;
|
||||
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());
|
||||
}
|
||||
let min_channels = hw_params.get_channels_min()?;
|
||||
let max_channels = hw_params.get_channels_max()?;
|
||||
|
||||
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 {
|
||||
if hw_params.test_channels(num).is_ok() {
|
||||
Some(num as ChannelCount)
|
||||
} else {
|
||||
None
|
||||
|
@ -422,30 +346,28 @@ impl Device {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: RAII
|
||||
alsa::snd_pcm_close(handle);
|
||||
Ok(output.into_iter())
|
||||
}
|
||||
|
||||
fn supported_input_configs(
|
||||
&self,
|
||||
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
|
||||
unsafe { self.supported_configs(alsa::SND_PCM_STREAM_CAPTURE) }
|
||||
self.supported_configs(alsa::Direction::Capture)
|
||||
}
|
||||
|
||||
fn supported_output_configs(
|
||||
&self,
|
||||
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
|
||||
unsafe { self.supported_configs(alsa::SND_PCM_STREAM_PLAYBACK) }
|
||||
self.supported_configs(alsa::Direction::Playback)
|
||||
}
|
||||
|
||||
// ALSA does not offer default stream formats, so instead we compare all supported formats by
|
||||
// the `SupportedStreamConfigRange::cmp_default_heuristics` order and select the greatest.
|
||||
fn default_config(
|
||||
&self,
|
||||
stream_t: alsa::snd_pcm_stream_t,
|
||||
stream_t: alsa::Direction,
|
||||
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
||||
let mut formats: Vec<_> = unsafe {
|
||||
let mut formats: Vec<_> = {
|
||||
match self.supported_configs(stream_t) {
|
||||
Err(SupportedStreamConfigsError::DeviceNotAvailable) => {
|
||||
return Err(DefaultStreamConfigError::DeviceNotAvailable);
|
||||
|
@ -480,17 +402,17 @@ impl Device {
|
|||
}
|
||||
|
||||
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
||||
self.default_config(alsa::SND_PCM_STREAM_CAPTURE)
|
||||
self.default_config(alsa::Direction::Capture)
|
||||
}
|
||||
|
||||
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
||||
self.default_config(alsa::SND_PCM_STREAM_PLAYBACK)
|
||||
self.default_config(alsa::Direction::Playback)
|
||||
}
|
||||
}
|
||||
|
||||
struct StreamInner {
|
||||
// The ALSA channel.
|
||||
channel: *mut alsa::snd_pcm_t,
|
||||
channel: alsa::pcm::PCM,
|
||||
|
||||
// When converting between file descriptors and `snd_pcm_t`, this is the number of
|
||||
// file descriptors that this `snd_pcm_t` uses.
|
||||
|
@ -513,8 +435,6 @@ struct StreamInner {
|
|||
}
|
||||
|
||||
// Assume that the ALSA library is built with thread safe option.
|
||||
unsafe impl Send for StreamInner {}
|
||||
|
||||
unsafe impl Sync for StreamInner {}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
|
@ -549,11 +469,17 @@ fn input_stream_worker(
|
|||
) {
|
||||
let mut ctxt = StreamWorkerContext::default();
|
||||
loop {
|
||||
match poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt, error_callback) {
|
||||
let flow = report_error(
|
||||
poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt),
|
||||
error_callback,
|
||||
)
|
||||
.unwrap_or(PollDescriptorsFlow::Continue);
|
||||
|
||||
match flow {
|
||||
PollDescriptorsFlow::Continue => continue,
|
||||
PollDescriptorsFlow::Return => return,
|
||||
PollDescriptorsFlow::Ready {
|
||||
available_frames,
|
||||
available_frames: _,
|
||||
stream_type,
|
||||
} => {
|
||||
assert_eq!(
|
||||
|
@ -561,11 +487,8 @@ fn input_stream_worker(
|
|||
StreamType::Input,
|
||||
"expected input stream, but polling descriptors indicated output",
|
||||
);
|
||||
process_input(
|
||||
stream,
|
||||
&mut ctxt.buffer,
|
||||
available_frames,
|
||||
data_callback,
|
||||
report_error(
|
||||
process_input(stream, &mut ctxt.buffer, data_callback),
|
||||
error_callback,
|
||||
);
|
||||
}
|
||||
|
@ -581,7 +504,13 @@ fn output_stream_worker(
|
|||
) {
|
||||
let mut ctxt = StreamWorkerContext::default();
|
||||
loop {
|
||||
match poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt, error_callback) {
|
||||
let flow = report_error(
|
||||
poll_descriptors_and_prepare_buffer(&rx, stream, &mut ctxt),
|
||||
error_callback,
|
||||
)
|
||||
.unwrap_or(PollDescriptorsFlow::Continue);
|
||||
|
||||
match flow {
|
||||
PollDescriptorsFlow::Continue => continue,
|
||||
PollDescriptorsFlow::Return => return,
|
||||
PollDescriptorsFlow::Ready {
|
||||
|
@ -605,6 +534,22 @@ fn output_stream_worker(
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PollDescriptorsFlow {
|
||||
Continue,
|
||||
Return,
|
||||
|
@ -619,8 +564,7 @@ fn poll_descriptors_and_prepare_buffer(
|
|||
rx: &TriggerReceiver,
|
||||
stream: &StreamInner,
|
||||
ctxt: &mut StreamWorkerContext,
|
||||
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static),
|
||||
) -> PollDescriptorsFlow {
|
||||
) -> Result<PollDescriptorsFlow, BackendSpecificError> {
|
||||
let StreamWorkerContext {
|
||||
ref mut descriptors,
|
||||
ref mut buffer,
|
||||
|
@ -636,68 +580,45 @@ fn poll_descriptors_and_prepare_buffer(
|
|||
});
|
||||
|
||||
// Add ALSA polling fds.
|
||||
descriptors.reserve(stream.num_descriptors);
|
||||
let len = descriptors.len();
|
||||
let filled = unsafe {
|
||||
alsa::snd_pcm_poll_descriptors(
|
||||
stream.channel,
|
||||
descriptors[len..].as_mut_ptr(),
|
||||
stream.num_descriptors as libc::c_uint,
|
||||
)
|
||||
};
|
||||
debug_assert_eq!(filled, stream.num_descriptors as libc::c_int);
|
||||
unsafe {
|
||||
descriptors.set_len(len + stream.num_descriptors);
|
||||
}
|
||||
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);
|
||||
|
||||
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: {}", io::Error::last_os_error());
|
||||
error_callback(BackendSpecificError { description }.into());
|
||||
return PollDescriptorsFlow::Continue;
|
||||
} else if res == 0 {
|
||||
let description = String::from("`libc::poll()` spuriously returned");
|
||||
error_callback(BackendSpecificError { description }.into());
|
||||
return PollDescriptorsFlow::Continue;
|
||||
let res = alsa::poll::poll(descriptors, -1)?;
|
||||
if res == 0 {
|
||||
let description = String::from("`alsa::poll()` spuriously returned");
|
||||
return Err(BackendSpecificError { description });
|
||||
}
|
||||
|
||||
if descriptors[0].revents != 0 {
|
||||
// The stream has been requested to be destroyed.
|
||||
rx.clear_pipe();
|
||||
return PollDescriptorsFlow::Return;
|
||||
return Ok(PollDescriptorsFlow::Return);
|
||||
}
|
||||
|
||||
let stream_type = match check_for_pollout_or_pollin(stream, descriptors[1..].as_mut_ptr()) {
|
||||
Ok(Some(ty)) => ty,
|
||||
Ok(None) => {
|
||||
let stream_type = match stream.channel.revents(&descriptors[1..])? {
|
||||
alsa::poll::Flags::OUT => StreamType::Output,
|
||||
alsa::poll::Flags::IN => StreamType::Input,
|
||||
_ => {
|
||||
// Nothing to process, poll again
|
||||
return PollDescriptorsFlow::Continue;
|
||||
}
|
||||
Err(err) => {
|
||||
error_callback(err.into());
|
||||
return PollDescriptorsFlow::Continue;
|
||||
return Ok(PollDescriptorsFlow::Continue);
|
||||
}
|
||||
};
|
||||
// Get the number of available samples for reading/writing.
|
||||
let available_samples = match get_available_samples(stream) {
|
||||
Ok(n) => n,
|
||||
Err(err) => {
|
||||
let description = format!("Failed to query the number of available samples: {}", err);
|
||||
error_callback(BackendSpecificError { description }.into());
|
||||
return PollDescriptorsFlow::Continue;
|
||||
}
|
||||
};
|
||||
let available_samples = get_available_samples(stream)?;
|
||||
|
||||
// Only go on if there is at least `stream.period_len` samples.
|
||||
if available_samples < stream.period_len {
|
||||
return PollDescriptorsFlow::Continue;
|
||||
return Ok(PollDescriptorsFlow::Continue);
|
||||
}
|
||||
|
||||
// Prepare the data buffer.
|
||||
|
@ -705,37 +626,26 @@ fn poll_descriptors_and_prepare_buffer(
|
|||
buffer.resize(buffer_size, 0u8);
|
||||
let available_frames = available_samples / stream.num_channels as usize;
|
||||
|
||||
PollDescriptorsFlow::Ready {
|
||||
Ok(PollDescriptorsFlow::Ready {
|
||||
stream_type,
|
||||
available_frames,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Read input data from ALSA and deliver it to the user.
|
||||
fn process_input(
|
||||
stream: &StreamInner,
|
||||
buffer: &mut [u8],
|
||||
available_frames: usize,
|
||||
data_callback: &mut (dyn FnMut(&Data) + Send + 'static),
|
||||
error_callback: &mut dyn FnMut(StreamError),
|
||||
) {
|
||||
let result = unsafe {
|
||||
alsa::snd_pcm_readi(
|
||||
stream.channel,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
available_frames as alsa::snd_pcm_uframes_t,
|
||||
)
|
||||
};
|
||||
if let Err(err) = check_errors(result as _) {
|
||||
let description = format!("`snd_pcm_readi` failed: {}", err);
|
||||
error_callback(BackendSpecificError { description }.into());
|
||||
return;
|
||||
}
|
||||
) -> Result<(), BackendSpecificError> {
|
||||
stream.channel.io().readi(buffer)?;
|
||||
let sample_format = stream.sample_format;
|
||||
let data = buffer.as_mut_ptr() as *mut ();
|
||||
let len = buffer.len() / sample_format.sample_size();
|
||||
let data = unsafe { Data::from_parts(data, len, sample_format) };
|
||||
data_callback(&data);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Request data from the user's function and write it via ALSA.
|
||||
|
@ -757,22 +667,17 @@ fn process_output(
|
|||
data_callback(&mut data);
|
||||
}
|
||||
loop {
|
||||
let result = unsafe {
|
||||
alsa::snd_pcm_writei(
|
||||
stream.channel,
|
||||
buffer.as_ptr() as *const _,
|
||||
available_frames as alsa::snd_pcm_uframes_t,
|
||||
)
|
||||
};
|
||||
if result == -libc::EPIPE as c_long {
|
||||
match stream.channel.io().writei(buffer) {
|
||||
Err(err) if err.errno() == Some(nix::errno::Errno::EPIPE) => {
|
||||
// buffer underrun
|
||||
// TODO: Notify the user of this.
|
||||
unsafe { alsa::snd_pcm_recover(stream.channel, result as i32, 0) };
|
||||
} else if let Err(err) = check_errors(result as _) {
|
||||
let description = format!("`snd_pcm_writei` failed: {}", err);
|
||||
error_callback(BackendSpecificError { description }.into());
|
||||
let _ = stream.channel.try_recover(err, false);
|
||||
}
|
||||
Err(err) => {
|
||||
error_callback(err.into());
|
||||
continue;
|
||||
} else if result as usize != available_frames {
|
||||
}
|
||||
Ok(result) if result != available_frames => {
|
||||
let description = format!(
|
||||
"unexpected number of frames written: expected {}, \
|
||||
result {} (this should never happen)",
|
||||
|
@ -780,11 +685,13 @@ fn process_output(
|
|||
);
|
||||
error_callback(BackendSpecificError { description }.into());
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
fn new_input<D, E>(
|
||||
|
@ -841,237 +748,126 @@ impl Drop for Stream {
|
|||
|
||||
impl StreamTrait for Stream {
|
||||
fn play(&self) -> Result<(), PlayStreamError> {
|
||||
unsafe {
|
||||
alsa::snd_pcm_pause(self.inner.channel, 0);
|
||||
}
|
||||
// TODO: error handling
|
||||
self.inner.channel.pause(false).ok();
|
||||
Ok(())
|
||||
}
|
||||
fn pause(&self) -> Result<(), PauseStreamError> {
|
||||
unsafe {
|
||||
alsa::snd_pcm_pause(self.inner.channel, 1);
|
||||
}
|
||||
// TODO: error handling
|
||||
self.inner.channel.pause(true).ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let mut revent = 0;
|
||||
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 { alsa::snd_pcm_avail_update(stream.channel) };
|
||||
if available == -32 {
|
||||
match stream.channel.avail_update() {
|
||||
Err(err) if err.errno() == Some(nix::errno::Errno::EPIPE) => {
|
||||
// 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)
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
Ok(available) => Ok(available as usize * stream.num_channels as usize),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set_hw_params_from_format(
|
||||
pcm_handle: *mut alsa::snd_pcm_t,
|
||||
hw_params: &HwParams,
|
||||
fn set_hw_params_from_format<'a>(
|
||||
pcm_handle: &'a alsa::pcm::PCM,
|
||||
config: &StreamConfig,
|
||||
sample_format: SampleFormat,
|
||||
) -> Result<(), String> {
|
||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) {
|
||||
return Err(format!("errors on pcm handle: {}", e));
|
||||
}
|
||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_access(
|
||||
pcm_handle,
|
||||
hw_params.0,
|
||||
alsa::SND_PCM_ACCESS_RW_INTERLEAVED,
|
||||
)) {
|
||||
return Err(format!("handle not acessible: {}", e));
|
||||
}
|
||||
) -> Result<alsa::pcm::HwParams<'a>, BackendSpecificError> {
|
||||
let hw_params = alsa::pcm::HwParams::any(pcm_handle)?;
|
||||
hw_params.set_access(alsa::pcm::Access::RWInterleaved)?;
|
||||
|
||||
let sample_format = if cfg!(target_endian = "big") {
|
||||
match sample_format {
|
||||
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_BE,
|
||||
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_BE,
|
||||
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_BE,
|
||||
SampleFormat::I16 => alsa::pcm::Format::S16BE,
|
||||
SampleFormat::U16 => alsa::pcm::Format::U16BE,
|
||||
SampleFormat::F32 => alsa::pcm::Format::FloatBE,
|
||||
}
|
||||
} else {
|
||||
match sample_format {
|
||||
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_LE,
|
||||
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_LE,
|
||||
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_LE,
|
||||
SampleFormat::I16 => alsa::pcm::Format::S16LE,
|
||||
SampleFormat::U16 => alsa::pcm::Format::U16LE,
|
||||
SampleFormat::F32 => alsa::pcm::Format::FloatLE,
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format(
|
||||
pcm_handle,
|
||||
hw_params.0,
|
||||
sample_format,
|
||||
)) {
|
||||
return Err(format!("format could not be set: {}", e));
|
||||
}
|
||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_rate(
|
||||
pcm_handle,
|
||||
hw_params.0,
|
||||
config.sample_rate.0 as libc::c_uint,
|
||||
0,
|
||||
)) {
|
||||
return Err(format!("sample rate could not be set: {}", e));
|
||||
}
|
||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_channels(
|
||||
pcm_handle,
|
||||
hw_params.0,
|
||||
config.channels as libc::c_uint,
|
||||
)) {
|
||||
return Err(format!("channel count could not be set: {}", e));
|
||||
}
|
||||
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)?;
|
||||
|
||||
// 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));
|
||||
hw_params.set_buffer_time_near(100_000, alsa::ValueOr::Nearest)?;
|
||||
|
||||
pcm_handle.hw_params(&hw_params)?;
|
||||
|
||||
Ok(hw_params)
|
||||
}
|
||||
|
||||
if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) {
|
||||
return Err(format!("hardware params could not be set: {}", e));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn set_sw_params_from_format(
|
||||
pcm_handle: *mut alsa::snd_pcm_t,
|
||||
fn set_sw_params_from_format(
|
||||
pcm_handle: &alsa::pcm::PCM,
|
||||
config: &StreamConfig,
|
||||
) -> Result<(usize, usize), String> {
|
||||
let mut sw_params = ptr::null_mut(); // TODO: RAII
|
||||
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
|
||||
));
|
||||
}
|
||||
) -> Result<(usize, usize), BackendSpecificError> {
|
||||
let sw_params = pcm_handle.sw_params_current()?;
|
||||
sw_params.set_start_threshold(0)?;
|
||||
|
||||
let (buffer_len, period_len) = {
|
||||
let mut buffer = 0;
|
||||
let mut period = 0;
|
||||
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));
|
||||
}
|
||||
let (buffer, period) = pcm_handle.get_params()?;
|
||||
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));
|
||||
return Err(BackendSpecificError {
|
||||
description: "initialization resulted in a null buffer".to_string(),
|
||||
});
|
||||
}
|
||||
sw_params.set_avail_min(period as alsa::pcm::Frames)?;
|
||||
let buffer = buffer as usize * config.channels as usize;
|
||||
let period = period as usize * config.channels as usize;
|
||||
(buffer, period)
|
||||
};
|
||||
|
||||
if let Err(e) = check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)) {
|
||||
return Err(format!("snd_pcm_sw_params failed: {}", e));
|
||||
}
|
||||
pcm_handle.sw_params(&sw_params)?;
|
||||
|
||||
alsa::snd_pcm_sw_params_free(sw_params);
|
||||
Ok((buffer_len, period_len))
|
||||
}
|
||||
|
||||
/// Wrapper around `hw_params`.
|
||||
struct HwParams(*mut alsa::snd_pcm_hw_params_t);
|
||||
|
||||
impl HwParams {
|
||||
pub fn alloc() -> HwParams {
|
||||
unsafe {
|
||||
let mut hw_params = ptr::null_mut();
|
||||
check_errors(alsa::snd_pcm_hw_params_malloc(&mut hw_params))
|
||||
.expect("unable to get hardware parameters");
|
||||
HwParams(hw_params)
|
||||
impl From<alsa::Error> for BackendSpecificError {
|
||||
fn from(err: alsa::Error) -> Self {
|
||||
BackendSpecificError {
|
||||
description: err.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HwParams {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
alsa::snd_pcm_hw_params_free(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for StreamInner {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
alsa::snd_pcm_close(self.channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn check_errors(err: libc::c_int) -> Result<(), String> {
|
||||
if err < 0 {
|
||||
unsafe {
|
||||
let s = ffi::CStr::from_ptr(alsa::snd_strerror(err))
|
||||
.to_bytes()
|
||||
.to_vec();
|
||||
let s = String::from_utf8(s).expect("Streaming error occured");
|
||||
return Err(s);
|
||||
impl From<alsa::Error> for BuildStreamError {
|
||||
fn from(err: alsa::Error) -> Self {
|
||||
let err: BackendSpecificError = err.into();
|
||||
err.into()
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
impl From<alsa::Error> for SupportedStreamConfigsError {
|
||||
fn from(err: alsa::Error) -> Self {
|
||||
let err: BackendSpecificError = err.into();
|
||||
err.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<alsa::Error> for PlayStreamError {
|
||||
fn from(err: alsa::Error) -> Self {
|
||||
let err: BackendSpecificError = err.into();
|
||||
err.into()
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue