Merge pull request #386 from alexmoon/alsa

Switch from vendored alsa-sys to alsa crate
This commit is contained in:
mitchmindtree 2020-04-15 17:56:18 +02:00 committed by GitHub
commit 7df5b4e973
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 258 additions and 4323 deletions

View File

@ -27,8 +27,9 @@ asio-sys = { version = "0.1", path = "asio-sys", optional = true }
parking_lot = "0.9" parking_lot = "0.9"
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies] [target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))'.dependencies]
alsa-sys = { version = "0.1", path = "alsa-sys" } alsa = "0.4.1"
libc = "0.2" nix = "0.15.0"
libc = "0.2.65"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
coreaudio-rs = { version = "0.9.1", default-features = false, features = ["audio_unit", "core_audio"] } coreaudio-rs = { version = "0.9.1", default-features = false, features = ["audio_unit", "core_audio"] }

3
alsa-sys/.gitignore vendored
View File

@ -1,3 +0,0 @@
/target
/Cargo.lock
.cargo/

View File

@ -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"

View File

@ -1,5 +0,0 @@
extern crate pkg_config;
fn main() {
pkg_config::find_library("alsa").unwrap();
}

File diff suppressed because it is too large Load Diff

View File

@ -1,139 +1,69 @@
use super::alsa; use super::alsa;
use super::check_errors;
use super::Device; use super::Device;
use std::ffi::CString;
use std::ptr;
use {BackendSpecificError, DevicesError}; use {BackendSpecificError, DevicesError};
/// ALSA implementation for `Devices`. /// ALSA implementation for `Devices`.
pub struct Devices { pub struct Devices {
// we keep the original list so that we can pass it to the free function hint_iter: alsa::device_name::HintIter,
global_list: *const *const u8,
// pointer to the next string ; contained within `global_list`
next_str: *const *const u8,
} }
impl Devices { impl Devices {
pub fn new() -> Result<Self, DevicesError> { pub fn new() -> Result<Self, DevicesError> {
unsafe { Ok(Devices {
// TODO: check in which situation this can fail. hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?,
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)
}
} }
} }
unsafe impl Send for Devices {} unsafe impl Send for Devices {}
unsafe impl Sync 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 { impl Iterator for Devices {
type Item = Device; type Item = Device;
fn next(&mut self) -> Option<Device> { fn next(&mut self) -> Option<Device> {
loop { loop {
unsafe { match self.hint_iter.next() {
if (*self.next_str).is_null() { None => return None,
return None; Some(hint) => {
} let name = hint.name;
let name = { let io = hint.direction;
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 = { if let Some(io) = io {
let n_ptr = alsa::snd_device_name_get_hint( if io != alsa::Direction::Playback {
*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);
if let Some(io) = io {
if io != "Output" {
continue;
}
}
let name = match name {
Some(name) => {
// Ignoring the `null` device.
if name == "null" {
continue; continue;
} }
name
} }
_ => continue,
};
// trying to open the PCM device to see if it can be opened let name = match name {
let name_zeroed = CString::new(&name[..]).unwrap(); Some(name) => {
// Ignoring the `null` device.
if name == "null" {
continue;
}
name
}
_ => continue,
};
// See if the device has an available output stream. // See if the device has an available output stream.
let mut playback_handle = ptr::null_mut(); let has_available_output = {
let has_available_output = alsa::snd_pcm_open( let playback_handle =
&mut playback_handle, alsa::pcm::PCM::new(&name, alsa::Direction::Playback, true);
name_zeroed.as_ptr() as *const _, playback_handle.is_ok()
alsa::SND_PCM_STREAM_PLAYBACK, };
alsa::SND_PCM_NONBLOCK,
) == 0;
if has_available_output {
alsa::snd_pcm_close(playback_handle);
}
// See if the device has an available input stream. // See if the device has an available input stream.
let mut capture_handle = ptr::null_mut(); let has_available_input = {
let has_available_input = alsa::snd_pcm_open( let capture_handle =
&mut capture_handle, alsa::pcm::PCM::new(&name, alsa::Direction::Capture, true);
name_zeroed.as_ptr() as *const _, capture_handle.is_ok()
alsa::SND_PCM_STREAM_CAPTURE, };
alsa::SND_PCM_NONBLOCK,
) == 0;
if has_available_input {
alsa::snd_pcm_close(capture_handle);
}
if has_available_output || has_available_input { if has_available_output || has_available_input {
return Some(Device(name)); return Some(Device(name));
}
} }
} }
} }
@ -149,3 +79,10 @@ pub fn default_input_device() -> Option<Device> {
pub fn default_output_device() -> Option<Device> { pub fn default_output_device() -> Option<Device> {
Some(Device("default".to_owned())) Some(Device("default".to_owned()))
} }
impl From<alsa::Error> for DevicesError {
fn from(err: alsa::Error) -> Self {
let err: BackendSpecificError = err.into();
err.into()
}
}

View File

@ -1,17 +1,17 @@
extern crate alsa_sys as alsa; extern crate alsa;
extern crate libc; extern crate libc;
use self::libc::c_long; use self::alsa::poll::Descriptors;
use crate::{ use crate::{
BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError, BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate, DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate,
StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange, StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange,
SupportedStreamConfigsError, SupportedStreamConfigsError,
}; };
use std::cmp;
use std::sync::Arc; use std::sync::Arc;
use std::thread::{self, JoinHandle}; use std::thread::{self, JoinHandle};
use std::vec::IntoIter as VecIntoIter; use std::vec::IntoIter as VecIntoIter;
use std::{cmp, ffi, io, ptr};
use traits::{DeviceTrait, HostTrait, StreamTrait}; use traits::{DeviceTrait, HostTrait, StreamTrait};
pub use self::enumerate::{default_input_device, default_output_device, Devices}; pub use self::enumerate::{default_input_device, default_output_device, Devices};
@ -94,7 +94,7 @@ impl DeviceTrait for Device {
E: FnMut(StreamError) + Send + 'static, E: FnMut(StreamError) + Send + 'static,
{ {
let stream_inner = 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); let stream = Stream::new_input(Arc::new(stream_inner), data_callback, error_callback);
Ok(stream) Ok(stream)
} }
@ -111,7 +111,7 @@ impl DeviceTrait for Device {
E: FnMut(StreamError) + Send + 'static, E: FnMut(StreamError) + Send + 'static,
{ {
let stream_inner = 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); let stream = Stream::new_output(Arc::new(stream_inner), data_callback, error_callback);
Ok(stream) Ok(stream)
} }
@ -169,55 +169,41 @@ impl Device {
&self, &self,
conf: &StreamConfig, conf: &StreamConfig,
sample_format: SampleFormat, sample_format: SampleFormat,
stream_type: alsa::snd_pcm_stream_t, stream_type: alsa::Direction,
) -> Result<StreamInner, BuildStreamError> { ) -> Result<StreamInner, BuildStreamError> {
let name = ffi::CString::new(self.0.clone()).expect("unable to clone device"); let name = &self.0;
let handle = unsafe { let handle = match alsa::pcm::PCM::new(name, stream_type, true).map_err(|e| (e, e.errno()))
let mut handle = ptr::null_mut(); {
match alsa::snd_pcm_open( Err((_, Some(nix::errno::Errno::EBUSY))) => {
&mut handle, return Err(BuildStreamError::DeviceNotAvailable)
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());
}
} }
handle Err((_, Some(nix::errno::Errno::EINVAL))) => {
return Err(BuildStreamError::InvalidArgument)
}
Err((e, _)) => return Err(e.into()),
Ok(handle) => handle,
}; };
let can_pause = unsafe { let can_pause = {
let hw_params = HwParams::alloc(); let hw_params = set_hw_params_from_format(&handle, conf, sample_format)?;
set_hw_params_from_format(handle, &hw_params, conf, sample_format) hw_params.can_pause()
.map_err(|description| BackendSpecificError { description })?; };
let (buffer_len, period_len) = set_sw_params_from_format(&handle, conf)?;
alsa::snd_pcm_hw_params_can_pause(hw_params.0) == 1 handle.prepare()?;
};
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());
}
let num_descriptors = { let num_descriptors = {
let num_descriptors = unsafe { alsa::snd_pcm_poll_descriptors_count(handle) }; let num_descriptors = handle.count();
if num_descriptors == 0 { if num_descriptors == 0 {
let description = "poll descriptor count for stream was 0".to_string(); let description = "poll descriptor count for stream was 0".to_string();
let err = BackendSpecificError { description }; let err = BackendSpecificError { description };
return Err(err.into()); return Err(err.into());
} }
num_descriptors as usize num_descriptors
}; };
handle.start()?;
let stream_inner = StreamInner { let stream_inner = StreamInner {
channel: handle, channel: handle,
sample_format, sample_format,
@ -228,12 +214,6 @@ impl Device {
can_pause, 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) Ok(stream_inner)
} }
@ -242,51 +222,33 @@ impl Device {
Ok(self.0.clone()) Ok(self.0.clone())
} }
unsafe fn supported_configs( fn supported_configs(
&self, &self,
stream_t: alsa::snd_pcm_stream_t, stream_t: alsa::Direction,
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> { ) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
let mut handle = ptr::null_mut(); let name = &self.0;
let device_name = match ffi::CString::new(&self.0[..]) {
Ok(name) => name, let handle = match alsa::pcm::PCM::new(name, stream_t, true).map_err(|e| (e, e.errno())) {
Err(err) => { Err((_, Some(nix::errno::Errno::ENOENT)))
let description = format!("failed to retrieve device name: {}", err); | Err((_, Some(nix::errno::Errno::EBUSY))) => {
let err = BackendSpecificError { description }; return Err(SupportedStreamConfigsError::DeviceNotAvailable)
return Err(err.into());
} }
Err((_, Some(nix::errno::Errno::EINVAL))) => {
return Err(SupportedStreamConfigsError::InvalidArgument)
}
Err((e, _)) => return Err(e.into()),
Ok(handle) => handle,
}; };
match alsa::snd_pcm_open( let hw_params = alsa::pcm::HwParams::any(&handle)?;
&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(_) => (),
};
// TODO: check endianess // 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_S8,
//SND_PCM_FORMAT_U8, //SND_PCM_FORMAT_U8,
(SampleFormat::I16, alsa::SND_PCM_FORMAT_S16_LE), (SampleFormat::I16, alsa::pcm::Format::S16LE),
//SND_PCM_FORMAT_S16_BE, //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_U16_BE,
//SND_PCM_FORMAT_S24_LE, //SND_PCM_FORMAT_S24_LE,
//SND_PCM_FORMAT_S24_BE, //SND_PCM_FORMAT_S24_BE,
@ -296,7 +258,7 @@ impl Device {
//SND_PCM_FORMAT_S32_BE, //SND_PCM_FORMAT_S32_BE,
//SND_PCM_FORMAT_U32_LE, //SND_PCM_FORMAT_U32_LE,
//SND_PCM_FORMAT_U32_BE, //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_FLOAT_BE,
//SND_PCM_FORMAT_FLOAT64_LE, //SND_PCM_FORMAT_FLOAT64_LE,
//SND_PCM_FORMAT_FLOAT64_BE, //SND_PCM_FORMAT_FLOAT64_BE,
@ -324,36 +286,15 @@ impl Device {
let mut supported_formats = Vec::new(); let mut supported_formats = Vec::new();
for &(sample_format, alsa_format) in FORMATS.iter() { 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); supported_formats.push(sample_format);
} }
} }
let mut min_rate = 0; let min_rate = hw_params.get_rate_min()?;
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_rate_min( let max_rate = hw_params.get_rate_max()?;
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 mut max_rate = 0; let sample_rates = if min_rate == max_rate || hw_params.test_rate(min_rate + 1).is_ok() {
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
{
vec![(min_rate, max_rate)] vec![(min_rate, max_rate)]
} else { } else {
const RATES: [libc::c_uint; 13] = [ const RATES: [libc::c_uint; 13] = [
@ -363,7 +304,7 @@ impl Device {
let mut rates = Vec::new(); let mut rates = Vec::new();
for &rate in RATES.iter() { 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)); rates.push((rate, rate));
} }
} }
@ -375,30 +316,13 @@ impl Device {
} }
}; };
let mut min_channels = 0; let min_channels = hw_params.get_channels_min()?;
if let Err(desc) = check_errors(alsa::snd_pcm_hw_params_get_channels_min( let max_channels = hw_params.get_channels_max()?;
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 max_channels = cmp::min(max_channels, 32); // TODO: limiting to 32 channels or too much stuff is returned 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) let supported_channels = (min_channels..max_channels + 1)
.filter_map(|num| { .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) Some(num as ChannelCount)
} else { } else {
None None
@ -422,30 +346,28 @@ impl Device {
} }
} }
// TODO: RAII
alsa::snd_pcm_close(handle);
Ok(output.into_iter()) Ok(output.into_iter())
} }
fn supported_input_configs( fn supported_input_configs(
&self, &self,
) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> { ) -> Result<SupportedInputConfigs, SupportedStreamConfigsError> {
unsafe { self.supported_configs(alsa::SND_PCM_STREAM_CAPTURE) } self.supported_configs(alsa::Direction::Capture)
} }
fn supported_output_configs( fn supported_output_configs(
&self, &self,
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> { ) -> 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 // 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. // the `SupportedStreamConfigRange::cmp_default_heuristics` order and select the greatest.
fn default_config( fn default_config(
&self, &self,
stream_t: alsa::snd_pcm_stream_t, stream_t: alsa::Direction,
) -> Result<SupportedStreamConfig, DefaultStreamConfigError> { ) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
let mut formats: Vec<_> = unsafe { let mut formats: Vec<_> = {
match self.supported_configs(stream_t) { match self.supported_configs(stream_t) {
Err(SupportedStreamConfigsError::DeviceNotAvailable) => { Err(SupportedStreamConfigsError::DeviceNotAvailable) => {
return Err(DefaultStreamConfigError::DeviceNotAvailable); return Err(DefaultStreamConfigError::DeviceNotAvailable);
@ -480,17 +402,17 @@ impl Device {
} }
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> { 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> { fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
self.default_config(alsa::SND_PCM_STREAM_PLAYBACK) self.default_config(alsa::Direction::Playback)
} }
} }
struct StreamInner { struct StreamInner {
// The ALSA channel. // 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 // When converting between file descriptors and `snd_pcm_t`, this is the number of
// file descriptors that this `snd_pcm_t` uses. // 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. // Assume that the ALSA library is built with thread safe option.
unsafe impl Send for StreamInner {}
unsafe impl Sync for StreamInner {} unsafe impl Sync for StreamInner {}
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
@ -549,11 +469,17 @@ fn input_stream_worker(
) { ) {
let mut ctxt = StreamWorkerContext::default(); let mut ctxt = StreamWorkerContext::default();
loop { 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::Continue => continue,
PollDescriptorsFlow::Return => return, PollDescriptorsFlow::Return => return,
PollDescriptorsFlow::Ready { PollDescriptorsFlow::Ready {
available_frames, available_frames: _,
stream_type, stream_type,
} => { } => {
assert_eq!( assert_eq!(
@ -561,11 +487,8 @@ fn input_stream_worker(
StreamType::Input, StreamType::Input,
"expected input stream, but polling descriptors indicated output", "expected input stream, but polling descriptors indicated output",
); );
process_input( report_error(
stream, process_input(stream, &mut ctxt.buffer, data_callback),
&mut ctxt.buffer,
available_frames,
data_callback,
error_callback, error_callback,
); );
} }
@ -581,7 +504,13 @@ fn output_stream_worker(
) { ) {
let mut ctxt = StreamWorkerContext::default(); let mut ctxt = StreamWorkerContext::default();
loop { 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::Continue => continue,
PollDescriptorsFlow::Return => return, PollDescriptorsFlow::Return => return,
PollDescriptorsFlow::Ready { 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 { enum PollDescriptorsFlow {
Continue, Continue,
Return, Return,
@ -619,8 +564,7 @@ fn poll_descriptors_and_prepare_buffer(
rx: &TriggerReceiver, rx: &TriggerReceiver,
stream: &StreamInner, stream: &StreamInner,
ctxt: &mut StreamWorkerContext, ctxt: &mut StreamWorkerContext,
error_callback: &mut (dyn FnMut(StreamError) + Send + 'static), ) -> Result<PollDescriptorsFlow, BackendSpecificError> {
) -> PollDescriptorsFlow {
let StreamWorkerContext { let StreamWorkerContext {
ref mut descriptors, ref mut descriptors,
ref mut buffer, ref mut buffer,
@ -636,68 +580,45 @@ fn poll_descriptors_and_prepare_buffer(
}); });
// Add ALSA polling fds. // Add ALSA polling fds.
descriptors.reserve(stream.num_descriptors);
let len = descriptors.len(); let len = descriptors.len();
let filled = unsafe { descriptors.resize(
alsa::snd_pcm_poll_descriptors( stream.num_descriptors + len,
stream.channel, libc::pollfd {
descriptors[len..].as_mut_ptr(), fd: 0,
stream.num_descriptors as libc::c_uint, events: 0,
) revents: 0,
}; },
debug_assert_eq!(filled, stream.num_descriptors as libc::c_int); );
unsafe { let filled = stream.channel.fill(&mut descriptors[len..])?;
descriptors.set_len(len + stream.num_descriptors); debug_assert_eq!(filled, stream.num_descriptors);
}
let res = unsafe { // Don't timeout, wait forever.
// Don't timeout, wait forever. let res = alsa::poll::poll(descriptors, -1)?;
libc::poll( if res == 0 {
descriptors.as_mut_ptr(), let description = String::from("`alsa::poll()` spuriously returned");
descriptors.len() as libc::nfds_t, return Err(BackendSpecificError { description });
-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;
} }
if descriptors[0].revents != 0 { if descriptors[0].revents != 0 {
// The stream has been requested to be destroyed. // The stream has been requested to be destroyed.
rx.clear_pipe(); 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()) { let stream_type = match stream.channel.revents(&descriptors[1..])? {
Ok(Some(ty)) => ty, alsa::poll::Flags::OUT => StreamType::Output,
Ok(None) => { alsa::poll::Flags::IN => StreamType::Input,
_ => {
// Nothing to process, poll again // Nothing to process, poll again
return PollDescriptorsFlow::Continue; return Ok(PollDescriptorsFlow::Continue);
}
Err(err) => {
error_callback(err.into());
return PollDescriptorsFlow::Continue;
} }
}; };
// Get the number of available samples for reading/writing. // Get the number of available samples for reading/writing.
let available_samples = match get_available_samples(stream) { let available_samples = 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;
}
};
// Only go on if there is at least `stream.period_len` samples. // Only go on if there is at least `stream.period_len` samples.
if available_samples < stream.period_len { if available_samples < stream.period_len {
return PollDescriptorsFlow::Continue; return Ok(PollDescriptorsFlow::Continue);
} }
// Prepare the data buffer. // Prepare the data buffer.
@ -705,37 +626,26 @@ fn poll_descriptors_and_prepare_buffer(
buffer.resize(buffer_size, 0u8); buffer.resize(buffer_size, 0u8);
let available_frames = available_samples / stream.num_channels as usize; let available_frames = available_samples / stream.num_channels as usize;
PollDescriptorsFlow::Ready { Ok(PollDescriptorsFlow::Ready {
stream_type, stream_type,
available_frames, available_frames,
} })
} }
// Read input data from ALSA and deliver it to the user. // Read input data from ALSA and deliver it to the user.
fn process_input( fn process_input(
stream: &StreamInner, stream: &StreamInner,
buffer: &mut [u8], buffer: &mut [u8],
available_frames: usize,
data_callback: &mut (dyn FnMut(&Data) + Send + 'static), data_callback: &mut (dyn FnMut(&Data) + Send + 'static),
error_callback: &mut dyn FnMut(StreamError), ) -> Result<(), BackendSpecificError> {
) { stream.channel.io().readi(buffer)?;
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;
}
let sample_format = stream.sample_format; let sample_format = stream.sample_format;
let data = buffer.as_mut_ptr() as *mut (); let data = buffer.as_mut_ptr() as *mut ();
let len = buffer.len() / sample_format.sample_size(); let len = buffer.len() / sample_format.sample_size();
let data = unsafe { Data::from_parts(data, len, sample_format) }; let data = unsafe { Data::from_parts(data, len, sample_format) };
data_callback(&data); data_callback(&data);
Ok(())
} }
// Request data from the user's function and write it via ALSA. // Request data from the user's function and write it via ALSA.
@ -757,31 +667,28 @@ fn process_output(
data_callback(&mut data); data_callback(&mut data);
} }
loop { loop {
let result = unsafe { match stream.channel.io().writei(buffer) {
alsa::snd_pcm_writei( Err(err) if err.errno() == Some(nix::errno::Errno::EPIPE) => {
stream.channel, // buffer underrun
buffer.as_ptr() as *const _, // TODO: Notify the user of this.
available_frames as alsa::snd_pcm_uframes_t, let _ = stream.channel.try_recover(err, false);
) }
}; Err(err) => {
if result == -libc::EPIPE as c_long { error_callback(err.into());
// buffer underrun continue;
// TODO: Notify the user of this. }
unsafe { alsa::snd_pcm_recover(stream.channel, result as i32, 0) }; Ok(result) if result != available_frames => {
} else if let Err(err) = check_errors(result as _) { let description = format!(
let description = format!("`snd_pcm_writei` failed: {}", err); "unexpected number of frames written: expected {}, \
error_callback(BackendSpecificError { description }.into()); result {} (this should never happen)",
continue; available_frames, result,
} else if result as usize != available_frames { );
let description = format!( error_callback(BackendSpecificError { description }.into());
"unexpected number of frames written: expected {}, \ continue;
result {} (this should never happen)", }
available_frames, result, _ => {
); break;
error_callback(BackendSpecificError { description }.into()); }
continue;
} else {
break;
} }
} }
} }
@ -841,237 +748,126 @@ impl Drop for Stream {
impl StreamTrait for Stream { impl StreamTrait for Stream {
fn play(&self) -> Result<(), PlayStreamError> { fn play(&self) -> Result<(), PlayStreamError> {
unsafe { self.inner.channel.pause(false).ok();
alsa::snd_pcm_pause(self.inner.channel, 0);
}
// TODO: error handling
Ok(()) Ok(())
} }
fn pause(&self) -> Result<(), PauseStreamError> { fn pause(&self) -> Result<(), PauseStreamError> {
unsafe { self.inner.channel.pause(true).ok();
alsa::snd_pcm_pause(self.inner.channel, 1);
}
// TODO: error handling
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. // Determine the number of samples that are available to read/write.
fn get_available_samples(stream: &StreamInner) -> Result<usize, BackendSpecificError> { fn get_available_samples(stream: &StreamInner) -> Result<usize, BackendSpecificError> {
let available = unsafe { alsa::snd_pcm_avail_update(stream.channel) }; match stream.channel.avail_update() {
if available == -32 { Err(err) if err.errno() == Some(nix::errno::Errno::EPIPE) => {
// buffer underrun // buffer underrun
// TODO: Notify the user some how. // TODO: Notify the user some how.
Ok(stream.buffer_len) 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); Err(err) => Err(err.into()),
let err = BackendSpecificError { description }; Ok(available) => Ok(available as usize * stream.num_channels as usize),
Err(err)
} else {
Ok((available * stream.num_channels as alsa::snd_pcm_sframes_t) as usize)
} }
} }
unsafe fn set_hw_params_from_format( fn set_hw_params_from_format<'a>(
pcm_handle: *mut alsa::snd_pcm_t, pcm_handle: &'a alsa::pcm::PCM,
hw_params: &HwParams,
config: &StreamConfig, config: &StreamConfig,
sample_format: SampleFormat, sample_format: SampleFormat,
) -> Result<(), String> { ) -> Result<alsa::pcm::HwParams<'a>, BackendSpecificError> {
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_any(pcm_handle, hw_params.0)) { let hw_params = alsa::pcm::HwParams::any(pcm_handle)?;
return Err(format!("errors on pcm handle: {}", e)); hw_params.set_access(alsa::pcm::Access::RWInterleaved)?;
}
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));
}
let sample_format = if cfg!(target_endian = "big") { let sample_format = if cfg!(target_endian = "big") {
match sample_format { match sample_format {
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_BE, SampleFormat::I16 => alsa::pcm::Format::S16BE,
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_BE, SampleFormat::U16 => alsa::pcm::Format::U16BE,
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_BE, SampleFormat::F32 => alsa::pcm::Format::FloatBE,
} }
} else { } else {
match sample_format { match sample_format {
SampleFormat::I16 => alsa::SND_PCM_FORMAT_S16_LE, SampleFormat::I16 => alsa::pcm::Format::S16LE,
SampleFormat::U16 => alsa::SND_PCM_FORMAT_U16_LE, SampleFormat::U16 => alsa::pcm::Format::U16LE,
SampleFormat::F32 => alsa::SND_PCM_FORMAT_FLOAT_LE, SampleFormat::F32 => alsa::pcm::Format::FloatLE,
} }
}; };
if let Err(e) = check_errors(alsa::snd_pcm_hw_params_set_format( hw_params.set_format(sample_format)?;
pcm_handle, hw_params.set_rate(config.sample_rate.0, alsa::ValueOr::Nearest)?;
hw_params.0, hw_params.set_channels(config.channels as u32)?;
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));
}
// If this isn't set manually a overlarge buffer may be used causing audio delay // 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( hw_params.set_buffer_time_near(100_000, alsa::ValueOr::Nearest)?;
pcm_handle,
hw_params.0,
&mut 100_000,
&mut 0,
)) {
return Err(format!("buffer time could not be set: {}", e));
}
if let Err(e) = check_errors(alsa::snd_pcm_hw_params(pcm_handle, hw_params.0)) { pcm_handle.hw_params(&hw_params)?;
return Err(format!("hardware params could not be set: {}", e));
}
Ok(()) Ok(hw_params)
} }
unsafe fn set_sw_params_from_format( fn set_sw_params_from_format(
pcm_handle: *mut alsa::snd_pcm_t, pcm_handle: &alsa::pcm::PCM,
config: &StreamConfig, config: &StreamConfig,
) -> Result<(usize, usize), String> { ) -> Result<(usize, usize), BackendSpecificError> {
let mut sw_params = ptr::null_mut(); // TODO: RAII let sw_params = pcm_handle.sw_params_current()?;
if let Err(e) = check_errors(alsa::snd_pcm_sw_params_malloc(&mut sw_params)) { sw_params.set_start_threshold(0)?;
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
));
}
let (buffer_len, period_len) = { let (buffer_len, period_len) = {
let mut buffer = 0; let (buffer, period) = pcm_handle.get_params()?;
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));
}
if buffer == 0 { if buffer == 0 {
return Err(format!("initialization resulted in a null buffer")); return Err(BackendSpecificError {
} description: "initialization resulted in a null buffer".to_string(),
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));
} }
sw_params.set_avail_min(period as alsa::pcm::Frames)?;
let buffer = buffer as usize * config.channels as usize; let buffer = buffer as usize * config.channels as usize;
let period = period as usize * config.channels as usize; let period = period as usize * config.channels as usize;
(buffer, period) (buffer, period)
}; };
if let Err(e) = check_errors(alsa::snd_pcm_sw_params(pcm_handle, sw_params)) { pcm_handle.sw_params(&sw_params)?;
return Err(format!("snd_pcm_sw_params failed: {}", e));
}
alsa::snd_pcm_sw_params_free(sw_params);
Ok((buffer_len, period_len)) Ok((buffer_len, period_len))
} }
/// Wrapper around `hw_params`. impl From<alsa::Error> for BackendSpecificError {
struct HwParams(*mut alsa::snd_pcm_hw_params_t); fn from(err: alsa::Error) -> Self {
BackendSpecificError {
impl HwParams { description: err.to_string(),
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 Drop for HwParams { impl From<alsa::Error> for BuildStreamError {
fn drop(&mut self) { fn from(err: alsa::Error) -> Self {
unsafe { let err: BackendSpecificError = err.into();
alsa::snd_pcm_hw_params_free(self.0); err.into()
}
} }
} }
impl Drop for StreamInner { impl From<alsa::Error> for SupportedStreamConfigsError {
#[inline] fn from(err: alsa::Error) -> Self {
fn drop(&mut self) { let err: BackendSpecificError = err.into();
unsafe { err.into()
alsa::snd_pcm_close(self.channel);
}
} }
} }
#[inline] impl From<alsa::Error> for PlayStreamError {
fn check_errors(err: libc::c_int) -> Result<(), String> { fn from(err: alsa::Error) -> Self {
if err < 0 { let err: BackendSpecificError = err.into();
unsafe { err.into()
let s = ffi::CStr::from_ptr(alsa::snd_strerror(err)) }
.to_bytes() }
.to_vec();
let s = String::from_utf8(s).expect("Streaming error occured"); impl From<alsa::Error> for PauseStreamError {
return Err(s); 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()
} }
Ok(())
} }