Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALSA: fix buffer size / period size configuration, JACK: update to 0.13.0 and fix compilation errors #914

Closed
Closed
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ num-traits = { version = "0.2.6", optional = true }
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
alsa = "0.9"
libc = "0.2"
jack = { version = "0.12", optional = true }
jack = { version = "0.13.0", optional = true }

[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
core-foundation-sys = "0.8.2" # For linking to CoreFoundation.framework and handling device name `CFString`s.
Expand Down
4 changes: 4 additions & 0 deletions examples/synth_tones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,14 @@ where
match config.sample_format() {
cpal::SampleFormat::I8 => make_stream::<i8>(&device, &config.into()),
cpal::SampleFormat::I16 => make_stream::<i16>(&device, &config.into()),
cpal::SampleFormat::I24 => make_stream::<i32>(&device, &config.into()),
// cpal::SampleFormat::I24_3 => make_stream::<[i8; 3]>(&device, &config.into()),
cpal::SampleFormat::I32 => make_stream::<i32>(&device, &config.into()),
cpal::SampleFormat::I64 => make_stream::<i64>(&device, &config.into()),
cpal::SampleFormat::U8 => make_stream::<u8>(&device, &config.into()),
cpal::SampleFormat::U16 => make_stream::<u16>(&device, &config.into()),
cpal::SampleFormat::U24 => make_stream::<u32>(&device, &config.into()),
// cpal::SampleFormat::U24_3 => make_stream::<[i8; 3]>(&device, &config.into()),
cpal::SampleFormat::U32 => make_stream::<u32>(&device, &config.into()),
cpal::SampleFormat::U64 => make_stream::<u64>(&device, &config.into()),
cpal::SampleFormat::F32 => make_stream::<f32>(&device, &config.into()),
Expand Down
75 changes: 57 additions & 18 deletions src/host/alsa/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,79 @@ use std::sync::{Arc, Mutex};

/// ALSA's implementation for `Devices`.
pub struct Devices {
hint_iter: alsa::device_name::HintIter,
builtin_pos: usize,
card_iter: alsa::card::Iter,
}

impl Devices {
pub fn new() -> Result<Self, DevicesError> {
Ok(Devices {
hint_iter: alsa::device_name::HintIter::new_str(None, "pcm")?,
builtin_pos: 0,
card_iter: alsa::card::Iter::new(),
})
}
}

unsafe impl Send for Devices {}
unsafe impl Sync for Devices {}

const BUILTINS: [&'static str; 5] = ["default", "pipewire", "pulse", "jack", "oss"];

impl Iterator for Devices {
type Item = Device;

fn next(&mut self) -> Option<Device> {
while self.builtin_pos < BUILTINS.len() {
let pos = self.builtin_pos;
self.builtin_pos += 1;
let name = BUILTINS[pos];

if let Ok(handles) = DeviceHandles::open(&name) {
return Some(Device {
name: name.to_string(),
pcm_id: name.to_string(),
handles: Arc::new(Mutex::new(handles)),
});
}
}

loop {
match self.hint_iter.next() {
None => return None,
Some(hint) => {
let name = match hint.name {
None => continue,
// Ignoring the `null` device.
Some(name) if name == "null" => continue,
Some(name) => name,
};
let Some(res) = self.card_iter.next() else {
return None;
};
let Ok(card) = res else { continue };

let ctl_id = format!("hw:{}", card.get_index());
let Ok(ctl) = alsa::Ctl::new(&ctl_id, false) else {
continue;
};
let Ok(cardinfo) = ctl.card_info() else {
continue;
};
let Ok(card_name) = cardinfo.get_name() else {
continue;
};

if let Ok(handles) = DeviceHandles::open(&name) {
return Some(Device {
name,
handles: Arc::new(Mutex::new(handles)),
});
}
}
// Using plughw adds the ALSA plug layer, which can do sample type conversion,
// sample rate convertion, ...
// It is convenient, but at the same time not suitable for pro-audio as it hides
// the actual device capabilities and perform audio manipulation under your feet,
// for example sample rate conversion, sample format conversion, adds dummy channels,
// ...
// For now, many hardware only support 24bit / 3 bytes, which isn't yet supported by
// cpal. So we have to enable plughw (unfortunately) for maximum compatibility.
const USE_PLUGHW: bool = true;
let pcm_id = if USE_PLUGHW {
format!("plughw:{}", card.get_index())
} else {
ctl_id
};
if let Ok(handles) = DeviceHandles::open(&pcm_id) {
return Some(Device {
name: card_name.to_string(),
pcm_id: pcm_id.to_string(),
handles: Arc::new(Mutex::new(handles)),
});
}
}
}
Expand All @@ -50,6 +87,7 @@ impl Iterator for Devices {
pub fn default_input_device() -> Option<Device> {
Some(Device {
name: "default".to_owned(),
pcm_id: "default".to_owned(),
handles: Arc::new(Mutex::new(Default::default())),
})
}
Expand All @@ -58,6 +96,7 @@ pub fn default_input_device() -> Option<Device> {
pub fn default_output_device() -> Option<Device> {
Some(Device {
name: "default".to_owned(),
pcm_id: "default".to_owned(),
handles: Arc::new(Mutex::new(Default::default())),
})
}
Expand Down
61 changes: 34 additions & 27 deletions src/host/alsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,10 @@ struct DeviceHandles {
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> {
fn open(pcm_id: &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();
let playback_err = handles.try_open(pcm_id, alsa::Direction::Playback).err();
let capture_err = handles.try_open(pcm_id, alsa::Direction::Capture).err();
if let Some(err) = capture_err.and(playback_err) {
Err(err)
} else {
Expand All @@ -202,7 +202,7 @@ impl DeviceHandles {
/// `Option` is guaranteed to be `Some(..)`.
fn try_open(
&mut self,
name: &str,
pcm_id: &str,
stream_type: alsa::Direction,
) -> Result<&mut Option<alsa::PCM>, alsa::Error> {
let handle = match stream_type {
Expand All @@ -211,7 +211,7 @@ impl DeviceHandles {
};

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

Ok(handle)
Expand All @@ -221,10 +221,10 @@ impl DeviceHandles {
/// If the handle is not yet opened, it will be opened and stored in `self`.
fn get_mut(
&mut self,
name: &str,
pcm_id: &str,
stream_type: alsa::Direction,
) -> Result<&mut alsa::PCM, alsa::Error> {
Ok(self.try_open(name, stream_type)?.as_mut().unwrap())
Ok(self.try_open(pcm_id, stream_type)?.as_mut().unwrap())
}

/// Take ownership of the `alsa::PCM` handle for a specific `stream_type`.
Expand All @@ -237,6 +237,7 @@ impl DeviceHandles {
#[derive(Clone)]
pub struct Device {
name: String,
pcm_id: String,
handles: Arc<Mutex<DeviceHandles>>,
}

Expand All @@ -251,7 +252,7 @@ impl Device {
.handles
.lock()
.unwrap()
.take(&self.name, stream_type)
.take(&self.pcm_id, stream_type)
.map_err(|e| (e, e.errno()));

let handle = match handle_result {
Expand Down Expand Up @@ -308,7 +309,7 @@ impl Device {
) -> Result<VecIntoIter<SupportedStreamConfigRange>, SupportedStreamConfigsError> {
let mut guard = self.handles.lock().unwrap();
let handle_result = guard
.get_mut(&self.name, stream_t)
.get_mut(&self.pcm_id, stream_t)
.map_err(|e| (e, e.errno()));

let handle = match handle_result {
Expand All @@ -323,16 +324,17 @@ impl Device {
let hw_params = alsa::pcm::HwParams::any(handle)?;

// TODO: check endianness
const FORMATS: [(SampleFormat, alsa::pcm::Format); 8] = [
const FORMATS: [(SampleFormat, alsa::pcm::Format); 10] = [
(SampleFormat::I8, alsa::pcm::Format::S8),
(SampleFormat::U8, alsa::pcm::Format::U8),
(SampleFormat::I16, alsa::pcm::Format::S16LE),
//SND_PCM_FORMAT_S16_BE,
(SampleFormat::U16, alsa::pcm::Format::U16LE),
//SND_PCM_FORMAT_U16_BE,
//SND_PCM_FORMAT_S24_LE,
(SampleFormat::I24, alsa::pcm::Format::S24LE),
//SND_PCM_FORMAT_S24_BE,
//SND_PCM_FORMAT_U24_LE,
(SampleFormat::U24, alsa::pcm::Format::U24LE),
//SND_PCM_FORMAT_U24_BE,
(SampleFormat::I32, alsa::pcm::Format::S32LE),
//SND_PCM_FORMAT_S32_BE,
Expand All @@ -350,9 +352,9 @@ impl Device {
//SND_PCM_FORMAT_MPEG,
//SND_PCM_FORMAT_GSM,
//SND_PCM_FORMAT_SPECIAL,
//SND_PCM_FORMAT_S24_3LE,
//(SampleFormat::I24_3, alsa::pcm::Format::S243LE),
//SND_PCM_FORMAT_S24_3BE,
//SND_PCM_FORMAT_U24_3LE,
//(SampleFormat::U24_3, alsa::pcm::Format::U243LE),
//SND_PCM_FORMAT_U24_3BE,
//SND_PCM_FORMAT_S20_3LE,
//SND_PCM_FORMAT_S20_3BE,
Expand Down Expand Up @@ -1009,13 +1011,15 @@ fn set_hw_params_from_format(
match sample_format {
SampleFormat::I8 => alsa::pcm::Format::S8,
SampleFormat::I16 => alsa::pcm::Format::S16BE,
// SampleFormat::I24 => alsa::pcm::Format::S24BE,
SampleFormat::I24 => alsa::pcm::Format::S24BE,
// SampleFormat::I24_3 => alsa::pcm::Format::S243BE,
SampleFormat::I32 => alsa::pcm::Format::S32BE,
// SampleFormat::I48 => alsa::pcm::Format::S48BE,
// SampleFormat::I64 => alsa::pcm::Format::S64BE,
SampleFormat::U8 => alsa::pcm::Format::U8,
SampleFormat::U16 => alsa::pcm::Format::U16BE,
// SampleFormat::U24 => alsa::pcm::Format::U24BE,
SampleFormat::U24 => alsa::pcm::Format::U24BE,
// SampleFormat::U24_3 => alsa::pcm::Format::U243BE,
SampleFormat::U32 => alsa::pcm::Format::U32BE,
// SampleFormat::U48 => alsa::pcm::Format::U48BE,
// SampleFormat::U64 => alsa::pcm::Format::U64BE,
Expand All @@ -1034,13 +1038,15 @@ fn set_hw_params_from_format(
match sample_format {
SampleFormat::I8 => alsa::pcm::Format::S8,
SampleFormat::I16 => alsa::pcm::Format::S16LE,
// SampleFormat::I24 => alsa::pcm::Format::S24LE,
SampleFormat::I24 => alsa::pcm::Format::S24LE,
// SampleFormat::I24_3 => alsa::pcm::Format::S243LE,
SampleFormat::I32 => alsa::pcm::Format::S32LE,
// SampleFormat::I48 => alsa::pcm::Format::S48LE,
// SampleFormat::I64 => alsa::pcm::Format::S64LE,
SampleFormat::U8 => alsa::pcm::Format::U8,
SampleFormat::U16 => alsa::pcm::Format::U16LE,
// SampleFormat::U24 => alsa::pcm::Format::U24LE,
SampleFormat::U24 => alsa::pcm::Format::U24LE,
// SampleFormat::U24_3 => alsa::pcm::Format::U243LE,
SampleFormat::U32 => alsa::pcm::Format::U32LE,
// SampleFormat::U48 => alsa::pcm::Format::U48LE,
// SampleFormat::U64 => alsa::pcm::Format::U64LE,
Expand All @@ -1061,18 +1067,19 @@ fn set_hw_params_from_format(
hw_params.set_rate(config.sample_rate.0, alsa::ValueOr::Nearest)?;
hw_params.set_channels(config.channels as u32)?;

match config.buffer_size {
BufferSize::Fixed(v) => {
hw_params.set_period_size_near((v / 4) as alsa::pcm::Frames, alsa::ValueOr::Nearest)?;
hw_params.set_buffer_size(v as alsa::pcm::Frames)?;
}
let period_size = match config.buffer_size {
BufferSize::Fixed(v) => v as alsa::pcm::Frames,
BufferSize::Default => {
// These values together represent a moderate latency and wakeup interval.
// Without them, we are at the mercy of the device
hw_params.set_period_time_near(25_000, alsa::ValueOr::Nearest)?;
hw_params.set_buffer_time_near(100_000, alsa::ValueOr::Nearest)?;
// This value represent a moderate latency and wakeup interval.
1024 as alsa::pcm::Frames
}
}
};
hw_params.set_period_size_near(period_size, alsa::ValueOr::Nearest)?;

// We shouldn't fail if the driver isn't happy here.
// `default` pcm sometimes fails here, but there no reason to as we
// provide a direction and 2 is strictly the minimum number of periods.
let _ = hw_params.set_periods(2, alsa::ValueOr::Greater);

pcm_handle.hw_params(&hw_params)?;

Expand Down
6 changes: 3 additions & 3 deletions src/host/jack/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl Stream {
let mut port_names: Vec<String> = vec![];
// Create ports
for i in 0..channels {
let port_try = client.register_port(&format!("in_{}", i), jack::AudioIn);
let port_try = client.register_port(&format!("in_{}", i), jack::AudioIn::default());
match port_try {
Ok(port) => {
// Get the port name in order to later connect it automatically
Expand Down Expand Up @@ -102,7 +102,7 @@ impl Stream {
let mut port_names: Vec<String> = vec![];
// Create ports
for i in 0..channels {
let port_try = client.register_port(&format!("out_{}", i), jack::AudioOut);
let port_try = client.register_port(&format!("out_{}", i), jack::AudioOut::default());
match port_try {
Ok(port) => {
// Get the port name in order to later connect it automatically
Expand Down Expand Up @@ -437,7 +437,7 @@ impl JackNotificationHandler {
}

impl jack::NotificationHandler for JackNotificationHandler {
fn shutdown(&mut self, _status: jack::ClientStatus, reason: &str) {
unsafe fn shutdown(&mut self, _status: jack::ClientStatus, reason: &str) {
self.send_error(format!("JACK was shut down for reason: {}", reason));
}

Expand Down
36 changes: 33 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
//! ```
//!
//! Before we can create a stream, we must decide what the configuration of the audio stream is
//! going to be.
//! going to be.
//! You can query all the supported configurations with the
//! [`supported_input_configs()`] and [`supported_output_configs()`] methods.
//! These produce a list of [`SupportedStreamConfigRange`] structs which can later be turned into
Expand Down Expand Up @@ -225,7 +225,7 @@ pub type FrameCount = u32;
/// behavior of the given host. Note, the default buffer size may be surprisingly
/// large, leading to latency issues. If low latency is desired, [`Fixed(FrameCount)`]
/// should be used in accordance with the [`SupportedBufferSize`] range produced by
/// the [`SupportedStreamConfig`] API.
/// the [`SupportedStreamConfig`] API.
///
/// [`Default`]: BufferSize::Default
/// [`Fixed(FrameCount)`]: BufferSize::Fixed
Expand Down Expand Up @@ -695,7 +695,7 @@ impl SupportedStreamConfigRange {
/// - Max sample rate
pub fn cmp_default_heuristics(&self, other: &Self) -> std::cmp::Ordering {
use std::cmp::Ordering::Equal;
use SampleFormat::{F32, I16, U16};
use SampleFormat::{F32, I16, I24, I32, U16, U24, U32};

let cmp_stereo = (self.channels == 2).cmp(&(other.channels == 2));
if cmp_stereo != Equal {
Expand All @@ -717,6 +717,36 @@ impl SupportedStreamConfigRange {
return cmp_f32;
}

let cmp_i32 = (self.sample_format == I32).cmp(&(other.sample_format == I32));
if cmp_i32 != Equal {
return cmp_i32;
}

let cmp_u32 = (self.sample_format == U32).cmp(&(other.sample_format == U32));
if cmp_u32 != Equal {
return cmp_u32;
}

let cmp_i24 = (self.sample_format == I24).cmp(&(other.sample_format == I24));
if cmp_i24 != Equal {
return cmp_i24;
}

let cmp_u24 = (self.sample_format == U24).cmp(&(other.sample_format == U24));
if cmp_u24 != Equal {
return cmp_u24;
}

// let cmp_i24_3 = (self.sample_format == I24_3).cmp(&(other.sample_format == I24_3));
// if cmp_i24_3 != Equal {
// return cmp_i24_3;
// }

// let cmp_u24_3 = (self.sample_format == U24_3).cmp(&(other.sample_format == U24_3));
// if cmp_u24_3 != Equal {
// return cmp_u24_3;
// }

let cmp_i16 = (self.sample_format == I16).cmp(&(other.sample_format == I16));
if cmp_i16 != Equal {
return cmp_i16;
Expand Down
Loading