From cdf0227ad46991a4e226bcdbd1d39da3057981a8 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Fri, 5 Nov 2021 20:31:24 +0100
Subject: [PATCH 01/11] Fixed a small alignment issue in the scraper GUI and
 removed an ugly hack.

---
 es-app/src/guis/GuiGameScraper.cpp       | 13 ++-----------
 es-core/src/components/ComponentList.cpp | 18 +++++++++---------
 2 files changed, 11 insertions(+), 20 deletions(-)

diff --git a/es-app/src/guis/GuiGameScraper.cpp b/es-app/src/guis/GuiGameScraper.cpp
index bcda1e93f..4e4eebf57 100644
--- a/es-app/src/guis/GuiGameScraper.cpp
+++ b/es-app/src/guis/GuiGameScraper.cpp
@@ -133,9 +133,6 @@ GuiGameScraper::GuiGameScraper(Window* window,
                    static_cast<float>(Renderer::getScreenHeight()) * 0.04f +
                    mButtonGrid->getSize().y + Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f;
 
-    // TODO: Temporary hack, see below.
-    height -= 7.0f * Renderer::getScreenHeightModifier();
-
     setSize(width, height);
     setPosition((Renderer::getScreenWidth() - mSize.x) / 2.0f,
                 (Renderer::getScreenHeight() - mSize.y) / 2.0f);
@@ -154,17 +151,11 @@ void GuiGameScraper::onSizeChanged()
                mSize.y / 2.0f);
     mGrid.setRowHeightPerc(2, mSystemName->getFont()->getLetterHeight() / mSize.y, false);
     mGrid.setRowHeightPerc(3, 0.04f, false);
-    mGrid.setRowHeightPerc(4, ((Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f)) / mSize.y, false);
-
-    // TODO: Replace this temporary hack with a proper solution. There is some kind of rounding
-    // issue somewhere that causes a small alignment error. This code partly compensates for this
-    // at higher resolutions than 1920x1080.
-    if (Renderer::getScreenHeightModifier() > 1.0f)
-        mSize.y -= 3.0f * Renderer::getScreenHeightModifier();
+    mGrid.setRowHeightPerc(4, (Font::get(FONT_SIZE_MEDIUM)->getHeight() * 8.0f) / mSize.y, false);
 
     mGrid.setColWidthPerc(1, 0.04f);
 
-    mGrid.setSize(mSize);
+    mGrid.setSize(glm::round(mSize));
     mBox.fitTo(mSize, glm::vec3{}, glm::vec2{-32.0f, -32.0f});
 
     // Add some extra margins to the game name.
diff --git a/es-core/src/components/ComponentList.cpp b/es-core/src/components/ComponentList.cpp
index c9d4d7b38..7304e6776 100644
--- a/es-core/src/components/ComponentList.cpp
+++ b/es-core/src/components/ComponentList.cpp
@@ -289,7 +289,7 @@ void ComponentList::render(const glm::mat4& parentTrans)
     dim.x = (trans[0].x * dim.x + trans[3].x) - trans[3].x;
     dim.y = (trans[1].y * dim.y + trans[3].y) - trans[3].y;
 
-    const int clipRectPosX{static_cast<int>(std::round(trans[3].x))};
+    const int clipRectPosX{static_cast<int>(std::ceil(trans[3].x))};
     const int clipRectPosY{static_cast<int>(std::round(trans[3].y))};
     const int clipRectSizeX{static_cast<int>(std::round(dim.x))};
     const int clipRectSizeY{static_cast<int>(std::round(dim.y))};
