Skip to content

ALSA: invalid handling of capture overruns #730

Closed

Description

@codepainters

I'm working on an application using cpal over ALSA to continuously capture audio stream on Raspberry Pi. I use both RPI and PC for testing. Note: I'm using a version from master branch, as I need both i16 and i32 sample formats, with i32 not being supported in stable version.

Unfortunately I'm experiencing odd behavior upon capture overruns (which I trigger setting buffer size too low).

  1. The most severe case is when poll() return EPOLERR which is not handled properly.
476794 1669022562.665530 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5, revents=POLLIN}])
476794 1669022562.666440 ioctl(5, SNDRV_PCM_IOCTL_STATUS_EXT,      0x7f6b10495f88) = 0
476794 1669022562.666532 ioctl(5, SNDRV_PCM_IOCTL_HWSYNC, 0x98) =      0
476794 1669022562.666577 ioctl(5, SNDRV_PCM_IOCTL_READI_FRAMES,      0x7f6b10496760) = 0
476794 1669022562.666707 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5, revents=POLLIN|POLLERR}])
476794 1669022562.669590 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5, revents=POLLIN|POLLERR}])
476794 1669022562.669765 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5, revents=POLLIN|POLLERR}])

And then I no longer get any more callbacks from cpal, but my application is eating up 100% of CPU core, as poll() is executed in a tight loop.

The problem is here:

cpal/src/host/alsa/mod.rs

Lines 746 to 753 in 7776c66

let stream_type = match stream.channel.revents(&descriptors[1..])? {
alsa::poll::Flags::OUT => StreamType::Output,
alsa::poll::Flags::IN => StreamType::Input,
_ => {
// Nothing to process, poll again
return Ok(PollDescriptorsFlow::Continue);
}
};

This match doesn't recognize POLLIN|POLLERR and returns PollDescriptorsFlow::Continue, causing the worker thread to poll() again immediately, leading to a tight loop.

The problem seems obvious, I'll try to come with a pull request.

  1. Another scenario is when the second ioctl call fails.
476936 1669022662.246764 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5, revents=POLLIN}])
476936 1669022662.247721 ioctl(5, SNDRV_PCM_IOCTL_STATUS_EXT, 0x7f70e2810f88) = 0
476936 1669022662.247884 ioctl(5, SNDRV_PCM_IOCTL_HWSYNC, 0x98) = -1 EPIPE (Broken pipe)
476936 1669022662.248019 ioctl(5, SNDRV_PCM_IOCTL_PREPARE, 0) = 0
476936 1669022662.248112 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 0 (Timeout)
476936 1669022662.448722 write(1, "Die! A backend-specific error has occurred: `alsa::poll()` spuriously returned\n", 79) = 79
476936 1669022662.449074 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 0 (Timeout)
476936 1669022662.649849 write(1, "Die! A backend-specific error has occurred: `alsa::poll()` spuriously returned\n", 79) = 79
476936 1669022662.650342 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 0 (Timeout)
476936 1669022662.851166 write(1, "Die! A backend-specific error has occurred: `alsa::poll()` spuriously returned\n", 79) = 79

In this scenario I no longer get any data callbacks, instead I get error callback periodically (poll timeout).

I believe the problem is here:

cpal/src/host/alsa/mod.rs

Lines 604 to 609 in 7776c66

PollDescriptorsFlow::XRun => {
if let Err(err) = stream.channel.prepare() {
error_callback(err.into());
}
continue;
}

The call to prepare() passes OK, but the stream is stopped - according to ALSA, this call changes the state to SND_PCM_STATE_PREPARED, there also need to be a call to start() to transition to SND_PCM_STATE_RUNNING and re-enable data flow.

I'm a bit confused here about what is the intended cpal behavior here:

  • should I assume that stream is broken when my error callback is called for the first time, drop the Stream and create a new one? In such case I believe cpal should do some cleanup (e.g. stop the polling loop).
  • should I assume that the error is temporary and the data flow will continue? In such case start() call is missing.
  1. The third scenario is when ioctl(SNDRV_PCM_IOCTL_READI_FRAMES) fails with EPIPE:
