From 6bc4a09c9b91c092e0249b32f99a5ee5d70bdbf0 Mon Sep 17 00:00:00 2001 From: Leon Styhre Date: Mon, 15 Nov 2021 22:43:06 +0100 Subject: [PATCH] Multiple thread safety improvements to AudioManager and VideoFFmpegComponent. Also some general refactoring and re-enabling of some SDL_AudioStream functions. --- es-app/src/MediaViewer.cpp | 7 +- es-app/src/guis/GuiGamelistOptions.cpp | 6 +- es-app/src/main.cpp | 3 +- es-app/src/views/SystemView.cpp | 4 +- es-app/src/views/SystemView.h | 2 +- es-app/src/views/ViewController.cpp | 17 ++-- .../src/views/gamelist/GridGameListView.cpp | 6 +- .../views/gamelist/ISimpleGameListView.cpp | 28 +++--- es-core/src/AudioManager.cpp | 68 ++++++------- es-core/src/AudioManager.h | 35 ++++--- es-core/src/Sound.cpp | 65 ++++++------ es-core/src/Sound.h | 13 ++- es-core/src/Window.cpp | 3 +- es-core/src/components/TextListComponent.h | 4 +- es-core/src/components/VideoComponent.cpp | 17 ++++ es-core/src/components/VideoComponent.h | 2 + .../src/components/VideoFFmpegComponent.cpp | 99 ++++++++++++------- 17 files changed, 206 insertions(+), 173 deletions(-) diff --git a/es-app/src/MediaViewer.cpp b/es-app/src/MediaViewer.cpp index c24e9cf1c..cd8fc8387 100644 --- a/es-app/src/MediaViewer.cpp +++ b/es-app/src/MediaViewer.cpp @@ -12,7 +12,6 @@ #if defined(BUILD_VLC_PLAYER) #include "components/VideoVlcComponent.h" #endif -#include "AudioManager.h" #include "Sound.h" #include "views/ViewController.h" @@ -59,7 +58,7 @@ bool MediaViewer::startMediaViewer(FileData* game) void MediaViewer::stopMediaViewer() { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); ViewController::get()->onStopVideo(); if (mVideo) { @@ -197,7 +196,7 @@ void MediaViewer::findMedia() void MediaViewer::showNext() { if (mHasImages && mCurrentImageIndex != static_cast(mImageFiles.size()) - 1) - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); bool showedVideo = false; @@ -229,7 +228,7 @@ void MediaViewer::showNext() void MediaViewer::showPrevious() { if ((mHasVideo && mDisplayingImage) || (!mHasVideo && mCurrentImageIndex != 0)) - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); if (mCurrentImageIndex == 0 && !mHasVideo) { return; diff --git a/es-app/src/guis/GuiGamelistOptions.cpp b/es-app/src/guis/GuiGamelistOptions.cpp index 7c457c4a5..bc7a0cf02 100644 --- a/es-app/src/guis/GuiGamelistOptions.cpp +++ b/es-app/src/guis/GuiGamelistOptions.cpp @@ -310,7 +310,7 @@ GuiGamelistOptions::~GuiGamelistOptions() } if (mSystem->getRootFolder()->getChildren().size() != 0 && mSystem->getName() != "recent") - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); } void GuiGamelistOptions::openGamelistFilter() @@ -356,7 +356,7 @@ void GuiGamelistOptions::startEditMode() } if (mSystem->getRootFolder()->getChildren().size() == 0) - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); delete this; } @@ -364,7 +364,7 @@ void GuiGamelistOptions::exitEditMode() { CollectionSystemsManager::get()->exitEditMode(); if (mSystem->getRootFolder()->getChildren().size() == 0) - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); delete this; } diff --git a/es-app/src/main.cpp b/es-app/src/main.cpp index c68c06bbb..4899f6766 100644 --- a/es-app/src/main.cpp +++ b/es-app/src/main.cpp @@ -704,8 +704,7 @@ int main(int argc, char* argv[]) MameNames::deinit(); CollectionSystemsManager::deinit(); SystemData::deleteSystems(); - NavigationSounds::getInstance()->deinit(); - Settings::deinit(); + NavigationSounds::getInstance().deinit(); #if defined(FREEIMAGE_LIB) // Call this ONLY when linking with FreeImage as a static library. diff --git a/es-app/src/views/SystemView.cpp b/es-app/src/views/SystemView.cpp index b377306fa..78a10eda5 100644 --- a/es-app/src/views/SystemView.cpp +++ b/es-app/src/views/SystemView.cpp @@ -284,14 +284,14 @@ bool SystemView::input(InputConfig* config, Input input) if (config->isMappedTo("a", input)) { stopScrolling(); ViewController::get()->goToGameList(getSelected()); - NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); return true; } if (Settings::getInstance()->getBool("RandomAddButton") && (config->isMappedTo("leftthumbstickclick", input) || config->isMappedTo("rightthumbstickclick", input))) { // Get a random system and jump to it. - NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND); setCursor(SystemData::getRandomSystem(getSelected())); return true; } diff --git a/es-app/src/views/SystemView.h b/es-app/src/views/SystemView.h index edf731903..f8bbe3ef1 100644 --- a/es-app/src/views/SystemView.h +++ b/es-app/src/views/SystemView.h @@ -77,7 +77,7 @@ protected: void onCursorChanged(const CursorState& state) override; virtual void onScroll() override { - NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND); } private: diff --git a/es-app/src/views/ViewController.cpp b/es-app/src/views/ViewController.cpp index 95acee4df..fdb25b500 100644 --- a/es-app/src/views/ViewController.cpp +++ b/es-app/src/views/ViewController.cpp @@ -12,7 +12,6 @@ #include "views/ViewController.h" -#include "AudioManager.h" #include "FileFilterIndex.h" #include "InputManager.h" #include "Log.h" @@ -413,7 +412,7 @@ void ViewController::goToNextGameList() assert(mState.viewing == GAME_LIST); SystemData* system = getState().getSystem(); assert(system); - NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(QUICKSYSSELECTSOUND); mNextSystem = true; goToGameList(system->getNext()); } @@ -423,7 +422,7 @@ void ViewController::goToPrevGameList() assert(mState.viewing == GAME_LIST); SystemData* system = getState().getSystem(); assert(system); - NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(QUICKSYSSELECTSOUND); mNextSystem = false; goToGameList(system->getPrev()); } @@ -717,7 +716,7 @@ void ViewController::launch(FileData* game) if (durationString != "disabled") mWindow->displayLaunchScreen(game->getSourceFileData()); - NavigationSounds::getInstance()->playThemeNavigationSound(LAUNCHSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(LAUNCHSOUND); // This is just a dummy animation in order for the launch screen or notification popup // to be displayed briefly, and for the navigation sound playing to be able to complete. @@ -976,13 +975,13 @@ void ViewController::preload() bool themeSoundSupport = false; for (SystemData* system : SystemData::sSystemVector) { if (system->getTheme()->hasView("all")) { - NavigationSounds::getInstance()->loadThemeNavigationSounds(system->getTheme()); + NavigationSounds::getInstance().loadThemeNavigationSounds(system->getTheme().get()); themeSoundSupport = true; break; } } if (!SystemData::sSystemVector.empty() && !themeSoundSupport) - NavigationSounds::getInstance()->loadThemeNavigationSounds(nullptr); + NavigationSounds::getInstance().loadThemeNavigationSounds(nullptr); } void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme) @@ -1070,17 +1069,17 @@ void ViewController::reloadAll() // Load navigation sounds, either from the theme if it supports it, or otherwise from // the bundled fallback sound files. - NavigationSounds::getInstance()->deinit(); + NavigationSounds::getInstance().deinit(); bool themeSoundSupport = false; for (SystemData* system : SystemData::sSystemVector) { if (system->getTheme()->hasView("all")) { - NavigationSounds::getInstance()->loadThemeNavigationSounds(system->getTheme()); + NavigationSounds::getInstance().loadThemeNavigationSounds(system->getTheme().get()); themeSoundSupport = true; break; } } if (!SystemData::sSystemVector.empty() && !themeSoundSupport) - NavigationSounds::getInstance()->loadThemeNavigationSounds(nullptr); + NavigationSounds::getInstance().loadThemeNavigationSounds(nullptr); mCurrentView->onShow(); updateHelpPrompts(); diff --git a/es-app/src/views/gamelist/GridGameListView.cpp b/es-app/src/views/gamelist/GridGameListView.cpp index af429a7ce..48ff1de60 100644 --- a/es-app/src/views/gamelist/GridGameListView.cpp +++ b/es-app/src/views/gamelist/GridGameListView.cpp @@ -168,15 +168,15 @@ bool GridGameListView::input(InputConfig* config, Input input) if (input.value == 0 && (config->isMappedLike("left", input) || config->isMappedLike("right", input) || (config->isMappedLike("up", input)) || (config->isMappedLike("down", input)))) - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); if (input.value != 0 && config->isMappedLike("righttrigger", input)) { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); mGrid.setCursor(mGrid.getLast()); } if (input.value != 0 && config->isMappedLike("lefttrigger", input)) { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); mGrid.setCursor(mGrid.getFirst()); } diff --git a/es-app/src/views/gamelist/ISimpleGameListView.cpp b/es-app/src/views/gamelist/ISimpleGameListView.cpp index ebe1fed30..16b393cfa 100644 --- a/es-app/src/views/gamelist/ISimpleGameListView.cpp +++ b/es-app/src/views/gamelist/ISimpleGameListView.cpp @@ -114,7 +114,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) // It's a folder. if (cursor->getChildren().size() > 0) { ViewController::get()->cancelViewTransitions(); - NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); mCursorStack.push(cursor); populateList(cursor->getChildrenListToDisplay(), cursor); @@ -140,7 +140,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) updateHelpPrompts(); } else { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); } } @@ -151,7 +151,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) if (mCursorStack.size()) { // Save the position to the cursor stack history. mCursorStackHistory.push_back(getCursor()); - NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND); populateList(mCursorStack.top()->getParent()->getChildrenListToDisplay(), mCursorStack.top()->getParent()); setCursor(mCursorStack.top()); @@ -161,7 +161,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) updateHelpPrompts(); } else { - NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND); onPauseVideo(); onFocusLost(); stopListScrolling(); @@ -178,14 +178,14 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) } else if (config->isMappedTo("x", input)) { if (getCursor()->getType() == PLACEHOLDER) { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); return true; } else if (config->isMappedTo("x", input) && mRoot->getSystem()->getThemeFolder() == "custom-collections" && mCursorStack.empty() && ViewController::get()->getState().viewing == ViewController::GAME_LIST) { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); // Jump to the randomly selected game. if (mRandomGame) { stopListScrolling(); @@ -197,7 +197,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) else if (mRoot->getSystem()->isGameSystem()) { stopListScrolling(); ViewController::get()->cancelViewTransitions(); - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); mWindow->startMediaViewer(getCursor()); return true; } @@ -228,7 +228,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER) { stopListScrolling(); // Jump to a random game. - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); FileData* randomGame = getCursor()->getSystem()->getRandomGame(getCursor()); if (randomGame) setCursor(randomGame); @@ -241,7 +241,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) ViewController::get()->getState().viewing == ViewController::GAME_LIST) { // Jump to the randomly selected game. if (mRandomGame) { - NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND); // If there is already an mCursorStackHistory entry for the collection, then // remove it so we don't get multiple entries. std::vector listEntries = @@ -257,7 +257,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) updateHelpPrompts(); } else { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); } } else if (config->isMappedTo("y", input) && @@ -272,19 +272,19 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) if (CollectionSystemsManager::get()->isEditing() && mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER && getCursor()->getParent()->getPath() == "collections") { - NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); + NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND); mWindow->queueInfoPopup("CAN'T ADD CUSTOM COLLECTIONS TO CUSTOM COLLECTIONS", 4000); } // Notify the user if attempting to add a placeholder to a custom collection. if (CollectionSystemsManager::get()->isEditing() && mRoot->getSystem()->isGameSystem() && getCursor()->getType() == PLACEHOLDER) { - NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); + NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND); mWindow->queueInfoPopup("CAN'T ADD PLACEHOLDERS TO CUSTOM COLLECTIONS", 4000); } else if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER && getCursor()->getParent()->getPath() != "collections") { if (getCursor()->getType() == GAME || getCursor()->getType() == FOLDER) - NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); + NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND); // When marking or unmarking a game as favorite, don't jump to the new position // it gets after the gamelist sorting. Instead retain the cursor position in the // list using the logic below. @@ -474,7 +474,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input) } } else if (config->isMappedTo("y", input) && getCursor()->isPlaceHolder()) { - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); } } } diff --git a/es-core/src/AudioManager.cpp b/es-core/src/AudioManager.cpp index c773a3ca3..9c986aa0e 100644 --- a/es-core/src/AudioManager.cpp +++ b/es-core/src/AudioManager.cpp @@ -14,16 +14,6 @@ #include -std::shared_ptr AudioManager::sInstance; -std::vector> AudioManager::sSoundVector; -SDL_AudioDeviceID AudioManager::sAudioDevice = 0; -SDL_AudioSpec AudioManager::sAudioFormat; -SDL_AudioStream* AudioManager::sConversionStream; - -bool AudioManager::sMuteStream = false; -bool AudioManager::sHasAudioDevice = true; -bool AudioManager::mIsClearingStream = false; - AudioManager::AudioManager() { // Init on construction. @@ -36,13 +26,10 @@ AudioManager::~AudioManager() deinit(); } -std::shared_ptr& AudioManager::getInstance() +AudioManager& AudioManager::getInstance() { - // Check if an AudioManager instance is already created, and if not then create it. - if (sInstance == nullptr) - sInstance = std::shared_ptr(new AudioManager); - - return sInstance; + static AudioManager instance; + return instance; } void AudioManager::init() @@ -126,15 +113,12 @@ void AudioManager::init() void AudioManager::deinit() { - // Due to bugs in SDL, freeing the stream causes random crashes. This is reported to the - // user on some operating systems such as macOS, and it's annoying to have a crash at the - // end of debugging session. So we'll simply disable the function until it has been properly - // fixed in the SDL library. - // SDL_FreeAudioStream(sConversionStream); + SDL_LockAudioDevice(sAudioDevice); + SDL_FreeAudioStream(sConversionStream); + SDL_UnlockAudioDevice(sAudioDevice); SDL_CloseAudio(); SDL_QuitSubSystem(SDL_INIT_AUDIO); - sInstance = nullptr; } void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len) @@ -172,13 +156,10 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len) soundIt++; } - int streamLength = 0; - // Process video stream audio generated by VideoFFmpegComponent. - if (!mIsClearingStream) - streamLength = SDL_AudioStreamAvailable(sConversionStream); + int streamLength = SDL_AudioStreamAvailable(sConversionStream); - if (streamLength <= 0 || mIsClearingStream) { + if (streamLength <= 0) { // If nothing is playing, pause the device until there is more audio to output. if (!stillPlaying) SDL_PauseAudioDevice(sAudioDevice, 1); @@ -213,7 +194,10 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len) // stream is not played when the video player has been stopped. Otherwise there would // be a short time period when the audio would keep playing after the video was stopped // and before the stream was cleared in clearStream(). - if (sMuteStream) { + std::unique_lock audioLock{mAudioLock}; + bool muteStream = sMuteStream; + audioLock.unlock(); + if (muteStream) { SDL_MixAudioFormat(stream, &converted.at(0), sAudioFormat.format, processedLength, 0); } else { @@ -227,13 +211,13 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len) SDL_PauseAudioDevice(sAudioDevice, 1); } -void AudioManager::registerSound(std::shared_ptr& sound) +void AudioManager::registerSound(std::shared_ptr sound) { // Add sound to sound vector. sSoundVector.push_back(sound); } -void AudioManager::unregisterSound(std::shared_ptr& sound) +void AudioManager::unregisterSound(std::shared_ptr sound) { for (unsigned int i = 0; i < sSoundVector.size(); i++) { if (sSoundVector.at(i) == sound) { @@ -286,32 +270,36 @@ void AudioManager::setupAudioStream(int sampleRate) void AudioManager::processStream(const void* samples, unsigned count) { - if (mIsClearingStream) - return; + SDL_LockAudioDevice(sAudioDevice); if (SDL_AudioStreamPut(sConversionStream, samples, count * sizeof(Uint8)) == -1) { LOG(LogError) << "Failed to put samples in the conversion stream:"; LOG(LogError) << SDL_GetError(); + SDL_UnlockAudioDevice(sAudioDevice); return; } if (count > 0) SDL_PauseAudioDevice(sAudioDevice, 0); + + SDL_UnlockAudioDevice(sAudioDevice); } void AudioManager::clearStream() { - // The SDL_AudioStreamClear() function is unstable and causes random crashes, so - // we have to implement a workaround instead where SDL_AudioStreamGet() is used - // to empty the stream. - // SDL_AudioStreamClear(sConversionStream); + SDL_LockAudioDevice(sAudioDevice); + SDL_AudioStreamClear(sConversionStream); + SDL_UnlockAudioDevice(sAudioDevice); + + // TEMPORARY: For evaluating if SDL_AudioStreamClear is stable. + return; // If sSoundVector is empty it means we are shutting down. In this case don't attempt // to clear the stream as this could lead to a crash. if (sSoundVector.empty()) return; - mIsClearingStream = true; + SDL_LockAudioDevice(sAudioDevice); // This code is required as there's seemingly a bug in SDL_AudioStreamAvailable(). // The function sometimes returns 0 even if there is data left in the buffer, possibly @@ -323,13 +311,13 @@ void AudioManager::clearStream() // Fortunately the SDL_AudioStreamGet() function acts correctly on any arbitrary sample size // so we can actually clear the entire buffer. If this workaround was not implemented, there // would be a sound glitch when some samples from the previous video would play any time a - // new video was started (assuming the issue was triggered be some remaining buffer data). + // new video was started (assuming the issue was triggered by some remaining stream data). std::vector writeBuffer(10000); if (SDL_AudioStreamPut(sConversionStream, reinterpret_cast(&writeBuffer.at(0)), 10000) == -1) { LOG(LogError) << "Failed to put samples in the conversion stream:"; LOG(LogError) << SDL_GetError(); - mIsClearingStream = false; + SDL_UnlockAudioDevice(sAudioDevice); return; } @@ -338,5 +326,5 @@ void AudioManager::clearStream() std::vector readBuffer(length); SDL_AudioStreamGet(sConversionStream, static_cast(&readBuffer.at(0)), length); - mIsClearingStream = false; + SDL_UnlockAudioDevice(sAudioDevice); } diff --git a/es-core/src/AudioManager.h b/es-core/src/AudioManager.h index e600220ed..76363f95d 100644 --- a/es-core/src/AudioManager.h +++ b/es-core/src/AudioManager.h @@ -11,6 +11,7 @@ #include #include +#include #include class Sound; @@ -19,13 +20,13 @@ class AudioManager { public: virtual ~AudioManager(); - static std::shared_ptr& getInstance(); + static AudioManager& getInstance(); void init(); void deinit(); - void registerSound(std::shared_ptr& sound); - void unregisterSound(std::shared_ptr& sound); + void registerSound(std::shared_ptr sound); + void unregisterSound(std::shared_ptr sound); void play(); void stop(); @@ -35,25 +36,33 @@ public: void processStream(const void* samples, unsigned count); void clearStream(); - void muteStream() { sMuteStream = true; } - void unmuteStream() { sMuteStream = false; } + void muteStream() + { + std::unique_lock audioLock{mAudioLock}; + sMuteStream = true; + } + void unmuteStream() + { + std::unique_lock audioLock{mAudioLock}; + sMuteStream = false; + } bool getHasAudioDevice() { return sHasAudioDevice; } - static SDL_AudioDeviceID sAudioDevice; - static SDL_AudioSpec sAudioFormat; + inline static SDL_AudioDeviceID sAudioDevice = 0; + inline static SDL_AudioSpec sAudioFormat; private: AudioManager(); static void mixAudio(void* unused, Uint8* stream, int len); static void mixAudio2(void* unused, Uint8* stream, int len); - static SDL_AudioStream* sConversionStream; - static std::vector> sSoundVector; - static std::shared_ptr sInstance; - static bool sMuteStream; - static bool sHasAudioDevice; - static bool mIsClearingStream; + + inline static std::mutex mAudioLock; + inline static SDL_AudioStream* sConversionStream; + inline static std::vector> sSoundVector; + inline static bool sMuteStream = false; + inline static bool sHasAudioDevice = true; }; #endif // ES_CORE_AUDIO_MANAGER_H diff --git a/es-core/src/Sound.cpp b/es-core/src/Sound.cpp index 037c3155b..0ece4f5c1 100644 --- a/es-core/src/Sound.cpp +++ b/es-core/src/Sound.cpp @@ -15,8 +15,6 @@ #include "ThemeData.h" #include "resources/ResourceManager.h" -NavigationSounds* NavigationSounds::sInstance = nullptr; - std::map> Sound::sMap; std::shared_ptr Sound::get(const std::string& path) @@ -26,16 +24,16 @@ std::shared_ptr Sound::get(const std::string& path) return it->second; std::shared_ptr sound = std::shared_ptr(new Sound(path)); - AudioManager::getInstance()->registerSound(sound); + AudioManager::getInstance().registerSound(sound); sMap[path] = sound; return sound; } -std::shared_ptr Sound::getFromTheme(const std::shared_ptr& theme, +std::shared_ptr Sound::getFromTheme(ThemeData* const theme, const std::string& view, const std::string& element) { - if (!theme) { + if (theme == nullptr) { LOG(LogDebug) << "Sound::getFromTheme(): Using fallback sound file for \"" << element << "\""; return get(ResourceManager::getInstance()->getResourcePath(":/sounds/" + element + ".wav")); @@ -57,7 +55,7 @@ Sound::Sound(const std::string& path) : mSampleData(nullptr) , mSamplePos(0) , mSampleLength(0) - , playing(false) + , mPlaying(false) { loadFile(path); } @@ -127,7 +125,7 @@ void Sound::init() void Sound::deinit() { - playing = false; + mPlaying = false; if (mSampleData != nullptr) { SDL_LockAudioDevice(AudioManager::sAudioDevice); @@ -148,28 +146,28 @@ void Sound::play() if (!Settings::getInstance()->getBool("NavigationSounds")) return; - if (!AudioManager::getInstance()->getHasAudioDevice()) + if (!AudioManager::getInstance().getHasAudioDevice()) return; SDL_LockAudioDevice(AudioManager::sAudioDevice); - if (playing) + if (mPlaying) // Replay from start. rewind the sample to the beginning. mSamplePos = 0; else // Flag our sample as playing. - playing = true; + mPlaying = true; SDL_UnlockAudioDevice(AudioManager::sAudioDevice); // Tell the AudioManager to start playing samples. - AudioManager::getInstance()->play(); + AudioManager::getInstance().play(); } void Sound::stop() { // Flag our sample as not playing and rewind its position. SDL_LockAudioDevice(AudioManager::sAudioDevice); - playing = false; + mPlaying = false; mSamplePos = 0; SDL_UnlockAudioDevice(AudioManager::sAudioDevice); } @@ -179,34 +177,27 @@ void Sound::setPosition(Uint32 newPosition) mSamplePos = newPosition; if (mSamplePos >= mSampleLength) { // Got to or beyond the end of the sample. stop playing. - playing = false; + mPlaying = false; mSamplePos = 0; } } -NavigationSounds* NavigationSounds::getInstance() +NavigationSounds& NavigationSounds::getInstance() { - if (sInstance == nullptr) - sInstance = new NavigationSounds(); - - return sInstance; + static NavigationSounds instance; + return instance; } void NavigationSounds::deinit() { - if (sInstance) { - for (auto sound : navigationSounds) { - AudioManager::getInstance()->unregisterSound(sound); - sound->deinit(); - } - navigationSounds.clear(); - delete sInstance; + for (auto sound : mNavigationSounds) { + AudioManager::getInstance().unregisterSound(sound); + sound->deinit(); } - - sInstance = nullptr; + mNavigationSounds.clear(); } -void NavigationSounds::loadThemeNavigationSounds(const std::shared_ptr& theme) +void NavigationSounds::loadThemeNavigationSounds(ThemeData* const theme) { if (theme) { LOG(LogDebug) << "NavigationSounds::loadThemeNavigationSounds(): " @@ -218,21 +209,21 @@ void NavigationSounds::loadThemeNavigationSounds(const std::shared_ptrnavigationSounds[soundID]->play(); + NavigationSounds::getInstance().mNavigationSounds[soundID]->play(); } bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID) { - return NavigationSounds::getInstance()->navigationSounds[soundID]->isPlaying(); + return NavigationSounds::getInstance().mNavigationSounds[soundID]->isPlaying(); } diff --git a/es-core/src/Sound.h b/es-core/src/Sound.h index cbc4b88af..394a425b6 100644 --- a/es-core/src/Sound.h +++ b/es-core/src/Sound.h @@ -30,7 +30,7 @@ public: void loadFile(const std::string& path); void play(); - bool isPlaying() const { return playing; } + bool isPlaying() const { return mPlaying; } void stop(); const Uint8* getData() const { return mSampleData; } @@ -39,7 +39,7 @@ public: Uint32 getLength() const { return mSampleLength; } static std::shared_ptr get(const std::string& path); - static std::shared_ptr getFromTheme(const std::shared_ptr& theme, + static std::shared_ptr getFromTheme(ThemeData* const theme, const std::string& view, const std::string& elem); @@ -52,7 +52,7 @@ private: Uint8* mSampleData; Uint32 mSamplePos; Uint32 mSampleLength; - bool playing; + bool mPlaying; }; enum NavigationSoundsID { @@ -68,16 +68,15 @@ enum NavigationSoundsID { class NavigationSounds { public: - static NavigationSounds* getInstance(); + static NavigationSounds& getInstance(); void deinit(); - void loadThemeNavigationSounds(const std::shared_ptr& theme); + void loadThemeNavigationSounds(ThemeData* const theme); void playThemeNavigationSound(NavigationSoundsID soundID); bool isPlayingThemeNavigationSound(NavigationSoundsID soundID); private: - static NavigationSounds* sInstance; - std::vector> navigationSounds; + std::vector> mNavigationSounds; }; #endif // ES_CORE_SOUND_H diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp index 68edeee2a..9e68784bc 100644 --- a/es-core/src/Window.cpp +++ b/es-core/src/Window.cpp @@ -15,7 +15,6 @@ #if defined(BUILD_VLC_PLAYER) #include "components/VideoVlcComponent.h" #endif -#include "AudioManager.h" #include "InputManager.h" #include "Log.h" #include "Sound.h" @@ -219,7 +218,7 @@ void Window::input(InputConfig* config, Input input) else if (config->isMappedTo("y", input) && input.value != 0) { // Jump to the game in its gamelist, but do not launch it. stopScreensaver(); - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); mScreensaver->goToGame(); // To force handling the wake up process. mSleeping = true; diff --git a/es-core/src/components/TextListComponent.h b/es-core/src/components/TextListComponent.h index 4d054aac0..7ef3632b3 100644 --- a/es-core/src/components/TextListComponent.h +++ b/es-core/src/components/TextListComponent.h @@ -102,8 +102,8 @@ public: protected: virtual void onScroll() override { - if (!NavigationSounds::getInstance()->isPlayingThemeNavigationSound(SCROLLSOUND)) - NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); + if (!NavigationSounds::getInstance().isPlayingThemeNavigationSound(SCROLLSOUND)) + NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND); } virtual void onCursorChanged(const CursorState& state) override; diff --git a/es-core/src/components/VideoComponent.cpp b/es-core/src/components/VideoComponent.cpp index 27de9c4de..cf93fcdab 100644 --- a/es-core/src/components/VideoComponent.cpp +++ b/es-core/src/components/VideoComponent.cpp @@ -90,9 +90,12 @@ void VideoComponent::setImage(std::string path) void VideoComponent::onShow() { + std::unique_lock playerLock(mPlayerMutex); mBlockPlayer = false; mPause = false; mShowing = true; + playerLock.unlock(); + manageState(); } @@ -110,22 +113,31 @@ void VideoComponent::onStopVideo() void VideoComponent::onPauseVideo() { + std::unique_lock playerLock(mPlayerMutex); mBlockPlayer = true; mPause = true; + playerLock.unlock(); + manageState(); } void VideoComponent::onUnpauseVideo() { + std::unique_lock playerLock(mPlayerMutex); mBlockPlayer = false; mPause = false; + playerLock.unlock(); + manageState(); } void VideoComponent::onScreensaverActivate() { + std::unique_lock playerLock(mPlayerMutex); mBlockPlayer = true; mPause = true; + playerLock.unlock(); + if (Settings::getInstance()->getString("ScreensaverType") == "dim") stopVideo(); else @@ -158,15 +170,20 @@ void VideoComponent::onGameLaunchedDeactivate() void VideoComponent::topWindow(bool isTop) { if (isTop) { + std::unique_lock playerLock(mPlayerMutex); mBlockPlayer = false; mPause = false; + playerLock.unlock(); + // Stop video when closing the menu to force a reload of the // static image (if the theme is configured as such). stopVideo(); } else { + std::unique_lock playerLock(mPlayerMutex); mBlockPlayer = true; mPause = true; + playerLock.unlock(); } manageState(); } diff --git a/es-core/src/components/VideoComponent.h b/es-core/src/components/VideoComponent.h index 2c14e0d0d..f363b7ccd 100644 --- a/es-core/src/components/VideoComponent.h +++ b/es-core/src/components/VideoComponent.h @@ -12,6 +12,7 @@ #include "GuiComponent.h" #include "components/ImageComponent.h" +#include #include class MediaViewer; @@ -109,6 +110,7 @@ private: protected: Window* mWindow; ImageComponent mStaticImage; + std::mutex mPlayerMutex; unsigned mVideoWidth; unsigned mVideoHeight; diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp index 3cbf4bbf0..20f5189a1 100644 --- a/es-core/src/components/VideoFFmpegComponent.cpp +++ b/es-core/src/components/VideoFFmpegComponent.cpp @@ -130,7 +130,10 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) if (mIsPlaying && mFormatContext) { unsigned int color; - if (mDecodedFrame && mFadeIn < 1) { + std::unique_lock pictureLock(mPictureMutex); + bool decodedFrame = mDecodedFrame; + pictureLock.unlock(); + if (decodedFrame && mFadeIn < 1) { const unsigned int fadeIn = static_cast(mFadeIn * 255.0f); color = Renderer::convertRGBAToABGR((fadeIn << 24) | (fadeIn << 16) | (fadeIn << 8) | 255); @@ -159,11 +162,13 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) for (int i = 0; i < 4; i++) vertices[i].pos = glm::round(vertices[i].pos); - // This is needed to avoid a slight gap before the video starts playing. - if (!mDecodedFrame) - return; + pictureLock.lock(); - mPictureMutex.lock(); + // This is needed to avoid a slight gap before the video starts playing. + if (!mDecodedFrame) { + pictureLock.unlock(); + return; + } if (!mOutputPicture.hasBeenRendered) { // Move the contents of mOutputPicture to a temporary vector in order to call @@ -188,7 +193,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) mOutputPicture.hasBeenRendered = true; } - mPictureMutex.unlock(); + pictureLock.unlock(); if (pictureSize > 0) { // Build a texture for the video frame. @@ -196,7 +201,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans) } } else { - mPictureMutex.unlock(); + pictureLock.unlock(); } mTexture->bind(); @@ -225,13 +230,12 @@ void VideoFFmpegComponent::updatePlayer() return; // Output any audio that has been added by the processing thread. - mAudioMutex.lock(); + std::unique_lock audioLock(mAudioMutex); if (mOutputAudio.size()) { - AudioManager::getInstance()->processStream(&mOutputAudio.at(0), - static_cast(mOutputAudio.size())); + AudioManager::getInstance().processStream(&mOutputAudio.at(0), + static_cast(mOutputAudio.size())); mOutputAudio.clear(); } - mAudioMutex.unlock(); if (mIsActuallyPlaying && mStartTimeAccumulation) { mAccumulatedTime += @@ -243,8 +247,10 @@ void VideoFFmpegComponent::updatePlayer() mTimeReference = std::chrono::high_resolution_clock::now(); + audioLock.unlock(); + if (!mFrameProcessingThread) { - AudioManager::getInstance()->unmuteStream(); + AudioManager::getInstance().unmuteStream(); mFrameProcessingThread = std::make_unique(&VideoFFmpegComponent::frameProcessing, this); } @@ -262,15 +268,19 @@ void VideoFFmpegComponent::frameProcessing() if (mAudioCodecContext) audioFilter = setupAudioFilters(); - while (mIsPlaying && !mPause && videoFilter && (!mAudioCodecContext || audioFilter)) { + bool isPlaying = true; + bool pause = false; + + while (isPlaying && !pause && videoFilter && (!mAudioCodecContext || audioFilter)) { readFrames(); - if (!mIsPlaying) - break; getProcessedFrames(); - if (!mIsPlaying) - break; outputFrames(); + std::unique_lock playerLock(mPlayerMutex); + isPlaying = mIsPlaying; + pause = mPause; + playerLock.unlock(); + // This 1 ms wait makes sure that the thread does not consume all available CPU cycles. SDL_Delay(1); } @@ -428,7 +438,7 @@ bool VideoFFmpegComponent::setupAudioFilters() { int returnValue = 0; char errorMessage[512]; - const int outSampleRates[] = {AudioManager::getInstance()->sAudioFormat.freq, -1}; + const int outSampleRates[] = {AudioManager::getInstance().sAudioFormat.freq, -1}; const enum AVSampleFormat outSampleFormats[] = {AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_NONE}; mAFilterInputs = avfilter_inout_alloc(); @@ -662,8 +672,10 @@ void VideoFFmpegComponent::readFrames() } } - if (readFrameReturn < 0) + if (readFrameReturn < 0) { + std::unique_lock playerLock(mPlayerMutex); mEndOfVideo = true; + } } void VideoFFmpegComponent::getProcessedFrames() @@ -739,6 +751,7 @@ void VideoFFmpegComponent::outputFrames() // there is an audio track. if (!mAudioCodecContext || (mAudioCodecContext && !mAudioFrameQueue.empty())) { if (!mStartTimeAccumulation) { + std::unique_lock audioLock(mAudioMutex); mTimeReference = std::chrono::high_resolution_clock::now(); mStartTimeAccumulation = true; mIsActuallyPlaying = true; @@ -748,7 +761,11 @@ void VideoFFmpegComponent::outputFrames() // Process the audio frames that have a PTS value below mAccumulatedTime (plus a small // buffer to avoid underflows). while (!mAudioFrameQueue.empty()) { - if (mAudioFrameQueue.front().pts < mAccumulatedTime + AUDIO_BUFFER) { + std::unique_lock audioLock(mAudioMutex); + auto accumulatedTime = mAccumulatedTime; + audioLock.unlock(); + + if (mAudioFrameQueue.front().pts < accumulatedTime + AUDIO_BUFFER) { // Enable only when needed, as this generates a lot of debug output. if (DEBUG_VIDEO) { LOG(LogDebug) << "Processing audio frame with PTS: " @@ -770,13 +787,13 @@ void VideoFFmpegComponent::outputFrames() if (outputSound) { // The audio is output to AudioManager from updatePlayer() in the main thread. - mAudioMutex.lock(); + audioLock.lock(); mOutputAudio.insert(mOutputAudio.end(), mAudioFrameQueue.front().resampledData.begin(), mAudioFrameQueue.front().resampledData.end()); - mAudioMutex.unlock(); + audioLock.unlock(); } mAudioFrameQueue.pop(); mAudioFrameCount++; @@ -786,11 +803,19 @@ void VideoFFmpegComponent::outputFrames() } } + std::unique_lock playerLock(mPlayerMutex); + bool isActuallyPlaying = mIsActuallyPlaying; + playerLock.unlock(); + // Process all available video frames that have a PTS value below mAccumulatedTime. // But if more than one frame is processed here, it means that the computer can't // keep up for some reason. - while (mIsActuallyPlaying && !mVideoFrameQueue.empty()) { - if (mVideoFrameQueue.front().pts < mAccumulatedTime) { + while (isActuallyPlaying && !mVideoFrameQueue.empty()) { + std::unique_lock audioLock(mAudioMutex); + double accumulatedTime = mAccumulatedTime; + audioLock.unlock(); + + if (mVideoFrameQueue.front().pts < accumulatedTime) { // Enable only when needed, as this generates a lot of debug output. if (DEBUG_VIDEO) { LOG(LogDebug) << "Processing video frame with PTS: " @@ -808,7 +833,7 @@ void VideoFFmpegComponent::outputFrames() } } - mPictureMutex.lock(); + std::unique_lock pictureLock(mPictureMutex); // Give some leeway for frames that have not yet been rendered but that have pts // values with a time difference relative to the frame duration that is under a @@ -818,10 +843,10 @@ void VideoFFmpegComponent::outputFrames() // can't keep up. This approach primarily decreases stuttering for videos with frame // rates close to, or at, the rendering frame rate, for example 59.94 and 60 FPS. if (mDecodedFrame && !mOutputPicture.hasBeenRendered) { - double timeDifference = mAccumulatedTime - mVideoFrameQueue.front().pts - + double timeDifference = accumulatedTime - mVideoFrameQueue.front().pts - mVideoFrameQueue.front().frameDuration * 2.0l; if (timeDifference < mVideoFrameQueue.front().frameDuration) { - mPictureMutex.unlock(); + pictureLock.unlock(); break; } } @@ -835,12 +860,12 @@ void VideoFFmpegComponent::outputFrames() mOutputPicture.height = mVideoFrameQueue.front().height; mOutputPicture.hasBeenRendered = false; - mPictureMutex.unlock(); + mDecodedFrame = true; + + pictureLock.unlock(); mVideoFrameQueue.pop(); mVideoFrameCount++; - - mDecodedFrame = true; } else { break; @@ -1383,16 +1408,18 @@ void VideoFFmpegComponent::startVideo() void VideoFFmpegComponent::stopVideo() { + std::unique_lock playerLock(mPlayerMutex); mIsPlaying = false; mIsActuallyPlaying = false; mStartDelayed = false; mPause = false; mEndOfVideo = false; + playerLock.unlock(); mTexture.reset(); if (mFrameProcessingThread) { if (mWindow->getVideoPlayerCount() == 0) - AudioManager::getInstance()->muteStream(); + AudioManager::getInstance().muteStream(); // Wait for the thread execution to complete. mFrameProcessingThread->join(); mFrameProcessingThread.reset(); @@ -1404,7 +1431,7 @@ void VideoFFmpegComponent::stopVideo() std::queue().swap(mAudioFrameQueue); // Clear the audio buffer. - AudioManager::getInstance()->clearStream(); + AudioManager::getInstance().clearStream(); if (mFormatContext) { av_frame_free(&mVideoFrame); @@ -1427,12 +1454,16 @@ void VideoFFmpegComponent::stopVideo() void VideoFFmpegComponent::pauseVideo() { if (mPause && mWindow->getVideoPlayerCount() == 0) - AudioManager::getInstance()->muteStream(); + AudioManager::getInstance().muteStream(); } void VideoFFmpegComponent::handleLooping() { - if (mIsPlaying && mEndOfVideo) { + std::unique_lock playerLock(mPlayerMutex); + bool endOfVideo = mEndOfVideo; + playerLock.unlock(); + + if (mIsPlaying && endOfVideo) { // If the screensaver video swap time is set to 0, it means we should // skip to the next game when the video has finished playing. if (mScreensaverMode &&