Skip to content
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

Fixed BufferSize not functioning as expected #495

Open
xasopheno opened this issue Nov 2, 2020 · 12 comments
Open

Fixed BufferSize not functioning as expected #495

xasopheno opened this issue Nov 2, 2020 · 12 comments

Comments

@xasopheno
Copy link

I'd like to switch to cpal from rust-portaudio, but I'm unable to reproduce the same behavior.

When using the beep.rs example. When I set the buffer size via the config like this:

  let config = StreamConfig {
    channels: 2,
    buffer_size: BufferSize::Fixed(2048),
    sample_rate: cpal::SampleRate(44_100),
  };

and then inspect the size of the output buffer length, these are the lengths I see.

[bin/pad.rs:57] &output.len() = 8190
[bin/pad.rs:57] &output.len() = 2544
[bin/pad.rs:57] &output.len() = 2030
[bin/pad.rs:57] &output.len() = 2544
[bin/pad.rs:57] &output.len() = 1598
[bin/pad.rs:57] &output.len() = 908
[bin/pad.rs:57] &output.len() = 840
[bin/pad.rs:57] &output.len() = 988
[bin/pad.rs:57] &output.len() = 484
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1462
[bin/pad.rs:57] &output.len() = 1482
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1338
[bin/pad.rs:57] &output.len() = 1606
[bin/pad.rs:57] &output.len() = 1476
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 810
[bin/pad.rs:57] &output.len() = 662
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 832
[bin/pad.rs:57] &output.len() = 642
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1472
[bin/pad.rs:57] &output.len() = 1470
[bin/pad.rs:57] &output.len() = 1474
[bin/pad.rs:57] &output.len() = 1472
[Finished running. Exit status: 0]

Is there a way for me to guarantee a fixed buffer size?

I am on Arch Linux.

Thank you!
Danny

@xasopheno
Copy link
Author

Tested this on osx and it functions as it would be expected. On my Arch system, I'm using ALSA.

@mitchmindtree
Copy link
Member

Can confirm, I also noticed this recently on ALSA (on NixOS).

@xasopheno
Copy link
Author

@mitchmindtree can you point me in the direction of the problem? I'm happy to dig into if you can give me an idea where to start.

@mitchmindtree
Copy link
Member

I think I would start by checking how the StreamConfig's buffer_size field gets passed through to and used by the ALSA backend, and seeing if anything looks suspicious there.

I would then maybe look for documentation and example demonstrations of specifying the buffer size in ALSA and see if there's anything we are missing or doing incorrectly.

Support for buffer sizes was only introduced recently in #401.

Hope this helps a little bit!

@xasopheno
Copy link
Author

Thank you. That helps a lot - I'll take a look. The first buffer seems to be requested correctly, but then the following buffers are of seemingly random sizes.

@sniperrifle2004
Copy link

sniperrifle2004 commented Dec 24, 2020

I believe there are multiple (possible) factors at work here:

  1. The buffer size is only an upper bound to the output buffer size. That's because the buffer size is the amount of audio that is buffered for playback in the audio device. The output buffer, however, is the space in that buffer to be filled. In principal if the device is properly configured the size of this will (at the moment) range between about a quarter and a half of the buffer size, but slight variations are expected. The first full buffer request is while the buffer is still empty.
  2. The period size (The amount of space that will (roughly) be empty) is not set properly yet. [ALSA] Improve stream setup parameters. #520 will fix that. I think this is an important factor in the observed erratic behaviour.
  3. Does this happen to be the alsa pulse plugin? That is the one plugin that comes to mind looking at that wide range of output buffer sizes.

@alexmoon
Copy link
Contributor

Right now, the way the ALSA worker threads in cpal work is as follows:

  • pollfd() the file descriptor associated with the stream, until it is ready for reading/writing
  • query the available space in ALSA's buffer
  • resize the buffer to be large enough to fill/drain ALSA's buffer
  • pass that buffer to the callback

This means, in general, that every call to the callback will receive a differently sized buffer, the size of which will depend on exactly when the worker thread queries the available buffer space. It should average to close to the period_len, but will certainly not be consistent as there can be variation on the order of milliseconds in when the thread wakes up.

In my opinion, a better approach would be to allocate a buffer sized to period_len up front. Then (for a playback stream) pass that whole buffer to the callback at once, write it into ALSA's buffer as space becomes available, only making another call to the callback once the whole buffer has been written out. That ensures the callback gets a consistently sized buffer and is called at a regular interval (on average).

I have a rewrite of the ALSA worker threads that incorporates this change, among other changes that fix some various bugs I've been experiencing with the current implementation, that I plan to submit as a pull request after #520 is merged.

@xasopheno
Copy link
Author

This all sounds great. After it's merged, I think I'd be able to transition WereSoCool from portaudio to cpal. Thank you for taking the time to work on it.

@strohel
Copy link

strohel commented Jun 17, 2021

Hi @alexmoon, #582 (which is a rebase of #520) has been merged. That should unblock your plan for a rewrite of the ALSA worker threads.

In fact, I've taken your branch master...alexmoon:alsa-worker and rebased it on cpal master: master...strohel:alsa-worker The last extra commit is my attempt to make user-visible behaviour consistent with other backends, but may be off. With it applied, I get buffer sizes exactly as requested.

If you want to drive that effort that would be cool. If not, I can submit that as a PR myself once I familiarize myself with the code.

@lu-zero
Copy link
Contributor

lu-zero commented Apr 3, 2022

What can be done to help making this progress?

@the-drunk-coder
Copy link

Can confirm the problem with Pipewire and the Alsa drop-in replacement ... the blocksize isn't as random as in the original post, but the buffer size changes from the first to the next block.

@Skilvingr
Copy link

For those who are still struggling with this, if you are using Pipewire, there's a way to force buffer size and sample rate through terminal.

pw-metadata -n settings returns current settings
pw-metadata -n settings 0 clock.force-quantum 1024 forces buffer size to 1024 samples
pw-metadata -n settings 0 clock.force-rate 44100 forces sample rate to 44100 Hz

I don't know how this could be implemented in cpal, but after playing a bit with buffer_size values in my StreamConfig, I've finally managed to get a fixed buffer size.

For further reading, this thread contains a lot of useful information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants