Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ lazy_static = "1.3"
alsa = "0.4.3"
nix = "0.15.0"
libc = "0.2.65"
parking_lot = "0.11"
jack = { version = "0.6.5", optional = true }

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
Expand Down
58 changes: 20 additions & 38 deletions src/host/alsa/enumerate.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::alsa;
use super::Device;
use super::parking_lot::Mutex;
use super::{Device, DeviceHandles};
use {BackendSpecificError, DevicesError};

/// ALSA implementation for `Devices`.
Expand All @@ -26,43 +27,18 @@ impl Iterator for Devices {
match self.hint_iter.next() {
None => return None,
Some(hint) => {
let name = hint.name;

let io = hint.direction;

if let Some(io) = io {
if io != alsa::Direction::Playback {
continue;
}
}

let name = match name {
Some(name) => {
// Ignoring the `null` device.
if name == "null" {
continue;
}
name
}
_ => continue,
};

// See if the device has an available output stream.
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 has_available_input = {
let capture_handle =
alsa::pcm::PCM::new(&name, alsa::Direction::Capture, true);
capture_handle.is_ok()
let name = match hint.name {
None => continue,
// Ignoring the `null` device.
Some(name) if name == "null" => continue,
Some(name) => name,
};

if has_available_output || has_available_input {
return Some(Device(name));
if let Ok(handles) = DeviceHandles::open(&name) {
return Some(Device {
name,
handles: Mutex::new(handles),
});
}
}
}
Expand All @@ -72,12 +48,18 @@ impl Iterator for Devices {

#[inline]
pub fn default_input_device() -> Option<Device> {
Some(Device("default".to_owned()))
Some(Device {
name: "default".to_owned(),
handles: Mutex::new(Default::default()),
})
}

#[inline]
pub fn default_output_device() -> Option<Device> {
Some(Device("default".to_owned()))
Some(Device {
name: "default".to_owned(),
handles: Mutex::new(Default::default()),
})
}

impl From<alsa::Error> for DevicesError {
Expand Down
84 changes: 76 additions & 8 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
extern crate alsa;
extern crate libc;
extern crate parking_lot;

use self::alsa::poll::Descriptors;
use self::parking_lot::Mutex;
use crate::{
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo,
Expand Down Expand Up @@ -163,8 +165,68 @@ impl Drop for TriggerReceiver {
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Device(String);
#[derive(Default)]
struct DeviceHandles {
playback: Option<alsa::PCM>,
capture: Option<alsa::PCM>,
}

impl DeviceHandles {
/// Create `DeviceHandles` for `name` and try to open a handle for both
/// directions. Returns `Ok` if either direction is opened successfully.
fn open(name: &str) -> Result<Self, alsa::Error> {
let mut handles = Self::default();
let playback_err = handles.try_open(name, alsa::Direction::Playback).err();
let capture_err = handles.try_open(name, alsa::Direction::Capture).err();
if let Some(err) = capture_err.and(playback_err) {
Err(err)
} else {
Ok(handles)
}
}

/// Get a mutable reference to the `Option` for a specific `stream_type`.
/// If the `Option` is `None`, the `alsa::PCM` will be opened and placed in
/// the `Option` before returning. If `handle_mut()` returns `Ok` the contained
/// `Option` is guaranteed to be `Some(..)`.
fn try_open(
&mut self,
name: &str,
stream_type: alsa::Direction,
) -> Result<&mut Option<alsa::PCM>, alsa::Error> {
let handle = match stream_type {
alsa::Direction::Playback => &mut self.playback,
alsa::Direction::Capture => &mut self.capture,
};

if handle.is_none() {
*handle = Some(alsa::pcm::PCM::new(name, stream_type, true)?);
}

Ok(handle)
}

/// Get a mutable reference to the `alsa::PCM` handle for a specific `stream_type`.
/// If the handle is not yet opened, it will be opened and stored in `self`.
fn get_mut(
&mut self,
name: &str,
stream_type: alsa::Direction,
) -> Result<&mut alsa::PCM, alsa::Error> {
Ok(self.try_open(name, stream_type)?.as_mut().unwrap())
}

/// Take ownership of the `alsa::PCM` handle for a specific `stream_type`.
/// If the handle is not yet opened, it will be opened and returned.
fn take(&mut self, name: &str, stream_type: alsa::Direction) -> Result<alsa::PCM, alsa::Error> {
Ok(self.try_open(name, stream_type)?.take().unwrap())
}
}

pub struct Device {
name: String,
handles: Mutex<DeviceHandles>,
}

impl Device {
fn build_stream_inner(
Expand All @@ -173,10 +235,13 @@ impl Device {
sample_format: SampleFormat,
stream_type: alsa::Direction,
) -> Result<StreamInner, BuildStreamError> {
let name = &self.0;
let handle_result = self
.handles
.lock()
.take(&self.name, stream_type)
.map_err(|e| (e, e.errno()));

let handle = match alsa::pcm::PCM::new(name, stream_type, true).map_err(|e| (e, e.errno()))
{
let handle = match handle_result {
Err((_, Some(nix::errno::Errno::EBUSY))) => {
return Err(BuildStreamError::DeviceNotAvailable)
}
Expand Down Expand Up @@ -229,16 +294,19 @@ impl Device {

#[inline]
fn name(&self) -> Result<String, DeviceNameError> {
Ok(self.0.clone())
Ok(self.name.clone())
}

fn supported_configs(
&self,
stream_t: alsa::Direction,
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
let name = &self.0;
let mut guard = self.handles.lock();
let handle_result = guard
.get_mut(&self.name, stream_t)
.map_err(|e| (e, e.errno()));

let handle = match alsa::pcm::PCM::new(name, stream_t, true).map_err(|e| (e, e.errno())) {
let handle = match handle_result {
Err((_, Some(nix::errno::Errno::ENOENT)))
| Err((_, Some(nix::errno::Errno::EBUSY))) => {
return Err(SupportedStreamConfigsError::DeviceNotAvailable)
Expand Down