Skip to content

Commit b75a21e

Browse files
committed
fix: improve ALSA period configuration for device compatibility
Use time-based period configuration with set_period_time_near for better device compatibility. Calculate period time based on buffer size and period count, and fall back to safe defaults if constraints are unavailable.
1 parent f7206e9 commit b75a21e

File tree

1 file changed

+49
-32
lines changed

1 file changed

+49
-32
lines changed

src/host/alsa/mod.rs

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ mod enumerate;
3131
const VALID_BUFFER_SIZE: RangeInclusive<alsa::pcm::Frames> =
3232
1..=FrameCount::MAX as alsa::pcm::Frames;
3333

34+
const FALLBACK_PERIOD_TIME: u32 = 25_000;
35+
const PREFERRED_PERIOD_COUNT: u32 = 2;
36+
3437
/// The default linux, dragonfly, freebsd and netbsd host type.
3538
#[derive(Debug)]
3639
pub struct Host;
@@ -1064,18 +1067,6 @@ fn hw_params_buffer_size_min_max(hw_params: &alsa::pcm::HwParams) -> (FrameCount
10641067
(min_buf, max_buf)
10651068
}
10661069

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-
10791070
fn set_hw_params_from_format(
10801071
pcm_handle: &alsa::pcm::PCM,
10811072
config: &StreamConfig,
@@ -1155,15 +1146,10 @@ fn set_hw_params_from_format(
11551146
/// Returns true if the periods were reasonably set. A false result indicates the device default
11561147
/// configuration is being used.
11571148
fn set_hw_params_periods(hw_params: &alsa::pcm::HwParams, buffer_size: BufferSize) -> bool {
1158-
const FALLBACK_PERIOD_TIME: u32 = 25_000;
1159-
11601149
// TODO: When the API is made available, this could rely on snd_pcm_hw_params_get_periods_min
11611150
// 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
11651151
hw_params
1166-
.set_periods(PERIOD_COUNT, alsa::ValueOr::Nearest)
1152+
.set_periods(PREFERRED_PERIOD_COUNT, alsa::ValueOr::Greater)
11671153
.unwrap_or_default();
11681154

11691155
let Some(period_count) = hw_params
@@ -1191,27 +1177,58 @@ fn set_hw_params_periods(hw_params: &alsa::pcm::HwParams, buffer_size: BufferSiz
11911177
buffer_size.clamp(min_buffer_size, max_buffer_size)
11921178
};
11931179

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)
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+
}
11981205
};
11991206

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-
};
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+
}
12061224

1207-
let Ok(buffer_size) =
1208-
hw_params.set_buffer_size_near(period_size * period_count as alsa::pcm::Frames)
1209-
else {
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 {
12101227
return false;
12111228
};
12121229

12131230
// Double-check the set size is within the CPAL range
1214-
VALID_BUFFER_SIZE.contains(&buffer_size)
1231+
VALID_BUFFER_SIZE.contains(&actual_buffer_size)
12151232
}
12161233

12171234
if let BufferSize::Fixed(val) = buffer_size {

0 commit comments

Comments
 (0)