@@ -383,12 +383,12 @@ void ComponentList::render(const glm::mat4& parentTrans)
         const float selectedRowHeight = getRowHeight(mEntries.at(mCursor).data);
 
         if (opacity == 1) {
-            Renderer::drawRect(0.0f, mSelectorBarOffset, mSize.x, selectedRowHeight, 0xFFFFFFFF,
-                               0xFFFFFFFF, false, opacity, trans,
+            Renderer::drawRect(0.0f, mSelectorBarOffset, std::ceil(mSize.x), selectedRowHeight,
+                               0xFFFFFFFF, 0xFFFFFFFF, false, opacity, trans,
                                Renderer::Blend::ONE_MINUS_DST_COLOR, Renderer::Blend::ZERO);
 
-            Renderer::drawRect(0.0f, mSelectorBarOffset, mSize.x, selectedRowHeight, 0x777777FF,
-                               0x777777FF, false, opacity, trans, Renderer::Blend::ONE,
+            Renderer::drawRect(0.0f, mSelectorBarOffset, std::ceil(mSize.x), selectedRowHeight,
+                               0x777777FF, 0x777777FF, false, opacity, trans, Renderer::Blend::ONE,
                                Renderer::Blend::ONE);
         }
 
@@ -403,13 +403,13 @@ void ComponentList::render(const glm::mat4& parentTrans)
     // Draw separators.
     float y = 0;
     for (unsigned int i = 0; i < mEntries.size(); i++) {
-        Renderer::drawRect(0.0f, y, mSize.x, 1.0f * Renderer::getScreenHeightModifier(), 0xC6C7C6FF,
-                           0xC6C7C6FF, false, opacity, trans);
+        Renderer::drawRect(0.0f, y, std::ceil(mSize.x), 1.0f * Renderer::getScreenHeightModifier(),
+                           0xC6C7C6FF, 0xC6C7C6FF, false, opacity, trans);
         y += getRowHeight(mEntries.at(i).data);
     }
 
-    Renderer::drawRect(0.0f, y, mSize.x, 1.0f * Renderer::getScreenHeightModifier(), 0xC6C7C6FF,
-                       0xC6C7C6FF, false, opacity, trans);
+    Renderer::drawRect(0.0f, y, std::ceil(mSize.x), 1.0f * Renderer::getScreenHeightModifier(),
+                       0xC6C7C6FF, 0xC6C7C6FF, false, opacity, trans);
     Renderer::popClipRect();
 }
 

From 46228c6a9dfbd516e03fbf4cc74c1bdc7497c323 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sat, 6 Nov 2021 20:47:30 +0100
Subject: [PATCH 02/11] Fixed an issue where an invalid UIMode entry in
 es_settings.xml could lead to a crash.

---
 es-app/src/views/UIModeController.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/es-app/src/views/UIModeController.cpp b/es-app/src/views/UIModeController.cpp
index 8c96bb349..f6239ecb2 100644
--- a/es-app/src/views/UIModeController.cpp
+++ b/es-app/src/views/UIModeController.cpp
@@ -39,6 +39,12 @@ UIModeController::UIModeController()
 {
     mPassKeySequence = Settings::getInstance()->getString("UIMode_passkey");
     mCurrentUIMode = Settings::getInstance()->getString("UIMode");
+    // Handle a potentially invalid entry in the configuration file.
+    if (mCurrentUIMode != "full" && mCurrentUIMode != "kid" && mCurrentUIMode != "kiosk") {
+        mCurrentUIMode = "full";
+        Settings::getInstance()->setString("UIMode", mCurrentUIMode);
+        Settings::getInstance()->saveFile();
+    }
 }
 
 void UIModeController::monitorUIMode()

From af337124d643661c36f51ac02cc771f45492003d Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sat, 6 Nov 2021 21:12:13 +0100
Subject: [PATCH 03/11] Fixed an issue where an invalid scraper entry in
 es_settings.xml could lead to a crash.

---
 es-app/src/guis/GuiScraperMenu.cpp |  5 +++++
 es-app/src/scrapers/Scraper.cpp    | 11 +++++++++--
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/es-app/src/guis/GuiScraperMenu.cpp b/es-app/src/guis/GuiScraperMenu.cpp
index 17a2526f7..45f6e1683 100644
--- a/es-app/src/guis/GuiScraperMenu.cpp
+++ b/es-app/src/guis/GuiScraperMenu.cpp
@@ -32,6 +32,11 @@ GuiScraperMenu::GuiScraperMenu(Window* window, std::string title)
     // just in case the scraper from settings has vanished.
     for (auto it = scrapers.cbegin(); it != scrapers.cend(); it++)
         mScraper->add(*it, *it, *it == Settings::getInstance()->getString("Scraper"));
+    // If there are no objects returned, then there must be a manually modified entry in the
+    // configuration file. Simply set the scraper to "screenscraper" in this case.
+    if (mScraper->getSelectedObjects().size() == 0)
+        mScraper->selectEntry(0);
+
     mMenu.addWithLabel("SCRAPE FROM", mScraper);
 
     // Search filters, getSearches() will generate a queue of games to scrape
diff --git a/es-app/src/scrapers/Scraper.cpp b/es-app/src/scrapers/Scraper.cpp
index e1fbbab6e..3193e4dca 100644
--- a/es-app/src/scrapers/Scraper.cpp
+++ b/es-app/src/scrapers/Scraper.cpp
@@ -80,8 +80,15 @@ std::vector<std::string> getScraperList()
 
 bool isValidConfiguredScraper()
 {
-    const std::string& name = Settings::getInstance()->getString("Scraper");
-    return scraper_request_funcs.find(name) != scraper_request_funcs.end();
+    std::string scraper = Settings::getInstance()->getString("Scraper");
+    // Handle a potentially invalid entry in the configuration file.
+    if (scraper != "screenscraper" && scraper != "thegamesdb") {
+        scraper = "screenscraper";
+        Settings::getInstance()->setString("Scraper", scraper);
+        Settings::getInstance()->saveFile();
+    }
+
+    return scraper_request_funcs.find(scraper) != scraper_request_funcs.end();
 }
 
 void ScraperSearchHandle::update()

From c673f06eb03348d0dfa9ee8e3b468297749c0df6 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sat, 6 Nov 2021 21:21:41 +0100
Subject: [PATCH 04/11] Improved the handling of invalid scraper configuration
 file entries.

---
 es-app/src/scrapers/Scraper.cpp | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/es-app/src/scrapers/Scraper.cpp b/es-app/src/scrapers/Scraper.cpp
index 3193e4dca..fdb53a8f7 100644
--- a/es-app/src/scrapers/Scraper.cpp
+++ b/es-app/src/scrapers/Scraper.cpp
@@ -32,7 +32,14 @@ const std::map<std::string, generate_scraper_requests_func> scraper_request_func
 
 std::unique_ptr<ScraperSearchHandle> startScraperSearch(const ScraperSearchParams& params)
 {
-    const std::string& name = Settings::getInstance()->getString("Scraper");
+    std::string name = Settings::getInstance()->getString("Scraper");
+    // Handle a potentially invalid entry in the configuration file.
+    if (name != "screenscraper" && name != "thegamesdb") {
+        name = "screenscraper";
+        Settings::getInstance()->setString("Scraper", name);
+        Settings::getInstance()->saveFile();
+    }
+
     std::unique_ptr<ScraperSearchHandle> handle(new ScraperSearchHandle());
 
     // Check if the scraper in the settings still exists as a registered scraping source.
@@ -80,15 +87,8 @@ std::vector<std::string> getScraperList()
 
 bool isValidConfiguredScraper()
 {
-    std::string scraper = Settings::getInstance()->getString("Scraper");
-    // Handle a potentially invalid entry in the configuration file.
-    if (scraper != "screenscraper" && scraper != "thegamesdb") {
-        scraper = "screenscraper";
-        Settings::getInstance()->setString("Scraper", scraper);
-        Settings::getInstance()->saveFile();
-    }
-
-    return scraper_request_funcs.find(scraper) != scraper_request_funcs.end();
+    const std::string& name = Settings::getInstance()->getString("Scraper");
+    return scraper_request_funcs.find(name) != scraper_request_funcs.end();
 }
 
 void ScraperSearchHandle::update()

From b4492abccdca10baceb9effbb6faaca3f57fd9cc Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sat, 6 Nov 2021 21:43:32 +0100
Subject: [PATCH 05/11] Removed the copying of es_settings.cfg to
 es_settings.xml on startup.

---
 es-core/src/Settings.cpp | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp
index ea25a2dcb..cd3879978 100644
--- a/es-core/src/Settings.cpp
+++ b/es-core/src/Settings.cpp
@@ -358,16 +358,9 @@ void Settings::saveFile()
 
 void Settings::loadFile()
 {
-    // Prior to ES-DE v1.1, the configuration file had the .cfg suffix instead of .xml
-    const std::string legacyConfigFile =
-        Utils::FileSystem::getHomePath() + "/.emulationstation/es_settings.cfg";
-
     const std::string configFile =
         Utils::FileSystem::getHomePath() + "/.emulationstation/es_settings.xml";
 
-    if (Utils::FileSystem::exists(legacyConfigFile) && !Utils::FileSystem::exists(configFile))
-        Utils::FileSystem::copyFile(legacyConfigFile, configFile, false);
-
     if (!Utils::FileSystem::exists(configFile))
         return;
 

From c5a1555de3fa686341fa9aae0c14b9ffcf382f23 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sun, 7 Nov 2021 18:14:38 +0100
Subject: [PATCH 06/11] Added proper frame drop support to the FFmpeg video
 player.

Also made multiple large optimizations.
---
 .../src/components/VideoFFmpegComponent.cpp   | 201 +++++++++++-------
 es-core/src/components/VideoFFmpegComponent.h |   2 +
 2 files changed, 129 insertions(+), 74 deletions(-)

diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp
index 6772db296..45c499d67 100644
--- a/es-core/src/components/VideoFFmpegComponent.cpp
+++ b/es-core/src/components/VideoFFmpegComponent.cpp
@@ -16,6 +16,7 @@
 #include "resources/TextureResource.h"
 
 #include <algorithm>
+#include <iomanip>
 
 enum AVHWDeviceType VideoFFmpegComponent::sDeviceType = AV_HWDEVICE_TYPE_NONE;
 enum AVPixelFormat VideoFFmpegComponent::sPixelFormat = AV_PIX_FMT_NONE;
@@ -165,17 +166,22 @@ void VideoFFmpegComponent::render(const glm::mat4& parentTrans)
         mPictureMutex.lock();
 
         if (!mOutputPicture.hasBeenRendered) {
-            // Copy 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
             // initFromPixels() only after the mutex unlock. This significantly reduces the
             // lock waits in outputFrames().
             size_t pictureSize = mOutputPicture.pictureRGBA.size();
-            std::vector<uint8_t> tempPictureRGBA(pictureSize);
+            std::vector<uint8_t> tempPictureRGBA;
             int pictureWidth = 0;
             int pictureHeight = 0;
 
             if (pictureSize > 0) {
-                tempPictureRGBA.insert(tempPictureRGBA.begin(), mOutputPicture.pictureRGBA.begin(),
-                                       mOutputPicture.pictureRGBA.end());
+                tempPictureRGBA.insert(tempPictureRGBA.begin(),
+                                       std::make_move_iterator(mOutputPicture.pictureRGBA.begin()),
+                                       std::make_move_iterator(mOutputPicture.pictureRGBA.end()));
+
+                mOutputPicture.pictureRGBA.erase(mOutputPicture.pictureRGBA.begin(),
+                                                 mOutputPicture.pictureRGBA.end());
+
                 pictureWidth = mOutputPicture.width;
                 pictureHeight = mOutputPicture.height;
 
@@ -528,90 +534,126 @@ void VideoFFmpegComponent::readFrames()
     if (mVideoFrameQueue.size() > 300 || mAudioFrameQueue.size() > 600)
         return;
 
+    int readLoops = 1;
+
+    // If we can't keep up the audio processing, then drop video frames as it's much worse
+    // to have stuttering audio than a lower video framerate.
+    if (mAudioStreamIndex >= 0 && mAudioFrameCount > mAudioTargetQueueSize / 2) {
+        if (static_cast<int>(mAudioFrameQueue.size()) < mAudioTargetQueueSize / 6)
+            readLoops = 5;
+        else if (static_cast<int>(mAudioFrameQueue.size()) < mAudioTargetQueueSize / 4)
+            readLoops = 3;
+        else if (static_cast<int>(mAudioFrameQueue.size()) < mAudioTargetQueueSize / 2)
+            readLoops = 2;
+    }
+
     if (mVideoCodecContext && mFormatContext) {
-        if (static_cast<int>(mVideoFrameQueue.size()) < mVideoTargetQueueSize ||
-            (mAudioStreamIndex >= 0 &&
-             static_cast<int>(mAudioFrameQueue.size()) < mAudioTargetQueueSize)) {
-            while ((readFrameReturn = av_read_frame(mFormatContext, mPacket)) >= 0) {
-                if (mPacket->stream_index == mVideoStreamIndex) {
-                    if (!avcodec_send_packet(mVideoCodecContext, mPacket) &&
-                        !avcodec_receive_frame(mVideoCodecContext, mVideoFrame)) {
+        for (int i = 0; i < readLoops; i++) {
+            if (static_cast<int>(mVideoFrameQueue.size()) < mVideoTargetQueueSize ||
+                (mAudioStreamIndex >= 0 &&
+                 static_cast<int>(mAudioFrameQueue.size()) < mAudioTargetQueueSize)) {
+                while ((readFrameReturn = av_read_frame(mFormatContext, mPacket)) >= 0) {
+                    if (mPacket->stream_index == mVideoStreamIndex) {
+                        if (!avcodec_send_packet(mVideoCodecContext, mPacket) &&
+                            !avcodec_receive_frame(mVideoCodecContext, mVideoFrame)) {
 
-                        int returnValue = 0;
+                            int returnValue = 0;
+                            mVideoFrameReadCount++;
 
-                        if (mSWDecoder) {
-                            returnValue = av_buffersrc_add_frame_flags(
-                                mVBufferSrcContext, mVideoFrame, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT);
-                        }
-                        else {
-                            AVFrame* destFrame = nullptr;
-                            destFrame = av_frame_alloc();
-
-                            if (mVideoFrame->format == sPixelFormat) {
-                                if (av_hwframe_transfer_data(destFrame, mVideoFrame, 0) < 0) {
-                                    LOG(LogError) << "VideoFFmpegComponent::readFrames(): "
-                                                     "Couldn't transfer decoded video frame to "
-                                                     "system memory";
-                                    av_frame_free(&destFrame);
-                                    av_packet_unref(mPacket);
-                                    break;
+                            if (mSWDecoder) {
+                                // Drop the frame if necessary.
+                                if (i == 0 || mAudioFrameCount == 0) {
+                                    returnValue = av_buffersrc_add_frame_flags(
+                                        mVBufferSrcContext, mVideoFrame,
+                                        AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT);
                                 }
                                 else {
-                                    destFrame->pts = mVideoFrame->pts;
-                                    destFrame->pkt_dts = mVideoFrame->pkt_dts;
-                                    destFrame->pict_type = mVideoFrame->pict_type;
-                                    destFrame->chroma_location = mVideoFrame->chroma_location;
-                                    destFrame->pkt_pos = mVideoFrame->pkt_pos;
-                                    destFrame->pkt_duration = mVideoFrame->pkt_duration;
-                                    destFrame->pkt_size = mVideoFrame->pkt_size;
+                                    mVideoFrameDroppedCount++;
                                 }
                             }
                             else {
-                                LOG(LogError) << "VideoFFmpegComponent::readFrames(): "
-                                                 "Couldn't decode video frame";
+                                if (i == 0 || mAudioFrameCount == 0) {
+                                    AVFrame* destFrame = nullptr;
+                                    destFrame = av_frame_alloc();
+
+                                    if (mVideoFrame->format == sPixelFormat) {
+                                        if (av_hwframe_transfer_data(destFrame, mVideoFrame, 0) <
+                                            0) {
+                                            LOG(LogError)
+                                                << "VideoFFmpegComponent::readFrames(): "
+                                                   "Couldn't transfer decoded video frame to "
+                                                   "system memory";
+                                            av_frame_free(&destFrame);
+                                            av_packet_unref(mPacket);
+                                            break;
+                                        }
+                                        else {
+                                            destFrame->pts = mVideoFrame->pts;
+                                            destFrame->pkt_dts = mVideoFrame->pkt_dts;
+                                            destFrame->pict_type = mVideoFrame->pict_type;
+                                            destFrame->chroma_location =
+                                                mVideoFrame->chroma_location;
+                                            destFrame->pkt_pos = mVideoFrame->pkt_pos;
+                                            destFrame->pkt_duration = mVideoFrame->pkt_duration;
+                                            destFrame->pkt_size = mVideoFrame->pkt_size;
+                                        }
+                                    }
+                                    else {
+                                        LOG(LogError) << "VideoFFmpegComponent::readFrames(): "
+                                                         "Couldn't decode video frame";
+                                    }
+
+                                    returnValue = av_buffersrc_add_frame_flags(
+                                        mVBufferSrcContext, destFrame,
+                                        AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT);
+                                    av_frame_free(&destFrame);
+                                }
+                                else {
+                                    mVideoFrameDroppedCount++;
+                                }
                             }
 
-                            returnValue = av_buffersrc_add_frame_flags(
-                                mVBufferSrcContext, destFrame, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT);
-                            av_frame_free(&destFrame);
-                        }
+                            if (returnValue < 0) {
+                                LOG(LogError) << "VideoFFmpegComponent::readFrames(): "
+                                                 "Couldn't add video frame to buffer source";
+                            }
 
-                        if (returnValue < 0) {
-                            LOG(LogError) << "VideoFFmpegComponent::readFrames(): "
-                                             "Couldn't add video frame to buffer source";
+                            av_packet_unref(mPacket);
+                            break;
                         }
+                        else {
+                            av_packet_unref(mPacket);
+                        }
+                    }
+                    else if (mPacket->stream_index == mAudioStreamIndex) {
+                        if (!avcodec_send_packet(mAudioCodecContext, mPacket) &&
+                            !avcodec_receive_frame(mAudioCodecContext, mAudioFrame)) {
 
-                        av_packet_unref(mPacket);
-                        break;
+                            // We have an audio frame that needs conversion and resampling.
+                            int returnValue = av_buffersrc_add_frame_flags(
+                                mABufferSrcContext, mAudioFrame, AV_BUFFERSRC_FLAG_KEEP_REF);
+
+                            if (returnValue < 0) {
+                                LOG(LogError) << "VideoFFmpegComponent::readFrames(): "
+                                                 "Couldn't add audio frame to buffer source";
+                            }
+
+                            av_packet_unref(mPacket);
+                            continue;
+                        }
+                        else {
+                            av_packet_unref(mPacket);
+                        }
                     }
                     else {
+                        // Ignore any stream that is not video or audio.
                         av_packet_unref(mPacket);
                     }
                 }
-                else if (mPacket->stream_index == mAudioStreamIndex) {
-                    if (!avcodec_send_packet(mAudioCodecContext, mPacket) &&
-                        !avcodec_receive_frame(mAudioCodecContext, mAudioFrame)) {
-
-                        // We have an audio frame that needs conversion and resampling.
-                        int returnValue = av_buffersrc_add_frame_flags(
-                            mABufferSrcContext, mAudioFrame, AV_BUFFERSRC_FLAG_KEEP_REF);
-
-                        if (returnValue < 0) {
-                            LOG(LogError) << "VideoFFmpegComponent::readFrames(): "
-                                             "Couldn't add audio frame to buffer source";
-                        }
-
-                        av_packet_unref(mPacket);
-                        continue;
-                    }
-                    else {
-                        av_packet_unref(mPacket);
-                    }
-                }
-                else {
-                    // Ignore any stream that is not video or audio.
-                    av_packet_unref(mPacket);
-                }
+            }
+            else {
+                // The target queue sizes have been reached.
+                break;
             }
         }
     }
@@ -649,10 +691,11 @@ void VideoFFmpegComponent::getProcessedFrames()
 
         int bufferSize = mVideoFrameResampled->width * mVideoFrameResampled->height * 4;
 
-        currFrame.frameRGBA.insert(currFrame.frameRGBA.begin(), &mVideoFrameResampled->data[0][0],
-                                   &mVideoFrameResampled->data[0][bufferSize]);
+        currFrame.frameRGBA.insert(
+            currFrame.frameRGBA.begin(), std::make_move_iterator(&mVideoFrameResampled->data[0][0]),
+            std::make_move_iterator(&mVideoFrameResampled->data[0][bufferSize]));
 
-        mVideoFrameQueue.push(currFrame);
+        mVideoFrameQueue.push(std::move(currFrame));
         av_frame_unref(mVideoFrameResampled);
     }
 
@@ -680,7 +723,7 @@ void VideoFFmpegComponent::getProcessedFrames()
                                        &mAudioFrameResampled->data[0][0],
                                        &mAudioFrameResampled->data[0][bufferSize]);
 
-        mAudioFrameQueue.push(currFrame);
+        mAudioFrameQueue.push(std::move(currFrame));
         av_frame_unref(mAudioFrameResampled);
     }
 }
@@ -751,6 +794,14 @@ void VideoFFmpegComponent::outputFrames()
                 LOG(LogDebug) << "Total video frames processed / video frame queue size: "
                               << mVideoFrameCount << " / "
                               << std::to_string(mVideoFrameQueue.size());
+                if (mVideoFrameDroppedCount > 0) {
+                    LOG(LogDebug) << "Video frames dropped: " << mVideoFrameDroppedCount << " of "
+                                  << mVideoFrameReadCount << " (" << std::setprecision(2)
+                                  << (static_cast<float>(mVideoFrameDroppedCount) /
+                                      static_cast<float>(mVideoFrameReadCount)) *
+                                         100.0f
+                                  << "%)";
+                }
             }
 
             mPictureMutex.lock();
@@ -1136,6 +1187,8 @@ void VideoFFmpegComponent::startVideo()
         mEndOfVideo = false;
         mVideoFrameCount = 0;
         mAudioFrameCount = 0;
+        mVideoFrameReadCount = 0;
+        mVideoFrameDroppedCount = 0;
         mOutputPicture = {};
 
         // Get an empty texture for rendering the video.
diff --git a/es-core/src/components/VideoFFmpegComponent.h b/es-core/src/components/VideoFFmpegComponent.h
index 255e2fe09..66b80e984 100644
--- a/es-core/src/components/VideoFFmpegComponent.h
+++ b/es-core/src/components/VideoFFmpegComponent.h
@@ -158,6 +158,8 @@ private:
 
     int mAudioFrameCount;
     int mVideoFrameCount;
+    int mVideoFrameReadCount;
+    int mVideoFrameDroppedCount;
 
     double mAccumulatedTime;
     bool mStartTimeAccumulation;

From 14e1b800fa917696d2d44040d9c87e4c080108d2 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sun, 7 Nov 2021 18:18:41 +0100
Subject: [PATCH 07/11] Made two optimizations in TextureData.

---
 es-core/src/resources/TextureData.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/es-core/src/resources/TextureData.cpp b/es-core/src/resources/TextureData.cpp
index e51908489..8182106f5 100644
--- a/es-core/src/resources/TextureData.cpp
+++ b/es-core/src/resources/TextureData.cpp
@@ -105,8 +105,9 @@ bool TextureData::initSVGFromMemory(const std::string& fileData)
 
         nsvgDeleteRasterizer(rast);
 
-        mDataRGBA.insert(mDataRGBA.begin(), tempVector.data(),
-                         tempVector.data() + (mWidth * mHeight * 4));
+        mDataRGBA.insert(mDataRGBA.begin(), std::make_move_iterator(tempVector.data()),
+                         std::make_move_iterator(tempVector.data() + (mWidth * mHeight * 4)));
+        tempVector.erase(tempVector.begin(), tempVector.end());
 
         ImageIO::flipPixelsVert(mDataRGBA.data(), mWidth, mHeight);
         mPendingRasterization = false;
@@ -160,7 +161,8 @@ bool TextureData::initFromRGBA(const unsigned char* dataRGBA, size_t width, size
         return true;
 
     mDataRGBA.reserve(width * height * 4);
-    mDataRGBA.insert(mDataRGBA.begin(), dataRGBA, dataRGBA + (width * height * 4));
+    mDataRGBA.insert(mDataRGBA.begin(), std::make_move_iterator(dataRGBA),
+                     std::make_move_iterator(dataRGBA + (width * height * 4)));
 
     mWidth = static_cast<int>(width);
     mHeight = static_cast<int>(height);

From c136f87a9f524ee9b66bec27e95973e8154a3842 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sun, 7 Nov 2021 19:21:01 +0100
Subject: [PATCH 08/11] Small optimization when opening the menu.

---
 es-core/src/Window.cpp | 11 ++++++++---
 es-core/src/Window.h   |  4 ++++
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/es-core/src/Window.cpp b/es-core/src/Window.cpp
index 39c419136..68edeee2a 100644
--- a/es-core/src/Window.cpp
+++ b/es-core/src/Window.cpp
@@ -128,6 +128,10 @@ bool Window::init()
     mBackgroundOverlay->setResize(static_cast<float>(Renderer::getScreenWidth()),
                                   static_cast<float>(Renderer::getScreenHeight()));
 
+#if defined(USE_OPENGL_21)
+    mPostprocessedBackground = TextureResource::get("");
+#endif
+
     mListScrollFont = Font::get(FONT_SIZE_LARGE);
 
     // Update our help because font sizes probably changed.
@@ -143,6 +147,10 @@ void Window::deinit()
     for (auto it = mGuiStack.cbegin(); it != mGuiStack.cend(); it++)
         (*it)->onHide();
 
+#if defined(USE_OPENGL_21)
+    mPostprocessedBackground.reset();
+#endif
+
     InputManager::getInstance()->deinit();
     ResourceManager::getInstance()->unloadAll();
 #if defined(BUILD_VLC_PLAYER)
@@ -429,9 +437,6 @@ void Window::render()
 #if (CLOCK_BACKGROUND_CREATION)
                 const auto backgroundStartTime = std::chrono::system_clock::now();
 #endif
-
-                std::shared_ptr<TextureResource> mPostprocessedBackground;
-                mPostprocessedBackground = TextureResource::get("");
                 unsigned char* processedTexture =
                     new unsigned char[Renderer::getScreenWidth() * Renderer::getScreenHeight() * 4];
 
diff --git a/es-core/src/Window.h b/es-core/src/Window.h
index d02ac3f31..a1c73d84e 100644
--- a/es-core/src/Window.h
+++ b/es-core/src/Window.h
@@ -167,6 +167,10 @@ private:
 
     std::queue<std::pair<std::string, int>> mInfoPopupQueue;
 
+#if defined(USE_OPENGL_21)
+    std::shared_ptr<TextureResource> mPostprocessedBackground;
+#endif
+
     std::string mListScrollText;
     std::shared_ptr<Font> mListScrollFont;
     unsigned char mListScrollOpacity;

From 433c77261ff6d9cfabd02e16c043a3dc4326ee2e Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sun, 7 Nov 2021 22:22:34 +0100
Subject: [PATCH 09/11] Fixed a small issue with the sizing of the badges.

---
 es-core/src/components/FlexboxComponent.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp
index 3b9fe9a0d..4d2998c1d 100644
--- a/es-core/src/components/FlexboxComponent.cpp
+++ b/es-core/src/components/FlexboxComponent.cpp
@@ -231,7 +231,7 @@ void FlexboxComponent::computeLayout()
         }
 
         // This rasterizes the SVG images so they look nice and smooth.
-        item.baseImage.setResize(item.baseImage.getSize());
+        item.baseImage.setResize(glm::ceil(item.baseImage.getSize()));
 
         itemsOnLastRow++;
         pos++;

From c64284808164cbc92083ee4cf3a8aebb61e977f0 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sun, 7 Nov 2021 22:49:23 +0100
Subject: [PATCH 10/11] Reverted the last commit as the issue does not seem to
 be in the code.

---
 es-core/src/components/FlexboxComponent.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/es-core/src/components/FlexboxComponent.cpp b/es-core/src/components/FlexboxComponent.cpp
index 4d2998c1d..3b9fe9a0d 100644
--- a/es-core/src/components/FlexboxComponent.cpp
+++ b/es-core/src/components/FlexboxComponent.cpp
@@ -231,7 +231,7 @@ void FlexboxComponent::computeLayout()
         }
 
         // This rasterizes the SVG images so they look nice and smooth.
-        item.baseImage.setResize(glm::ceil(item.baseImage.getSize()));
+        item.baseImage.setResize(item.baseImage.getSize());
 
         itemsOnLastRow++;
         pos++;

From ac4710a18ef820245db40c1666885d8a1b5274a6 Mon Sep 17 00:00:00 2001
From: Leon Styhre <leon@leonstyhre.com>
Date: Sun, 7 Nov 2021 23:54:52 +0100
Subject: [PATCH 11/11] Added proper support for the Raspberry Pi 4.

---
 CMakeLists.txt                                | 69 +++++++++++++------
 es-app/src/guis/GuiMenu.cpp                   |  2 +-
 es-core/src/Settings.cpp                      | 23 ++++---
 .../src/components/VideoFFmpegComponent.cpp   |  2 +-
 4 files changed, 64 insertions(+), 32 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 08e7418a4..eafc59c04 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,6 +35,7 @@ option(RPI "Set to ON to enable Raspberry Pi specific build" ${RPI})
 option(CEC "Set to ON to enable CEC" ${CEC})
 option(VLC_PLAYER "Set to ON to build the VLC-based video player" ${VLC_PLAYER})
 option(CLANG_TIDY "Set to ON to build using the clang-tidy static analyzer" ${CLANG_TIDY})
+option(VIDEO_HW_DECODING "Set to OFF to disable FFmpeg HW decoding" ON)
 
 if(CLANG_TIDY)
     find_program(CLANG_TIDY_BINARY NAMES clang-tidy)
@@ -58,14 +59,7 @@ endif()
 #---------------------------------------------------------------------------------------------------
 # OpenGL setup.
 
-# Check if we're running on a Raspberry Pi.
-if(EXISTS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/bcm_host.h")
-    message("-- Building on a Raspberry Pi (bcm_host.h found)")
-    # Setting BCMHOST seems to break OpenGL ES on the RPi 4 so set RPI instead.
-    #set(BCMHOST found)
-    set(RPI ON)
-    set(GLSYSTEM "Embedded OpenGL" CACHE STRING "The OpenGL system to be used")
-elseif(GLES OR RPI)
+if(GLES)
     set(GLSYSTEM "Embedded OpenGL" CACHE STRING "The OpenGL system to be used")
 else()
     set(GLSYSTEM "Desktop OpenGL" CACHE STRING "The OpenGL system to be used")
@@ -73,6 +67,32 @@ endif()
 
 set_property(CACHE GLSYSTEM PROPERTY STRINGS "Desktop OpenGL" "Embedded OpenGL")
 
+#---------------------------------------------------------------------------------------------------
+# Raspberry Pi setup.
+
+# If manually set to RPI (used for testing purposes).
+if(RPI)
+    set(VIDEO_HW_DECODING OFF)
+endif()
+
+# Raspberry Pi OS 32-bit (armv7l)
+if(EXISTS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/bcm_host.h")
+    set(RPI ON)
+    set(RPI_32 ON)
+    set(VIDEO_HW_DECODING OFF)
+    set(BCMHOST found)
+    message("-- Building on a Raspberry Pi (32-bit OS)")
+endif()
+
+# Raspberry Pi OS 64-bit (aarch64)
+if(EXISTS "/usr/include/bcm_host.h")
+    set(RPI ON)
+    set(RPI_64 ON)
+    set(VIDEO_HW_DECODING OFF)
+    set(BCMHOST found)
+    message("-- Building on a Raspberry Pi (64-bit OS)")
+endif()
+
 #---------------------------------------------------------------------------------------------------
 # Package dependencies.
 
@@ -196,18 +216,22 @@ else()
     add_definitions(-DUSE_OPENGLES_10)
 endif()
 
+if(RPI)
+    add_definitions(-D_RPI_)
+endif()
+
 if(VLC_PLAYER)
     add_definitions(-DBUILD_VLC_PLAYER)
 endif()
 
-if(DEFINED BCMHOST OR RPI)
-    add_definitions(-D_RPI_)
-endif()
-
 if(DEFINED libCEC_FOUND)
     add_definitions(-DHAVE_LIBCEC)
 endif()
 
+if(VIDEO_HW_DECODING)
+    add_definitions(-DVIDEO_HW_DECODING)
+endif()
+
 # GLM library options.
 add_definitions(-DGLM_FORCE_CXX17)
 add_definitions(-DGLM_FORCE_XYZW_ONLY)
@@ -279,7 +303,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux")
     list(APPEND COMMON_INCLUDE_DIRS ${ALSA_INCLUDE_DIRS})
 endif()
 
-if(DEFINED BCMHOST OR RPI)
+if(RPI_32)
     list(APPEND COMMON_INCLUDE_DIRS "${CMAKE_FIND_ROOT_PATH}/opt/vc/include"
                                     "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vcos"
                                     "${CMAKE_FIND_ROOT_PATH}/opt/vc/include/interface/vmcs_host/linux"
@@ -358,9 +382,6 @@ endif()
 
 # Add libCEC libraries.
 if(DEFINED libCEC_FOUND)
-    if(DEFINED BCMHOST OR RPI)
-        list(APPEND COMMON_LIBRARIES bcm_host vchiq_arm)
-    endif()
     list(APPEND COMMON_LIBRARIES dl ${libCEC_LIBRARIES})
 endif()
 
@@ -369,14 +390,22 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux")
     list(APPEND COMMON_LIBRARIES ${ALSA_LIBRARY})
 endif()
 
+# Raspberry Pi.
 if(DEFINED BCMHOST)
-    link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib")
-    list(APPEND COMMON_LIBRARIES bcm_host brcmEGL ${OPENGLES_LIBRARIES})
-elseif(RPI)
-    link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib")
+    list(APPEND COMMON_LIBRARIES bcm_host vchiq_arm)
+    if(RPI_32)
+        link_directories("${CMAKE_FIND_ROOT_PATH}/opt/vc/lib")
+    endif()
+endif()
+
+# Note: Building with GLES support on the Raspberry Pi currently seems to be broken.
+if(GLES AND RPI_32)
+    list(APPEND COMMON_LIBRARIES brcmEGL ${OPENGLES_LIBRARIES})
+elseif(GLES AND RPI_64)
     list(APPEND COMMON_LIBRARIES ${OPENGLES_LIBRARIES})
 endif()
 
+# OpenGL.
 if(GLSYSTEM MATCHES "Desktop OpenGL")
     list(APPEND COMMON_LIBRARIES ${OPENGL_LIBRARIES})
 else()
diff --git a/es-app/src/guis/GuiMenu.cpp b/es-app/src/guis/GuiMenu.cpp
index 7ca851bb6..acb13f325 100644
--- a/es-app/src/guis/GuiMenu.cpp
+++ b/es-app/src/guis/GuiMenu.cpp
@@ -1010,7 +1010,7 @@ void GuiMenu::openOtherOptions()
     }
 #endif
 
-#if !defined(_RPI_)
+#if defined(VIDEO_HW_DECODING)
     // Whether to enable hardware decoding for the FFmpeg video player.
     auto video_hardware_decoding = std::make_shared<SwitchComponent>(mWindow);
     video_hardware_decoding->setState(Settings::getInstance()->getBool("VideoHardwareDecoding"));
diff --git a/es-core/src/Settings.cpp b/es-core/src/Settings.cpp
index cd3879978..f066a4f76 100644
--- a/es-core/src/Settings.cpp
+++ b/es-core/src/Settings.cpp
@@ -98,7 +98,7 @@ void Settings::setDefaults()
 
     // Scraper.
     mStringMap["Scraper"] = {"screenscraper", "screenscraper"};
-    mBoolMap["ScraperUseAccountScreenScraper"] = {false, false};
+    mBoolMap["ScraperUseAccountScreenScraper"] = {true, true};
     mStringMap["ScraperUsernameScreenScraper"] = {"", ""};
     mStringMap["ScraperPasswordScreenScraper"] = {"", ""};
 
@@ -154,7 +154,11 @@ void Settings::setDefaults()
     // UI settings -> media viewer settings.
     mBoolMap["MediaViewerKeepVideoRunning"] = {true, true};
     mBoolMap["MediaViewerStretchVideos"] = {false, false};
+#if defined(_RPI_)
+    mBoolMap["MediaViewerVideoScanlines"] = {false, false};
+#else
     mBoolMap["MediaViewerVideoScanlines"] = {true, true};
+#endif
     mBoolMap["MediaViewerVideoBlur"] = {false, false};
     mBoolMap["MediaViewerScreenshotScanlines"] = {true, true};
 
@@ -177,7 +181,11 @@ void Settings::setDefaults()
     mIntMap["ScreensaverSwapVideoTimeout"] = {0, 0};
     mBoolMap["ScreensaverStretchVideos"] = {false, false};
     mBoolMap["ScreensaverVideoGameInfo"] = {true, true};
+#if defined(_RPI_)
+    mBoolMap["ScreensaverVideoScanlines"] = {false, false};
+#else
     mBoolMap["ScreensaverVideoScanlines"] = {true, true};
+#endif
     mBoolMap["ScreensaverVideoBlur"] = {false, false};
 
     mBoolMap["MenuBlurBackground"] = {true, true};
@@ -199,8 +207,8 @@ void Settings::setDefaults()
     mBoolMap["EnableMenuKidMode"] = {false, false};
 
     // Sound settings.
-    mIntMap["SoundVolumeNavigation"] = {80, 80};
-    mIntMap["SoundVolumeVideos"] = {100, 100};
+    mIntMap["SoundVolumeNavigation"] = {70, 70};
+    mIntMap["SoundVolumeVideos"] = {80, 80};
     mBoolMap["GamelistVideoAudio"] = {true, true};
     mBoolMap["MediaViewerVideoAudio"] = {true, true};
     mBoolMap["ScreensaverVideoAudio"] = {false, false};
@@ -221,7 +229,7 @@ void Settings::setDefaults()
     // Other settings.
     mStringMap["MediaDirectory"] = {"", ""};
 #if defined(_RPI_)
-    mIntMap["MaxVRAM"] = {80, 80};
+    mIntMap["MaxVRAM"] = {180, 180};
 #else
     mIntMap["MaxVRAM"] = {256, 256};
 #endif
@@ -230,12 +238,7 @@ void Settings::setDefaults()
     mStringMap["FullscreenMode"] = {"normal", "normal"};
 #endif
 #if defined(BUILD_VLC_PLAYER)
-#if defined(_RPI_)
-    // As the FFmpeg video player is not HW accelerated, use VLC as default on this weak device.
-    mStringMap["VideoPlayer"] = {"vlc", "vlc"};
-#else
     mStringMap["VideoPlayer"] = {"ffmpeg", "ffmpeg"};
-#endif
 #endif
     mStringMap["ExitButtonCombo"] = {"F4", "F4"};
     mStringMap["SaveGamelistsMode"] = {"always", "always"};
@@ -246,7 +249,7 @@ void Settings::setDefaults()
 #if defined(_WIN64)
     mBoolMap["LaunchWorkaround"] = {true, true};
 #endif
-#if !defined(_RPI_)
+#if !defined(VIDEO_HW_DECODING)
     mBoolMap["VideoHardwareDecoding"] = {false, false};
 #endif
     mBoolMap["VideoUpscaleFrameRate"] = {false, false};
diff --git a/es-core/src/components/VideoFFmpegComponent.cpp b/es-core/src/components/VideoFFmpegComponent.cpp
index 45c499d67..9bb31a8f3 100644
--- a/es-core/src/components/VideoFFmpegComponent.cpp
+++ b/es-core/src/components/VideoFFmpegComponent.cpp
@@ -1227,7 +1227,7 @@ void VideoFFmpegComponent::startVideo()
 
         // Video stream setup.
 
-#if defined(_RPI_)
+#if !defined(VIDEO_HW_DECODING)
         bool hwDecoding = false;
 #else
         bool hwDecoding = Settings::getInstance()->getBool("VideoHardwareDecoding");