Multiple thread safety improvements to AudioManager and VideoFFmpegComponent.

Also some general refactoring and re-enabling of some SDL_AudioStream functions.
This commit is contained in:
Leon Styhre 2021-11-15 22:43:06 +01:00
parent b742951dc0
commit 6bc4a09c9b
17 changed files with 206 additions and 173 deletions

View file

@ -12,7 +12,6 @@
#if defined(BUILD_VLC_PLAYER) #if defined(BUILD_VLC_PLAYER)
#include "components/VideoVlcComponent.h" #include "components/VideoVlcComponent.h"
#endif #endif
#include "AudioManager.h"
#include "Sound.h" #include "Sound.h"
#include "views/ViewController.h" #include "views/ViewController.h"
@ -59,7 +58,7 @@ bool MediaViewer::startMediaViewer(FileData* game)
void MediaViewer::stopMediaViewer() void MediaViewer::stopMediaViewer()
{ {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
ViewController::get()->onStopVideo(); ViewController::get()->onStopVideo();
if (mVideo) { if (mVideo) {
@ -197,7 +196,7 @@ void MediaViewer::findMedia()
void MediaViewer::showNext() void MediaViewer::showNext()
{ {
if (mHasImages && mCurrentImageIndex != static_cast<int>(mImageFiles.size()) - 1) if (mHasImages && mCurrentImageIndex != static_cast<int>(mImageFiles.size()) - 1)
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
bool showedVideo = false; bool showedVideo = false;
@ -229,7 +228,7 @@ void MediaViewer::showNext()
void MediaViewer::showPrevious() void MediaViewer::showPrevious()
{ {
if ((mHasVideo && mDisplayingImage) || (!mHasVideo && mCurrentImageIndex != 0)) if ((mHasVideo && mDisplayingImage) || (!mHasVideo && mCurrentImageIndex != 0))
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
if (mCurrentImageIndex == 0 && !mHasVideo) { if (mCurrentImageIndex == 0 && !mHasVideo) {
return; return;

View file

@ -310,7 +310,7 @@ GuiGamelistOptions::~GuiGamelistOptions()
} }
if (mSystem->getRootFolder()->getChildren().size() != 0 && mSystem->getName() != "recent") if (mSystem->getRootFolder()->getChildren().size() != 0 && mSystem->getName() != "recent")
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
} }
void GuiGamelistOptions::openGamelistFilter() void GuiGamelistOptions::openGamelistFilter()
@ -356,7 +356,7 @@ void GuiGamelistOptions::startEditMode()
} }
if (mSystem->getRootFolder()->getChildren().size() == 0) if (mSystem->getRootFolder()->getChildren().size() == 0)
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
delete this; delete this;
} }
@ -364,7 +364,7 @@ void GuiGamelistOptions::exitEditMode()
{ {
CollectionSystemsManager::get()->exitEditMode(); CollectionSystemsManager::get()->exitEditMode();
if (mSystem->getRootFolder()->getChildren().size() == 0) if (mSystem->getRootFolder()->getChildren().size() == 0)
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
delete this; delete this;
} }

View file

@ -704,8 +704,7 @@ int main(int argc, char* argv[])
MameNames::deinit(); MameNames::deinit();
CollectionSystemsManager::deinit(); CollectionSystemsManager::deinit();
SystemData::deleteSystems(); SystemData::deleteSystems();
NavigationSounds::getInstance()->deinit(); NavigationSounds::getInstance().deinit();
Settings::deinit();
#if defined(FREEIMAGE_LIB) #if defined(FREEIMAGE_LIB)
// Call this ONLY when linking with FreeImage as a static library. // Call this ONLY when linking with FreeImage as a static library.

View file

@ -284,14 +284,14 @@ bool SystemView::input(InputConfig* config, Input input)
if (config->isMappedTo("a", input)) { if (config->isMappedTo("a", input)) {
stopScrolling(); stopScrolling();
ViewController::get()->goToGameList(getSelected()); ViewController::get()->goToGameList(getSelected());
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND);
return true; return true;
} }
if (Settings::getInstance()->getBool("RandomAddButton") && if (Settings::getInstance()->getBool("RandomAddButton") &&
(config->isMappedTo("leftthumbstickclick", input) || (config->isMappedTo("leftthumbstickclick", input) ||
config->isMappedTo("rightthumbstickclick", input))) { config->isMappedTo("rightthumbstickclick", input))) {
// Get a random system and jump to it. // Get a random system and jump to it.
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND); NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND);
setCursor(SystemData::getRandomSystem(getSelected())); setCursor(SystemData::getRandomSystem(getSelected()));
return true; return true;
} }

View file

@ -77,7 +77,7 @@ protected:
void onCursorChanged(const CursorState& state) override; void onCursorChanged(const CursorState& state) override;
virtual void onScroll() override virtual void onScroll() override
{ {
NavigationSounds::getInstance()->playThemeNavigationSound(SYSTEMBROWSESOUND); NavigationSounds::getInstance().playThemeNavigationSound(SYSTEMBROWSESOUND);
} }
private: private:

View file