476728 1669022521.635527 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5,        revents=POLLIN}])
476728 1669022521.636362 ioctl(5, SNDRV_PCM_IOCTL_STATUS_EXT, 0x7f8b2368af88) = 0
476728 1669022521.636521 ioctl(5, SNDRV_PCM_IOCTL_HWSYNC, 0x98) = 0
476728 1669022521.636681 ioctl(5, SNDRV_PCM_IOCTL_READI_FRAMES, 0x7f8b2368b760) = -1 EPIPE (Broken pipe)
476728 1669022521.636777 write(1, "Die! A backend-specific error has occurred: ALSA function 'snd_pcm_readi' failed with error 'EPIPE: Broken pipe'\n", 113) = 113
476728 1669022521.636823 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5,        revents=POLLIN|POLLERR}])
476728 1669022521.636860 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5,        revents=POLLIN|POLLERR}])
476728 1669022521.636892 poll([{fd=3, events=POLLIN}, {fd=5, events=POLLIN|POLLERR|POLLNVAL}], 2, 200) = 1 ([{fd=5,        revents=POLLIN|POLLERR}])

Here, the error from process_input() is properly delivered to my error callback, but then we enter the tight loop scenario, due to POLLERR not being recognized.

Activity

  1. ashleysmithgpu commented on Feb 16, 2024

    @ashleysmithgpu

    Just a quick note that I ran in to this issue as-well. I was getting constant ALSA lib pcm.c:8545:(snd_pcm_recover) "overrun" occurred errors. I changed the code to add the suggested:

                PollDescriptorsFlow::XRun => {
                    if let Err(err) = stream.channel.prepare() {
                        error_callback(err.into());
                    }
                    if let Err(err) = stream.channel.start() {
                        error_callback(err.into());
                    }
                    continue;
                }
    

    And it seems to work fine now. I still get the overruns, but it gracefully continues.
    I am running on a raspberry PI zero w with the respeaker 2mic board attached.

  2. codepainters commented on Feb 18, 2024

    @codepainters
    ContributorAuthor

    I have moved away from CPAL altogether. My application only needs to work on Linux (Windows was a nice-to-have feature), and I only need a simple record capability (and I also use Respeaker!).

    I decided to switch to raw alsa, which solved all my problems. I was initially afraid of the low-level interface, but it turned out surprisingly easy.

  3. sandreas commented on Mar 3, 2026

    @sandreas

    @roderickvd

    Just would like to ask, if anybody ever had looked into this. I'm running into the same issue where audio completely breaks and I get

    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    # ....
    

    in a repeated loop. Unfortunately I'm bound to a device specific ALSA (kernel 5.10), so I cannot upgrade to pipewire or something more modern.

    I'm capable of writing Rust code, so if somebody can point me into the right direction, I may be able to submit a pull request.

    If it is just this:

                PollDescriptorsFlow::XRun => {
                    if let Err(err) = stream.channel.prepare() {
                        error_callback(err.into());
                    }
                    if let Err(err) = stream.channel.start() {
                        error_callback(err.into());
                    }
                    continue;
                }

    could integrate that - if there is no tests that have to be updated.

    I also experimented with raising the buffer sizes to prevent underruns (XRUN):

    pcm.!default {
        type plug
        slave {
            pcm "hw:2,0"
            rate 44100
            format S16_LE
            channels 2
            buffer_size 4096
            period_size 1024
        }
    }
    

    but I think that my hardware is not supporting fixed buffer sizes, since the playback is not working this way.

    I would really appreciate any help.

  4. added a commit that references this issue on Mar 3, 2026
    d2e9e89
  5. roderickvd commented on Mar 3, 2026

    @roderickvd
    Member

    @sandreas thanks for the nudge. You'd be of great help if you could help me test #1112 properly on several devices. Your old kernel is an interesting use case; let me know how it goes there as well as on newer targets, for both playback and capture streams.

  6. sandreas commented on Mar 4, 2026

    @sandreas

    You'd be of great help if you could help me test #1112 properly on several devices.

    Sure.

    Using rodio in my project I bumped from 0.21.1to 0.22.1with cpal bumped 0.16.0 to 0.17.3 I noticed that straight audio playback is no longer possible on my Kernel 5.1.0 device - it's just fast forwarding, while on my Notebook (6.12.74-1-lts) it is working fine with a non ALSA backend. I hadn't the time to investigate further, so I'm not sure why this happened (not necessarily cpal, but possible).

    Here is repository and the commit to update rodio / cpal:

    I'm building a DIY portable audio player using a more exotic piece of hardware (LicheeRV Nano, riscv64-linux-musl). It uses a buildroot image based on kernel 5.10 as well as an Apple USB-C to 3.5mm adapter for audio output. Development happens on linux, which is "reproducing" the hardware environment as good as it gets and except GPIO it goes pretty well. However, this is one of the things that turned out to be different :-)

    I report back ASAP.

  7. roderickvd commented on Mar 4, 2026

    @roderickvd
    Member

    What do you mean with "fast-forwarding"? If you mean to say that your samples are being consumed at a much faster pace than they are played back, then that's 99% sure due to BufferSize::Default now following the system defaults. To solve that, configure the system or use BufferSize::Fixed. See:

    Let me know about #1112 though! That's v0.18 in the making.

  8. sandreas commented on Mar 5, 2026

    @sandreas

    What do you mean with "fast-forwarding"? If you mean to say that your samples are being consumed at a much faster pace than they are played back, then that's 99% sure due to BufferSize::Default now following the system defaults.

    Yeah I meant that samples are consumed too fast - thanks for clarifying this.

    Unfortunately I'm currently fighting instability issues on the device. This could have a lot of reasons (e.g. power supply, kernel, faulty hardware or a new buildroot) so I have to narrow this down first. I have some spare hardware and old images so this should not take too long, but until this is "fixed" I cannot really test something reliably.

    As soon as I can I'm going to test #1112 and report back. Should not take longer than next week. Thanks for taking the time to reach out.

  9. sandreas commented on Mar 8, 2026

    @sandreas

    @roderickvd

    Ok, I'm one step further. I only bumped rodio and cpal along with applying instructions from UPGRADE.md and the audio playback is non-functional. Reverting the change (see nanowave-player/nanowave-ui@0f6cecd) makes audio playback work again, but then I still run into the polling error after a little while:

    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    ...
    

    Typically after the polling error nothing can heal the system (not even re-plugging the usb-c audio device) and sometimes the problem even survives a reboot (that's why it felt so unstable I think).

    Maybe you could point me into the right direction / documentation on how to make the buffersize act like 1.6.0 in my specific use case? I checked the commits but there were so many I did not find the right one. In the rare case I'd find a generic solution to the polling problem I'd submit a PR ASAP.

    Regarding #1112: I'm on it. Currently trying to understand the code and working into the project. I plan to fork and put some debug output next week, but first I need to find the playback issue with the 0.16.0 -> 0.17.3 upgrade, otherwise testing will become a nightmare trying to fix 2 things at a time.

  10. roderickvd commented on Mar 8, 2026

    @roderickvd
    Member

    Well, just for kicks does #1112 solve your polling problem too?

  11. roderickvd commented on Mar 8, 2026

    @roderickvd
    Member

    And don't forget to specify your buffer size. I don't see that in your commit, and is usually the reason for breakage between 0.16 and 0.17.

  12. sandreas commented on Mar 8, 2026

    @sandreas

    Well, just for kicks does #1112 solve your polling problem too?

    Oh, that was quick... I can't test, because playback is not working correctly. The polling issue only happens on normal playback speed, which somehow broke in my code updating cpal from 0.16.0 -> 0.17.3 without changing anything else.

    But AFAIK the polling problem did not occur on 0.17.3 so far. Since it is fast forwarding, my testfiles have not long enough playback time to run into the polling issue.

    And don't forget to specify your buffer size. I don't see that in your commit, and is usually the reason for breakage between 0.16 and 0.17.

    The problem is: I don't know where and what value the buffer size must be to work like expected. Could you give me a hint where to find this? The links from above did not reveal a solution to me.

  13. roderickvd commented on Mar 8, 2026

    @roderickvd
    Member

    Sorry I had linked the wrong Rodio issue. It should have been RustAudio/rodio#853.

  14. 1 remaining item

  15. roderickvd commented on Mar 8, 2026

    @roderickvd
    Member

    It’s all explained in RustAudio/rodio#853.

  16. sandreas commented on Mar 8, 2026

    @sandreas

    @roderickvd
    Thanks again for taking the time.

    I tried to make rodio 0.22.2 to work with cpal 0.18.0 in different ways, e.g. by overriding via .cargo/config.toml:

    [patch.crates-io]
    # forked variant
    # cpal = { git = "file:///home/andreas/projects/sandreas/cpal" }
    # rodio = { git = "file:///home/andreas/projects/sandreas/rodio" }
    
    # path variant (with forked repo)
    # cpal = { path = "file:///home/andreas/projects/sandreas/cpal" }
    # rodio = { path = "file:///home/andreas/projects/sandreas/rodio" }
    
    cpal = { git = "https://github.com/RustAudio/cpal", branch = "master"}
    rodio = { git = "https://github.com/RustAudio/rodio", branch = "master"}

    but I always get an incompatible Device trait, which makes no sense to me, the type is correct. This must be something going over my head.

                let builder_result = DeviceSinkBuilder::from_device(selected_device).unwrap();
        |                                  ------------------------------ ^^^^^^^^^^^^^^^ expected `rodio::Device`, found `cpal::Device`

    which made absolutely no sense to me, the type seems to be correct.

    Tommorow I'm going to sink another 2 hours into this, but for today it's enough :-)

  17. sandreas commented on Mar 8, 2026

    @sandreas

    Ok, I did not find peace today so I poured some more time into it...

    I tricked rodio into thinking current master cpal is 0.17.3 by forking cpal repo and changing 0.18.0 to 0.17.3 in the Cargo.toml.

    I verified to have the right code by adding a simple println!("sandreas: ...") in the specific parts I'm calling in my player.

    After cargo update and compiling it for the LicheeRV Nano, it shows up the sandreas: ... command line output, but the fast-forward problem seems NOT to be fixed! I'm not using BufferSize::Fixed(2048) yet, but I just wanted to report some progress.

    The version change to the cpal Cargo.toml and println!() outputs were only commited locally, because it contained no actual code changes

    https://github.com/nanowave-player/nanowave-ui/compare/cpal-upgrade?expand=1

    The next step is to integrate BufferSize::Fixed(..), then I try to experiment with this part: RustAudio/rodio#853 (comment) for my rodio fork, but I've not so high hopes for this, since I'm using a non-default device (USB-C Audio adapter).

    Feel free to correct me if I did something wrong or missed something, otherwise I'll report back as soon as I tested the BufferSize part.

  18. sandreas commented on Mar 8, 2026

    @sandreas

    Next update:

    Setting a fixed BufferSize of 2048 does also not fix the problem (as well as other values, e.g. 256)... my log output confirms it is using the unmodified most current master of cpal and rodio.

  19. sandreas commented on Mar 9, 2026

    @sandreas

    Ok, this morning I decided to test the beep example on the device and ran into the issue within the JetBrains IDE on my Notebook just pressing the run button! (screenshot attached).

    Image

    This repository is the state it was in at that point here:
    https://github.com/sandreas/cpal-beep-example

    The forked cpal repo is here (no changes except a println for verification and a patched Cargo.toml to trick version dependencies):
    https://github.com/sandreas/cpal

    The changes I made from the original beep example:

    • Changed config to &config (beep example did not compile initially)
    • Added an opt sleep and a param sleep to run.... that's it

    Some more hints:

    • Before the sleep parameter addition the beep was working as expected on my Notebook.
    • It happened only ONCE
    • It was not reproducible later with the same procedure.

    **So I think we can conclude: **

    • THIS issue here has to be considered separate from the fast forward / get_pos issue
    • It is not a rodio issue (rodio is not even in the repo)
    • It is not an issue with strange architectures (also happens on x64)
    • It may be a hardware / kernel / system-state related thing that needs extra careful error handling / re-initializing to be overcome.

    Awaiting instructions...

  20. roderickvd commented on Mar 9, 2026

    @roderickvd
    Member

    That is a useful minimal example, but difficult for me to diagnose as I can't reproduce it locally.
    I would really like that you try #1122 on this minimal example. It refactors the polling worker, and may have fixed it.

    Second please let me know your notebook's audio configuration. Is it running PulseAudio or PipeWire? Does it also happen if you run cargo run on the command-line, outside of the IDE?

  21. sandreas commented on Mar 9, 2026

    @sandreas

    Thanks for responding so quickly.

    That is a useful minimal example, but difficult for me to diagnose as I can't reproduce it locally.

    Understandable - I can't reproduce this on my notebook either, seems it was a one time (un-)lucky coincidence.

    Does it also happen if you run cargo run on the command-line, outside of the IDE?

    As I said, I was not able to reproduce this a second time... so I can't really tell you if this would also happen with cargo run...

    I would really like that you try #1122 on this minimal example. It refactors the polling worker, and may have fixed it.

    Ok, no problem. It's probably gonna take until tomorrow, but I plan to provide feedback for:

    • Notebook (probably not very meaningful, since I was not able to reproduce it)
    • LicheeRV Nano (the target device)

    Here is the audio configuration on ArchLinux Gnome (please let me know if you need something additional):

    > inxi -A
    Audio:
      Device-1: Intel Sunrise Point-LP HD Audio driver: snd_hda_intel
      API: ALSA v: k6.12.74-1-lts status: kernel-api
      Server-1: PipeWire v: 1.4.10 status: active
    

    Hope I find something helpful ;)

  22. sandreas commented on Mar 9, 2026

    @sandreas

    Ok, I did some more research.

    Since cpal 0.17.3 with rodio 0.22.2 did break the playback and always switches to fast-forward mode without hearing any sound on the headphones, I first tried to apply the patch from here, which did NOT fix the problem on my RISC-V device. I suspect because I'm not using the default device, there is either some other code path, that is broken or the problem is somewhere else.

    My next approach was going back to cpal 0.16.0 with rodio 0.21.1and started porting the cpalchanges from 0.17.3 including the ones from #1122 to 0.16.0 to have at least more info about the polling problem. Somewhere on the way of porting I lost the headphone output of any sound while the position updater shows it keeps playing without switching into fast-forward mode (timer feels correct).

    So the next step will be going back to see at which commit / piece of code I am losing the headphone output and going forward to see, when the fast-forward issue appears (if it does).

    In conclusion:

    • alsa: refactor and enhance worker loop #1122 looks good so far, there were some messages that indicated underruns on my notebook but due to the loop it cracked once and kept playing (at least it felt like nothing completely broke and it kept playing, which is a step forward)
    • The beep sample with cpal master has valid sound output on the RISC-V device. I patched it to make the beep last 60 seconds and I had no underruns at all, but I'm not sure how good of a data point this is, because the beep sample is not eating any ram and does not need any decoding, so I suspect the underruns only happen with a real world player decoding audio in realtime
    • get_pos much further ahead than actual playback position rodio#853 is hard to point my finger on (at least for me with my limited knowledge). Although you were really clear about suspecting rodio to select the wrong default device, all my attempts to apply patches that fixed it for others failed on my RISC-V system. I used main of cpal and rodio together with some manual patches and it just kept fast-fowarding on the RISC-V, while it worked on my ArchLinux Notebook

    Many things happenened between 0.16.0 and 0.18.0. There was also an update from the alsa dependency from 0.9 to 0.11. I would think that the fast-forward problem is a combination of rodio and cpal changes, which makes it especially hard to track, because rodio and cpal both changed APIs, so that you cannot track one project but must track the problem in combination. My guess is, that the problem lies here:

    So long for now, I'll keep you up to date.

  23. sandreas commented on Mar 10, 2026

    @sandreas

    @roderickvd
    Ok, I think I got it... or at least a possibly useful datapoint.

    I extracted the playback part out of the slint app to be quicker on testing and optimized it a bit - the project is here:
    https://github.com/sandreas/raudio

    While testing I noticed something strange:

    • If I execute my playback test function and followed by start playing a file afterwards, the timing is messed up
    • wav files play SLOWER, m4b files play FASTER (so it depends on the format)
    • Without executing the playback test, everything plays normal (even without setting the buffer size)
    • Triggering the error, the bigger the BufferSize is, the SLOWER is wav playback and the FASTER is m4b playback

    The error is reproducible on my desktop (hopefully on yours, too):

    Start raudio via:

    cargo run -- --file="./path/to/file.m4b"

    then type t, then Enter (you should hear a little test sound)
    then type f, then Enter (you should hear the file you specified in about 2x the speed)

    I'm not sure this report belongs here, maybe it is something in rodio and it is not the polling, but at least I now got a working playback with

    rodio = "0.22.2"
    cpal = "0.17.3"

    The poll error is probably fixed with the branch you provided and the next thing to verify on my list. Hope it helps.

  24. sandreas commented on Mar 13, 2026

    @sandreas

    @roderickvd

    I think I might have found something interesting:

    audio stream error: A backend-specific error has occurred: unexpected number of frames written: expected 940, result 24 (this should never happen)
    

    It is a pretty reliable way to reproduce the polling problem (this issue) and the fast-forward issue (RustAudio/rodio#853) on my system.

    IMPORTANT: It seems to depend on the used audio file - so maybe the decoder / combination of decoding and playback. Unfortunately I cannot provide a file that raises the issue here publicly (copyright), but maybe we find a way to provide it just for reproduction purposes.

    The following commands show how my reproducing tool (raudio) can be used with a publicly available file, though the error does not happen here (or at least not in it's full beauty).

    git clone https://github.com/sandreas/raudio
    cd raudio
    mkdir -p media
    wget "https://cdn.pixabay.com/download/audio/2022/03/10/audio_d3bcefc824.mp3?filename=freesound_community-science-teacher-lecturing-33676.mp3" -O ./media/test.mp3
    cargo run -- --file=./media/test.mp3 --quick
       Compiling raudio v0.1.0 (/tmp/raudio)
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
         Running `target/debug/raudio --file=./media/test.mp3 --quick`
    file result: Ok(File { fd: 8, path: "/tmp/raudio/media/test.mp3", read: true, write: false })
    audio stream error: Buffer underrun/overrun occurred.

    Using the file that reproduces the error looks like this:

    cargo run -- --file=./media/test.m4b --quick
      Compiling raudio v0.1.0 (/home/andreas/projects/sandreas/raudio)
       Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
        Running `target/debug/raudio --file=./media/test.m4b --quick`
    file result: Ok(File { fd: 8, path: "/home/andreas/projects/sandreas/raudio/media/test.m4b", read: true, write: false })
    audio stream error: Buffer underrun/overrun occurred.
    audio stream error: A backend-specific error has occurred: unexpected number of frames written: expected 940, result 24 (this should never happen)
    audio stream error: Buffer underrun/overrun occurred.

    Or this:

    a::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
    

    I could invite you into a private repository and share the file that produces the error. I'm pretty sure you'd have a hard time reproducing it not having the file.

    So, what would be the next steps?

  25. roderickvd commented on Mar 13, 2026

    @roderickvd
    Member

    Thanks for your continued efforts. I've been busy this week.

    • Triggering the error, the bigger the BufferSize is, the SLOWER is wav playback and the FASTER is m4b playback

    Super weird... do you mean the position indicated, or does the pitch change?

    The poll error is probably fixed with the branch you provided and the next thing to verify on my list.

    How did it go?

    I could invite you into a private repository and share the file that produces the error. I'm pretty sure you'd have a hard time reproducing it not having the file.

    That'd be great.

    Are other M4B files also causing issues? WAV/MP3 are easier format to decode than M4B, so if you're running this without release optimizations then that may trigger underruns. That could explain the file-dependent behavior.

    What happens when compiling with --release?
    Or if you're only using non-AAC (M4A/M4B) files?

  26. roderickvd commented on Mar 13, 2026

    @roderickvd
    Member

    Also I'm reading that you say "t" for test needs to be played first. So that triggers Rodio's SineWave which could be a different sampling rate than your audio files, causing Rodio's sample rate converter and queue to get funny. That'd be a Rodio bug.

    For this cpal issue, it'd be necessary to have a minimal example without all the Rodio bits and isolate/verify it's cpal or not.
    Those backend-specific errors should go away with #1122 and I suspect the rest is a Rodio queue or more likely mixer thing.

  27. sandreas commented on Mar 14, 2026

    @sandreas

    which could be a different sampling rate than your audio files, causing Rodio's sample rate converter and queue to get funny. That'd be a Rodio bug.

    The sampling rate! Of course... why didn't I think of that:

    ┌───────────────────┬───────────────────────────┐
    │            format │ MPEG-4 Part 14: audio/mp4 │
    │           bitrate │ 64                        │
    │       sample-rate │ 22050                     │
    │          duration │ 00:41:54.178              │
    │               vbr │ True                      │
    │          channels │ 2 (Stereo (2/0.0))        │
    │ embedded pictures │ 1                         │
    │     1 meta format │ Native tagging / MPEG-4   │
    └───────────────────┴───────────────────────────┘
    

    So the fast-forward thing is pretty likely something in rodio.

    For this cpal issue, it'd be necessary to have a minimal example without all the Rodio bits and isolate/verify it's cpal or not.
    Those backend-specific errors should go away with #1122 and I suspect the rest is a Rodio queue or more likely mixer thing.

    Absolutely - well, it's kind of hard to redo the error JUST using cpal, because I can't easily simulate what rodio is doing here. Anyway, we'll assume that this issue here is fixed (or at least worked around) with with #1122 until rodio integrates the next release and I can find something else with the debugger. Some API conflicts in cpal master (&config -> config) are preventing me from using rodio and cpal with the 1122 PR together to rebuild the full behavior of rodio in cpal only. I was not able to reproduce the polling issue with #1122 yet.

    Thank you for helping me out and providing me guidance. Great work!

    For the sake of completeness: The shortest I can come up with ATM for cpal 0.17.3 WITH rodio is below and here is a file that can be publicly shared while reproducing the error:

    test.zip

    Or just create your own:

    # create a file that reproduces the error with ffmpeg
    ffmpeg -i test.mp3 -f mp4 -ar 22050 -b:a 64k test.m4b

    "short" example

    use clap::Parser;
    use rodio::cpal::traits::HostTrait;
    use rodio::cpal::{BufferSize, StreamConfig};
    use rodio::{cpal, DeviceSinkBuilder, DeviceTrait, Source};
    use std::fs::File;
    use std::path::Path;
    use std::thread::sleep;
    use std::time::Duration;
    mod player;
    
    #[derive(Parser, Debug)]
    #[command(version, about = "rodio example", long_about = None)]
    struct Opt {
        /// The audio device to use
        #[arg(short, long)]
        device: Option<String>,
    
        #[arg(short, long, default_value = "")]
        file: Option<String>,
    
        #[arg(short, long)]
        quick: bool,
    }
    
    fn main() {
        let opt: Opt = Opt::parse();
    
        let host = cpal::default_host();
    
        let device = if let Some(device) = opt.device.clone() {
            let id = &device.parse().expect("failed to parse device id");
            host.device_by_id(id)
        } else {
            host.default_output_device()
        }
        .expect("failed to find output device");
    
        let mut config: StreamConfig = device.default_output_config().unwrap().into();
        // test must be played first!! otherwise the playback is fine
        // After "test" it plays wav too slow (1/2 speed), and m4b too fast (2x speed)
        // possible side effect: the bigger the buffer, the slower the wav and faster m4b playback
    
        // config.buffer_size = BufferSize::Fixed(1024);
        // config.buffer_size = BufferSize::Fixed(128*1024);
        config.buffer_size = BufferSize::Fixed(512);
    
        let stream = DeviceSinkBuilder::from_device(device)
            .expect("failed to create sink builder")
            .with_config(&config)
            .open_stream()
            .expect("failed to open stream");
    
        let file = if let Some(f) = opt.file.clone()
            && Path::exists(Path::new(&f))
        {
            f
        } else {
            "".to_string()
        };
    
        let sink = rodio::Player::connect_new(stream.mixer());
        sink.clear();
        let waves = vec![230f32, 270f32, 330f32, 270f32, 230f32];
        for w in waves {
            let source = rodio::source::SineWave::new(w).amplify(0.1);
            sink.append(source);
            sink.play();
            sleep(Duration::from_millis(200));
            sink.stop();
            sink.clear();
        }
    
        let path = Path::new(file.as_str());
        let file_result = File::open(path);
    
        // the follwing println! needs to be commented out / in for a "compile change" which pretty reliable results in
        // audio stream error: A backend-specific error has occurred: `alsa::poll()` returned POLLERR
        // workflow:
        // - comment out this line, then cargo run ...
        // - comment in the line, then cargo run ... => stream error should happen after a few seconds
        println!("file result: {:?}", file_result);
    
        if let Ok(file) = file_result {
            let decoder_result = rodio::Decoder::try_from(file);
            if let Ok(decoder) = decoder_result {
                sink.clear();
                sink.append(decoder);
                sink.play();
                sink.sleep_until_end();
            }
        }
        return;
    }
  28. added a commit that references this issue on Mar 15, 2026
    5931fa2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions