Switch from vendored alsa-sys to alsa crate

This commit is contained in:
Alex Moon 2020-04-03 12:14:52 -04:00
parent f3e7c46205
commit 62d540d396
7 changed files with 255 additions and 4322 deletions

View File

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

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,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,17 @@ 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 +66,7 @@ impl Iterator for Devices {
}
}
}
}
#[inline]
pub fn default_input_device() -> Option<Device> {
@ -149,3 +77,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()
}
}

View File

@ -1,6 +1,7 @@
extern crate alsa_sys as alsa;
extern crate alsa;
extern crate libc;
use self::alsa::poll::Descriptors;
use crate::{
BackendSpecificError, BuildStreamError, ChannelCount, Data, DefaultStreamConfigError,
DeviceNameError, DevicesError, PauseStreamError, PlayStreamError, SampleFormat, SampleRate,
@ -10,7 +11,7 @@ use crate::{
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use std::vec::IntoIter as VecIntoIter;
use std::{cmp, ffi, io, ptr};
use std::cmp;
use traits::{DeviceTrait, HostTrait, StreamTrait};
pub use self::enumerate::{default_input_device, default_output_device, Devices};
@ -93,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)
}
@ -110,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)
}
@ -168,55 +169,40 @@ 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,
@ -227,12 +213,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)
}
@ -241,51 +221,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,
@ -295,7 +257,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,
@ -323,36 +285,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] = [
@ -362,7 +303,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));
}
}
@ -374,30 +315,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
@ -421,30 +345,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);
@ -479,17 +401,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.
@ -512,8 +434,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)]
@ -548,11 +468,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!(
@ -560,11 +486,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,
);
}
@ -580,7 +503,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 {
@ -604,6 +533,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,
@ -618,8 +563,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,
@ -635,68 +579,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.
@ -704,37 +625,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.
@ -756,22 +666,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 i64 {
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)",
@ -779,11 +684,13 @@ fn process_output(
);
error_callback(BackendSpecificError { description }.into());
continue;
} else {
}
_ => {
break;
}
}
}
}
impl Stream {
fn new_input<D, E>(
@ -840,237 +747,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(())
}
fn pause(&self) -> Result<(), PauseStreamError> {
unsafe {
alsa::snd_pcm_pause(self.inner.channel, 1);
}
// TODO: error handling
self.inner.channel.pause(true)?;
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()
}
}