cpal icon indicating copy to clipboard operation
cpal copied to clipboard

Fixed BufferSize not functioning as expected

Open xasopheno opened this issue 4 years ago • 12 comments

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 avatar Nov 02 '20 21:11 xasopheno

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

xasopheno avatar Nov 02 '20 22:11 xasopheno

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

mitchmindtree avatar Nov 04 '20 19:11 mitchmindtree

@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.

xasopheno avatar Nov 05 '20 04:11 xasopheno

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 https://github.com/RustAudio/cpal/pull/401.

Hope this helps a little bit!

mitchmindtree avatar Nov 05 '20 15:11 mitchmindtree

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.

xasopheno avatar Nov 05 '20 23:11 xasopheno

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. #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.

sniperrifle2004 avatar Dec 24 '20 09:12 sniperrifle2004

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.

alexmoon avatar Dec 24 '20 16:12 alexmoon

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.

xasopheno avatar Dec 24 '20 23:12 xasopheno

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 https://github.com/RustAudio/cpal/compare/master...alexmoon:alsa-worker and rebased it on cpal master: https://github.com/RustAudio/cpal/compare/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.

strohel avatar Jun 17 '21 17:06 strohel

What can be done to help making this progress?

lu-zero avatar Apr 03 '22 11:04 lu-zero

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.

the-drunk-coder avatar Aug 23 '23 06:08 the-drunk-coder

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.

Skilvingr avatar Dec 14 '23 16:12 Skilvingr