Skip to content

Commit f6c1225

Browse files
authored
Merge pull request #506 from alexmoon/alsa-open-once
Avoid calling alsa::PCM::new() multiple times.
2 parents 296cf5c + 9c4e240 commit f6c1225

File tree

3 files changed

+97
-46
lines changed

3 files changed

+97
-46
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ lazy_static = "1.3"
3030
alsa = "0.4.3"
3131
nix = "0.15.0"
3232
libc = "0.2.65"
33+
parking_lot = "0.11"
3334
jack = { version = "0.6.5", optional = true }
3435

3536
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]

src/host/alsa/enumerate.rs

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use super::alsa;
2-
use super::Device;
2+
use super::parking_lot::Mutex;
3+
use super::{Device, DeviceHandles};
34
use {BackendSpecificError, DevicesError};
45

56
/// ALSA implementation for `Devices`.
@@ -26,43 +27,18 @@ impl Iterator for Devices {
2627
match self.hint_iter.next() {
2728
None => return None,
2829
Some(hint) => {
29-
let name = hint.name;
30-
31-
let io = hint.direction;
32-
33-
if let Some(io) = io {
34-
if io != alsa::Direction::Playback {
35-
continue;
36-
}
37-
}
38-
39-
let name = match name {
40-
Some(name) => {
41-
// Ignoring the `null` device.
42-
if name == "null" {
43-
continue;
44-
}
45-
name
46-
}
47-
_ => continue,
48-
};
49-
50-
// See if the device has an available output stream.
51-
let has_available_output = {
52-
let playback_handle =
53-
alsa::pcm::PCM::new(&name, alsa::Direction::Playback, true);
54-
playback_handle.is_ok()
55-
};
56-
57-
// See if the device has an available input stream.
58-
let has_available_input = {
59-
let capture_handle =
60-
alsa::pcm::PCM::new(&name, alsa::Direction::Capture, true);
61-
capture_handle.is_ok()
30+
let name = match hint.name {
31+
None => continue,
32+
// Ignoring the `null` device.
33+
Some(name) if name == "null" => continue,
34+
Some(name) => name,
6235
};
6336

64-
if has_available_output || has_available_input {
65-
return Some(Device(name));
37+
if let Ok(handles) = DeviceHandles::open(&name) {
38+
return Some(Device {
39+
name,
40+
handles: Mutex::new(handles),
41+
});
6642
}
6743
}
6844
}
@@ -72,12 +48,18 @@ impl Iterator for Devices {
7248

7349
#[inline]
7450
pub fn default_input_device() -> Option<Device> {
75-
Some(Device("default".to_owned()))
51+
Some(Device {
52+
name: "default".to_owned(),
53+
handles: Mutex::new(Default::default()),
54+
})
7655
}
7756

7857
#[inline]
7958
pub fn default_output_device() -> Option<Device> {
80-
Some(Device("default".to_owned()))
59+
Some(Device {
60+
name: "default".to_owned(),
61+
handles: Mutex::new(Default::default()),
62+
})
8163
}
8264

8365
impl From<alsa::Error> for DevicesError {

src/host/alsa/mod.rs

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
extern crate alsa;
22
extern crate libc;
3+
extern crate parking_lot;
34

45
use self::alsa::poll::Descriptors;
6+
use self::parking_lot::Mutex;
57
use crate::{
68
BackendSpecificError, BufferSize, BuildStreamError, ChannelCount, Data,
79
DefaultStreamConfigError, DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo,
@@ -163,8 +165,68 @@ impl Drop for TriggerReceiver {
163165
}
164166
}
165167

166-
#[derive(Clone, Debug, PartialEq, Eq)]
167-
pub struct Device(String);
168+
#[derive(Default)]
169+
struct DeviceHandles {
170+
playback: Option<alsa::PCM>,
171+
capture: Option<alsa::PCM>,
172+
}
173+
174+
impl DeviceHandles {
175+
/// Create `DeviceHandles` for `name` and try to open a handle for both
176+
/// directions. Returns `Ok` if either direction is opened successfully.
177+
fn open(name: &str) -> Result<Self, alsa::Error> {
178+
let mut handles = Self::default();
179+
let playback_err = handles.try_open(name, alsa::Direction::Playback).err();
180+
let capture_err = handles.try_open(name, alsa::Direction::Capture).err();
181+
if let Some(err) = capture_err.and(playback_err) {
182+
Err(err)
183+
} else {
184+
Ok(handles)
185+
}
186+
}
187+
188+
/// Get a mutable reference to the `Option` for a specific `stream_type`.
189+
/// If the `Option` is `None`, the `alsa::PCM` will be opened and placed in
190+
/// the `Option` before returning. If `handle_mut()` returns `Ok` the contained
191+
/// `Option` is guaranteed to be `Some(..)`.
192+
fn try_open(
193+
&mut self,
194+
name: &str,
195+
stream_type: alsa::Direction,
196+
) -> Result<&mut Option<alsa::PCM>, alsa::Error> {
197+
let handle = match stream_type {
198+
alsa::Direction::Playback => &mut self.playback,
199+
alsa::Direction::Capture => &mut self.capture,
200+
};
201+
202+
if handle.is_none() {
203+
*handle = Some(alsa::pcm::PCM::new(name, stream_type, true)?);
204+
}
205+
206+
Ok(handle)
207+
}
208+
209+
/// Get a mutable reference to the `alsa::PCM` handle for a specific `stream_type`.
210+
/// If the handle is not yet opened, it will be opened and stored in `self`.
211+
fn get_mut(
212+
&mut self,
213+
name: &str,
214+
stream_type: alsa::Direction,
215+
) -> Result<&mut alsa::PCM, alsa::Error> {
216+
Ok(self.try_open(name, stream_type)?.as_mut().unwrap())
217+
}
218+
219+
/// Take ownership of the `alsa::PCM` handle for a specific `stream_type`.
220+
/// If the handle is not yet opened, it will be opened and returned.
221+
fn take(&mut self, name: &str, stream_type: alsa::Direction) -> Result<alsa::PCM, alsa::Error> {
222+
Ok(self.try_open(name, stream_type)?.take().unwrap())
223+
}
224+
}
225+
226+
pub struct Device {
227+
name: String,
228+
handles: Mutex<DeviceHandles>,
229+
}
168230

169231
impl Device {
170232
fn build_stream_inner(
@@ -173,10 +235,13 @@ impl Device {
173235
sample_format: SampleFormat,
174236
stream_type: alsa::Direction,
175237
) -> Result<StreamInner, BuildStreamError> {
176-
let name = &self.0;
238+
let handle_result = self
239+
.handles
240+
.lock()
241+
.take(&self.name, stream_type)
242+
.map_err(|e| (e, e.errno()));
177243

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

230295
#[inline]
231296
fn name(&self) -> Result<String, DeviceNameError> {
232-
Ok(self.0.clone())
297+
Ok(self.name.clone())
233298
}
234299

235300
fn supported_configs(
236301
&self,
237302
stream_t: alsa::Direction,
238303
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
239-
let name = &self.0;
304+
let mut guard = self.handles.lock();
305+
let handle_result = guard
306+
.get_mut(&self.name, stream_t)
307+
.map_err(|e| (e, e.errno()));
240308

241-
let handle = match alsa::pcm::PCM::new(name, stream_t, true).map_err(|e| (e, e.errno())) {
309+
let handle = match handle_result {
242310
Err((_, Some(nix::errno::Errno::ENOENT)))
243311
| Err((_, Some(nix::errno::Errno::EBUSY))) => {
244312
return Err(SupportedStreamConfigsError::DeviceNotAvailable)

0 commit comments

Comments
 (0)