This has only been tested with the a Focusright 2i2 (driver version: 4.116.9, latest as of today) and Unreal 5.2.1.
It’s unclear to me whether this is a Focusright driver issue or a bug in the engine.
This hang repros on my machine and a colleague’s machine (with the same audio interface and drivers). [Side note: shoutout to my colleague for helping me figure out this out]
It would be great if someone else could follow the basic repro steps below to gather a bit more data on this, with other audio hardware.
Repro
- Disconnect your audio interface
- Run the Editor
- Enable Audio Capture Timecode Provider plugin
- Set Project Settings → Timecode Provider to AudioCaptureTimecodeProvider
- Relaunch the Editor (this step probably isn’t needed, but just so we’re in total sync)
- Set Project Settings → Timecode Provider to any other Timecode Provider
- The Editor hangs
Technical Details
I’ve dug into the C++ and it would appear that what’s happening is:
- On Editor launch (or when the Timecode Provider is set to AudioCaptureTimecodeProvider),
RtApi :: RtApi()
initializesstream_.mutex
:
RtApi :: RtApi()
{
stream_.state = STREAM_CLOSED;
stream_.mode = UNINITIALIZED;
stream_.apiHandle = 0;
stream_.userBuffer[0] = 0;
stream_.userBuffer[1] = 0;
MUTEX_INITIALIZE( &stream_.mutex ); // <-- HERE
showWarnings_ = true;
firstErrorOccurred_ = false;
}
stream_.mutex.LockCount
== -1 after this line.
void RtApiDs :: callbackEvent()
is called and a loop is spawned to continuously read data from the audio device’s input:
MUTEX_LOCK( &stream_.mutex ); // <-- stream_.mutex.LockCount = -2 after this line
if (...) {}
else { // mode == INPUT
while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) {
// See comments for playback.
double millis = (endRead - safeReadPointer) * 1000.0;
millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate);
if ( millis < 1.0 ) millis = 1.0;
Sleep( (DWORD) millis );
// Wake up and find out where we are now.
result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer );
if ( FAILED( result ) ) {
errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!";
errorText_ = errorStream_.str();
MUTEX_UNLOCK( &stream_.mutex );
error( RtAudioError::SYSTEM_ERROR );
return;
}
if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset
}
}
- Now if we set the Timecode Provider in Project Settings as described in repro step #6, the engine attempts to destroy the previous Timecode Provider:
bool UEngine::SetTimecodeProvider(UTimecodeProvider* InTimecodeProvider)
{
if (InTimecodeProvider != TimecodeProvider)
{
if (TimecodeProvider && bIsCurrentTimecodeProviderInitialized)
{
TimecodeProvider->Shutdown(this); // <-- HERE
}
bIsCurrentTimecodeProviderInitialized = false;
TimecodeProvider = IsValid(InTimecodeProvider) ? InTimecodeProvider : nullptr;
if (TimecodeProvider)
{
bIsCurrentTimecodeProviderInitialized = TimecodeProvider->Initialize(this);
}
OnTimecodeProviderChanged().Broadcast();
}
return bIsCurrentTimecodeProviderInitialized;
}
The AudioCaptureTimecodeProvider destructor is called, resulting in calls to RtApiDs::stopStream
followed by RtApiDs::closeStream
. Note that closeStream
sets stream_.callbackInfo.isRunning
to false, which ends the while loop in #2, however we will never get this far. Why? Because RtApiDs::stopStream()
does this:
if ( stream_.mode != DUPLEX )
MUTEX_LOCK( &stream_.mutex ); // <---DEADLOCK
stream_.mutex.LockCount
is -2 when this line is reached, and it would seem that the mutex cannot be acquired, as we’re waiting for data to stop streaming in our while loop (#2).
If I set a breakpoint on this line and set LockCount
to -1 instead of -2, the deadlock doesn’t occur and things appear to run normally.
It would seem that when the audio interface is disconnected, the driver (or Windows) is streaming endless amounts of useless data, and so the while loop in #2 never ends, and the mutex cannot be acquired to stop the stream.
System details
Edition | Windows 10 Pro |
---|---|
Version | 22H2 |
Installed on | 6/29/2023 |
OS build | 19045.3448 |
Experience | Windows Feature Experience Pack 1000.19044.1000.0 |
Device name | - |
---|---|
Processor | 12th Gen Intel(R) Core™ i9-12900K 3.20 GHz |
Installed RAM | 96.0 GB (95.8 GB usable) |
Device ID | - |
Product ID | - |
System type | 64-bit operating system, x64-based processor |
Pen and touch | No pen or touch input is available for this display |