Skip to content

use set_buffer_size_near to calculate fixed alsa buffer size #990

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

attackgoat
Copy link

@attackgoat attackgoat commented Jul 23, 2025

Fixes #743
Closes #917

Hardware parameters cannot be set to some arbitrary values within the supported range.

Two alternate fixes are:

  • Expose the "near" ALSA function to the user
  • Return the actual buffer size used

I am not familiar enough with CPAL usage to know if the alternate fixes are needed, but for my use cases they are not.

@roderickvd
Copy link
Collaborator

Agreed, this is the right thing to do. This highly related commit in librespot actually combines the two approaches you mention: librespot-org/librespot@d5efb8a

@roderickvd
Copy link
Collaborator

@attackgoat as I became a maintainer today, and agree with your approach, I would like to work with you to getting to a mergable state. Could you update it to:

  • take the device default when the "near" function fails? (look at the librespot example for inspiration)
  • add a changelog entry

Thanks!

@roderickvd roderickvd self-assigned this Jul 29, 2025
@roderickvd
Copy link
Collaborator

I see that #917 also does this (and more).

@attackgoat
Copy link
Author

I see that #917 also does this (and more).

That PR seems to be the better direction, although it causes me the same issue with large buffers. For now I've addressed the feedback but we may want to close this PR if #917 is cleaned up.

@roderickvd
Copy link
Collaborator

Thanks a lot @attackgoat. The author of #917 expressed that he is no longer working on it. Would you be willing to merge the two approaches?

if let BufferSize::Default = buffer_size {
// 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)?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm questioning whether an error here should immediately generate a function result. As you've found, there may be reasons why a device returns a different period size and we probably want to use the actual period size for the buffer time.

As well - though this is opinionated - I'd probably rather have initialization succeed (so we can play sound) rather than fail hard just because the exact period or buffer time can't be set.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit handles various failures better and relies on the actual ALSA-provided period size after setting time.

@attackgoat attackgoat requested a review from roderickvd August 14, 2025 11:29
Copy link
Collaborator

@roderickvd roderickvd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot, this is really going in the right direction. Some points to make it even more robust - I've got prior experience where I know that ALSA drivers can be really fickle.

At any time just let me know if and how I can help.

CHANGELOG.md Outdated
@@ -1,6 +1,7 @@
# Unreleased

- ALSA(process_output): Pass `silent=true` to `PCM.try_recover`, so it doesn't write to stderr.
- ALSA: Fix buffer size selection. (error 22)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is going to be capable of doing a lot more 😉
It would be good to concisely but clearly capture the changes to the period size, buffer size, and fallback handling.

// `default` pcm sometimes fails here, but there no reason to as we attempt to provide a size or
// minimum number of periods.
hw_params
.set_buffer_size(period_size * PERIOD_COUNT as alsa::pcm::Frames)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fallback is stricter than the primary path: should bed we not also use set_buffer_size_near here?

fn set_hw_params_periods(hw_params: &alsa::pcm::HwParams, buffer_size: BufferSize) -> bool {
const FALLBACK_PERIOD_TIME: u32 = 25_000;

// When the API is made available, this could rely on snd_pcm_hw_params_get_periods_min and
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, let's make a "TODO" as a future reminder.

const PERIOD_COUNT: u32 = 2;

/// Returns true if the buffer size was reasonably set.
fn set_hw_params_buffer_size(hw_params: &alsa::pcm::HwParams, buffer_size: FrameCount) -> bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make buffer size setting more robust by inspecting the device capabilities and clamping to the supported buffer sizes. Something like:

fn clamp_frames_to_device_caps(hw_params: &alsa::pcm::HwParams, requested: FrameCount) -> FrameCount {
    let min_buf = hw_params.get_buffer_size_min().unwrap_or(1);
    let max_buf = hw_params.get_buffer_size_max().unwrap_or(Frames::MAX); // usize::MAX? or something more sensical?
    requested.clamp(min_buf, max_buf)
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added this and also updated these other bits to use the same logic:

  • Supported stream configuration min/max clamped to valid range
  • Buffer size set after using BufferSize::Default checked for valid range

.set_period_size_near(period_size, alsa::ValueOr::Greater)
.is_err()
{
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If setting the period size fails, we still could continue getting the actual period size and tuning the buffer size to it.

Copy link
Author

@attackgoat attackgoat Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this path fails, the fallback will use period time to calculate a valid buffer size. The last commit (4da16f1) has moved this all around a bit.

/// Returns true if the buffer size was reasonably set.
fn set_hw_params_buffer_size(hw_params: &alsa::pcm::HwParams, buffer_size: FrameCount) -> bool {
// Desired period size
let period_size = (buffer_size / PERIOD_COUNT) as alsa::pcm::Frames;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Period size we could similarly clamp by inspecting device capabilities.

I know it sounds like _near and ValueOr would yield something usable in all cases, but from librespot experience I know that not all ALSA drivers do.

@attackgoat attackgoat requested a review from roderickvd August 18, 2025 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ALSA function 'snd_pcm_hw_params_set_buffer_size' failed when setting buffer size
2 participants