Skip to content

Commit 0438220

Browse files
committed
fix: simplify ALSA buffer size and period configuration logic
This mostly reverts #990 for device compatibility, by letting ALSA calculate the period size from the device default period count. Forcing the period count to 2 caused underruns on some systems.
1 parent f7206e9 commit 0438220

File tree

2 files changed

+17
-119
lines changed

2 files changed

+17
-119
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
- Added `Sample::bits_per_sample` method.
44
- ALSA(process_output): Pass `silent=true` to `PCM.try_recover`, so it doesn't write to stderr.
5-
- ALSA: Fix buffer and period size by selecting the closest supported values.
6-
- ALSA: Change ALSA periods from 4 to 2.
5+
- ALSA: Fix BufferSize::Fixed by selecting the nearest supported frame count.
6+
- ALSA: Change BufferSize::Default to use the device defaults instead of 4 periods of 25 ms.
77
- ALSA: Change card enumeration to work like `aplay -L` does.
88
- ASIO: Fix linker flags for MinGW cross-compilation.
99
- CoreAudio: Change `Device::supported_configs` to return a single element containing the available sample rate range when all elements have the same `mMinimum` and `mMaximum` values.

src/host/alsa/mod.rs

Lines changed: 15 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ extern crate libc;
44
use std::{
55
cell::Cell,
66
cmp, fmt,
7-
ops::RangeInclusive,
87
sync::{Arc, Mutex},
98
thread::{self, JoinHandle},
109
time::Duration,
@@ -28,9 +27,6 @@ pub type SupportedOutputConfigs = VecIntoIter<SupportedStreamConfigRange>;
2827

2928
mod enumerate;
3029

31-
const VALID_BUFFER_SIZE: RangeInclusive<alsa::pcm::Frames> =
32-
1..=FrameCount::MAX as alsa::pcm::Frames;
33-
3430
/// The default linux, dragonfly, freebsd and netbsd host type.
3531
#[derive(Debug)]
3632
pub struct Host;
@@ -1064,18 +1060,6 @@ fn hw_params_buffer_size_min_max(hw_params: &alsa::pcm::HwParams) -> (FrameCount
10641060
(min_buf, max_buf)
10651061
}
10661062

1067-
fn hw_params_period_size_min_max(hw_params: &alsa::pcm::HwParams) -> (FrameCount, FrameCount) {
1068-
let min_buf = hw_params
1069-
.get_period_size_min()
1070-
.map(clamp_frame_count)
1071-
.unwrap_or(1);
1072-
let max_buf = hw_params
1073-
.get_period_size_max()
1074-
.map(clamp_frame_count)
1075-
.unwrap_or(FrameCount::MAX);
1076-
(min_buf, max_buf)
1077-
}
1078-
10791063
fn set_hw_params_from_format(
10801064
pcm_handle: &alsa::pcm::PCM,
10811065
config: &StreamConfig,
@@ -1134,117 +1118,31 @@ fn set_hw_params_from_format(
11341118
}
11351119
};
11361120

1121+
// Set the sample format, rate, and channels - if this fails, the format is not supported.
11371122
hw_params.set_format(sample_format)?;
11381123
hw_params.set_rate(config.sample_rate.0, alsa::ValueOr::Nearest)?;
11391124
hw_params.set_channels(config.channels as u32)?;
11401125

1141-
if !set_hw_params_periods(&hw_params, config.buffer_size) {
1142-
return Err(BackendSpecificError {
1143-
description: format!(
1144-
"Buffer size '{:?}' is not supported by this backend",
1145-
config.buffer_size
1146-
),
1147-
});
1126+
// Set buffer size if requested. ALSA will calculate the period size from this buffer size as:
1127+
// period_size = nearest_set_buffer_size / default_periods
1128+
//
1129+
// If not requested, ALSA will calculate the period size from the device defaults:
1130+
// period_size = default_buffer_size / default_periods
1131+
if let BufferSize::Fixed(buffer_size) = config.buffer_size {
1132+
hw_params
1133+
.set_buffer_size_near(buffer_size as _)
1134+
.map_err(|_| BackendSpecificError {
1135+
description: format!(
1136+
"Buffer size '{:?}' is not supported by this backend",
1137+
config.buffer_size
1138+
),
1139+
})?;
11481140
}
1149-
11501141
pcm_handle.hw_params(&hw_params)?;
11511142

11521143
Ok(hw_params.can_pause())
11531144
}
11541145

1155-
/// Returns true if the periods were reasonably set. A false result indicates the device default
1156-
/// configuration is being used.
1157-
fn set_hw_params_periods(hw_params: &alsa::pcm::HwParams, buffer_size: BufferSize) -> bool {
1158-
const FALLBACK_PERIOD_TIME: u32 = 25_000;
1159-
1160-
// TODO: When the API is made available, this could rely on snd_pcm_hw_params_get_periods_min
1161-
// and snd_pcm_hw_params_get_periods_max
1162-
const PERIOD_COUNT: u32 = 2;
1163-
1164-
// Restrict the configuration space to contain only one periods count
1165-
hw_params
1166-
.set_periods(PERIOD_COUNT, alsa::ValueOr::Nearest)
1167-
.unwrap_or_default();
1168-
1169-
let Some(period_count) = hw_params
1170-
.get_periods()
1171-
.ok()
1172-
.filter(|&period_count| period_count > 0)
1173-
else {
1174-
return false;
1175-
};
1176-
1177-
/// Returns true if the buffer size was reasonably set.
1178-
///
1179-
/// The buffer is a ring buffer. The buffer size always has to be greater than one period size.
1180-
/// Commonly this is 2*period size, but some hardware can do 8 periods per buffer. It is also
1181-
/// possible for the buffer size to not be an integer multiple of the period size.
1182-
///
1183-
/// See: https://www.alsa-project.org/wiki/FramesPeriods
1184-
fn set_hw_params_buffer_size(
1185-
hw_params: &alsa::pcm::HwParams,
1186-
period_count: u32,
1187-
mut buffer_size: FrameCount,
1188-
) -> bool {
1189-
buffer_size = {
1190-
let (min_buffer_size, max_buffer_size) = hw_params_buffer_size_min_max(hw_params);
1191-
buffer_size.clamp(min_buffer_size, max_buffer_size)
1192-
};
1193-
1194-
// Desired period size
1195-
let period_size = {
1196-
let (min_period_size, max_period_size) = hw_params_period_size_min_max(hw_params);
1197-
(buffer_size / period_count).clamp(min_period_size, max_period_size)
1198-
};
1199-
1200-
// Actual period size
1201-
let Ok(period_size) =
1202-
hw_params.set_period_size_near(period_size as _, alsa::ValueOr::Greater)
1203-
else {
1204-
return false;
1205-
};
1206-
1207-
let Ok(buffer_size) =
1208-
hw_params.set_buffer_size_near(period_size * period_count as alsa::pcm::Frames)
1209-
else {
1210-
return false;
1211-
};
1212-
1213-
// Double-check the set size is within the CPAL range
1214-
VALID_BUFFER_SIZE.contains(&buffer_size)
1215-
}
1216-
1217-
if let BufferSize::Fixed(val) = buffer_size {
1218-
return set_hw_params_buffer_size(hw_params, period_count, val);
1219-
}
1220-
1221-
if hw_params
1222-
.set_period_time_near(FALLBACK_PERIOD_TIME, alsa::ValueOr::Nearest)
1223-
.is_err()
1224-
{
1225-
return false;
1226-
}
1227-
1228-
let period_size = if let Ok(period_size) = hw_params.get_period_size() {
1229-
period_size
1230-
} else {
1231-
return false;
1232-
};
1233-
1234-
// We should not fail if the driver is unhappy here.
1235-
// `default` pcm sometimes fails here, but there no reason to as we attempt to provide a size or
1236-
// minimum number of periods.
1237-
let Ok(buffer_size) =
1238-
hw_params.set_buffer_size_near(period_size * period_count as alsa::pcm::Frames)
1239-
else {
1240-
return hw_params.set_buffer_size_min(1).is_ok()
1241-
&& hw_params.set_buffer_size_max(FrameCount::MAX as _).is_ok();
1242-
};
1243-
1244-
// Double-check the set size is within the CPAL range
1245-
VALID_BUFFER_SIZE.contains(&buffer_size)
1246-
}
1247-
12481146
fn set_sw_params_from_format(
12491147
pcm_handle: &alsa::pcm::PCM,
12501148
config: &StreamConfig,

0 commit comments

Comments
 (0)