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)
#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<int>(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;

View file

@ -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;
}

View file

@ -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.

View file

@ -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;
}

View file

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

View file

@ -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();

View file

@ -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());
}

View file

@ -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<FileData*> 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);
}
}
}

View file

@ -14,16 +14,6 @@
#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()
{
// Init on construction.
@ -36,13 +26,10 @@ AudioManager::~AudioManager()
deinit();
}
std::shared_ptr<AudioManager>& 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<AudioManager>(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<std::mutex> 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>& sound)
void AudioManager::registerSound(std::shared_ptr<Sound> sound)
{
// Add sound to sound vector.
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++) {
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<Uint8> writeBuffer(10000);
if (SDL_AudioStreamPut(sConversionStream, reinterpret_cast<const void*>(&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<Uint8> readBuffer(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 <memory>
#include <mutex>
#include <vector>
class Sound;
@ -19,13 +20,13 @@ class AudioManager
{
public:
virtual ~AudioManager();
static std::shared_ptr<AudioManager>& getInstance();
static AudioManager& getInstance();
void init();
void deinit();
void registerSound(std::shared_ptr<Sound>& sound);
void unregisterSound(std::shared_ptr<Sound>& sound);
void registerSound(std::shared_ptr<Sound> sound);
void unregisterSound(std::shared_ptr<Sound> 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<std::mutex> audioLock{mAudioLock};
sMuteStream = true;
}
void unmuteStream()
{
std::unique_lock<std::mutex> 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<std::shared_ptr<Sound>> sSoundVector;
static std::shared_ptr<AudioManager> sInstance;
static bool sMuteStream;
static bool sHasAudioDevice;
static bool mIsClearingStream;
inline static std::mutex mAudioLock;
inline static SDL_AudioStream* sConversionStream;
inline static std::vector<std::shared_ptr<Sound>> sSoundVector;
inline static bool sMuteStream = false;
inline static bool sHasAudioDevice = true;
};
#endif // ES_CORE_AUDIO_MANAGER_H

View file

@ -15,8 +15,6 @@
#include "ThemeData.h"
#include "resources/ResourceManager.h"
NavigationSounds* NavigationSounds::sInstance = nullptr;
std::map<std::string, std::shared_ptr<Sound>> Sound::sMap;
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;
std::shared_ptr<Sound> sound = std::shared_ptr<Sound>(new Sound(path));
AudioManager::getInstance()->registerSound(sound);
AudioManager::getInstance().registerSound(sound);
sMap[path] = 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& 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<ThemeData>& theme)
void NavigationSounds::loadThemeNavigationSounds(ThemeData* const theme)
{
if (theme) {
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";
}
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowse"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselect"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "select"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "back"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "scroll"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "favorite"));
navigationSounds.push_back(Sound::getFromTheme(theme, "all", "launch"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "systembrowse"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "quicksysselect"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "select"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "back"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "scroll"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "favorite"));
mNavigationSounds.push_back(Sound::getFromTheme(theme, "all", "launch"));
}
void NavigationSounds::playThemeNavigationSound(NavigationSoundsID soundID)
{
NavigationSounds::getInstance()->navigationSounds[soundID]->play();
NavigationSounds::getInstance().mNavigationSounds[soundID]->play();
}
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 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<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& 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<ThemeData>& theme);
void loadThemeNavigationSounds(ThemeData* const theme);
void playThemeNavigationSound(NavigationSoundsID soundID);
bool isPlayingThemeNavigationSound(NavigationSoundsID soundID);
private:
static NavigationSounds* sInstance;
std::vector<std::shared_ptr<Sound>> navigationSounds;
std::vector<std::shared_ptr<Sound>> mNavigationSounds;
};
#endif // ES_CORE_SOUND_H

View file

@ -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;

View file

@ -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;

View file

@ -90,9 +90,12 @@ void VideoComponent::setImage(std::string path)
void VideoComponent::onShow()
{
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = false;
mPause = false;
mShowing = true;
playerLock.unlock();
manageState();
}
@ -110,22 +113,31 @@ void VideoComponent::onStopVideo()
void VideoComponent::onPauseVideo()
{
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = true;
mPause = true;
playerLock.unlock();
manageState();
}
void VideoComponent::onUnpauseVideo()
{
std::unique_lock<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = false;
mPause = false;
playerLock.unlock();
manageState();
}
void VideoComponent::onScreensaverActivate()
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> playerLock(mPlayerMutex);
mBlockPlayer = true;
mPause = true;
playerLock.unlock();
}
manageState();
}

View file

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

View file

@ -130,7 +130,10 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
if (mIsPlaying && mFormatContext) {
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);
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<std::mutex> audioLock(mAudioMutex);
if (mOutputAudio.size()) {
AudioManager::getInstance()->processStream(&mOutputAudio.at(0),
static_cast<unsigned int>(mOutputAudio.size()));
AudioManager::getInstance().processStream(&mOutputAudio.at(0),
static_cast<unsigned int>(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<std::thread>(&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<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.
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<std::mutex> 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<std::mutex> 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<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.
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<std::mutex> 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<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.
if (DEBUG_VIDEO) {
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
// 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<std::mutex> 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<AudioFrame>().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<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
// skip to the next game when the video has finished playing.
if (mScreensaverMode &&