@ -12,7 +12,6 @@
#include "views/ViewController.h" #include "views/ViewController.h"
#include "AudioManager.h"
#include "FileFilterIndex.h" #include "FileFilterIndex.h"
#include "InputManager.h" #include "InputManager.h"
#include "Log.h" #include "Log.h"
@ -413,7 +412,7 @@ void ViewController::goToNextGameList()
assert(mState.viewing == GAME_LIST); assert(mState.viewing == GAME_LIST);
SystemData* system = getState().getSystem(); SystemData* system = getState().getSystem();
assert(system); assert(system);
NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND); NavigationSounds::getInstance().playThemeNavigationSound(QUICKSYSSELECTSOUND);
mNextSystem = true; mNextSystem = true;
goToGameList(system->getNext()); goToGameList(system->getNext());
} }
@ -423,7 +422,7 @@ void ViewController::goToPrevGameList()
assert(mState.viewing == GAME_LIST); assert(mState.viewing == GAME_LIST);
SystemData* system = getState().getSystem(); SystemData* system = getState().getSystem();
assert(system); assert(system);
NavigationSounds::getInstance()->playThemeNavigationSound(QUICKSYSSELECTSOUND); NavigationSounds::getInstance().playThemeNavigationSound(QUICKSYSSELECTSOUND);
mNextSystem = false; mNextSystem = false;
goToGameList(system->getPrev()); goToGameList(system->getPrev());
} }
@ -717,7 +716,7 @@ void ViewController::launch(FileData* game)
if (durationString != "disabled") if (durationString != "disabled")
mWindow->displayLaunchScreen(game->getSourceFileData()); 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 // 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. // 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; bool themeSoundSupport = false;
for (SystemData* system : SystemData::sSystemVector) { for (SystemData* system : SystemData::sSystemVector) {
if (system->getTheme()->hasView("all")) { if (system->getTheme()->hasView("all")) {
NavigationSounds::getInstance()->loadThemeNavigationSounds(system->getTheme()); NavigationSounds::getInstance().loadThemeNavigationSounds(system->getTheme().get());
themeSoundSupport = true; themeSoundSupport = true;
break; break;
} }
} }
if (!SystemData::sSystemVector.empty() && !themeSoundSupport) if (!SystemData::sSystemVector.empty() && !themeSoundSupport)
NavigationSounds::getInstance()->loadThemeNavigationSounds(nullptr); NavigationSounds::getInstance().loadThemeNavigationSounds(nullptr);
} }
void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme) 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 // Load navigation sounds, either from the theme if it supports it, or otherwise from
// the bundled fallback sound files. // the bundled fallback sound files.
NavigationSounds::getInstance()->deinit(); NavigationSounds::getInstance().deinit();
bool themeSoundSupport = false; bool themeSoundSupport = false;
for (SystemData* system : SystemData::sSystemVector) { for (SystemData* system : SystemData::sSystemVector) {
if (system->getTheme()->hasView("all")) { if (system->getTheme()->hasView("all")) {
NavigationSounds::getInstance()->loadThemeNavigationSounds(system->getTheme()); NavigationSounds::getInstance().loadThemeNavigationSounds(system->getTheme().get());
themeSoundSupport = true; themeSoundSupport = true;
break; break;
} }
} }
if (!SystemData::sSystemVector.empty() && !themeSoundSupport) if (!SystemData::sSystemVector.empty() && !themeSoundSupport)
NavigationSounds::getInstance()->loadThemeNavigationSounds(nullptr); NavigationSounds::getInstance().loadThemeNavigationSounds(nullptr);
mCurrentView->onShow(); mCurrentView->onShow();
updateHelpPrompts(); updateHelpPrompts();

View file

@ -168,15 +168,15 @@ bool GridGameListView::input(InputConfig* config, Input input)
if (input.value == 0 && if (input.value == 0 &&
(config->isMappedLike("left", input) || config->isMappedLike("right", input) || (config->isMappedLike("left", input) || config->isMappedLike("right", input) ||
(config->isMappedLike("up", input)) || (config->isMappedLike("down", 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)) { if (input.value != 0 && config->isMappedLike("righttrigger", input)) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mGrid.setCursor(mGrid.getLast()); mGrid.setCursor(mGrid.getLast());
} }
if (input.value != 0 && config->isMappedLike("lefttrigger", input)) { if (input.value != 0 && config->isMappedLike("lefttrigger", input)) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mGrid.setCursor(mGrid.getFirst()); mGrid.setCursor(mGrid.getFirst());
} }

View file

@ -114,7 +114,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
// It's a folder. // It's a folder.
if (cursor->getChildren().size() > 0) { if (cursor->getChildren().size() > 0) {
ViewController::get()->cancelViewTransitions(); ViewController::get()->cancelViewTransitions();
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND);
mCursorStack.push(cursor); mCursorStack.push(cursor);
populateList(cursor->getChildrenListToDisplay(), cursor); populateList(cursor->getChildrenListToDisplay(), cursor);
@ -140,7 +140,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
updateHelpPrompts(); updateHelpPrompts();
} }
else { else {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
} }
} }
@ -151,7 +151,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
if (mCursorStack.size()) { if (mCursorStack.size()) {
// Save the position to the cursor stack history. // Save the position to the cursor stack history.
mCursorStackHistory.push_back(getCursor()); mCursorStackHistory.push_back(getCursor());
NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND); NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND);
populateList(mCursorStack.top()->getParent()->getChildrenListToDisplay(), populateList(mCursorStack.top()->getParent()->getChildrenListToDisplay(),
mCursorStack.top()->getParent()); mCursorStack.top()->getParent());
setCursor(mCursorStack.top()); setCursor(mCursorStack.top());
@ -161,7 +161,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
updateHelpPrompts(); updateHelpPrompts();
} }
else { else {
NavigationSounds::getInstance()->playThemeNavigationSound(BACKSOUND); NavigationSounds::getInstance().playThemeNavigationSound(BACKSOUND);
onPauseVideo(); onPauseVideo();
onFocusLost(); onFocusLost();
stopListScrolling(); stopListScrolling();
@ -178,14 +178,14 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
} }
else if (config->isMappedTo("x", input)) { else if (config->isMappedTo("x", input)) {
if (getCursor()->getType() == PLACEHOLDER) { if (getCursor()->getType() == PLACEHOLDER) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
return true; return true;
} }
else if (config->isMappedTo("x", input) && else if (config->isMappedTo("x", input) &&
mRoot->getSystem()->getThemeFolder() == "custom-collections" && mRoot->getSystem()->getThemeFolder() == "custom-collections" &&
mCursorStack.empty() && mCursorStack.empty() &&
ViewController::get()->getState().viewing == ViewController::GAME_LIST) { ViewController::get()->getState().viewing == ViewController::GAME_LIST) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
// Jump to the randomly selected game. // Jump to the randomly selected game.
if (mRandomGame) { if (mRandomGame) {
stopListScrolling(); stopListScrolling();
@ -197,7 +197,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
else if (mRoot->getSystem()->isGameSystem()) { else if (mRoot->getSystem()->isGameSystem()) {
stopListScrolling(); stopListScrolling();
ViewController::get()->cancelViewTransitions(); ViewController::get()->cancelViewTransitions();
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mWindow->startMediaViewer(getCursor()); mWindow->startMediaViewer(getCursor());
return true; return true;
} }
@ -228,7 +228,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER) { if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER) {
stopListScrolling(); stopListScrolling();
// Jump to a random game. // Jump to a random game.
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
FileData* randomGame = getCursor()->getSystem()->getRandomGame(getCursor()); FileData* randomGame = getCursor()->getSystem()->getRandomGame(getCursor());
if (randomGame) if (randomGame)
setCursor(randomGame); setCursor(randomGame);
@ -241,7 +241,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
ViewController::get()->getState().viewing == ViewController::GAME_LIST) { ViewController::get()->getState().viewing == ViewController::GAME_LIST) {
// Jump to the randomly selected game. // Jump to the randomly selected game.
if (mRandomGame) { if (mRandomGame) {
NavigationSounds::getInstance()->playThemeNavigationSound(SELECTSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SELECTSOUND);
// If there is already an mCursorStackHistory entry for the collection, then // If there is already an mCursorStackHistory entry for the collection, then
// remove it so we don't get multiple entries. // remove it so we don't get multiple entries.
std::vector<FileData*> listEntries = std::vector<FileData*> listEntries =
@ -257,7 +257,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
updateHelpPrompts(); updateHelpPrompts();
} }
else { else {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
} }
} }
else if (config->isMappedTo("y", input) && else if (config->isMappedTo("y", input) &&
@ -272,19 +272,19 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
if (CollectionSystemsManager::get()->isEditing() && if (CollectionSystemsManager::get()->isEditing() &&
mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER && mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER &&
getCursor()->getParent()->getPath() == "collections") { getCursor()->getParent()->getPath() == "collections") {
NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND);
mWindow->queueInfoPopup("CAN'T ADD CUSTOM COLLECTIONS TO CUSTOM COLLECTIONS", 4000); mWindow->queueInfoPopup("CAN'T ADD CUSTOM COLLECTIONS TO CUSTOM COLLECTIONS", 4000);
} }
// Notify the user if attempting to add a placeholder to a custom collection. // Notify the user if attempting to add a placeholder to a custom collection.
if (CollectionSystemsManager::get()->isEditing() && if (CollectionSystemsManager::get()->isEditing() &&
mRoot->getSystem()->isGameSystem() && getCursor()->getType() == PLACEHOLDER) { mRoot->getSystem()->isGameSystem() && getCursor()->getType() == PLACEHOLDER) {
NavigationSounds::getInstance()->playThemeNavigationSound(FAVORITESOUND); NavigationSounds::getInstance().playThemeNavigationSound(FAVORITESOUND);
mWindow->queueInfoPopup("CAN'T ADD PLACEHOLDERS TO CUSTOM COLLECTIONS", 4000); mWindow->queueInfoPopup("CAN'T ADD PLACEHOLDERS TO CUSTOM COLLECTIONS", 4000);
} }
else if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER && else if (mRoot->getSystem()->isGameSystem() && getCursor()->getType() != PLACEHOLDER &&
getCursor()->getParent()->getPath() != "collections") { getCursor()->getParent()->getPath() != "collections") {
if (getCursor()->getType() == GAME || getCursor()->getType() == FOLDER) 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 // 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 // it gets after the gamelist sorting. Instead retain the cursor position in the
// list using the logic below. // list using the logic below.
@ -474,7 +474,7 @@ bool ISimpleGameListView::input(InputConfig* config, Input input)
} }
} }
else if (config->isMappedTo("y", input) && getCursor()->isPlaceHolder()) { else if (config->isMappedTo("y", input) && getCursor()->isPlaceHolder()) {
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
} }
} }
} }

