Repository navigation
ALSA: invalid handling of capture overruns #730
Description
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).
- The most severe case is when
poll()returnEPOLERRwhich 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:
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.
- Another scenario is when the second
ioctlcall 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:
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
Streamand create a new one? In such case I believecpalshould 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.
- The third scenario is when
ioctl(SNDRV_PCM_IOCTL_READI_FRAMES)fails withEPIPE:
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
ashleysmithgpu commentedon Feb 16, 2024
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" occurrederrors. I changed the code to add the suggested: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.
codepainters commentedon Feb 18, 2024
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.
sandreas commentedon Mar 3, 2026
@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
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:
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):
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.
fix(alsa): correct handling of capture overruns
roderickvd commentedon Mar 3, 2026
@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.
sandreas commentedon Mar 4, 2026
Sure.
Using
rodioin my project I bumped from0.21.1to0.22.1withcpalbumped0.16.0to0.17.3I 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 necessarilycpal, 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.
roderickvd commentedon Mar 4, 2026
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::Defaultnow following the system defaults. To solve that, configure the system or useBufferSize::Fixed. See:Let me know about #1112 though! That's v0.18 in the making.
sandreas commentedon Mar 5, 2026
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.
sandreas commentedon Mar 8, 2026
@roderickvd
Ok, I'm one step further. I only bumped
rodioandcpalalong with applying instructions fromUPGRADE.mdand 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: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.0in 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.3upgrade, otherwise testing will become a nightmare trying to fix 2 things at a time.roderickvd commentedon Mar 8, 2026
Well, just for kicks does #1112 solve your polling problem too?
roderickvd commentedon Mar 8, 2026
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.
sandreas commentedon Mar 8, 2026
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.3without changing anything else.But AFAIK the polling problem did not occur on
0.17.3so far. Since it is fast forwarding, my testfiles have not long enough playback time to run into the polling issue.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.
roderickvd commentedon Mar 8, 2026
Sorry I had linked the wrong Rodio issue. It should have been RustAudio/rodio#853.
1 remaining item
roderickvd commentedon Mar 8, 2026
It’s all explained in RustAudio/rodio#853.
sandreas commentedon Mar 8, 2026
@roderickvd
Thanks again for taking the time.
I tried to make rodio
0.22.2to work with cpal0.18.0in different ways, e.g. by overriding via.cargo/config.toml:but I always get an incompatible
Devicetrait, which makes no sense to me, the type is correct. This must be something going over my head.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 :-)
sandreas commentedon Mar 8, 2026
Ok, I did not find peace today so I poured some more time into it...
I tricked rodio into thinking current master
cpalis0.17.3by forkingcpalrepo and changing0.18.0to0.17.3in theCargo.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 updateand compiling it for the LicheeRV Nano, it shows up thesandreas: ...command line output, but the fast-forward problem seems NOT to be fixed! I'm not usingBufferSize::Fixed(2048)yet, but I just wanted to report some progress.The version change to the cpal
Cargo.tomlandprintln!()outputs were only commited locally, because it contained no actual code changeshttps://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.
sandreas commentedon Mar 8, 2026
Next update:
Setting a fixed BufferSize of
2048does 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 ofcpalandrodio.sandreas commentedon Mar 9, 2026
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).
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:
configto&config(beep example did not compile initially)sleepand a paramsleeptorun.... that's itSome more hints:
**So I think we can conclude: **
Awaiting instructions...
roderickvd commentedon Mar 9, 2026
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 runon the command-line, outside of the IDE?sandreas commentedon Mar 9, 2026
Thanks for responding so quickly.
Understandable - I can't reproduce this on my notebook either, seems it was a one time (un-)lucky coincidence.
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...
Ok, no problem. It's probably gonna take until tomorrow, but I plan to provide feedback for:
Here is the audio configuration on ArchLinux Gnome (please let me know if you need something additional):
Hope I find something helpful ;)
sandreas commentedon Mar 9, 2026
Ok, I did some more research.
Since
cpal 0.17.3withrodio 0.22.2did 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.0withrodio 0.21.1and started porting thecpalchanges from0.17.3including the ones from #1122 to0.16.0to 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:
cpal masterhas 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 realtimerodioto select the wrong default device, all my attempts to apply patches that fixed it for others failed on my RISC-V system. I usedmainofcpalandrodiotogether with some manual patches and it just kept fast-fowarding on the RISC-V, while it worked on my ArchLinux NotebookMany things happenened between 0.16.0 and 0.18.0. There was also an update from the
alsadependency from0.9to0.11. I would think that the fast-forward problem is a combination ofrodioandcpalchanges, which makes it especially hard to track, becauserodioandcpalboth changed APIs, so that you cannot track one project but must track the problem in combination. My guess is, that the problem lies here:cpal/src/host/alsa/mod.rs
Line 328 in 7a4d350
So long for now, I'll keep you up to date.
sandreas commentedon Mar 10, 2026
@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:
wavfiles play SLOWER,m4bfiles play FASTER (so it depends on the format)The error is reproducible on my desktop (hopefully on yours, too):
Start
raudiovia: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
The poll error is probably fixed with the branch you provided and the next thing to verify on my list. Hope it helps.
sandreas commentedon Mar 13, 2026
@roderickvd
I think I might have found something interesting:
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).Using the file that reproduces the error looks like this:
Or this:
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?
roderickvd commentedon Mar 13, 2026
Thanks for your continued efforts. I've been busy this week.
Super weird... do you mean the position indicated, or does the pitch change?
How did it go?
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?
roderickvd commentedon Mar 13, 2026
Also I'm reading that you say "t" for test needs to be played first. So that triggers Rodio's
SineWavewhich 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.
sandreas commentedon Mar 14, 2026
The sampling rate! Of course... why didn't I think of that:
So the fast-forward thing is pretty likely something in
rodio.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
fix(alsa): overhaul polling, error handling, and stream lifecycle (Ru…