cpal
cpal copied to clipboard
Fixed BufferSize not functioning as expected
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
Tested this on osx and it functions as it would be expected. On my Arch system, I'm using ALSA.
Can confirm, I also noticed this recently on ALSA (on NixOS).
@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.
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!
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.
I believe there are multiple (possible) factors at work here:
- 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.
- 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.
- 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.
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.
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.
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.
What can be done to help making this progress?
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.
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.