View file

@ -14,16 +14,6 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
std::shared_ptr<AudioManager> AudioManager::sInstance;
std::vector<std::shared_ptr<Sound>> 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() AudioManager::AudioManager()
{ {
// Init on construction. // Init on construction.
@ -36,13 +26,10 @@ AudioManager::~AudioManager()
deinit(); deinit();
} }
std::shared_ptr<AudioManager>& AudioManager::getInstance() AudioManager& AudioManager::getInstance()
{ {
// Check if an AudioManager instance is already created, and if not then create it. static AudioManager instance;
if (sInstance == nullptr) return instance;
sInstance = std::shared_ptr<AudioManager>(new AudioManager);
return sInstance;
} }
void AudioManager::init() void AudioManager::init()
@ -126,15 +113,12 @@ void AudioManager::init()
void AudioManager::deinit() void AudioManager::deinit()
{ {
// Due to bugs in SDL, freeing the stream causes random crashes. This is reported to the SDL_LockAudioDevice(sAudioDevice);
// user on some operating systems such as macOS, and it's annoying to have a crash at the SDL_FreeAudioStream(sConversionStream);
// end of debugging session. So we'll simply disable the function until it has been properly SDL_UnlockAudioDevice(sAudioDevice);
// fixed in the SDL library.
// SDL_FreeAudioStream(sConversionStream);
SDL_CloseAudio(); SDL_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
sInstance = nullptr;
} }
void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len) void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len)
@ -172,13 +156,10 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len)
soundIt++; soundIt++;
} }
int streamLength = 0;
// Process video stream audio generated by VideoFFmpegComponent. // Process video stream audio generated by VideoFFmpegComponent.
if (!mIsClearingStream) int streamLength = SDL_AudioStreamAvailable(sConversionStream);
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 nothing is playing, pause the device until there is more audio to output.
if (!stillPlaying) if (!stillPlaying)
SDL_PauseAudioDevice(sAudioDevice, 1); 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 // 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 // be a short time period when the audio would keep playing after the video was stopped
// and before the stream was cleared in clearStream(). // and before the stream was cleared in clearStream().
if (sMuteStream) { std::unique_lock<std::mutex> audioLock{mAudioLock};
bool muteStream = sMuteStream;
audioLock.unlock();
if (muteStream) {
SDL_MixAudioFormat(stream, &converted.at(0), sAudioFormat.format, processedLength, 0); SDL_MixAudioFormat(stream, &converted.at(0), sAudioFormat.format, processedLength, 0);
} }
else { else {
@ -227,13 +211,13 @@ void AudioManager::mixAudio(void* /*unused*/, Uint8* stream, int len)
SDL_PauseAudioDevice(sAudioDevice, 1); SDL_PauseAudioDevice(sAudioDevice, 1);
} }
void AudioManager::registerSound(std::shared_ptr<Sound>& sound) void AudioManager::registerSound(std::shared_ptr<Sound> sound)
{ {
// Add sound to sound vector. // Add sound to sound vector.
sSoundVector.push_back(sound); sSoundVector.push_back(sound);
} }
void AudioManager::unregisterSound(std::shared_ptr<Sound>& sound) void AudioManager::unregisterSound(std::shared_ptr<Sound> sound)
{ {
for (unsigned int i = 0; i < sSoundVector.size(); i++) { for (unsigned int i = 0; i < sSoundVector.size(); i++) {
if (sSoundVector.at(i) == sound) { if (sSoundVector.at(i) == sound) {
@ -286,32 +270,36 @@ void AudioManager::setupAudioStream(int sampleRate)
void AudioManager::processStream(const void* samples, unsigned count) void AudioManager::processStream(const void* samples, unsigned count)
{ {
if (mIsClearingStream) SDL_LockAudioDevice(sAudioDevice);
return;
if (SDL_AudioStreamPut(sConversionStream, samples, count * sizeof(Uint8)) == -1) { if (SDL_AudioStreamPut(sConversionStream, samples, count * sizeof(Uint8)) == -1) {
LOG(LogError) << "Failed to put samples in the conversion stream:"; LOG(LogError) << "Failed to put samples in the conversion stream:";
LOG(LogError) << SDL_GetError(); LOG(LogError) << SDL_GetError();
SDL_UnlockAudioDevice(sAudioDevice);
return; return;
} }
if (count > 0) if (count > 0)
SDL_PauseAudioDevice(sAudioDevice, 0); SDL_PauseAudioDevice(sAudioDevice, 0);
SDL_UnlockAudioDevice(sAudioDevice);
} }
void AudioManager::clearStream() void AudioManager::clearStream()
{ {
// The SDL_AudioStreamClear() function is unstable and causes random crashes, so SDL_LockAudioDevice(sAudioDevice);
// we have to implement a workaround instead where SDL_AudioStreamGet() is used SDL_AudioStreamClear(sConversionStream);
// to empty the stream. SDL_UnlockAudioDevice(sAudioDevice);
// SDL_AudioStreamClear(sConversionStream);
// 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 // 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. // to clear the stream as this could lead to a crash.
if (sSoundVector.empty()) if (sSoundVector.empty())
return; return;
mIsClearingStream = true; SDL_LockAudioDevice(sAudioDevice);
// This code is required as there's seemingly a bug in SDL_AudioStreamAvailable(). // 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 // 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 // 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 // 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 // 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<Uint8> writeBuffer(10000); std::vector<Uint8> writeBuffer(10000);
if (SDL_AudioStreamPut(sConversionStream, reinterpret_cast<const void*>(&writeBuffer.at(0)), if (SDL_AudioStreamPut(sConversionStream, reinterpret_cast<const void*>(&writeBuffer.at(0)),
10000) == -1) { 10000) == -1) {
LOG(LogError) << "Failed to put samples in the conversion stream:"; LOG(LogError) << "Failed to put samples in the conversion stream:";
LOG(LogError) << SDL_GetError(); LOG(LogError) << SDL_GetError();
mIsClearingStream = false; SDL_UnlockAudioDevice(sAudioDevice);
return; return;
} }
@ -338,5 +326,5 @@ void AudioManager::clearStream()
std::vector<Uint8> readBuffer(length); std::vector<Uint8> readBuffer(length);
SDL_AudioStreamGet(sConversionStream, static_cast<void*>(&readBuffer.at(0)), length); SDL_AudioStreamGet(sConversionStream, static_cast<void*>(&readBuffer.at(0)), length);
mIsClearingStream = false; SDL_UnlockAudioDevice(sAudioDevice);
} }

View file

@ -11,6 +11,7 @@
#include <SDL2/SDL_audio.h> #include <SDL2/SDL_audio.h>
#include <memory> #include <memory>
#include <mutex>
#include <vector> #include <vector>
class Sound; class Sound;
@ -19,13 +20,13 @@ class AudioManager
{ {
public: public:
virtual ~AudioManager(); virtual ~AudioManager();
static std::shared_ptr<AudioManager>& getInstance(); static AudioManager& getInstance();
void init(); void init();
void deinit(); void deinit();
void registerSound(std::shared_ptr<Sound>& sound); void registerSound(std::shared_ptr<Sound> sound);
void unregisterSound(std::shared_ptr<Sound>& sound); void unregisterSound(std::shared_ptr<Sound> sound);
void play(); void play();
void stop(); void stop();
@ -35,25 +36,33 @@ public:
void processStream(const void* samples, unsigned count); void processStream(const void* samples, unsigned count);
void clearStream(); void clearStream();
void muteStream() { sMuteStream = true; } void muteStream()
void unmuteStream() { sMuteStream = false; } {
std::unique_lock<std::mutex> audioLock{mAudioLock};
sMuteStream = true;
}
void unmuteStream()
{
std::unique_lock<std::mutex> audioLock{mAudioLock};
sMuteStream = false;
}
bool getHasAudioDevice() { return sHasAudioDevice; } bool getHasAudioDevice() { return sHasAudioDevice; }
static SDL_AudioDeviceID sAudioDevice; inline static SDL_AudioDeviceID sAudioDevice = 0;
static SDL_AudioSpec sAudioFormat; inline static SDL_AudioSpec sAudioFormat;
private: private:
AudioManager(); AudioManager();
static void mixAudio(void* unused, Uint8* stream, int len); static void mixAudio(void* unused, Uint8* stream, int len);
static void mixAudio2(void* unused, Uint8* stream, int len); static void mixAudio2(void* unused, Uint8* stream, int len);
static SDL_AudioStream* sConversionStream;
static std::vector<std::shared_ptr<Sound>> sSoundVector; inline static std::mutex mAudioLock;
static std::shared_ptr<AudioManager> sInstance; inline static SDL_AudioStream* sConversionStream;
static bool sMuteStream; inline static std::vector<std::shared_ptr<Sound>> sSoundVector;
static bool sHasAudioDevice; inline static bool sMuteStream = false;
static bool mIsClearingStream; inline static bool sHasAudioDevice = true;
}; };
#endif // ES_CORE_AUDIO_MANAGER_H #endif // ES_CORE_AUDIO_MANAGER_H

View file

@ -15,8 +15,6 @@
#include "ThemeData.h" #include "ThemeData.h"
#include "resources/ResourceManager.h" #include "resources/ResourceManager.h"
NavigationSounds* NavigationSounds::sInstance = nullptr;
std::map<std::string, std::shared_ptr<Sound>> Sound::sMap; std::map<std::string, std::shared_ptr<Sound>> Sound::sMap;
std::shared_ptr<Sound> Sound::get(const std::string& path) std::shared_ptr<Sound> Sound::get(const std::string& path)
@ -26,16 +24,16 @@ std::shared_ptr<Sound> Sound::get(const std::string& path)
return it->second; return it->second;
std::shared_ptr<Sound> sound = std::shared_ptr<Sound>(new Sound(path)); std::shared_ptr<Sound> sound = std::shared_ptr<Sound>(new Sound(path));
AudioManager::getInstance()->registerSound(sound); AudioManager::getInstance().registerSound(sound);
sMap[path] = sound; sMap[path] = sound;
return sound; return sound;
} }
std::shared_ptr<Sound> Sound::getFromTheme(const std::shared_ptr<ThemeData>& theme, std::shared_ptr<Sound> Sound::getFromTheme(ThemeData* const theme,
const std::string& view, const std::string& view,
const std::string& element) const std::string& element)
{ {
if (!theme) { if (theme == nullptr) {
LOG(LogDebug) << "Sound::getFromTheme(): Using fallback sound file for \"" << element LOG(LogDebug) << "Sound::getFromTheme(): Using fallback sound file for \"" << element
<< "\""; << "\"";
return get(ResourceManager::getInstance()->getResourcePath(":/sounds/" + element + ".wav")); return get(ResourceManager::getInstance()->getResourcePath(":/sounds/" + element + ".wav"));
@ -57,7 +55,7 @@ Sound::Sound(const std::string& path)
: mSampleData(nullptr) : mSampleData(nullptr)
, mSamplePos(0) , mSamplePos(0)
, mSampleLength(0) , mSampleLength(0)
, playing(false) , mPlaying(false)
{ {
loadFile(path); loadFile(path);
} }
@ -127,7 +125,7 @@ void Sound::init()
void Sound::deinit() void Sound::deinit()
{ {
playing = false; mPlaying = false;
if (mSampleData != nullptr) { if (mSampleData != nullptr) {
SDL_LockAudioDevice(AudioManager::sAudioDevice); SDL_LockAudioDevice(AudioManager::sAudioDevice);
@ -148,28 +146,28 @@ void Sound::play()
if (!Settings::getInstance()->getBool("NavigationSounds")) if (!Settings::getInstance()->getBool("NavigationSounds"))
return; return;
if (!AudioManager::getInstance()->getHasAudioDevice()) if (!AudioManager::getInstance().getHasAudioDevice())
return; return;
SDL_LockAudioDevice(AudioManager::sAudioDevice); SDL_LockAudioDevice(AudioManager::sAudioDevice);
if (playing) if (mPlaying)
// Replay from start. rewind the sample to the beginning. // Replay from start. rewind the sample to the beginning.
mSamplePos = 0; mSamplePos = 0;
else else
// Flag our sample as playing. // Flag our sample as playing.
playing = true; mPlaying = true;
SDL_UnlockAudioDevice(AudioManager::sAudioDevice); SDL_UnlockAudioDevice(AudioManager::sAudioDevice);
// Tell the AudioManager to start playing samples. // Tell the AudioManager to start playing samples.
AudioManager::getInstance()->play(); AudioManager::getInstance().play();
} }
void Sound::stop() void Sound::stop()
{ {
// Flag our sample as not playing and rewind its position. // Flag our sample as not playing and rewind its position.
SDL_LockAudioDevice(AudioManager::sAudioDevice); SDL_LockAudioDevice(AudioManager::sAudioDevice);
playing = false; mPlaying = false;
mSamplePos = 0; mSamplePos = 0;
SDL_UnlockAudioDevice(AudioManager::sAudioDevice); SDL_UnlockAudioDevice(AudioManager::sAudioDevice);
} }
@ -179,34 +177,27 @@ void Sound::setPosition(Uint32 newPosition)
mSamplePos = newPosition; mSamplePos = newPosition;
if (mSamplePos >= mSampleLength) { if (mSamplePos >= mSampleLength) {
// Got to or beyond the end of the sample. stop playing. // Got to or beyond the end of the sample. stop playing.
playing = false; mPlaying = false;
mSamplePos = 0; mSamplePos = 0;
} }
} }
NavigationSounds* NavigationSounds::getInstance() NavigationSounds& NavigationSounds::getInstance()
{ {
if (sInstance == nullptr) static NavigationSounds instance;
sInstance = new NavigationSounds(); return instance;
return sInstance;
} }
void NavigationSounds::deinit() void NavigationSounds::deinit()
{ {
if (sInstance) { for (auto sound : mNavigationSounds) {
for (auto sound : navigationSounds) { AudioManager::getInstance().unregisterSound(sound);
AudioManager::getInstance()->unregisterSound(sound);
sound->deinit(); sound->deinit();
} }
navigationSounds.clear(); mNavigationSounds.clear();
delete sInstance;
} }
sInstance = nullptr; void NavigationSounds::loadThemeNavigationSounds(ThemeData* const theme)
}
void NavigationSounds::loadThemeNavigationSounds(const std::shared_ptr<ThemeData>& theme)
{ {
if (theme) { if (theme) {
LOG(LogDebug) << "NavigationSounds::loadThemeNavigationSounds(): " LOG(LogDebug) << "NavigationSounds::loadThemeNavigationSounds(): "
@ -218,21 +209,21 @@ void NavigationSounds::loadThemeNavigationSounds(const std::shared_ptr<ThemeData
"Theme set does not include navigation sound support, using fallback sounds"; "Theme set does not include navigation sound support, using fallback sounds";
} }
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowse")); mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowse"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselect")); mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselect"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "select")); mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "select"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "back")); mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "back"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "scroll")); mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "scroll"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "favorite")); mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "favorite"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "launch")); mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "launch"));
} }
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID) void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)
{ {
NavigationSounds::getInstance()->navigationSounds[soundID]->play(); NavigationSounds::getInstance().mNavigationSounds[soundID]->play();
} }
bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID) bool NavigationSounds::isPlayingThemeNavigationSound(NavigationSoundsID soundID)
{ {
return NavigationSounds::getInstance()->navigationSounds[soundID]->isPlaying(); return NavigationSounds::getInstance().mNavigationSounds[soundID]->isPlaying();
} }

