Skip to content

Commit ea41597

Browse files
committed
refactor: 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 b75a21e commit ea41597

File tree

2 files changed

+17
-136
lines changed

2 files changed

+17
-136
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 & 134 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,12 +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-
34-
const FALLBACK_PERIOD_TIME: u32 = 25_000;
35-
const PREFERRED_PERIOD_COUNT: u32 = 2;
36-
3730
/// The default linux, dragonfly, freebsd and netbsd host type.
3831
#[derive(Debug)]
3932
pub struct Host;
@@ -1125,143 +1118,31 @@ fn set_hw_params_from_format(
11251118
}
11261119
};
11271120

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

1132-
if !set_hw_params_periods(&hw_params, config.buffer_size) {
1133-
return Err(BackendSpecificError {
1134-
description: format!(
1135-
"Buffer size '{:?}' is not supported by this backend",
1136-
config.buffer_size
1137-
),
1138-
});
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+
})?;
11391140
}
1140-
11411141
pcm_handle.hw_params(&hw_params)?;
11421142

11431143
Ok(hw_params.can_pause())
11441144
}
11451145

1146-
/// Returns true if the periods were reasonably set. A false result indicates the device default
1147-
/// configuration is being used.
1148-
fn set_hw_params_periods(hw_params: &alsa::pcm::HwParams, buffer_size: BufferSize) -> bool {
1149-
// TODO: When the API is made available, this could rely on snd_pcm_hw_params_get_periods_min
1150-
// and snd_pcm_hw_params_get_periods_max
1151-
hw_params
1152-
.set_periods(PREFERRED_PERIOD_COUNT, alsa::ValueOr::Greater)
1153-
.unwrap_or_default();
1154-
1155-
let Some(period_count) = hw_params
1156-
.get_periods()
1157-
.ok()
1158-
.filter(|&period_count| period_count > 0)
1159-
else {
1160-
return false;
1161-
};
1162-
1163-
/// Returns true if the buffer size was reasonably set.
1164-
///
1165-
/// The buffer is a ring buffer. The buffer size always has to be greater than one period size.
1166-
/// Commonly this is 2*period size, but some hardware can do 8 periods per buffer. It is also
1167-
/// possible for the buffer size to not be an integer multiple of the period size.
1168-
///
1169-
/// See: https://www.alsa-project.org/wiki/FramesPeriods
1170-
fn set_hw_params_buffer_size(
1171-
hw_params: &alsa::pcm::HwParams,
1172-
period_count: u32,
1173-
mut buffer_size: FrameCount,
1174-
) -> bool {
1175-
buffer_size = {
1176-
let (min_buffer_size, max_buffer_size) = hw_params_buffer_size_min_max(hw_params);
1177-
buffer_size.clamp(min_buffer_size, max_buffer_size)
1178-
};
1179-
1180-
// Use time-based period configuration for device compatibility. Some devices have better
1181-
// compatibility with set_period_time_near() than set_period_size_near(). Calculate a
1182-
// period time that works with the requested buffer.
1183-
1184-
// Calculate desired period time based on buffer size and period count.
1185-
// We can't yet get period_time constraints from alsa-rs, so we'll use period_size
1186-
// constraints and convert them to time constraints.
1187-
let (min_period_size, max_period_size) = match (
1188-
hw_params.get_period_size_min(),
1189-
hw_params.get_period_size_max(),
1190-
) {
1191-
(Ok(min), Ok(max)) => (min, max),
1192-
_ => {
1193-
// Fall back to a safe default if we can't get constraints
1194-
let Ok(_) =
1195-
hw_params.set_period_time_near(FALLBACK_PERIOD_TIME, alsa::ValueOr::Nearest)
1196-
else {
1197-
return false;
1198-
};
1199-
let Ok(actual_buffer_size) = hw_params.set_buffer_size_near(buffer_size as _)
1200-
else {
1201-
return false;
1202-
};
1203-
return VALID_BUFFER_SIZE.contains(&actual_buffer_size);
1204-
}
1205-
};
1206-
1207-
// Calculate desired period size and convert to time
1208-
// period_time = (period_size_frames / sample_rate) * 1_000_000 microseconds
1209-
let desired_period_size_frames =
1210-
(buffer_size / period_count).clamp(min_period_size as u32, max_period_size as u32);
1211-
1212-
// Get actual sample rate that was set (don't assume 44100)
1213-
if let Ok(sample_rate) = hw_params.get_rate() {
1214-
let desired_period_time_us =
1215-
(desired_period_size_frames as u64 * 1_000_000) / sample_rate as u64;
1216-
1217-
// Set the period time
1218-
let Ok(_) = hw_params
1219-
.set_period_time_near(desired_period_time_us as u32, alsa::ValueOr::Nearest)
1220-
else {
1221-
return false;
1222-
};
1223-
}
1224-
1225-
// Now set the buffer size to the user's requested size
1226-
let Ok(actual_buffer_size) = hw_params.set_buffer_size_near(buffer_size as _) else {
1227-
return false;
1228-
};
1229-
1230-
// Double-check the set size is within the CPAL range
1231-
VALID_BUFFER_SIZE.contains(&actual_buffer_size)
1232-
}
1233-
1234-
if let BufferSize::Fixed(val) = buffer_size {
1235-
return set_hw_params_buffer_size(hw_params, period_count, val);
1236-
}
1237-
1238-
if hw_params
1239-
.set_period_time_near(FALLBACK_PERIOD_TIME, alsa::ValueOr::Nearest)
1240-
.is_err()
1241-
{
1242-
return false;
1243-
}
1244-
1245-
let period_size = if let Ok(period_size) = hw_params.get_period_size() {
1246-
period_size
1247-
} else {
1248-
return false;
1249-
};
1250-
1251-
// We should not fail if the driver is unhappy here.
1252-
// `default` pcm sometimes fails here, but there no reason to as we attempt to provide a size or
1253-
// minimum number of periods.
1254-
let Ok(buffer_size) =
1255-
hw_params.set_buffer_size_near(period_size * period_count as alsa::pcm::Frames)
1256-
else {
1257-
return hw_params.set_buffer_size_min(1).is_ok()
1258-
&& hw_params.set_buffer_size_max(FrameCount::MAX as _).is_ok();
1259-
};
1260-
1261-
// Double-check the set size is within the CPAL range
1262-
VALID_BUFFER_SIZE.contains(&buffer_size)
1263-
}
1264-
12651146
fn set_sw_params_from_format(
12661147
pcm_handle: &alsa::pcm::PCM,
12671148
config: &StreamConfig,

0 commit comments

Comments
 (0)