From e6f37131699b8906eee9fc47e572d1ba2132e2c1 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Thu, 28 May 2020 04:38:16 +0200 Subject: [PATCH] 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) --- neo/framework/Session.cpp | 6 +++ neo/sound/snd_local.h | 10 +++++ neo/sound/snd_system.cpp | 81 ++++++++++++++++++++++++++++++++++++ neo/sys/stub/openal_stub.cpp | 11 +++++ 4 files changed, 108 insertions(+) diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp index 73585a2e..46c3fe36 100644 --- a/neo/framework/Session.cpp +++ b/neo/framework/Session.cpp @@ -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; diff --git a/neo/sound/snd_local.h b/neo/sound/snd_local.h index f85f8c6d..8705c7bd 100644 --- a/neo/sound/snd_local.h +++ b/neo/sound/snd_local.h @@ -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; diff --git a/neo/sound/snd_system.cpp b/neo/sound/snd_system.cpp index 84f538e9..792b03d5 100644 --- a/neo/sound/snd_system.cpp +++ b/neo/sound/snd_system.cpp @@ -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 diff --git a/neo/sys/stub/openal_stub.cpp b/neo/sys/stub/openal_stub.cpp index 09578903..9496bf28 100644 --- a/neo/sys/stub/openal_stub.cpp +++ b/neo/sys/stub/openal_stub.cpp @@ -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 ) {