Skip to content

Commit 4da16f1

Browse files
committed
Clamp ALSA buffer size to valid CPAL values
1 parent 35443ce commit 4da16f1

File tree

2 files changed

+79
-27
lines changed

2 files changed

+79
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Unreleased
22

33
- ALSA(process_output): Pass `silent=true` to `PCM.try_recover`, so it doesn't write to stderr.
4-
- ALSA: Fix buffer size selection. (error 22)
4+
- ALSA: Fix buffer and period size selection by rounding to supported values. Actual buffer size may be different from the requested size or may be a device-specified default size. Additionally sets ALSA "periods" to 2 (previously 4). (error 22)
55
- CoreAudio: `Device::supported_configs` now returns a single element containing the available sample rate range when all elements have the same `mMinimum` and `mMaximum` values (which is the most common case).
66
- CoreAudio: Detect default audio device lazily when building a stream, instead of during device enumeration.
77
- iOS: Fix example by properly activating audio session.

src/host/alsa/mod.rs

Lines changed: 78 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::{
1313
use std::cell::Cell;
1414
use std::cmp;
1515
use std::convert::TryInto;
16+
use std::ops::RangeInclusive;
1617
use std::sync::{Arc, Mutex};
1718
use std::thread::{self, JoinHandle};
1819
use std::time::Duration;
@@ -25,6 +26,9 @@ pub type SupportedOutputConfigs = VecIntoIter<SupportedStreamConfigRange>;
2526

2627
mod enumerate;
2728

29+
const VALID_BUFFER_SIZE: RangeInclusive<alsa::pcm::Frames> =
30+
1..=FrameCount::MAX as alsa::pcm::Frames;
31+
2832
/// The default linux, dragonfly, freebsd and netbsd host type.
2933
#[derive(Debug)]
3034
pub struct Host;
@@ -413,12 +417,10 @@ impl Device {
413417
})
414418
.collect::<Vec<_>>();
415419

416-
let min_buffer_size = hw_params.get_buffer_size_min()?;
417-
let max_buffer_size = hw_params.get_buffer_size_max()?;
418-
420+
let (min_buffer_size, max_buffer_size) = hw_params_buffer_size_min_max(&hw_params);
419421
let buffer_size_range = SupportedBufferSize::Range {
420-
min: min_buffer_size as u32,
421-
max: max_buffer_size as u32,
422+
min: min_buffer_size,
423+
max: max_buffer_size,
422424
};
423425

424426
let mut output = Vec::with_capacity(
@@ -1040,6 +1042,35 @@ impl StreamTrait for Stream {
10401042
}
10411043
}
10421044

