thestk / rtaudio

A set of C++ classes that provide a common API for realtime audio input/output across Linux (native ALSA, JACK, PulseAudio and OSS), Macintosh OS X (CoreAudio and JACK), and Windows (DirectSound, ASIO, and WASAPI) operating systems.
Other
1.51k stars 322 forks source link

ASIO backend broken #323

Closed dyfer closed 2 years ago

dyfer commented 3 years ago

Hello, It seems that ASIO backend is now broken. The application compiles, but it freezes when accessing the API. I've experienced this under MinGW 8.1.0.

After running git bisect it seems that the breaking commit is 45ae9708045f03f96ec2665e37966cb6e841263b

To test this I've compiled audioprobe:

g++ -Wall -D__WINDOWS_ASIO__ -I`pwd` -Iinclude -o audioprobe tests/audioprobe.cpp RtAudio.cpp include/asio.cpp include/asiolist.cpp include/asiodrivers.cpp include/iasiothiscallresolver.cpp -lole32

With the following result:

$ ./audioprobe.exe

RtAudio Version 5.1.0

Compiled APIs:
  Windows ASIO

Current API: Windows ASIO
# hangs indefinitely here
ntonnaett commented 3 years ago

Did you try to compile audioprobe with one of the provided build systems? -DRTAUDIO_EXPORT seems to be missing.

dyfer commented 3 years ago

No, but:

dyfer commented 3 years ago

I think I see where the problem is. The function that hangs is getDeviceInfo. It seems that getDeviceInfo for the ASIO backend also calls getDefault(In|Out)putDevice. The change in https://github.com/thestk/rtaudio/commit/45ae9708045f03f96ec2665e37966cb6e841263b adds call to getDeviceInfo to getDefault(In|Out)putDevice and we enter an endless loop...

According to previous logic, the "default" device with ASIO was the first device (ASIO doesn't have a concept of default devices, AFAIU). In that case in this section of getDeviceInfo we could just check if the first device has inputs/outputs and set the default flag accordingly.

if ( info.outputChannels > 0 )
  if ( device == 0 ) info.isDefaultOutput = true;
if ( info.inputChannels > 0 )
  if ( device == 0 ) info.isDefaultInput = true;

This would, AFAICT, replicate the previous behavior with ASIO. However, maybe there's a better way to go about the default device designation with ASIO altogether? I don't know.

dyfer commented 3 years ago

Another observation: RtApiCore also calls getDefaultOutputDevice() inside getDeviceInfo(), but getDefaultOutputDevice() is reimplemented for RtApiCore and does not call getDeviceInfo(). So maybe a more consistent fix for this is to reimplement getDeviceInfo() for ASIO to simply return 0? Whichever path we take, getDefault(In|Out)putDevice under ASIO will always in the end return the first device, whether that device had isDefault(In|Out)put flag set or not (the latter only being possible if the first device has only inputs or only outputs, which is rare...).

dyfer commented 3 years ago

After giving this some more thought, I'm thinking that the best way to fix this would be to decide how the default device designation is handled: is the flag to be set in the paGetDeviceInfo using system calls, or should paGetDeviceInfo call getDefault(In|Out)putDevice which itself uses system calls?

To clarify, I think these are the two approaches:

  1. use system calls to designate input/output device in paGetDeviceInfo and check for that flag looping through the devices in getDefault(In|Out)putDevice
  2. use system calls in getDefault(In|Out)putDevice to get default input/output and then call getDefault(In|Out)putDevice inside paGetDeviceInfo to get the flag.

It seems that right now these approaches are mixed. RtApiCore seems to use approach 2., while RtApiPulse seems to use approach 1.

dyfer commented 3 years ago

I would really love to fix this. @garyscavone do you have any input on the desired approach?

radarsat1 commented 3 years ago

Hi @dyfer, I think it might be up to you to make a PR here as I am unable to test ASIO right now. You can make a call on which approach you prefer, but I guess option 2 seems more straightforward to me.

garyscavone commented 2 years ago

I just fixed this in commit 6054ff7.