RustAudio / cpal

Cross-platform audio I/O library in pure Rust
Apache License 2.0
2.72k stars 362 forks source link

Fixed BufferSize not functioning as expected #495

Open xasopheno opened 4 years ago

xasopheno commented 4 years ago

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 commented 4 years ago

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

mitchmindtree commented 4 years ago

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

xasopheno commented 4 years ago

@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 commented 4 years ago

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!

xasopheno commented 4 years ago

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 commented 3 years ago

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.
alexmoon commented 3 years ago

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

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 commented 3 years ago

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 commented 3 years ago

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.

lu-zero commented 2 years ago

What can be done to help making this progress?

the-drunk-coder commented 1 year ago

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 commented 11 months ago

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.