diff --git a/src/multimedia/audio/qrtaudioengine.cpp b/src/multimedia/audio/qrtaudioengine.cpp index 90bd161..b0fdd20 100644 --- a/src/multimedia/audio/qrtaudioengine.cpp +++ b/src/multimedia/audio/qrtaudioengine.cpp @@ -161,9 +161,11 @@ void QRtAudioEngine::audioCallback(QSpan outputBuffer) noexcept QT_MM_NON if (!finishedVoices.empty()) { withRTSanDisabled([&] { - for (const SharedVoice &voice : finishedVoices) { + for (SharedVoice &voice : finishedVoices) { m_rtVoiceRegistry.erase(voice); - bool stopSent = sendRtToAppNotification(StopNotification{ voice }); + // VOID: hand the real-time thread's last reference to the application thread, + // so ~QSoundEffectVoice (and its QSocketNotifier) never runs on this thread + bool stopSent = sendRtToAppNotification(StopNotification{ std::move(voice) }); if (stopSent) sendNotification = true; } @@ -181,28 +183,18 @@ void QRtAudioEngine::cleanupRetiredVoices() noexcept QT_MM_NONBLOCKING { bool notifyApp = false; -#if __cpp_lib_erase_if >= 202002L - using std::erase_if; -#else - auto erase_if = [](auto &c, auto &&pred) { - auto old_size = c.size(); - for (auto first = c.begin(), last = c.end(); first != last;) { - if (pred(*first)) - first = c.erase(first); - else - ++first; - } - return old_size - c.size(); - }; -#endif withRTSanDisabled([&] { - erase_if(m_rtVoiceRegistry, [&](const SharedVoice &voice) { - bool voiceIsActive = voice->isActive(); - if (!voiceIsActive) - notifyApp = sendRtToAppNotification(StopNotification{ voice }); - - return !voiceIsActive; - }); + // VOID: erase before notifying, so the notification holds the last reference and + // the voice is destroyed on the application thread + for (auto it = m_rtVoiceRegistry.begin(); it != m_rtVoiceRegistry.end();) { + if ((*it)->isActive()) { + ++it; + continue; + } + SharedVoice voice = *it; + it = m_rtVoiceRegistry.erase(it); + notifyApp = sendRtToAppNotification(StopNotification{ std::move(voice) }); + } }); if (notifyApp)