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.49k stars 318 forks source link

SIGSEGV when opening two streams #372

Closed Foaly closed 1 year ago

Foaly commented 1 year ago

Hey all!

I have stumbled upon a weird bug in my audio engine and I was able to reduce it to a minimal example.

The bug results in a SIGSEGV and manifests itself when two audio streams are opened on the same sound card. As you can see in the example below the first stream is opened, then the second stream is opened and closed and then the first stream is started. The starting call results in the SIGSEGV crash. Weirdly enough if I run the program in the debugger there is no stack trace, so maybe the crash happens somewhere in the driver?

Am I using the API wrong somehow? I admit that the setup is a bit weird, but I would at least expect some kind of error message and not a crash. I wish I could provide some more debug info, but as there is no stack trace I don't know where to dig deeper. I am happy to help out though!

Thanks in advance!

Some more system specs:

Here is the console output when running the example using the debugger:

17:42:01: Debugging .\examples\Example.exe ...
avcore\audiocore\client\audioclient\audioclientcore.cpp(1110)\AUDIOSES.DLL!00007FFA67D326A3: (caller: 00007FFA67CFA890) ReturnHr(1) tid(1f34) 88890019 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1514)\AUDIOSES.DLL!00007FFA67D34565: (caller: 00007FFA67CF47DD) ReturnHr(2) tid(1f34) 887C0036 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1514)\AUDIOSES.DLL!00007FFA67D34565: (caller: 00007FFA67CF47DD) ReturnHr(3) tid(1f34) 887C0036 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1514)\AUDIOSES.DLL!00007FFA67D34565: (caller: 00007FFA67CF47DD) ReturnHr(4) tid(1f34) 887C0036 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1471)\AUDIOSES.DLL!00007FFA67D34662: (caller: 00007FFA67CF47DD) ReturnHr(5) tid(1f34) 80070057 Falscher Parameter.
avcore\audiocore\client\audioclient\audioclientcore.cpp(1110)\AUDIOSES.DLL!00007FFA67D326A3: (caller: 00007FFA67CFA890) ReturnHr(6) tid(1f34) 88890019 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1514)\AUDIOSES.DLL!00007FFA67D34565: (caller: 00007FFA67CF47DD) ReturnHr(7) tid(1f34) 887C0036 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1514)\AUDIOSES.DLL!00007FFA67D34565: (caller: 00007FFA67CF47DD) ReturnHr(8) tid(1f34) 887C0036 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1514)\AUDIOSES.DLL!00007FFA67D34565: (caller: 00007FFA67CF47DD) ReturnHr(9) tid(1f34) 887C0036 
avcore\audiocore\client\audioclient\audioclientcore.cpp(1471)\AUDIOSES.DLL!00007FFA67D34662: (caller: 00007FFA67CF47DD) ReturnHr(10) tid(1f34) 80070057 Falscher Parameter.
Buffer size: 512

17:42:02: ./examples/Example.exe crashed (SIGSEGV).

Minimal example:

#include <chrono>
#include <iostream>
#include <thread>

#include <RtAudio.h>

int audioCallback(void* outputBuffer, void* inputBuffer, unsigned int frameCount, double streamTime, RtAudioStreamStatus status, void* userData)
{
    return 0;
}

int main()
{
    // global variables
    constexpr std::size_t sampleRate = 48000;
    unsigned int bufferSize = 512;
    constexpr unsigned int channelCount = 2;
    constexpr RtAudioFormat format = RTAUDIO_FLOAT32;

    // try opening an audio stream for later use
    RtAudio::StreamParameters outputStreamParams;
    outputStreamParams.deviceId = 0;
    outputStreamParams.nChannels = channelCount;

    RtAudio audioOutputStream( RtAudio::WINDOWS_ASIO );

    try
    {
        audioOutputStream.openStream(&outputStreamParams,
                                     nullptr,
                                     format,
                                     static_cast<unsigned int>(sampleRate),
                                     &bufferSize,
                                     audioCallback);
    }
    catch (RtAudioError& e)
    {
        e.printMessage();
    }

    // try opening another audio stream for probing the buffer size
    RtAudio::StreamParameters outputStreamParams2;
    outputStreamParams2.deviceId = 0;
    outputStreamParams2.nChannels = channelCount;

    RtAudio audioOutputStream2( RtAudio::WINDOWS_ASIO );

    bufferSize = 0; // 0 indicates we want to use the smallest supported size

    try
    {
        audioOutputStream2.openStream(&outputStreamParams2,
                                     nullptr,
                                     format,
                                     static_cast<unsigned int>(sampleRate),
                                     &bufferSize,
                                     audioCallback);
        audioOutputStream2.closeStream();
    }
    catch (RtAudioError& e)
    {
        e.printMessage();
    }

    std::cout << "Buffer size: " << bufferSize << std::endl;

    audioOutputStream.startStream();  // SIGSEGV happens here

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(3s);
}
garyscavone commented 1 year ago

Generally, the ASIO API does not support opening the same device multiple times. I am somewhat surprised that your second call to openStream() does not fail but maybe that is because you close it right away and do not attempt to start the stream. I suspect the SIGSEGV is related to the driver or API code but you should be able to avoid the problem by not attempting to have two instances at the same time with the same device.

Foaly commented 1 year ago

Thank you for your answer! I was able to work around this problem, by adapting my structure a bit, so the streams are opened and closed sequentially instead of simultaniously. It would still be nice if there were some kind of error message or some other mechanism that prevents you from opening two streams on the same device if thats not supported instead of the segfault :)

A follow up question: if you say the ASIO API does not support opening the same device multiple times, that means I can not open a stream with 2 channel (1+2) on device A for output and another stream with 2 channel (3+4) on the same device A for input and handle them in different classes/callbacks, right? There could only be one stream on device A with 4 channel and I would have to handle the input/output mapping myself in one callback. Am I understanding that correctly? Thanks again for the help :)

garyscavone commented 1 year ago

Yes, the ASIO limitations should, if possible, be better checked within the class ... on the "to do" list. Regarding your follow-up question, you would open a single two-channel duplex stream on that device, specifying a channel offset if necessary. It is nearly always better to have a single duplex stream than two separate input and output streams, simply for synchronicity.

Foaly commented 1 year ago

Thank you for putting it on the list :) That is great to hear!

Regarding the second question: Thank you very much for letting me know I will try out the duplex stream with an offset setup. But my question was aiming at, if it is even possible to open multiple streams on the same device using ASIO, because I am using RtAudio to transmit laser data, so synchronicity is not as big of an issue as with audio. I have a dynamically changing number of inputs and ouputs, so I am more worried about drop-outs when the number of changes and I have to close&reopen the one stream instead of having many streams, each handling one input or output, that I can open and close as needed.

garyscavone commented 1 year ago

I updated RtAudio to fix this problem. It is now possible to have more than one instance of RtAudio using ASIO at the same time but once one of them has a stream open, any other instances will be prohibited from probing devices or opening a stream. This is because the ASIO API does not allow more than one driver to run at a time, so RtAudio is just enforcing that.

Foaly commented 1 year ago

Amazing thank you so much! 🚀