OpenAL: Try to reset disconnected devices, fixes #209

OpenAL devices can disconnect, and with some luck they're back after
a few seconds. This especially seems to happen with Intels Windows GPU
driver and display-audio when switching the resolution or enabling
fullscreen, see #209
Now a disconnect is detected and we try to reset the device for 20
seconds, hoping it comes back. This needs at least openal-soft 1.17.0
to build and 1.20.0 or newer to actually work.

Also added missing stub functions in openal_stub.cpp (used by dedicated
server so it doesn't have to link libopenal)
This commit is contained in:
Daniel Gibson 2020-05-28 04:38:16 +02:00
parent 964efa191d
commit e6f3713169
4 changed files with 108 additions and 0 deletions

View file

@ -2520,12 +2520,18 @@ void idSessionLocal::UpdateScreen( bool outOfSequence ) {
idSessionLocal::Frame
===============
*/
extern bool CheckOpenALDeviceAndRecoverIfNeeded();
void idSessionLocal::Frame() {
if ( com_asyncSound.GetInteger() == 0 ) {
soundSystem->AsyncUpdate( Sys_Milliseconds() );
}
// DG: periodically check if sound device is still there and try to reset it if not
// (calling this from idSoundSystem::AsyncUpdate(), which runs in a separate thread
// by default, causes a deadlock when calling idCommon->Warning())
CheckOpenALDeviceAndRecoverIfNeeded();
// Editors that completely take over the game
if ( com_editorActive && ( com_editors & ( EDITOR_RADIANT | EDITOR_GUI ) ) ) {
return;

View file

@ -691,6 +691,11 @@ public:
ALuint AllocOpenALSource( idSoundChannel *chan, bool looping, bool stereo );
void FreeOpenALSource( ALuint handle );
// returns true if openalDevice is still available,
// otherwise it will try to recover the device and return false while it's gone
// (display audio sound devices sometimes disappear for a few seconds when switching resolution)
bool CheckDeviceAndRecoverIfNeeded();
idSoundCache * soundCache;
idSoundWorldLocal * currentSoundWorld; // the one to mix each async tic
@ -747,6 +752,11 @@ public:
// mark available during initialization, or through an explicit test
static int EFXAvailable;
// DG: for CheckDeviceAndRecoverIfNeeded()
LPALCRESETDEVICESOFT alcResetDeviceSOFT; // needs ALC_SOFT_HRTF extension
int resetRetryCount;
unsigned int lastCheckTime;
static idCVar s_noSound;
static idCVar s_device;
static idCVar s_quadraticFalloff;

View file

@ -277,6 +277,17 @@ void SoundSystemRestart_f( const idCmdArgs &args ) {
soundSystem->SetMute( false );
}
// DG: make this function callable from idSessionLocal::Frame() without having to
// change the public idSoundSystem interface - that would break mod DLL compat,
// and this is not relevant for gamecode.
bool CheckOpenALDeviceAndRecoverIfNeeded()
{
if(soundSystemLocal.isInitialized)
return soundSystemLocal.CheckDeviceAndRecoverIfNeeded();
return true;
}
/*
===============
idSoundSystemLocal::Init
@ -313,6 +324,11 @@ void idSoundSystemLocal::Init() {
graph = NULL;
// DG: added these for CheckDeviceAndRecoverIfNeeded()
alcResetDeviceSOFT = NULL;
resetRetryCount = 0;
lastCheckTime = 0;
// DG: no point in initializing OpenAL if sound is disabled with s_noSound
if ( s_noSound.GetBool() ) {
common->Printf( "Sound disabled with s_noSound 1 !\n" );
@ -385,6 +401,14 @@ void idSoundSystemLocal::Init() {
common->Printf( "OpenAL renderer: %s\n", alGetString(AL_RENDERER) );
common->Printf( "OpenAL version: %s\n", alGetString(AL_VERSION) );
// DG: extensions needed for CheckDeviceAndRecoverIfNeeded()
bool hasAlcExtDisconnect = alcIsExtensionPresent( openalDevice, "ALC_EXT_disconnect" ) != AL_FALSE;
bool hasAlcSoftHrtf = alcIsExtensionPresent( openalDevice, "ALC_SOFT_HRTF" ) != AL_FALSE;
if ( hasAlcExtDisconnect && hasAlcSoftHrtf ) {
common->Printf( "OpenAL: found extensions for resetting disconnected devices\n" );
alcResetDeviceSOFT = (LPALCRESETDEVICESOFT)alcGetProcAddress( openalDevice, "alcResetDeviceSOFT" );
}
// try to obtain EFX extensions
if (alcIsExtensionPresent(openalDevice, "ALC_EXT_EFX")) {
common->Printf( "OpenAL: found EFX extension\n" );
@ -570,6 +594,63 @@ bool idSoundSystemLocal::ShutdownHW() {
return true;
}
/*
===============
idSoundSystemLocal::CheckDeviceAndRecoverIfNeeded
DG: returns true if openalDevice is still available,
otherwise it will try to recover the device and return false while it's gone
(display audio sound devices sometimes disappear for a few seconds when switching resolution)
===============
*/
bool idSoundSystemLocal::CheckDeviceAndRecoverIfNeeded()
{
static const int maxRetries = 20;
if ( alcResetDeviceSOFT == NULL ) {
return true; // we can't check or reset, just pretend everything is fine..
}
unsigned int curTime = Sys_Milliseconds();
if ( curTime - lastCheckTime >= 1000 ) // check once per second
{
lastCheckTime = curTime;
ALCint connected; // ALC_CONNECTED needs ALC_EXT_disconnect (we check for that in Init())
alcGetIntegerv( openalDevice, ALC_CONNECTED, 1, &connected );
if ( connected ) {
resetRetryCount = 0;
return true;
}
if ( resetRetryCount == 0 ) {
common->Warning( "OpenAL device disconnected! Will try to reconnect.." );
resetRetryCount = 1;
} else if ( resetRetryCount > maxRetries ) { // give up after 20 seconds
if ( resetRetryCount == maxRetries+1 ) {
common->Warning( "OpenAL device still disconnected! Giving up!" );
++resetRetryCount; // this makes sure the warning is only shown once
// TODO: can we shut down sound without things blowing up?
// if we can, we could do that if we don't have alcResetDeviceSOFT but ALC_EXT_disconnect
}
return false;
}
if ( alcResetDeviceSOFT( openalDevice, NULL ) ) {
common->Printf( "OpenAL: resetting device succeeded!\n" );
resetRetryCount = 0;
return true;
}
++resetRetryCount;
return false;
}
return resetRetryCount == 0; // if it's 0, state on last check was ok
}
/*
===============
idSoundSystemLocal::GetCurrent44kHzTime

View file

@ -65,6 +65,17 @@ ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent( ALCdevice *device, const
return AL_FALSE;
}
ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) {}
ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) {
return ALC_NO_ERROR;
}
ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) {
return NULL;
}
AL_API void AL_APIENTRY alDeleteBuffers( ALsizei n, const ALuint* buffers ) { }
AL_API ALboolean AL_APIENTRY alIsExtensionPresent( const ALchar* fname ) {