PortAudio / portaudio

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

faulty Asio driver present (without device) causes exception crash on PaAsio_Initialize #960

Open cuikp opened 2 months ago

cuikp commented 2 months ago

Recently began using portaudio via P/Invoke. Apparently portaudio attempts to load (and then unload) all ASIO drivers present on a given machine in PaAsio_Initialize(). However, if a driver is faulty and fails to load, portaudio will still try to unload it, causing an exception to be thrown.

This particular situation may be difficult to reproduce unless one purposely installs a faulty driver and then runs portaudio. As luck had it I happened to have a bad ASIO driver already on my system so I discovered the problem straightaway.

Tinkering with the source code, I find that the exception is thrown in the ASIO SDK in:

LONG AsioDriverList::asioCloseDriver (int drvID)
{
    LPASIODRVSTRUCT lpdrv = 0;
    IASIO           *iasio;

    if ((lpdrv = getDrvStruct(drvID,lpdrvlist)) != 0) {
        if (lpdrv->asiodrv) {
            iasio = (IASIO *)lpdrv->asiodrv;
            iasio->Release();
            lpdrv->asiodrv = 0;
        }
    }

    return 0;
}

However, this code will not try to unload the (faulty) driver if lpdrv->asiodrv is nullptr, so this opens up a workaround (the following), which I have tested and thus managed to avoid the driver error.

In the static PaError InitPaDeviceInfoFromAsioDriver() method (pa_asio.cpp) I added the following in the else block:

 if( result == paNoError )
 {
      ...
  }
  else
  {
      //PA_DEBUG(("Loading result was NOT successful for:%d name = %s\n", driverIndex, deviceInfo->name));
      **return PaErrorCode::paNotInitialized;**
  }

That is, we can return the specific PaErrorCode of paNotInitialized when attempt to get the device info fails.

Then, I made the following change when the call is actually made from PaAsio_Initialize():

PaError PaAsio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
{
...

 for( i=0; i < driverCount; ++i )
 {
  PA_DEBUG(("ASIO names[%d]:%s\n",i,names[i]));

 ...

/* Attempt to init device info from the asio driver... */
{
    PaAsioDeviceInfo *asioDeviceInfo = &deviceInfoArray[ (*hostApi)->info.deviceCount ];
    PaDeviceInfo *deviceInfo = &asioDeviceInfo->commonDeviceInfo;

    deviceInfo->structVersion = 2;
    deviceInfo->hostApi = hostApiIndex;

    deviceInfo->name = names[i];

    PaError InitInfo = InitPaDeviceInfoFromAsioDriver(asioHostApi, names[i], i, deviceInfo, asioDeviceInfo);
    //if( InitPaDeviceInfoFromAsioDriver( asioHostApi, names[i], i, deviceInfo, asioDeviceInfo ) == paNoError )
    if( InitInfo == paNoError )
    {
        (*hostApi)->deviceInfos[ (*hostApi)->info.deviceCount ] = deviceInfo;
        ++(*hostApi)->info.deviceCount;
    }
    else
    {
       //////////// ADDED ELSE BLOCK HERE /////////////////////////////
         if (InitInfo == PaErrorCode::paNotInitialized) {

            LPASIODRVSTRUCT thisLP = asioHostApi->asioDrivers->lpdrvlist;

            for (int j = 0; j < i && thisLP != nullptr; ++j) {
                thisLP = thisLP->next;
            }

            if (thisLP != nullptr)
                thisLP->asiodrv = nullptr;
        }

        OutputDebugStringA(("Skipping ASIO device: " + std::to_string(i) + " " + names[i] + "\n\n").c_str());
       continue;
       ///////////////////////////////////////////////////
    }

In other words, if the returned PaErrorCode is paNotInitialized, it loops through the LPASIODRVSTRUCT to find the current one, and sets its asiodrv to nullptr. Once set to nullptr, the SDK's methods AsioDriverList::asioCloseDriver() (as well as deleteDrvStruct() ) will skip trying to unload it, thus solving the problem.

(A pull request could be made if this explanation is not clear enough to implement.)

RossBencina commented 1 month ago

I think it's worth submitting working code as a draft PR so that we know exactly what you're proposing.

Is there any way I could install this crashing driver?

Also, you wrote:

I find that the exception is thrown in the ASIO SDK

Where is the exception caught?