From 892ef99daa436b6349892c189d54d765b7997c50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Mar 2026 13:22:27 +0100 Subject: [PATCH 1/4] Misc cleanups and fixes --- Core/ControlMapper.cpp | 2 +- UI/PauseScreen.cpp | 3 ++- Windows/WASAPIContext.cpp | 8 +++----- Windows/WASAPIContext.h | 3 ++- assets/lang/be_BY.ini | 2 +- assets/lang/bg_BG.ini | 2 +- assets/lang/cz_CZ.ini | 2 +- assets/lang/de_DE.ini | 2 +- assets/lang/dr_ID.ini | 2 +- assets/lang/en_US.ini | 2 +- assets/lang/es_ES.ini | 2 +- assets/lang/gl_ES.ini | 2 +- assets/lang/he_IL.ini | 2 +- assets/lang/he_IL_invert.ini | 2 +- assets/lang/jv_ID.ini | 2 +- assets/lang/ku_SO.ini | 2 +- assets/lang/lt-LT.ini | 2 +- assets/lang/ms_MY.ini | 2 +- assets/lang/no_NO.ini | 2 +- assets/lang/ro_RO.ini | 2 +- assets/lang/sv_SE.ini | 2 +- 21 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Core/ControlMapper.cpp b/Core/ControlMapper.cpp index 9be8998ea6..9cdb42d3ce 100644 --- a/Core/ControlMapper.cpp +++ b/Core/ControlMapper.cpp @@ -58,7 +58,7 @@ float ControlMapper::GetDeviceAxisThreshold(int device, const InputMapping &mapp if (absCoValue > 0.0f) { // Bias down the threshold if the other axis is active. const float biasedThreshold = g_Config.fAnalogStickThreshold * (1.0f - absCoValue * 0.35f); - INFO_LOG(Log::System, "coValue: %f threshold: %f", absCoValue, biasedThreshold); + // INFO_LOG(Log::System, "coValue: %f threshold: %f", absCoValue, biasedThreshold); return biasedThreshold; } } diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 85bb56ae3e..be63b1d2de 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -842,9 +842,10 @@ void GamePauseScreen::OnExit(UI::EventParams &e) { std::string confirmExitMessage = GetConfirmExitMessage(); if (!confirmExitMessage.empty()) { + auto mm = GetI18NCategory(I18NCat::MAINMENU); auto di = GetI18NCategory(I18NCat::DIALOG); std::string_view title = di->T("Are you sure you want to exit?"); - screenManager()->push(new UI::MessagePopupScreen(title, confirmExitMessage, di->T("Exit"), di->T("Cancel"), [this](bool result) { + screenManager()->push(new UI::MessagePopupScreen(title, confirmExitMessage, mm->T("Exit"), di->T("Cancel"), [this](bool result) { if (result) { if (g_Config.bPauseMenuExitsEmulator) { System_ExitApp(); diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index f4614268b8..0e3a097267 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -67,7 +67,6 @@ WASAPIContext::~WASAPIContext() { } Stop(); enumerator_->UnregisterEndpointNotificationCallback(¬ificationClient_); - delete[] tempBuf_; } WASAPIContext::AudioFormat WASAPIContext::Classify(const WAVEFORMATEX *format) { @@ -162,8 +161,7 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late } // Get rid of any old tempBuf_. - delete[] tempBuf_; - tempBuf_ = nullptr; + tempBuf_.reset(); if (SUCCEEDED(hr)) { audioClient3_->GetMixFormat(&format_); @@ -279,7 +277,7 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late audioClient_->GetService(IID_PPV_ARGS(&renderClient_)); if (createBuffer) { - tempBuf_ = new float[reportedBufferSize_ * 2]; + tempBuf_ = std::make_unique(reportedBufferSize_ * 2); } } @@ -372,7 +370,7 @@ void WASAPIContext::AudioLoop() { } } else { // We decided previously that we need conversion, so mix to our temp buffer... - callback_(tempBuf_, framesToWrite, format_->nSamplesPerSec, userdata_); + callback_(tempBuf_.get(), framesToWrite, format_->nSamplesPerSec, userdata_); // .. and convert according to format (we support multi-channel float and s16) if (format == AudioFormat::S16 && buffer) { // Need to convert. diff --git a/Windows/WASAPIContext.h b/Windows/WASAPIContext.h index 37f7e8e93c..2285a0b478 100644 --- a/Windows/WASAPIContext.h +++ b/Windows/WASAPIContext.h @@ -9,6 +9,7 @@ #include #include #include +#include #include class WASAPIContext : public AudioBackend { @@ -97,5 +98,5 @@ private: std::string deviceId_; std::atomic defaultDeviceChanged_{}; - float *tempBuf_ = nullptr; + std::unique_ptr tempBuf_; }; diff --git a/assets/lang/be_BY.ini b/assets/lang/be_BY.ini index cfa82bf6f9..08c5d7e458 100644 --- a/assets/lang/be_BY.ini +++ b/assets/lang/be_BY.ini @@ -157,7 +157,7 @@ Mouse = Налады мышы Mouse sensitivity = Адчувальнасць мышы Mouse smoothing = Згладжванне мышшу Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = Няма (Адкл.) Off = Выкл. On-screen touch controls = Сэнсарнае кіраванне на экране diff --git a/assets/lang/bg_BG.ini b/assets/lang/bg_BG.ini index f446e54a35..1d436d3644 100644 --- a/assets/lang/bg_BG.ini +++ b/assets/lang/bg_BG.ini @@ -157,7 +157,7 @@ Mouse = Настройки на мишката Mouse sensitivity = Чувствителност на мишката Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = Нищо (изключено) Off = Off On-screen touch controls = Управление с докосване на екрана diff --git a/assets/lang/cz_CZ.ini b/assets/lang/cz_CZ.ini index e8ed2db178..e591bb4a0c 100644 --- a/assets/lang/cz_CZ.ini +++ b/assets/lang/cz_CZ.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = Žádné (zakázáno) Off = Vypnuto On-screen touch controls = Dotykové ovládání na obrazovce diff --git a/assets/lang/de_DE.ini b/assets/lang/de_DE.ini index b660f7419a..3cfd92b6d5 100644 --- a/assets/lang/de_DE.ini +++ b/assets/lang/de_DE.ini @@ -157,7 +157,7 @@ Mouse = Maus-Einstellungen Mouse sensitivity = Mausempfindlichkeit Mouse smoothing = Mausglättung Mouse wheel button-release delay = Verzögerung beim Loslassen der Mausradtaste -MouseControl Tip = Du kannst jetzt die Maus im Steuerungszuordnungs-Bildschirm zuordnen, indem du auf das Symbol „M“ drückst +MouseControl Tip = Du kannst Maus-Eingaben im Steuerungszuordnungs-Bildschirm zuordnen, indem du auf das Symbol „M“ drückst None (Disabled) = Keine (Deaktiviert) Off = Aus On-screen touch controls = Berührungs-Bildschirmtasten diff --git a/assets/lang/dr_ID.ini b/assets/lang/dr_ID.ini index a80fb517c6..b4822b22ac 100644 --- a/assets/lang/dr_ID.ini +++ b/assets/lang/dr_ID.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) Off = Off On-screen touch controls = Tobolo'na jo layar sentu diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 1ad916f0db..ad633f6984 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -179,7 +179,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) Off = Off On-screen touch controls = On-screen touch controls diff --git a/assets/lang/es_ES.ini b/assets/lang/es_ES.ini index a0ef136b98..4e633c028c 100644 --- a/assets/lang/es_ES.ini +++ b/assets/lang/es_ES.ini @@ -157,7 +157,7 @@ Mouse = Ajustes de ratón Mouse sensitivity = Sensibilidad de ratón Mouse smoothing = Suavidad de ratón Mouse wheel button-release delay = Retraso al soltar botón de la rueda del mouse -MouseControl Tip = Ahora puedes configurar el mouse en la pantalla de configuración de mando presionando el ícono 'M'. +MouseControl Tip = Puedes configurar el mouse en la pantalla de configuración de mando presionando el ícono 'M'. None (Disabled) = Ninguno (desactivado) Off = No On-screen touch controls = Controles táctiles en pantalla diff --git a/assets/lang/gl_ES.ini b/assets/lang/gl_ES.ini index 392b537bb7..e7d1d30a7a 100644 --- a/assets/lang/gl_ES.ini +++ b/assets/lang/gl_ES.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = Desactivado Off = Off On-screen touch controls = Controis táctiles en pantalla diff --git a/assets/lang/he_IL.ini b/assets/lang/he_IL.ini index 600c7cd069..d1bbbb1041 100644 --- a/assets/lang/he_IL.ini +++ b/assets/lang/he_IL.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) Off = Off On-screen touch controls = מקשי מגע על המסך diff --git a/assets/lang/he_IL_invert.ini b/assets/lang/he_IL_invert.ini index ba7873213c..414640311b 100644 --- a/assets/lang/he_IL_invert.ini +++ b/assets/lang/he_IL_invert.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) Off = Off On-screen touch controls = ךסמה לע עגמ ישקמ diff --git a/assets/lang/jv_ID.ini b/assets/lang/jv_ID.ini index 02e9dbd0c1..cc28f66359 100644 --- a/assets/lang/jv_ID.ini +++ b/assets/lang/jv_ID.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = Ora ana (Ora Aktif) Off = Mati On-screen touch controls = Tombol Layar-sentuh diff --git a/assets/lang/ku_SO.ini b/assets/lang/ku_SO.ini index 2b8accf0b4..ba4c0d7bdc 100644 --- a/assets/lang/ku_SO.ini +++ b/assets/lang/ku_SO.ini @@ -170,7 +170,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) Off = Off On-screen touch controls = On-screen touch controls diff --git a/assets/lang/lt-LT.ini b/assets/lang/lt-LT.ini index fff7b028f8..370193958f 100644 --- a/assets/lang/lt-LT.ini +++ b/assets/lang/lt-LT.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = Nieko (išjungta) Off = Off On-screen touch controls = Ekrano liečiami kontrolės mygtukai diff --git a/assets/lang/ms_MY.ini b/assets/lang/ms_MY.ini index 965c678bc6..f1c99803e3 100644 --- a/assets/lang/ms_MY.ini +++ b/assets/lang/ms_MY.ini @@ -157,7 +157,7 @@ Mouse = Tetapan Tetikus Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) Off = Off On-screen touch controls = Kawalan sentuh di skrin diff --git a/assets/lang/no_NO.ini b/assets/lang/no_NO.ini index 1c9d818d30..9b0fab0bfa 100644 --- a/assets/lang/no_NO.ini +++ b/assets/lang/no_NO.ini @@ -157,7 +157,7 @@ Mouse = Mouse settings Mouse sensitivity = Mouse sensitivity Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = None (disabled) Off = Off On-screen touch controls = Touch-kontroller på skjermen diff --git a/assets/lang/ro_RO.ini b/assets/lang/ro_RO.ini index fbfba9ee64..4abb681a02 100644 --- a/assets/lang/ro_RO.ini +++ b/assets/lang/ro_RO.ini @@ -157,7 +157,7 @@ Mouse = Setări Mouse Mouse sensitivity = Sensitivitate Mouse Mouse smoothing = Mouse smoothing Mouse wheel button-release delay = Mouse wheel button-release delay -MouseControl Tip = You can now map mouse in control mapping screen by pressing the 'M' icon. +MouseControl Tip = You can map mouse inputs in the control mapping screen by pressing the 'M' icon. None (Disabled) = Fara (dezactivat) Off = Off On-screen touch controls = Controale Touch pe Ecran diff --git a/assets/lang/sv_SE.ini b/assets/lang/sv_SE.ini index 217904495f..3c02da8701 100644 --- a/assets/lang/sv_SE.ini +++ b/assets/lang/sv_SE.ini @@ -157,7 +157,7 @@ Mouse = Musinställningar Mouse sensitivity = Muskänslighet Mouse smoothing = Musutjämning Mouse wheel button-release delay = Släppfördröjning för knapp på mushjul -MouseControl Tip = Du kan nu mappa musrörelser genom att klicka på "M"-ikonen. +MouseControl Tip = Du kan mappa musrörelser genom att klicka på "M"-ikonen. None (Disabled) = Ingen (av) Off = Av On-screen touch controls = Touch-kontroller på skärmen From 2052ebc60c1cd0e6e4552c1dd280c5fc720781e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Mar 2026 14:40:30 +0100 Subject: [PATCH 2/4] Fix switching of audio devices on Windows. Fixes #21337 --- Common/Audio/AudioBackend.h | 1 + Core/Config.cpp | 2 +- Core/Config.h | 2 +- SDL/SDLMain.cpp | 2 +- UI/GameSettingsScreen.cpp | 4 +- UI/NativeApp.cpp | 2 +- Windows/WASAPIContext.cpp | 76 ++++++++++++++++++++++++++++++++++--- Windows/WASAPIContext.h | 30 ++++++++------- 8 files changed, 93 insertions(+), 26 deletions(-) diff --git a/Common/Audio/AudioBackend.h b/Common/Audio/AudioBackend.h index be4e6ee1be..f23588c222 100644 --- a/Common/Audio/AudioBackend.h +++ b/Common/Audio/AudioBackend.h @@ -32,5 +32,6 @@ public: virtual int BufferSize() const = 0; virtual int PeriodFrames() const = 0; virtual void DescribeOutputFormat(char *buffer, size_t bufferSize) const { buffer[0] = '-'; buffer[1] = '\0'; } + virtual std::string GetCurrentDeviceName() const { return ""; } virtual void FrameUpdate(bool allowAutoChange) {} }; diff --git a/Core/Config.cpp b/Core/Config.cpp index 4f6ee43dce..c14be35502 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -886,7 +886,7 @@ static const ConfigSetting soundSettings[] = { ConfigSetting("GamePreviewVolume", SETTING(g_Config, iGamePreviewVolume), &DefaultGamePreviewVolume, CfgFlag::DEFAULT), ConfigSetting("AudioDevice", SETTING(g_Config, sAudioDevice), "", CfgFlag::DEFAULT), - ConfigSetting("AutoAudioDevice", SETTING(g_Config, bAutoAudioDevice), true, CfgFlag::DEFAULT), + ConfigSetting("AutoAudioDevice", SETTING(g_Config, bAutoSwitchAudioDevice), true, CfgFlag::DEFAULT), ConfigSetting("AudioMixWithOthers", SETTING(g_Config, bAudioMixWithOthers), true, CfgFlag::DEFAULT), ConfigSetting("AudioRespectSilentMode", SETTING(g_Config, bAudioRespectSilentMode), false, CfgFlag::DEFAULT), ConfigSetting("UseOldAtrac", SETTING(g_Config, bUseOldAtrac), false, CfgFlag::DEFAULT), diff --git a/Core/Config.h b/Core/Config.h index fef2956e4a..ecce9de329 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -410,7 +410,7 @@ public: bool bExtraAudioBuffering; // For bluetooth std::string sAudioDevice; - bool bAutoAudioDevice; + bool bAutoSwitchAudioDevice; bool bUseOldAtrac; // iOS only for now diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 04c16ca7a0..25ff82fa03 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -1290,7 +1290,7 @@ static void ProcessSDLEvent(SDL_Window *window, const SDL_Event &event, InputSta break; } // Don't start auto switching for a couple of seconds, because some devices init on start. - bool doAutoSwitch = g_Config.bAutoAudioDevice; + bool doAutoSwitch = g_Config.bAutoSwitchAudioDevice; if ((time_now_d() - g_audioStartTime) < 3.0) { INFO_LOG(Log::Audio, "Ignoring new audio device: %s (current: %s)", name, g_Config.sAudioDevice.c_str()); doAutoSwitch = false; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 10e4793ff1..70231828b9 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -796,7 +796,7 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) { WARN_LOG(Log::Audio, "InitOutputDevice failed"); } }); - CheckBox *autoAudio = audioSettings->Add(new CheckBox(&g_Config.bAutoAudioDevice, a->T("Use new audio devices automatically"))); + CheckBox *autoAudio = audioSettings->Add(new CheckBox(&g_Config.bAutoSwitchAudioDevice, a->T("Use new audio devices automatically"))); autoAudio->SetEnabledFunc([]()->bool { return g_Config.sAudioDevice.empty(); }); @@ -808,7 +808,7 @@ void GameSettingsScreen::CreateAudioSettings(UI::ViewGroup *audioSettings) { audioSettings->Add(new ItemHeader(a->T("Audio backend"))); if (sdlAudio) { - audioSettings->Add(new CheckBox(&g_Config.bAutoAudioDevice, a->T("Use new audio devices automatically"))); + audioSettings->Add(new CheckBox(&g_Config.bAutoSwitchAudioDevice, a->T("Use new audio devices automatically"))); } #if PPSSPP_PLATFORM(ANDROID) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 84596b04ec..4abdcfaa50 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -1017,7 +1017,7 @@ void NativeFrame(GraphicsContext *graphicsContext) { g_iconCache.FrameUpdate(); if (g_audioBackend) { - g_audioBackend->FrameUpdate(g_Config.bAutoAudioDevice); + g_audioBackend->FrameUpdate(g_Config.bAutoSwitchAudioDevice); } // NOTE: We must begin the frame before update, so we can do texture size queries and stuff in Measure etc. diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index 0e3a097267..1799cd3f8d 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -152,7 +152,7 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late GetDeviceDesc(device.Get(), &desc); INFO_LOG(Log::Audio, "Activating audio device: %s", desc.name.c_str()); - deviceId_ = uniqueId; + curDeviceId_ = uniqueId; HRESULT hr = E_FAIL; // Try IAudioClient3 first if not in "safe" mode. It's probably safe anyway, but still, let's use the legacy client as a safe fallback option. @@ -242,6 +242,7 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late } } else { // All good, nothing to convert. + _dbg_assert_(format_); } } else { // Some other format. @@ -291,13 +292,13 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late } void WASAPIContext::Start() { + _dbg_assert_(!audioThread_.joinable()); running_ = true; audioThread_ = std::thread([this]() { AudioLoop(); }); } void WASAPIContext::Stop() { running_ = false; - if (audioClient_) audioClient_->Stop(); if (audioEvent_) SetEvent(audioEvent_); if (audioThread_.joinable()) audioThread_.join(); @@ -311,14 +312,37 @@ void WASAPIContext::Stop() { CoTaskMemFree(format_); format_ = nullptr; } + { + std::lock_guard guard(deviceLock_); + curDeviceId_.clear(); + } } void WASAPIContext::FrameUpdate(bool allowAutoChange) { - if (deviceId_.empty() && defaultDeviceChanged_ && allowAutoChange) { - defaultDeviceChanged_ = false; - Stop(); - Start(); + std::string deviceIdToInit; + { + std::lock_guard guard(deviceLock_); + if (!defaultDeviceChanged_) { + return; + } + + if (allowAutoChange) { + // Check if there actually was a change, we ignore false positives. + { + if (newDeviceId_ == curDeviceId_) { + // False positive, ignore. + defaultDeviceChanged_ = false; + return; + } + deviceIdToInit = newDeviceId_; + newDeviceId_.clear(); + } + defaultDeviceChanged_ = false; + } } + + bool reverted; + InitOutputDevice(deviceIdToInit, latencyMode_, &reverted); } void WASAPIContext::AudioLoop() { @@ -343,6 +367,11 @@ void WASAPIContext::AudioLoop() { return; } + if (!format_) { + ERROR_LOG(Log::Audio, "Can't start audio - no format"); + return; + } + const AudioFormat format = Classify(format_); const int nChannels = format_->nChannels; @@ -445,3 +474,38 @@ void WASAPIContext::DescribeOutputFormat(char *buffer, size_t bufferSize) const } snprintf(buffer, bufferSize, "%d Hz %s %d-bit, %d ch%s", sampleRateHz, fmt, sampleBits, numChannels, audioClient3_ ? " (ac3)" : " (ac)"); } + + +HRESULT STDMETHODCALLTYPE WASAPIContext::DeviceNotificationClient::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device) { + if (flow != eRender) { + INFO_LOG(Log::Audio, "Default WASAPI audio recording device changed! Currently ignoring."); + return S_OK; + } + INFO_LOG(Log::Audio, "Default device changed to %s! role=%d", ConvertWStringToUTF8(device).c_str(), role); + if (role == eConsole) { + // PostMessage(hwnd, WM_APP + 1, 0, 0); + std::lock_guard guard(engine_->deviceLock_); + engine_->defaultDeviceChanged_ = true; + engine_->newDeviceId_ = ConvertWStringToUTF8(device); + } + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WASAPIContext::DeviceNotificationClient::OnDeviceAdded(LPCWSTR device) { + INFO_LOG(Log::Audio, "Audio device added! device=%s", ConvertWStringToUTF8(device).c_str()); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WASAPIContext::DeviceNotificationClient::OnDeviceRemoved(LPCWSTR device) { + INFO_LOG(Log::Audio, "Audio device removed! device=%s", ConvertWStringToUTF8(device).c_str()); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WASAPIContext::DeviceNotificationClient::OnDeviceStateChanged(LPCWSTR device, DWORD state) { + INFO_LOG(Log::Audio, "Audio device state changed! device=%s state=%08x", ConvertWStringToUTF8(device).c_str(), state); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE WASAPIContext::DeviceNotificationClient::OnPropertyValueChanged(LPCWSTR device, const PROPERTYKEY key) { + return S_OK; +} diff --git a/Windows/WASAPIContext.h b/Windows/WASAPIContext.h index 2285a0b478..334c4e1e65 100644 --- a/Windows/WASAPIContext.h +++ b/Windows/WASAPIContext.h @@ -2,6 +2,7 @@ #include "Audio/AudioBackend.h" #include +#include #include #include @@ -47,24 +48,22 @@ public: return E_NOINTERFACE; } - HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR) override { - if (flow == eRender && role == eConsole) { - // PostMessage(hwnd, WM_APP + 1, 0, 0); - engine_->defaultDeviceChanged_ = true; - } - return S_OK; - } - - HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR) override { return S_OK; } - HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR) override { return S_OK; } - HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) override { return S_OK; } - HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY) override { return S_OK; } + HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device) override; + HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device) override; + HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device) override; + HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR device, DWORD state) override; + HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR device, const PROPERTYKEY key) override; private: WASAPIContext *engine_; }; void DescribeOutputFormat(char *buffer, size_t bufferSize) const override; + std::string GetCurrentDeviceName() const override { + std::lock_guard guard(deviceLock_); + return curDeviceId_; + } + private: void Start(); void Stop(); @@ -95,8 +94,11 @@ private: RenderCallback callback_{}; void *userdata_ = nullptr; LatencyMode latencyMode_ = LatencyMode::Aggressive; - std::string deviceId_; - std::atomic defaultDeviceChanged_{}; + + mutable std::mutex deviceLock_; + std::string curDeviceId_; + std::string newDeviceId_; + bool defaultDeviceChanged_ = false; std::unique_ptr tempBuf_; }; From b6a0e051aa8c0697e2d7dd82980e386436683c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Mar 2026 15:05:35 +0100 Subject: [PATCH 3/4] Minor fixes in WASAPIContext.cpp/h --- Windows/WASAPIContext.cpp | 27 +++++++++++++++++++++++---- Windows/WASAPIContext.h | 4 +++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index 1799cd3f8d..e11cd8c08c 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -70,7 +70,7 @@ WASAPIContext::~WASAPIContext() { } WASAPIContext::AudioFormat WASAPIContext::Classify(const WAVEFORMATEX *format) { - if (format->wFormatTag == WAVE_FORMAT_PCM && format->wBitsPerSample == 2) { + if (format->wFormatTag == WAVE_FORMAT_PCM && format->wBitsPerSample == 16) { return AudioFormat::S16; } else if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { const WAVEFORMATEXTENSIBLE *ex = (const WAVEFORMATEXTENSIBLE *)format; @@ -165,6 +165,8 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late if (SUCCEEDED(hr)) { audioClient3_->GetMixFormat(&format_); + curSamplesPerSec_ = format_->nSamplesPerSec; + // We only use AudioClient3 if we got the format we wanted (stereo float). if (format_->nChannels != 2 || Classify(format_) != AudioFormat::Float) { // Let's fall back to the old path. The docs seem to be wrong, if you try to create an @@ -205,12 +207,18 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late return false; } - audioClient_->GetMixFormat(&format_); + hr = audioClient_->GetMixFormat(&format_); + if (FAILED(hr)) { + _dbg_assert_(false); + return false; + } // If there are too many channels, try asking for a 2-channel output format. DWORD extraStreamFlags = 0; const AudioFormat fmt = Classify(format_); + curSamplesPerSec_ = format_->nSamplesPerSec; + bool createBuffer = false; if (fmt == AudioFormat::Float) { if (format_->nChannels != 2) { @@ -292,7 +300,12 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late } void WASAPIContext::Start() { - _dbg_assert_(!audioThread_.joinable()); + if (audioThread_.joinable()) { + _dbg_assert_(false); + ERROR_LOG(Log::Audio, "Audio thread already running!"); + return; + } + running_ = true; audioThread_ = std::thread([this]() { AudioLoop(); }); } @@ -300,6 +313,7 @@ void WASAPIContext::Start() { void WASAPIContext::Stop() { running_ = false; if (audioEvent_) SetEvent(audioEvent_); + // Stop is actually called on the audioclient in the thread, while exiting. if (audioThread_.joinable()) audioThread_.join(); renderClient_.Reset(); @@ -448,7 +462,8 @@ void WASAPIContext::AudioLoop() { if (audioClient3_) { audioClient3_->Stop(); - } else { + } + if (audioClient_) { audioClient_->Stop(); } @@ -458,6 +473,10 @@ void WASAPIContext::AudioLoop() { } void WASAPIContext::DescribeOutputFormat(char *buffer, size_t bufferSize) const { + if (!format_) { + snprintf(buffer, bufferSize, "No format"); + return; + } const int numChannels = format_->nChannels; const int sampleBits = format_->wBitsPerSample; const int sampleRateHz = format_->nSamplesPerSec; diff --git a/Windows/WASAPIContext.h b/Windows/WASAPIContext.h index 334c4e1e65..1c216403e4 100644 --- a/Windows/WASAPIContext.h +++ b/Windows/WASAPIContext.h @@ -18,6 +18,7 @@ public: WASAPIContext(); ~WASAPIContext(); + // This is only called on init. void SetRenderCallback(RenderCallback callback, void *userdata) override { callback_ = callback; userdata_ = userdata; @@ -31,7 +32,7 @@ public: int PeriodFrames() const override { return actualPeriodFrames_; } // NOTE: This may have the wrong value (too large) until audio has started playing. int BufferSize() const override { return reportedBufferSize_; } - int SampleRate() const override { return format_->nSamplesPerSec; } + int SampleRate() const override { return curSamplesPerSec_; } // Implements device change notifications class DeviceNotificationClient : public IMMNotificationClient { @@ -85,6 +86,7 @@ private: WAVEFORMATEX *format_ = nullptr; HANDLE audioEvent_ = nullptr; std::thread audioThread_; + int curSamplesPerSec_ = 0; UINT32 defaultPeriodFrames = 0, fundamentalPeriodFrames = 0, minPeriodFrames = 0, maxPeriodFrames = 0; std::atomic running_ = true; UINT32 actualPeriodFrames_ = 0; // may not be the requested. From 84456449204d285318afc58ec9395f13f802309b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 4 Mar 2026 15:15:53 +0100 Subject: [PATCH 4/4] Additional cleanup --- Windows/WASAPIContext.cpp | 16 ++++++++++------ Windows/WASAPIContext.h | 5 ++++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index e11cd8c08c..a2e5e92fb6 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -152,7 +152,10 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late GetDeviceDesc(device.Get(), &desc); INFO_LOG(Log::Audio, "Activating audio device: %s", desc.name.c_str()); - curDeviceId_ = uniqueId; + { + std::lock_guard guard(deviceLock_); + curDeviceId_ = uniqueId; + } HRESULT hr = E_FAIL; // Try IAudioClient3 first if not in "safe" mode. It's probably safe anyway, but still, let's use the legacy client as a safe fallback option. @@ -174,15 +177,15 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late audioClient3_.Reset(); // Fall through to AudioClient creation below. } else { - audioClient3_->GetSharedModeEnginePeriod(format_, &defaultPeriodFrames, &fundamentalPeriodFrames, &minPeriodFrames, &maxPeriodFrames); + audioClient3_->GetSharedModeEnginePeriod(format_, &defaultPeriodFrames_, &fundamentalPeriodFrames_, &minPeriodFrames_, &maxPeriodFrames_); - INFO_LOG(Log::Audio, "AudioClient3: default: %d fundamental: %d min: %d max: %d\n", (int)defaultPeriodFrames, (int)fundamentalPeriodFrames, (int)minPeriodFrames, (int)maxPeriodFrames); - INFO_LOG(Log::Audio, "initializing with %d frame period at %d Hz, meaning %0.1fms\n", (int)minPeriodFrames, (int)format_->nSamplesPerSec, FramesToMs(minPeriodFrames, format_->nSamplesPerSec)); + INFO_LOG(Log::Audio, "AudioClient3: default: %d fundamental: %d min: %d max: %d\n", (int)defaultPeriodFrames_, (int)fundamentalPeriodFrames_, (int)minPeriodFrames_, (int)maxPeriodFrames_); + INFO_LOG(Log::Audio, "initializing with %d frame period at %d Hz, meaning %0.1fms\n", (int)minPeriodFrames_, (int)format_->nSamplesPerSec, FramesToMs(minPeriodFrames_, format_->nSamplesPerSec)); audioEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr); HRESULT result = audioClient3_->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - minPeriodFrames, + minPeriodFrames_, format_, nullptr ); @@ -191,7 +194,7 @@ bool WASAPIContext::InitOutputDevice(std::string_view uniqueId, LatencyMode late audioClient3_.Reset(); return false; } - actualPeriodFrames_ = minPeriodFrames; + actualPeriodFrames_ = minPeriodFrames_; audioClient3_->GetBufferSize(&reportedBufferSize_); audioClient3_->SetEventHandle(audioEvent_); @@ -318,6 +321,7 @@ void WASAPIContext::Stop() { renderClient_.Reset(); audioClient_.Reset(); + audioClient3_.Reset(); if (audioEvent_) { CloseHandle(audioEvent_); audioEvent_ = nullptr; diff --git a/Windows/WASAPIContext.h b/Windows/WASAPIContext.h index 1c216403e4..64d655c41f 100644 --- a/Windows/WASAPIContext.h +++ b/Windows/WASAPIContext.h @@ -87,7 +87,10 @@ private: HANDLE audioEvent_ = nullptr; std::thread audioThread_; int curSamplesPerSec_ = 0; - UINT32 defaultPeriodFrames = 0, fundamentalPeriodFrames = 0, minPeriodFrames = 0, maxPeriodFrames = 0; + UINT32 defaultPeriodFrames_ = 0; + UINT32 fundamentalPeriodFrames_ = 0; + UINT32 minPeriodFrames_ = 0; + UINT32 maxPeriodFrames_ = 0; std::atomic running_ = true; UINT32 actualPeriodFrames_ = 0; // may not be the requested. UINT32 reportedBufferSize_ = 0;