1045+
// Overly safe clamp because alsa Frames are i64
1046+
fn clamp_frame_count(buffer_size: alsa::pcm::Frames) -> FrameCount {
1047+
buffer_size.clamp(1, FrameCount::MAX as _) as _
1048+
}
1049+
1050+
fn hw_params_buffer_size_min_max(hw_params: &alsa::pcm::HwParams) -> (FrameCount, FrameCount) {
1051+
let min_buf = hw_params
1052+
.get_buffer_size_min()
1053+
.map(clamp_frame_count)
1054+
.unwrap_or(1);
1055+
let max_buf = hw_params
1056+
.get_buffer_size_max()
1057+
.map(clamp_frame_count)
1058+
.unwrap_or(FrameCount::MAX);
1059+
(min_buf, max_buf)
1060+
}
1061+
1062+
fn hw_params_period_size_min_max(hw_params: &alsa::pcm::HwParams) -> (FrameCount, FrameCount) {
1063+
let min_buf = hw_params
1064+
.get_period_size_min()
1065+
.map(clamp_frame_count)
1066+
.unwrap_or(1);
1067+
let max_buf = hw_params
1068+
.get_period_size_max()
1069+
.map(clamp_frame_count)
1070+
.unwrap_or(FrameCount::MAX);
1071+
(min_buf, max_buf)
1072+
}
1073+
10431074
fn set_hw_params_from_format(
10441075
pcm_handle: &alsa::pcm::PCM,
10451076
config: &StreamConfig,
@@ -1116,32 +1147,43 @@ fn set_hw_params_from_format(
11161147
fn set_hw_params_periods(hw_params: &alsa::pcm::HwParams, buffer_size: BufferSize) -> bool {
11171148
const FALLBACK_PERIOD_TIME: u32 = 25_000;
11181149

1119-
// When the API is made available, this could rely on snd_pcm_hw_params_get_periods_min and
1120-
// snd_pcm_hw_params_get_periods_max
1150+
// TODO: When the API is made available, this could rely on snd_pcm_hw_params_get_periods_min
1151+
// and snd_pcm_hw_params_get_periods_max
11211152
const PERIOD_COUNT: u32 = 2;
11221153

11231154
/// Returns true if the buffer size was reasonably set.
1124-
fn set_hw_params_buffer_size(hw_params: &alsa::pcm::HwParams, buffer_size: FrameCount) -> bool {
1125-
// Desired period size
1126-
let period_size = (buffer_size / PERIOD_COUNT) as alsa::pcm::Frames;
1155+
fn set_hw_params_buffer_size(
1156+
hw_params: &alsa::pcm::HwParams,
1157+
mut buffer_size: FrameCount,
1158+
) -> bool {
1159+
buffer_size = {
1160+
let (min_buffer_size, max_buffer_size) = hw_params_buffer_size_min_max(hw_params);
1161+
buffer_size.clamp(min_buffer_size, max_buffer_size)
1162+
};
11271163

1128-
if hw_params
1129-
.set_period_size_near(period_size, alsa::ValueOr::Greater)
1130-
.is_err()
1131-
{
1132-
return false;
1133-
}
1164+
// Desired period size
1165+
let period_size = {
1166+
let (min_period_size, max_period_size) = hw_params_period_size_min_max(hw_params);
1167+
(buffer_size / PERIOD_COUNT).clamp(min_period_size, max_period_size)
1168+
};
11341169

11351170
// Actual period size
1136-
let period_size = if let Ok(period_size) = hw_params.get_period_size() {
1137-
period_size
1138-
} else {
1171+
let Ok(period_size) =
1172+
hw_params.set_period_size_near(period_size as _, alsa::ValueOr::Greater)
1173+
else {
11391174
return false;
11401175
};
11411176

1142-
hw_params
1143-
.set_buffer_size_near(period_size * PERIOD_COUNT as alsa::pcm::Frames)
1144-
.is_ok()
1177+
if let Ok(buffer_size) =
1178+
hw_params.set_buffer_size_near(period_size * PERIOD_COUNT as alsa::pcm::Frames)
1179+
{
1180+
// Double-check the set size is within the CPAL range
1181+
if VALID_BUFFER_SIZE.contains(&buffer_size) {
1182+
return true;
1183+
}
1184+
}
1185+
1186+
false
11451187
}
11461188

11471189
if let BufferSize::Fixed(val) = buffer_size {
@@ -1166,12 +1208,22 @@ fn set_hw_params_periods(hw_params: &alsa::pcm::HwParams, buffer_size: BufferSiz
11661208
// We should not fail if the driver is unhappy here.
11671209
// `default` pcm sometimes fails here, but there no reason to as we attempt to provide a size or
11681210
// minimum number of periods.
1211+
if let Ok(buffer_size) =
1212+
hw_params.set_buffer_size_near(period_size * PERIOD_COUNT as alsa::pcm::Frames)
1213+
{
1214+
// Double-check the set size is within the CPAL range
1215+
if VALID_BUFFER_SIZE.contains(&buffer_size) {
1216+
return true;
1217+
}
1218+
}
1219+
1220+
hw_params.set_buffer_size_min(1).unwrap_or_default();
1221+
hw_params
1222+
.set_buffer_size_max(FrameCount::MAX as _)
1223+
.unwrap_or_default();
11691224
hw_params
1170-
.set_buffer_size(period_size * PERIOD_COUNT as alsa::pcm::Frames)
1225+
.set_periods(PERIOD_COUNT, alsa::ValueOr::Nearest)
11711226
.is_ok()
1172-
|| hw_params
1173-
.set_periods(PERIOD_COUNT, alsa::ValueOr::Greater)
1174-
.is_ok()
11751227
}
11761228

11771229
fn set_sw_params_from_format(

0 commit comments

Comments
 (0)