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"
|
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"] }
|
||||||
|
|
|
@ -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,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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue