From 7e9ee0610a7f3886889c216b3d02abd03cc62dc4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 4 Feb 2026 23:43:06 +0100 Subject: [PATCH] Fix missing audio on OpenAL when changing active device #3621 --- Source/Engine/Audio/AudioClip.cpp | 4 +- Source/Engine/Audio/AudioClip.h | 1 + .../Engine/Audio/OpenAL/AudioBackendOAL.cpp | 78 +++++++++++++++++-- Source/Engine/Level/Level.h | 13 ++++ Source/Engine/Video/VideoPlayer.h | 1 + 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/Source/Engine/Audio/AudioClip.cpp b/Source/Engine/Audio/AudioClip.cpp index 336ddb7bd..4e258e81a 100644 --- a/Source/Engine/Audio/AudioClip.cpp +++ b/Source/Engine/Audio/AudioClip.cpp @@ -132,9 +132,7 @@ int32 AudioClip::GetFirstBufferIndex(float time, float& offset) const if (_buffersStartTimes[i + 1] > time) { offset = time - _buffersStartTimes[i]; -#if BUILD_DEBUG - ASSERT(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f); -#endif + ASSERT_LOW_LAYER(Math::Abs(GetBufferStartTime(i) + offset - time) < 0.001f); return i; } } diff --git a/Source/Engine/Audio/AudioClip.h b/Source/Engine/Audio/AudioClip.h index d35bd18fc..ac30b4c85 100644 --- a/Source/Engine/Audio/AudioClip.h +++ b/Source/Engine/Audio/AudioClip.h @@ -18,6 +18,7 @@ class AudioSource; API_CLASS(NoSpawn) class FLAXENGINE_API AudioClip : public BinaryAsset, public StreamableResource { DECLARE_BINARY_ASSET_HEADER(AudioClip, 2); + friend class AudioBackendOAL; public: /// diff --git a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp index 2891cec02..fb883f952 100644 --- a/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp +++ b/Source/Engine/Audio/OpenAL/AudioBackendOAL.cpp @@ -14,6 +14,9 @@ #include "Engine/Audio/AudioListener.h" #include "Engine/Audio/AudioSource.h" #include "Engine/Audio/AudioSettings.h" +#include "Engine/Content/Content.h" +#include "Engine/Level/Level.h" +#include "Engine/Video/VideoPlayer.h" // Include OpenAL library // Source: https://github.com/kcat/openal-soft @@ -73,6 +76,7 @@ namespace ALC ALCdevice* Device = nullptr; ALCcontext* Context = nullptr; AudioBackend::FeatureFlags Features = AudioBackend::FeatureFlags::None; + bool Inited = false; CriticalSection Locker; Dictionary SourcesData; @@ -164,12 +168,9 @@ namespace ALC float Time; }; - void RebuildContext(const Array& states) + void RebuildContext() { - LOG(Info, "Rebuilding audio contexts"); - ClearContext(); - if (Device == nullptr) return; @@ -182,10 +183,16 @@ namespace ALC Context = alcCreateContext(Device, attrList); alcMakeContextCurrent(Context); - + } + + void RebuildListeners() + { for (AudioListener* listener : Audio::Listeners) Listener::Rebuild(listener); - + } + + void RebuildSources(const Array& states) + { for (int32 i = 0; i < states.Count(); i++) { AudioSource* source = Audio::Sources[i]; @@ -205,6 +212,13 @@ namespace ALC } } + void RebuildContext(const Array& states) + { + RebuildContext(); + RebuildListeners(); + RebuildSources(states); + } + void RebuildContext(bool isChangingDevice) { Array states; @@ -629,6 +643,7 @@ AudioBackend::FeatureFlags AudioBackendOAL::Base_Features() void AudioBackendOAL::Base_OnActiveDeviceChanged() { + PROFILE_CPU(); PROFILE_MEM(Audio); // Cleanup @@ -659,9 +674,55 @@ void AudioBackendOAL::Base_OnActiveDeviceChanged() LOG(Fatal, "Failed to open OpenAL device ({0}).", String(name)); return; } + if (ALC::Inited) + LOG(Info, "Changed audio device to: {}", String(Audio::GetActiveDevice()->Name)); - // Setup - ALC::RebuildContext(states); + // Rebuild context + ALC::RebuildContext(); + if (ALC::Inited) + { + // Reload all audio clips to recreate their buffers + for (Asset* asset : Content::GetAssets()) + { + if (auto* audioClip = ScriptingObject::Cast(asset)) + { + ScopeLock lock(audioClip->Locker); + + // Clear old buffer IDs + for (uint32& bufferID : audioClip->Buffers) + bufferID = 0; + + if (audioClip->IsStreamable()) + { + // Let the streaming recreate missing buffers + audioClip->RequestStreamingUpdate(); + } + else + { + // Reload audio clip + auto assetLock = audioClip->Storage->Lock(); + audioClip->LoadChunk(0); + audioClip->Buffers[0] = AudioBackend::Buffer::Create(); + audioClip->WriteBuffer(0); + + } + } + } + + // Reload all videos to recreate their buffers + for (VideoPlayer* videoPlayer : Level::GetActors(true)) + { + VideoBackendPlayer& player = videoPlayer->_player; + + // Clear audio state + for (uint32& bufferID : player.AudioBuffers) + bufferID = 0; + player.NextAudioBuffer = 0; + player.AudioSource = 0; + } + } + ALC::RebuildListeners(); + ALC::RebuildSources(states); } void AudioBackendOAL::Base_SetDopplerFactor(float value) @@ -782,6 +843,7 @@ bool AudioBackendOAL::Base_Init() if (ALC::IsExtensionSupported("AL_SOFT_source_spatialize")) ALC::Features = EnumAddFlags(ALC::Features, FeatureFlags::SpatialMultiChannel); #endif + ALC::Inited = true; // Log service info LOG(Info, "{0} ({1})", String(alGetString(AL_RENDERER)), String(alGetString(AL_VERSION))); diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 110b9ac61..e3d43b7c5 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -473,6 +473,19 @@ public: /// Found actors list. API_FUNCTION() static Array GetActors(API_PARAM(Attributes="TypeReference(typeof(Actor))") const MClass* type, bool activeOnly = false); + /// + /// Finds all the actors of the given type in all the loaded scenes. + /// + /// Type of the object. + /// Finds only active actors. + /// Found actors list. + template + static Array GetActors(bool activeOnly = false) + { + Array actors = GetActors(T::GetStaticClass(), activeOnly); + return *(Array*)&actors; + } + /// /// Finds all the scripts of the given type in an actor or all the loaded scenes. /// diff --git a/Source/Engine/Video/VideoPlayer.h b/Source/Engine/Video/VideoPlayer.h index e1b7e877d..4447337ea 100644 --- a/Source/Engine/Video/VideoPlayer.h +++ b/Source/Engine/Video/VideoPlayer.h @@ -15,6 +15,7 @@ class FLAXENGINE_API VideoPlayer : public Actor { DECLARE_SCENE_OBJECT(VideoPlayer); API_AUTO_SERIALIZATION(); + friend class AudioBackendOAL; public: ///