-
Notifications
You must be signed in to change notification settings - Fork 321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DirectSound hangs/glitches when using small input host buffers #775
Comments
Looking a bit closer at the code, I think I understand now why the host input buffer size is affected by portaudio/src/hostapi/dsound/pa_win_ds.c Lines 1800 to 1803 in d07842c
This does still suggest that allocating a host input buffer that is larger than necessary should be benign (the only downside is increased memory usage), so we might want to impose a clamp on the input buffer size to account for the 31.25ms read cursor granularity. |
Hi Etienne, I really appreciate your engagement with improving PortAudio. However I'm having a hard time digesting this long bug report, it could benefit from more focus. Does your last comment basically contradict the original report? Maybe you update the original report to reflect your current view. Would it be possible for you to simplify and clarify the report, perhaps by breaking it up under the following headings:
|
Sure.
It does not. It only contradicts the quoted section. That was just me thinking out loud about potential fixes. I apologize for the rambling.
I think this is covered by the first part of my report so I'll just repeat it here: When running
This happens regardless of half duplex or full duplex mode. Output seems unaffected.
One way to reproduce is to run
The "timed out" errors are the cues here - the stream callback never fires. The rest is just
DirectSound only provides 31.25 ms read cursor granularity on capture buffers, regardless of the size of the capture buffer, regardless of sample rate, and (seemingly) regardless of hardware or software configuration. This appears to be a bug/limitation in DirectSound itself. Because of this limitation, if PortAudio allocates a DS capture buffer whose length is less than 31.25 ms, the DS read cursor is stuck at 0 and never makes progress. This in turn means the stream callback never fires.
The fix I have in mind is to make PortAudio always allocate a capture buffer that is at least 62.50 ms to work around this DirectSound limitation. This should not affect input latency because the size of the capture buffer does not affect latency (PortAudio will consume input data as soon as the read cursor moves regardless). The only downside is slightly increased memory usage for the capture buffer, but that's obviously a better outcome than the stream not working at all. |
Thanks.
Which Windows versions have you tested this on, and/or what is the source of this information, and/or to which Windows versions do you think that this applies to? |
My original investigation in dechamps/FlexASIO#29 dates back to 2018, I must have been running on Windows 10 back then. I can still reproduce it today on Windows 11 22H2. I was not the one who noticed it first - it was a bug report from an user of my app. Since then, more users reported this issue to me over time, see e.g. dechamps/FlexASIO#50, dechamps/FlexASIO#110, dechamps/FlexASIO#140. Presumably I would have received even more reports if I hadn't worked around it in my app to avoid small buffer sizes. Also keep in mind that a DirectSound user faced the exact same problem all the way back in 2011 on Windows 7, citing the exact same read cursor granularity of 31.25 ms. Given the above, I would basically assume this issue affects all Windows versions starting from Windows 7 at least. |
Out of curiosity I ran a few tests to see if there was a similar "minimum granularity" for output DS buffers as well. I was able to observe write cursor granularity of 1.25 ms which is basically good enough for all intents and purposes. So it looks like we only need some kind of fix for the input host buffer, and we can leave the existing output host buffer logic alone. |
This works around a DirectSound limitation where input host buffer sizes smaller than 31.25 ms are basically unworkable and make PortAudio hang. The workaround is to impose a minimal buffer size of 2*31.25 ms on input-only and full-duplex streams. This is enough for the read cursor to advance twice around the buffer, basically resulting in de facto double buffering. This change was tested with `paloopback` under a wide variety of half/full-duplex, framesPerBuffer, and suggested latency parameters. (Note the testing was done on top of PortAudio#772 as otherwise paloopback is not usable.) Fixes PortAudio#775
This works around a DirectSound limitation where input host buffer sizes smaller than 31.25 ms are basically unworkable and make PortAudio hang. The workaround is to impose a minimal buffer size of 2*31.25 ms on input-only and full-duplex streams. This is enough for the read cursor to advance twice around the buffer, basically resulting in de facto double buffering. This change was tested with `paloopback` under a wide variety of half/full-duplex, framesPerBuffer, and suggested latency parameters. (Note the testing was done on top of PortAudio#772 as otherwise paloopback is not usable.) Fixes PortAudio#775
I have just conducted a preliminary test on my system using AudioMulch (slightly dated version of PortAudio) on Windows 10 with Realtek HD sound on my ASUS motherboard. 48KHz sample rate. PA callback buffer size = 64 frames. I do observe some kind of stalling, however it appears to be different from what's described above. The test I did was input+output, monitoring input on headphones. My settings are expressed as follows: "buffer sizes" in frames: 256, 512, 1024, 2048, 4096, 8192, 16384, 32768. These are converted to suggested latency parameters for PortAudio
(of course I support other sample rates, but it's best to use the native rate for this test). some results are as follows:
So it seems that if the output latency is configured large enough, a small input latency (~5ms) works fine on my system. (Note here that "input latency" is the From this I conclude that the "31.25 ms input read cursor granularity" conjectured above is only present on some systems. The fact that the output latency setting is changing the input latency behavior suggests to me that something else might be going on. @dechamps do you observe any similar change of behavior if you try different output latencies while keeping the input latency constant? |
This is perfectly unsurprising and does not contradict my report. This is because, in full duplex mode, the PortAudio DS code only uses a single buffer size for both input and output, and that buffer size is calculated based on the largest of input and output latency: portaudio/src/hostapi/dsound/pa_win_ds.c Lines 1788 to 1790 in 68e963a
Therefore, if you use full duplex mode and a high output latency, what you're doing is hiding the problem.
To the contrary - with your experiment you literally reproduced precisely the problem that I originally described!
If you look at I hope that by reproducing the problem yourself you are now convinced that this likely affects most, if not all, systems. |
This works around a DirectSound limitation where input host buffer sizes smaller than 31.25 ms are basically unworkable and make PortAudio hang. The workaround is to impose a minimal buffer size of 2*31.25 ms on input-only and full-duplex streams. This is enough for the read cursor to advance twice around the buffer, basically resulting in de facto double buffering. This change was tested with `paloopback` under a wide variety of half/full-duplex, framesPerBuffer, and suggested latency parameters. (Note the testing was done on top of PortAudio#772 as otherwise paloopback is not usable.) Fixes PortAudio#775
This works around a DirectSound limitation where input host buffer sizes smaller than 31.25 ms are basically unworkable and make PortAudio hang. The workaround is to impose a minimal buffer size of 2*31.25 ms on input-only and full-duplex streams. This is enough for the read cursor to advance twice around the buffer, basically resulting in de facto double buffering. This change was tested with `paloopback` under a wide variety of half/full-duplex, framesPerBuffer, and suggested latency parameters. (Note the testing was done on top of PortAudio#772 as otherwise paloopback is not usable.) Fixes PortAudio#775
This works around a DirectSound limitation where input host buffer sizes smaller than 31.25 ms are basically unworkable and make PortAudio hang. The workaround is to impose a minimal buffer size of 2*31.25 ms on input-only and full-duplex streams. This is enough for the read cursor to advance twice around the buffer, basically resulting in de facto double buffering. This change was tested with `paloopback` under a wide variety of half/full-duplex, framesPerBuffer, and suggested latency parameters. (Note the testing was done on top of PortAudio#772 as otherwise paloopback is not usable.) Fixes PortAudio#775
When running
paloopback -r48000 -s512
with a DirectSound input device:--inputLatency 25
, the DirectSound host API hangs (as in, the stream callback is never called);--inputLatency 30
, the stream callbacks follow an irregular cadence and glitches are reported;--inputLatency 35
, the stream appears to be fine.This happens regardless of half duplex or full duplex mode. Output seems unaffected.
I've known about this issue since 2018, see dechamps/FlexASIO#29. (I apologize for waiting this long to report it upstream.) Given it was indirectly reported to me by a variety of users over time, I suspect it affects most PortAudio and most Windows versions (at least the modern ones) on most hardware.
The discussion on dechamps/FlexASIO#29 basically sums up the root cause, but just to confirm, here are some results of printf debugging directly on the DirectSound host API code from
paloopback
with the above parameters:--inputLatency 100
:--inputLatency 25
:This confirms the root cause from dechamps/FlexASIO#29 - DirectSound seems to be incapable of providing better than 31.25 ms input read cursor granularity (note this number stays constant regardless of sample rate). I found another DirectSound user complaining about the same problem in 2011. (Note that this only affects capture - playback cursors appear to be significantly more granular in my testing.)
As a result, if the host input buffer is less than 31.25 ms in size, the read cursor stays stuck at 0, the PortAudio DS host API code never makes forward progress, and the stream callback is never called.
Everything points at this being a limitation/bug of DirectSound itself. However, it seems like PortAudio could at least work around it by provisioning large enough buffers, so that PortAudio users don't have to "tiptoe around" buffer size minefields.
Somewhat related to this, I am not quite sure I understand why
suggestedLatency
has any effect on the DirectSound input host buffer size in the first place. Given DirectSound uses circular buffers with cursors, my understanding is that the buffer size has no effect on input latency - PortAudio simply "chases the cursor" and will process input data as soon as the cursor moves, without waiting for the input host buffer to fill up (which it shouldn't anyway, as it would overflow). It seems likesuggestedLatency
should not be a factor in the input host buffer size calculation; instead, the DS input host buffer size should be a tradeoff between memory usage and likelihood of input buffer overflow. Corollary: PortAudio can set the host input buffer size to be as large as necessary to avoid the problem described above without affecting input latency at all.What really drives the input latency here is read cursor granularity. Unfortunately, given DirectSound seems stuck with an implicit, undocumented 31.25 ms granularity with no way to way to adjust it (that I could find), it looks like that would squash any hope of achieving anything resembling low latency for DirectSound input devices.
The text was updated successfully, but these errors were encountered: