PortAudio / portaudio

PortAudio is a cross-platform, open-source C language library for real-time audio input and output.
Other
1.39k stars 291 forks source link

Pa_Initialize() interrupts other audio streams on Windows #858

Closed arkrow closed 7 months ago

arkrow commented 9 months ago

Describe the bug Calling Pa_Initialize() to initialize PortAudio interrupts any currently playing audio streams momentarily on Windows if exclusive mode is enabled on the default sound device. Strangely, this issue only occurs if the PortAudio binary being used was compiled with ASIO, otherwise this bug does not occur.

To Reproduce

  1. On Windows 10/11, enable exclusive mode on the default sound device
  2. Use a PortAudio binary compiled with ASIO, and call Pa_Initialize()

Expected behavior Other audio streams are uninterrupted while PortAudio is initializing.

Actual behavior All other audio streams are interrupted while PortAudio is initializing, sometimes without recovery.

Desktop:

Additional context An initial discussion of this issue was started on python-sounddevice, which can be seen here: https://github.com/spatialaudio/python-sounddevice/issues/496

dechamps commented 9 months ago

Strangely, this issue only occurs if the PortAudio binary being used was compiled with ASIO

This is not surprising. The way PortAudio initialization works is fundamentally flawed when ASIO is involved, and always has been. See #696.

My suggestion would be stop building PortAudio with ASIO support, and instead use the ASIO API directly from your code if you absolutely require ASIO support.

dechamps commented 9 months ago

Though thinking about this a bit more, I find it a bit odd that you're describing a bug that is triggered specifically by exclusive mode being enabled on the default Windows audio device - I never heard of that particular failure mode before. Is this bug reproducible even with no ASIO drivers installed on the system? If not, can you pinpoint precisely which ASIO driver is causing it?

arkrow commented 9 months ago

This is not surprising. The way PortAudio initialization works is fundamentally flawed when ASIO is involved, and always has been. See #696.

My suggestion would be stop building PortAudio with ASIO support, and instead use the ASIO API directly from your code if you absolutely require ASIO support.

I see. It's unfortunate that the issue is upstream, but your suggestion is understandable. Thanks for the quick response.

Though thinking about this a bit more, I find it a bit odd that you're describing a bug that is triggered specifically by exclusive mode being enabled on the default Windows audio device - I never heard of that particular failure mode before.

I'd imagine disabling exclusive mode--and thus having all audio streams only run in shared mode--incidentally prevents the conditions for the issue to happen, as no application can exclusively control the audio device and consequently kill other active audio streams, if improperly initialized/interfaced with.

Is this bug reproducible even with no ASIO drivers installed on the system? If not, can you pinpoint precisely which ASIO driver is causing it?

I'm a complete amateur when it comes to low-level audio interfaces; that said, I don't believe I have any ASIO drivers installed, unless the default audio driver counts. In that case, I believe it would be the RTHDASIO64.dll binary provided by Realtek on my system.

dechamps commented 9 months ago

I'd imagine disabling exclusive mode--and thus having all audio streams only run in shared mode--incidentally prevents the conditions for the issue to happen, as no application can exclusively control the audio device and consequently kill other active audio streams, if improperly initialized/interfaced with

The thing is, that concept of "exclusive mode" is only relevant to the Windows Audio Engine. Not every audio code path goes through the Windows Audio Engine. WDM-KS doesn't, for example, and will therefore completely ignore that setting. I wouldn't expect a manufacturer-provided ASIO driver to go through the Windows Audio Engine. Instead, I would expect it to bypass the entire stack and talk to the hardware directly through some side channel.

However, it is definitely true that the author of an ASIO driver technically doesn't have to bypass the Windows Audio Engine - they could simply use it like any other app (especially if they're lazy…).

I don't believe I have any ASIO drivers installed, unless the default audio driver counts

There is no such thing as a "default ASIO driver". Windows doesn't come with any ASIO drivers. That said, I guess it's possible a manufacturer-provided audio driver that comes bundled with your computer (or perhaps even installed through Windows Update) could come with an ASIO driver that gets installed as a side effect of installing the Windows audio driver.

You can remove ASIO drivers by removing subkeys from the HKEY_LOCAL_MACHINE\SOFTWARE\ASIO registry key (for 64-bit apps - if your app is 32-bit that's HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\ASIO). This should allow you to test any combination of installed drivers. You can make a backup of the key by exporting it and then restore it later when you're done.

In that case, I believe it would be the RTHDASIO64.dll binary provided by Realtek on my system.

My main hypothesis at this point is that Realtek implemented that ASIO driver by simply making it use WASAPI Exclusive behind the scenes, and as a result it takes over exclusive mode when it's initialized. That would explain your symptoms. To be fair, implementing an ASIO driver that way is perfectly reasonable and Realtek is likely blameless here; it's PortAudio that's being unreasonable by attempting to load every single ASIO driver in the system in its initialization sequence, as explained in #696.

arkrow commented 9 months ago

My main hypothesis at this point is that Realtek implemented that ASIO driver by simply making it use WASAPI Exclusive behind the scenes, and as a result it takes over exclusive mode when it's initialized. That would explain your symptoms. To be fair, implementing an ASIO driver that way is perfectly reasonable and Realtek is likely blameless here; it's PortAudio that's being unreasonable by attempting to load every single ASIO driver in the system in its initialization sequence, as explained in #696.

I've removed the Realtek ASIO driver and installed the FlexASIO driver instead to test if the behaviour persists. I can confirm that the issue doesn't occur, which is great news. In the end, it comes down to the host ASIO driver(s)' implementation, as you mentioned previously, which is unfortunately unpredictable and outside the control of application developers.

Would it be possible to extend Pa_Initialize() with optional parameters, to enable/disable support for certain host APIs at runtime? Or, is shipping two separate differently compiled binaries to enable/disable ASIO support the only solution for developers using PortAudio?

Thanks for the detailed and informative replies, both here and in the other threads.

RossBencina commented 7 months ago

So would I be correct to say that this issue is actually an issue in the Realtek ASIO driver and the problem is resolved?

As for your suggestion, it is covered here: https://github.com/PortAudio/portaudio/issues/10

arkrow commented 7 months ago

Yes, correct. The Realtek ASIO driver turned out to be the root cause of this issue. I'll be closing this issue since the problem's origin has been identified and lies outside this project.

That said, thanks for the progress on #10. Providing more granular control and the ability to selectively enable host APIs at run-time rather than build-time--depending on the host environment and the Pa_Initialize() caller's needs--would be a great addition to the API.