From e020af8b4ae6e206709f4cea9d0ed9e8635fbe63 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 3 Mar 2023 17:31:09 +0100 Subject: [PATCH] Switch to new default device if default device changes --- CHANGELOG.md | 1 + apps/openmw/mwsound/openal_output.cpp | 110 ++++++++++++++++++++++---- apps/openmw/mwsound/openal_output.hpp | 3 + 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 050767312e..fcb25447f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Bug #3842: Body part skeletons override the main skeleton Bug #4127: Weapon animation looks choppy Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game + Bug #4382: Sound output device does not change when it should Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 48d473487d..b130dc5d6b 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -165,6 +166,17 @@ namespace getALError(); } + std::basic_string_view getDeviceName(ALCdevice* device) + { + const ALCchar* name = nullptr; + if (alcIsExtensionPresent(device, "ALC_ENUMERATE_ALL_EXT")) + name = alcGetString(device, ALC_ALL_DEVICES_SPECIFIER); + if (alcGetError(device) != AL_NO_ERROR || !name) + name = alcGetString(device, ALC_DEVICE_SPECIFIER); + if (name == nullptr) // Prevent assigning nullptr to std::string + return {}; + return name; + } } namespace MWSound @@ -314,8 +326,7 @@ namespace MWSound // struct OpenAL_Output::StreamThread { - typedef std::vector StreamVec; - StreamVec mStreams; + std::vector mStreams; std::atomic mQuitNow; std::mutex mMutex; @@ -342,7 +353,7 @@ namespace MWSound std::unique_lock lock(mMutex); while (!mQuitNow) { - StreamVec::iterator iter = mStreams.begin(); + auto iter = mStreams.begin(); while (iter != mStreams.end()) { if ((*iter)->process() == false) @@ -368,7 +379,7 @@ namespace MWSound void remove(OpenAL_SoundStream* stream) { std::lock_guard lock(mMutex); - StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream); + auto iter = std::find(mStreams.begin(), mStreams.end(), stream); if (iter != mStreams.end()) mStreams.erase(iter); } @@ -379,9 +390,69 @@ namespace MWSound mStreams.clear(); } + StreamThread(const StreamThread& rhs) = delete; + StreamThread& operator=(const StreamThread& rhs) = delete; + }; + + class OpenAL_Output::DefaultDeviceThread + { + public: + std::basic_string mCurrentName; + private: - StreamThread(const StreamThread& rhs); - StreamThread& operator=(const StreamThread& rhs); + OpenAL_Output& mOutput; + + std::atomic mQuitNow; + std::mutex mMutex; + std::condition_variable mCondVar; + std::thread mThread; + + DefaultDeviceThread(const DefaultDeviceThread&) = delete; + DefaultDeviceThread& operator=(const DefaultDeviceThread&) = delete; + + void run() + { + std::unique_lock lock(mMutex); + while (!mQuitNow) + { + { + const std::lock_guard openLock(mOutput.mReopenMutex); + auto defaultName = getDeviceName(nullptr); + if (mCurrentName != defaultName) + { + Log(Debug::Info) << "Default audio device changed"; + ALCboolean reopened + = alcReopenDeviceSOFT(mOutput.mDevice, nullptr, mOutput.mContextAttributes.data()); + if (reopened == AL_FALSE) + { + mCurrentName = defaultName; + Log(Debug::Warning) << "Failed to switch to new audio device"; + } + else + mCurrentName = getDeviceName(mOutput.mDevice); + } + } + mCondVar.wait_for(lock, std::chrono::seconds(2)); + } + } + + public: + DefaultDeviceThread(OpenAL_Output& output, std::basic_string_view name) + : mCurrentName(name) + , mOutput(output) + , mQuitNow(false) + , mThread([this] { run(); }) + { + } + + ~DefaultDeviceThread() + { + mQuitNow = true; + mMutex.lock(); + mMutex.unlock(); + mCondVar.notify_all(); + mThread.join(); + } }; OpenAL_SoundStream::OpenAL_SoundStream(ALuint src, DecoderPtr decoder) @@ -614,7 +685,7 @@ namespace MWSound void OpenAL_Output::onDisconnect() { - if (!mInitialized) + if (!mInitialized || !alcReopenDeviceSOFT) return; const std::lock_guard lock(mReopenMutex); Log(Debug::Warning) << "Audio device disconnected, attempting to reopen..."; @@ -623,6 +694,12 @@ namespace MWSound reopened = alcReopenDeviceSOFT(mDevice, nullptr, mContextAttributes.data()); if (reopened == AL_FALSE) Log(Debug::Error) << "Failed to reopen audio device"; + else + { + Log(Debug::Info) << "Reopened audio device"; + if (mDefaultDeviceThread) + mDefaultDeviceThread->mCurrentName = getDeviceName(mDevice); + } } bool OpenAL_Output::init(const std::string& devname, const std::string& hrtfname, HrtfMode hrtfmode) @@ -647,11 +724,7 @@ namespace MWSound return false; } - const ALCchar* name = nullptr; - if (alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT")) - name = alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER); - if (alcGetError(mDevice) != AL_NO_ERROR || !name) - name = alcGetString(mDevice, ALC_DEVICE_SPECIFIER); + auto name = getDeviceName(mDevice); Log(Debug::Info) << "Opened \"" << name << "\""; ALCint major = 0, minor = 0; @@ -722,9 +795,9 @@ namespace MWSound { getALFunc(alEventControlSOFT, "alEventControlSOFT"); getALFunc(alEventCallbackSOFT, "alEventCallbackSOFT"); - if (alcIsExtensionPresent(mDevice, "ALC_SOFT_reopen_device")) - getALFunc(alcReopenDeviceSOFT, "alcReopenDeviceSOFT"); } + if (alcIsExtensionPresent(mDevice, "ALC_SOFT_reopen_device")) + getALFunc(alcReopenDeviceSOFT, "alcReopenDeviceSOFT"); if (alEventControlSOFT) { static const std::array events{ { AL_EVENT_TYPE_DISCONNECTED_SOFT } }; @@ -733,6 +806,14 @@ namespace MWSound } else Log(Debug::Warning) << "Cannot detect audio device changes"; + if (mDeviceName.empty() && !name.empty()) + { + // If we opened the default device, switch devices if a new default is selected + if (alcReopenDeviceSOFT) + mDefaultDeviceThread = std::make_unique(*this, name); + else + Log(Debug::Warning) << "Cannot switch audio devices if the default changes"; + } if (!ALC.SOFT_HRTF) Log(Debug::Warning) << "HRTF status unavailable"; @@ -892,6 +973,7 @@ namespace MWSound void OpenAL_Output::deinit() { mStreamThread->removeAll(); + mDefaultDeviceThread.release(); for (ALuint source : mFreeSources) alDeleteSources(1, &source); diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index 79ceaf6ae6..eed23ac659 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -58,6 +58,9 @@ namespace MWSound std::vector mContextAttributes; std::mutex mReopenMutex; + class DefaultDeviceThread; + std::unique_ptr mDefaultDeviceThread; + void initCommon2D(ALuint source, const osg::Vec3f& pos, ALfloat gain, ALfloat pitch, bool loop, bool useenv); void initCommon3D(ALuint source, const osg::Vec3f& pos, ALfloat mindist, ALfloat maxdist, ALfloat gain, ALfloat pitch, bool loop, bool useenv);