The issue shows when multiple clips are loaded and tried to be played almost simultaneously (for example when loading multiple clips command-line like mrv2 can do). The result is that the RtAudio api fails with all sort of errors (the most common being "RtApi::openStream: output device parameter value is invalid." ).
Currently, tlRender instantiates RtAudio from a thread and uses its audioSystem->getDefaultOutputDevice() function.
audioSystem::getDefaultOutputDevice() will call the following from the main thread as well as from the child thread when the audio thread is created:
size_t audioSystem::getDefaultOutputDevice()
{
//...
out = p.rtAudio->getDefaultOutputDevice(); // with PulseAudio this will always be 0
const size_t rtDeviceCount = p.rtAudio->getDeviceCount(); // this is the problem function
In RtAudio, the getDeviceCount() function is also called when the openStream call is called.
The getDeviceCount() function in rtAudio is this:
unsigned int RtApiPulse::getDeviceCount( void )
{
collectDeviceInfo(); // we follow with this problematic function
return rt_pa_info.dev.size();
}
Okay, I'll skip some functions, and go to the meat of the problem in RtAudio's Pulse code:
static void rt_pa_sink_info_cb(...)
{
// This is the actual routine that fills in the rt_pa_info.dev vector.
}
static void rt_pa_context_state_callback(....)
{
...
case PA_CONTEXT_READY:
rt_pa_info.dev.clear();
// .....
pa_context_get_sink_info_list(context, rt_pa_sink_info_cb, NULL);
}
The problem here is that the pa_context_get_sink_info_List calls the rt_pa_sink_info_cb asynchronically, so it is not guaranteed to fill in the rt_pa_info.dev vector right away. That should be dealt with callling a reference counter. Also, RtAudio is not using the threaded calls of PulseAudio, which might also be problematic.
From the net, here's some sample code that in my opinion seems more correct than what RtAudio is using:
lock();
// Get default input and output devices
pa_operation *operation = pa_context_get_server_info(m_context, serverInfoCallback, this);
while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(m_mainLoop);
pa_operation_unref(operation);
// Get output devices
operation = pa_context_get_sink_info_list(m_context, sinkInfoCallback, this);
while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(m_mainLoop);
pa_operation_unref(operation);
// Get input devices
operation = pa_context_get_source_info_list(m_context, sourceInfoCallback, this);
while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING)
pa_threaded_mainloop_wait(m_mainLoop);
pa_operation_unref(operation);
unlock();