View file

@ -30,7 +30,7 @@ public:
void loadFile(const std::string& path); void loadFile(const std::string& path);
void play(); void play();
bool isPlaying() const { return playing; } bool isPlaying() const { return mPlaying; }
void stop(); void stop();
const Uint8* getData() const { return mSampleData; } const Uint8* getData() const { return mSampleData; }
@ -39,7 +39,7 @@ public:
Uint32 getLength() const { return mSampleLength; } Uint32 getLength() const { return mSampleLength; }
static std::shared_ptr<Sound> get(const std::string& path); static std::shared_ptr<Sound> get(const std::string& path);
static std::shared_ptr<Sound> getFromTheme(const std::shared_ptr<ThemeData>& theme, static std::shared_ptr<Sound> getFromTheme(ThemeData* const theme,
const std::string& view, const std::string& view,
const std::string& elem); const std::string& elem);
@ -52,7 +52,7 @@ private:
Uint8* mSampleData; Uint8* mSampleData;
Uint32 mSamplePos; Uint32 mSamplePos;
Uint32 mSampleLength; Uint32 mSampleLength;
bool playing; bool mPlaying;
}; };
enum NavigationSoundsID { enum NavigationSoundsID {
@ -68,16 +68,15 @@ enum NavigationSoundsID {
class NavigationSounds class NavigationSounds
{ {
public: public:
static NavigationSounds* getInstance(); static NavigationSounds& getInstance();
void deinit(); void deinit();
void loadThemeNavigationSounds(const std::shared_ptr<ThemeData>& theme); void loadThemeNavigationSounds(ThemeData* const theme);
void playThemeNavigationSound(NavigationSoundsID soundID); void playThemeNavigationSound(NavigationSoundsID soundID);
bool isPlayingThemeNavigationSound(NavigationSoundsID soundID); bool isPlayingThemeNavigationSound(NavigationSoundsID soundID);
private: private:
static NavigationSounds* sInstance; std::vector<std::shared_ptr<Sound>> mNavigationSounds;
std::vector<std::shared_ptr<Sound>> navigationSounds;
}; };
#endif // ES_CORE_SOUND_H #endif // ES_CORE_SOUND_H

View file

@ -15,7 +15,6 @@
#if defined(BUILD_VLC_PLAYER) #if defined(BUILD_VLC_PLAYER)
#include "components/VideoVlcComponent.h" #include "components/VideoVlcComponent.h"
#endif #endif
#include "AudioManager.h"
#include "InputManager.h" #include "InputManager.h"
#include "Log.h" #include "Log.h"
#include "Sound.h" #include "Sound.h"
@ -219,7 +218,7 @@ void Window::input(InputConfig* config, Input input)
else if (config->isMappedTo("y", input) && input.value != 0) { else if (config->isMappedTo("y", input) && input.value != 0) {
// Jump to the game in its gamelist, but do not launch it. // Jump to the game in its gamelist, but do not launch it.
stopScreensaver(); stopScreensaver();
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
mScreensaver->goToGame(); mScreensaver->goToGame();
// To force handling the wake up process. // To force handling the wake up process.
mSleeping = true; mSleeping = true;

View file

@ -102,8 +102,8 @@ public:
protected: protected:
virtual void onScroll() override virtual void onScroll() override
{ {
if (!NavigationSounds::getInstance()->isPlayingThemeNavigationSound(SCROLLSOUND)) if (!NavigationSounds::getInstance().isPlayingThemeNavigationSound(SCROLLSOUND))
NavigationSounds::getInstance()->playThemeNavigationSound(SCROLLSOUND); NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
} }
virtual void onCursorChanged(const CursorState& state) override; virtual void onCursorChanged(const CursorState& state) override;

View file

@ -90,9 +90,12 @@ void VideoComponent::setImage(std::string path)
void VideoComponent::onShow() void VideoComponent::onShow()
{ {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = false; mBlockPlayer = false;
mPause = false; mPause = false;
mShowing = true; mShowing = true;
playerLock.unlock();
manageState(); manageState();
} }
@ -110,22 +113,31 @@ void VideoComponent::onStopVideo()
void VideoComponent::onPauseVideo() void VideoComponent::onPauseVideo()
{ {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = true; mBlockPlayer = true;
mPause = true; mPause = true;
playerLock.unlock();
manageState(); manageState();
} }
void VideoComponent::onUnpauseVideo() void VideoComponent::onUnpauseVideo()
{ {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = false; mBlockPlayer = false;
mPause = false; mPause = false;
playerLock.unlock();
manageState(); manageState();
} }
void VideoComponent::onScreensaverActivate() void VideoComponent::onScreensaverActivate()
{ {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = true; mBlockPlayer = true;
mPause = true; mPause = true;
playerLock.unlock();
if (Settings::getInstance()->getString("ScreensaverType") == "dim") if (Settings::getInstance()->getString("ScreensaverType") == "dim")
stopVideo(); stopVideo();
else else
@ -158,15 +170,20 @@ void VideoComponent::onGameLaunchedDeactivate()
void VideoComponent::topWindow(bool isTop) void VideoComponent::topWindow(bool isTop)
{ {
if (isTop) { if (isTop) {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = false; mBlockPlayer = false;
mPause = false; mPause = false;
playerLock.unlock();
// Stop video when closing the menu to force a reload of the // Stop video when closing the menu to force a reload of the
// static image (if the theme is configured as such). // static image (if the theme is configured as such).
stopVideo(); stopVideo();
} }
else { else {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = true; mBlockPlayer = true;
mPause = true; mPause = true;
playerLock.unlock();
} }
manageState(); manageState();
} }

View file

@ -12,6 +12,7 @@
#include "GuiComponent.h" #include "GuiComponent.h"
#include "components/ImageComponent.h" #include "components/ImageComponent.h"
#include <mutex>
#include <string> #include <string>
class MediaViewer; class MediaViewer;
@ -109,6 +110,7 @@ private:
protected: protected:
Window* mWindow; Window* mWindow;
ImageComponent mStaticImage; ImageComponent mStaticImage;
std::mutex mPlayerMutex;
unsigned mVideoWidth; unsigned mVideoWidth;
unsigned mVideoHeight; unsigned mVideoHeight;

View file

@ -130,7 +130,10 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
if (mIsPlaying && mFormatContext) { if (mIsPlaying && mFormatContext) {
unsigned int color; unsigned int color;
if (mDecodedFrame && mFadeIn < 1) { std::unique_lock<std::mutex> pictureLock(mPictureMutex);
bool decodedFrame = mDecodedFrame;
pictureLock.unlock();
if (decodedFrame && mFadeIn < 1) {
const unsigned int fadeIn = static_cast<int>(mFadeIn * 255.0f); const unsigned int fadeIn = static_cast<int>(mFadeIn * 255.0f);
color = color =
Renderer::convertRGBAToABGR((fadeIn << 24) | (fadeIn << 16) | (fadeIn << 8) | 255); 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++) for (int i = 0; i < 4; i++)
vertices[i].pos = glm::round(vertices[i].pos); vertices[i].pos = glm::round(vertices[i].pos);
// This is needed to avoid a slight gap before the video starts playing. pictureLock.lock();
if (!mDecodedFrame)
return;
mPictureMutex.lock(); // This is needed to avoid a slight gap before the video starts playing.
if (!mDecodedFrame) {
pictureLock.unlock();
return;
}
if (!mOutputPicture.hasBeenRendered) { if (!mOutputPicture.hasBeenRendered) {
// Move the contents of mOutputPicture to a temporary vector in order to call // 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; mOutputPicture.hasBeenRendered = true;
} }
mPictureMutex.unlock(); pictureLock.unlock();
if (pictureSize > 0) { if (pictureSize > 0) {
// Build a texture for the video frame. // Build a texture for the video frame.
@ -196,7 +201,7 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
} }
} }
else { else {
mPictureMutex.unlock(); pictureLock.unlock();
} }
mTexture->bind(); mTexture->bind();
@ -225,13 +230,12 @@ void VideoFFmpegComponent::updatePlayer()
return; return;
// Output any audio that has been added by the processing thread. // Output any audio that has been added by the processing thread.
mAudioMutex.lock(); std::unique_lock<std::mutex> audioLock(mAudioMutex);
if (mOutputAudio.size()) { if (mOutputAudio.size()) {
AudioManager::getInstance()->processStream(&mOutputAudio.at(0), AudioManager::getInstance().processStream(&mOutputAudio.at(0),
static_cast<unsigned int>(mOutputAudio.size())); static_cast<unsigned int>(mOutputAudio.size()));
mOutputAudio.clear(); mOutputAudio.clear();
} }
mAudioMutex.unlock();
if (mIsActuallyPlaying && mStartTimeAccumulation) { if (mIsActuallyPlaying && mStartTimeAccumulation) {
mAccumulatedTime += mAccumulatedTime +=
@ -243,8 +247,10 @@ void VideoFFmpegComponent::updatePlayer()
mTimeReference = std::chrono::high_resolution_clock::now(); mTimeReference = std::chrono::high_resolution_clock::now();
audioLock.unlock();
if (!mFrameProcessingThread) { if (!mFrameProcessingThread) {
AudioManager::getInstance()->unmuteStream(); AudioManager::getInstance().unmuteStream();
mFrameProcessingThread = mFrameProcessingThread =
std::make_unique<std::thread>(&VideoFFmpegComponent::frameProcessing, this); std::make_unique<std::thread>(&VideoFFmpegComponent::frameProcessing, this);
} }
@ -262,15 +268,19 @@ void VideoFFmpegComponent::frameProcessing()
if (mAudioCodecContext) if (mAudioCodecContext)
audioFilter = setupAudioFilters(); audioFilter = setupAudioFilters();
while (mIsPlaying && !mPause && videoFilter && (!mAudioCodecContext || audioFilter)) { bool isPlaying = true;
bool pause = false;
while (isPlaying && !pause && videoFilter && (!mAudioCodecContext || audioFilter)) {
readFrames(); readFrames();
if (!mIsPlaying)
break;
getProcessedFrames(); getProcessedFrames();
if (!mIsPlaying)
break;
outputFrames(); outputFrames();
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
isPlaying = mIsPlaying;
pause = mPause;
playerLock.unlock();
// This 1 ms wait makes sure that the thread does not consume all available CPU cycles. // This 1 ms wait makes sure that the thread does not consume all available CPU cycles.
SDL_Delay(1); SDL_Delay(1);
} }
@ -428,7 +438,7 @@ bool VideoFFmpegComponent::setupAudioFilters()
{ {
int returnValue = 0; int returnValue = 0;
char errorMessage[512]; 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}; const enum AVSampleFormat outSampleFormats[] = {AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_NONE};
mAFilterInputs = avfilter_inout_alloc(); mAFilterInputs = avfilter_inout_alloc();
@ -662,9 +672,11 @@ void VideoFFmpegComponent::readFrames()
} }
} }
if (readFrameReturn < 0) if (readFrameReturn < 0) {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mEndOfVideo = true; mEndOfVideo = true;
} }
}
void VideoFFmpegComponent::getProcessedFrames() void VideoFFmpegComponent::getProcessedFrames()
{ {
@ -739,6 +751,7 @@ void VideoFFmpegComponent::outputFrames()
// there is an audio track. // there is an audio track.
if (!mAudioCodecContext || (mAudioCodecContext && !mAudioFrameQueue.empty())) { if (!mAudioCodecContext || (mAudioCodecContext && !mAudioFrameQueue.empty())) {
if (!mStartTimeAccumulation) { if (!mStartTimeAccumulation) {
std::unique_lock<std::mutex> audioLock(mAudioMutex);
mTimeReference = std::chrono::high_resolution_clock::now(); mTimeReference = std::chrono::high_resolution_clock::now();
mStartTimeAccumulation = true; mStartTimeAccumulation = true;
mIsActuallyPlaying = true; mIsActuallyPlaying = true;
@ -748,7 +761,11 @@ void VideoFFmpegComponent::outputFrames()
// Process the audio frames that have a PTS value below mAccumulatedTime (plus a small // Process the audio frames that have a PTS value below mAccumulatedTime (plus a small
// buffer to avoid underflows). // buffer to avoid underflows).
while (!mAudioFrameQueue.empty()) { while (!mAudioFrameQueue.empty()) {
if (mAudioFrameQueue.front().pts < mAccumulatedTime + AUDIO_BUFFER) { std::unique_lock<std::mutex> 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. // Enable only when needed, as this generates a lot of debug output.
if (DEBUG_VIDEO) { if (DEBUG_VIDEO) {
LOG(LogDebug) << "Processing audio frame with PTS: " LOG(LogDebug) << "Processing audio frame with PTS: "
@ -770,13 +787,13 @@ void VideoFFmpegComponent::outputFrames()
if (outputSound) { if (outputSound) {
// The audio is output to AudioManager from updatePlayer() in the main thread. // The audio is output to AudioManager from updatePlayer() in the main thread.
mAudioMutex.lock(); audioLock.lock();
mOutputAudio.insert(mOutputAudio.end(), mOutputAudio.insert(mOutputAudio.end(),
mAudioFrameQueue.front().resampledData.begin(), mAudioFrameQueue.front().resampledData.begin(),
mAudioFrameQueue.front().resampledData.end()); mAudioFrameQueue.front().resampledData.end());
mAudioMutex.unlock(); audioLock.unlock();
} }
mAudioFrameQueue.pop(); mAudioFrameQueue.pop();
mAudioFrameCount++; mAudioFrameCount++;
@ -786,11 +803,19 @@ void VideoFFmpegComponent::outputFrames()
} }
} }
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
bool isActuallyPlaying = mIsActuallyPlaying;
playerLock.unlock();
// Process all available video frames that have a PTS value below mAccumulatedTime. // 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 // But if more than one frame is processed here, it means that the computer can't
// keep up for some reason. // keep up for some reason.
while (mIsActuallyPlaying && !mVideoFrameQueue.empty()) { while (isActuallyPlaying && !mVideoFrameQueue.empty()) {
if (mVideoFrameQueue.front().pts < mAccumulatedTime) { std::unique_lock<std::mutex> audioLock(mAudioMutex);
double accumulatedTime = mAccumulatedTime;
audioLock.unlock();
if (mVideoFrameQueue.front().pts < accumulatedTime) {
// Enable only when needed, as this generates a lot of debug output. // Enable only when needed, as this generates a lot of debug output.
if (DEBUG_VIDEO) { if (DEBUG_VIDEO) {
LOG(LogDebug) << "Processing video frame with PTS: " LOG(LogDebug) << "Processing video frame with PTS: "
@ -808,7 +833,7 @@ void VideoFFmpegComponent::outputFrames()
} }
} }
mPictureMutex.lock(); std::unique_lock<std::mutex> pictureLock(mPictureMutex);
// Give some leeway for frames that have not yet been rendered but that have pts // 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 // 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 // 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. // rates close to, or at, the rendering frame rate, for example 59.94 and 60 FPS.
if (mDecodedFrame && !mOutputPicture.hasBeenRendered) { if (mDecodedFrame && !mOutputPicture.hasBeenRendered) {
double timeDifference = mAccumulatedTime - mVideoFrameQueue.front().pts - double timeDifference = accumulatedTime - mVideoFrameQueue.front().pts -
mVideoFrameQueue.front().frameDuration * 2.0l; mVideoFrameQueue.front().frameDuration * 2.0l;
if (timeDifference < mVideoFrameQueue.front().frameDuration) { if (timeDifference < mVideoFrameQueue.front().frameDuration) {
mPictureMutex.unlock(); pictureLock.unlock();
break; break;
} }
} }
@ -835,12 +860,12 @@ void VideoFFmpegComponent::outputFrames()
mOutputPicture.height = mVideoFrameQueue.front().height; mOutputPicture.height = mVideoFrameQueue.front().height;
mOutputPicture.hasBeenRendered = false; mOutputPicture.hasBeenRendered = false;
mPictureMutex.unlock(); mDecodedFrame = true;
pictureLock.unlock();
mVideoFrameQueue.pop(); mVideoFrameQueue.pop();
mVideoFrameCount++; mVideoFrameCount++;
mDecodedFrame = true;
} }
else { else {
break; break;
@ -1383,16 +1408,18 @@ void VideoFFmpegComponent::startVideo()
void VideoFFmpegComponent::stopVideo() void VideoFFmpegComponent::stopVideo()
{ {
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mIsPlaying = false; mIsPlaying = false;
mIsActuallyPlaying = false; mIsActuallyPlaying = false;
mStartDelayed = false; mStartDelayed = false;
mPause = false; mPause = false;
mEndOfVideo = false; mEndOfVideo = false;
playerLock.unlock();
mTexture.reset(); mTexture.reset();
if (mFrameProcessingThread) { if (mFrameProcessingThread) {
if (mWindow->getVideoPlayerCount() == 0) if (mWindow->getVideoPlayerCount() == 0)
AudioManager::getInstance()->muteStream(); AudioManager::getInstance().muteStream();
// Wait for the thread execution to complete. // Wait for the thread execution to complete.
mFrameProcessingThread->join(); mFrameProcessingThread->join();
mFrameProcessingThread.reset(); mFrameProcessingThread.reset();
@ -1404,7 +1431,7 @@ void VideoFFmpegComponent::stopVideo()
std::queue<AudioFrame>().swap(mAudioFrameQueue); std::queue<AudioFrame>().swap(mAudioFrameQueue);
// Clear the audio buffer. // Clear the audio buffer.
AudioManager::getInstance()->clearStream(); AudioManager::getInstance().clearStream();
if (mFormatContext) { if (mFormatContext) {
av_frame_free(&mVideoFrame); av_frame_free(&mVideoFrame);
@ -1427,12 +1454,16 @@ void VideoFFmpegComponent::stopVideo()
void VideoFFmpegComponent::pauseVideo() void VideoFFmpegComponent::pauseVideo()
{ {
if (mPause && mWindow->getVideoPlayerCount() == 0) if (mPause && mWindow->getVideoPlayerCount() == 0)
AudioManager::getInstance()->muteStream(); AudioManager::getInstance().muteStream();
} }
void VideoFFmpegComponent::handleLooping() void VideoFFmpegComponent::handleLooping()
{ {
if (mIsPlaying && mEndOfVideo) { std::unique_lock<std::mutex> playerLock(mPlayerMutex);
bool endOfVideo = mEndOfVideo;
playerLock.unlock();
if (mIsPlaying && endOfVideo) {
// If the screensaver video swap time is set to 0, it means we should // 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. // skip to the next game when the video has finished playing.
if (mScreensaverMode && if (mScreensaverMode &&