From d9780407088cc30f317974f1b12e432b8b50a82a Mon Sep 17 00:00:00 2001 From: Jeremy Nieth Date: Sun, 10 May 2026 15:14:46 +0200 Subject: [PATCH 1/8] Add periodic intra refreshes to mitigate HEVCerrors --- Pages/AppPage.xaml.cpp | 1 + Pages/HostSettingsPage.xaml | 25 +++++++++++++++++-------- Pages/HostSettingsPage.xaml.cpp | 21 +++++++++++++++++++++ Pages/HostSettingsPage.xaml.h | 2 ++ State/ApplicationState.cpp | 2 ++ State/MoonlightClient.cpp | 2 +- State/MoonlightHost.h | 10 ++++++++++ State/StreamConfiguration.h | 1 + Streaming/FFmpegDecoder.cpp | 20 +++++++++++++++++--- Streaming/FFmpegDecoder.h | 5 +++-- 10 files changed, 75 insertions(+), 14 deletions(-) diff --git a/Pages/AppPage.xaml.cpp b/Pages/AppPage.xaml.cpp index 2558f454..18040020 100644 --- a/Pages/AppPage.xaml.cpp +++ b/Pages/AppPage.xaml.cpp @@ -171,6 +171,7 @@ void AppPage::Connect(int appId) { config->framePacing = host->FramePacing; config->enableStats = host->EnableStats; config->enableGraphs = host->EnableGraphs; + config->idrInterval = host->IdrInterval; if (config->enableHDR) { host->VideoCodec = "HEVC (H.265)"; } diff --git a/Pages/HostSettingsPage.xaml b/Pages/HostSettingsPage.xaml index ae0f31fa..3a75243d 100644 --- a/Pages/HostSettingsPage.xaml +++ b/Pages/HostSettingsPage.xaml @@ -40,6 +40,8 @@ + + Resolution @@ -90,26 +92,33 @@ Best for Xbox One. Locks rendering frame rate to refresh rate and evenly spaces frames. - Show performance stats: + Periodic decoder refresh: + + Mitigates HEVC corruption. Refreshes every (Target FPS)*(Interval) frames. + + Refresh interval (Seconds) + + + Show performance stats: - Show performance graphs: + Show performance graphs: + Name="XboxOneGraphsNote" Grid.Row="13" Grid.Column="1" Grid.ColumnSpan="2" Visibility="Collapsed"> Graphs are unavailable on Xbox One when system resolution is set to 4K. - Other: - - + Other: + + diff --git a/Pages/HostSettingsPage.xaml.cpp b/Pages/HostSettingsPage.xaml.cpp index 0aeaa137..dc7be1a7 100644 --- a/Pages/HostSettingsPage.xaml.cpp +++ b/Pages/HostSettingsPage.xaml.cpp @@ -121,6 +121,15 @@ void HostSettingsPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEv XboxOneGraphsNote->Visibility = Windows::UI::Xaml::Visibility::Collapsed; } } + + if (host->IdrInterval > 0) { + EnableIdrIntervalCheckbox->IsChecked = true; + IdrIntervalSlider->IsEnabled = true; + } else { + EnableIdrIntervalCheckbox->IsChecked = false; + IdrIntervalSlider->Value = 0; + IdrIntervalSlider->IsEnabled = false; + } } void HostSettingsPage::backButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) @@ -191,6 +200,18 @@ void HostSettingsPage::FramePacing_SelectionChanged(Platform::Object^ sender, Wi host->FramePacing = selectedFramePacing; } +void HostSettingsPage::EnableIdrIntervalCheckbox_Checked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) { + IdrIntervalSlider->IsEnabled = true; + IdrIntervalSlider->Minimum = 2; + IdrIntervalSlider->Value = 10; +} + +void HostSettingsPage::EnableIdrIntervalCheckbox_Unchecked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) { + IdrIntervalSlider->IsEnabled = false; + IdrIntervalSlider->Minimum = 0; + IdrIntervalSlider->Value = 0; +} + void HostSettingsPage::GlobalSettingsOption_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e) { this->Frame->Navigate(Windows::UI::Xaml::Interop::TypeName(MoonlightSettings::typeid)); diff --git a/Pages/HostSettingsPage.xaml.h b/Pages/HostSettingsPage.xaml.h index bee0ac74..37dcbe15 100644 --- a/Pages/HostSettingsPage.xaml.h +++ b/Pages/HostSettingsPage.xaml.h @@ -110,6 +110,8 @@ namespace moonlight_xbox_dx void BitrateInput_TextChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::TextChangedEventArgs^ e); void AutoStartSelector_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e); void FramePacing_SelectionChanged(Platform::Object^ sender, Windows::UI::Xaml::Controls::SelectionChangedEventArgs^ e); + void EnableIdrIntervalCheckbox_Checked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); + void EnableIdrIntervalCheckbox_Unchecked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e); void GlobalSettingsOption_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); void BitrateInput_KeyDown(Platform::Object^ sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs^ e); void OnLoaded(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); diff --git a/State/ApplicationState.cpp b/State/ApplicationState.cpp index 611792ca..02e0212a 100644 --- a/State/ApplicationState.cpp +++ b/State/ApplicationState.cpp @@ -51,6 +51,7 @@ Concurrency::task moonlight_xbox_dx::ApplicationState::Init() if (a.contains("serverAddress")) h->ServerAddress = Utils::StringFromStdString(a["serverAddress"].get()); if (a.contains("macaddress")) h->MacAddress = Utils::StringFromStdString(a["macaddress"].get()); else h->ComputerName = h->LastHostname; + if (a.contains("idr_interval")) h->IdrInterval = a["idr_interval"]; this->SavedHosts->Append(h); } } @@ -109,6 +110,7 @@ Concurrency::task moonlight_xbox_dx::ApplicationState::UpdateFile() hostJson["enable_stats"] = host->EnableStats; hostJson["enable_graphs"] = host->EnableGraphs; hostJson["serverAddress"] = Utils::PlatformStringToStdString(host->ServerAddress); + hostJson["idr_interval"] = host->IdrInterval; std::string macAddr = Utils::PlatformStringToStdString(host->MacAddress); if (macAddr != "00:00:00:00:00:00" && macAddr != "") diff --git a/State/MoonlightClient.cpp b/State/MoonlightClient.cpp index 67731e5a..227e8162 100644 --- a/State/MoonlightClient.cpp +++ b/State/MoonlightClient.cpp @@ -314,7 +314,7 @@ int MoonlightClient::StartStreaming(std::shared_ptr res, St callbacks.rumble = connection_rumble; callbacks.rumbleTriggers = connection_trigger_rumble; - FFMpegDecoder::instance().CompleteInitialization(res, &config, sConfig->framePacing == "Immediate"); + FFMpegDecoder::instance().CompleteInitialization(res, &config, sConfig->framePacing == "Immediate", sConfig->idrInterval); DECODER_RENDERER_CALLBACKS rCallbacks = FFMpegDecoder::getDecoder(); AUDIO_RENDERER_CALLBACKS aCallbacks = AudioPlayer::getDecoder(); diff --git a/State/MoonlightHost.h b/State/MoonlightHost.h index 52c2e126..620f6f6d 100644 --- a/State/MoonlightHost.h +++ b/State/MoonlightHost.h @@ -31,6 +31,7 @@ namespace moonlight_xbox_dx { bool enableSOPS = false; bool enableStats = false; bool enableGraphs = true; + int idrInterval = 0; Windows::Foundation::Collections::IVector^ apps; public: //Thanks to https://phsucharee.wordpress.com/2013/06/19/data-binding-and-ccx-inotifypropertychanged/ @@ -284,5 +285,14 @@ namespace moonlight_xbox_dx { OnPropertyChanged("EnableGraphs"); } } + + property int IdrInterval + { + int get() { return this->idrInterval; } + void set(int value) { + this->idrInterval = value; + OnPropertyChanged("IdrInterval"); + } + } }; } diff --git a/State/StreamConfiguration.h b/State/StreamConfiguration.h index f0baa3c6..30a9b7e5 100644 --- a/State/StreamConfiguration.h +++ b/State/StreamConfiguration.h @@ -21,6 +21,7 @@ namespace moonlight_xbox_dx property bool enableSOPS; property bool enableStats; property bool enableGraphs; + property int idrInterval; }; moonlight_xbox_dx::StreamConfiguration^ GetStreamConfig(); diff --git a/Streaming/FFmpegDecoder.cpp b/Streaming/FFmpegDecoder.cpp index 1a483fb8..c4f58315 100644 --- a/Streaming/FFmpegDecoder.cpp +++ b/Streaming/FFmpegDecoder.cpp @@ -85,9 +85,10 @@ namespace moonlight_xbox_dx { Utils::Logf(shouldPrefixThisMessage ? "[ffmpeg] %s" : "%s", lineBuffer); } - void FFMpegDecoder::CompleteInitialization(const std::shared_ptr& res, STREAM_CONFIGURATION *config, bool framePacingImmediate) { + void FFMpegDecoder::CompleteInitialization(const std::shared_ptr& res, STREAM_CONFIGURATION *config, bool framePacingImmediate, int idrInterval) { this->m_deviceResources = res; this->fps = config->fps; + this->idrInterval = idrInterval; Pacer::instance().init(res, config->fps, res->GetRefreshRate(), framePacingImmediate); } @@ -95,12 +96,11 @@ namespace moonlight_xbox_dx { this->videoFormat = videoFormat; this->width = width; this->height = height; - this->fps = 60; // correctly set in CompleteInitialization this->m_LastFrameNumber = 0; this->ffmpeg_buffer_size = 0; this->m_StreamEpochQpc = 0; - + this->m_FramesSinceIDR = 0; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,10,100) avcodec_register_all(); @@ -280,6 +280,20 @@ namespace moonlight_xbox_dx { return DR_NEED_IDR; } + // Request new IDR frames periodically to mitigate stream corruption. + if (idrInterval > 0) { + if (frame->pict_type == AV_PICTURE_TYPE_I) { + m_FramesSinceIDR = 0; + } else { + m_FramesSinceIDR++; + } + + if (m_FramesSinceIDR >= (fps * idrInterval)) { + LiRequestIdrFrame(); + m_FramesSinceIDR = 0; // avoid requesting multiple IDR frames in a row + } + } + // Capture a frame timestamp to measuring pacing delay QueryPerformanceCounter(&decodeEnd); frame_attach_userdata(frame, decodeEnd.QuadPart); diff --git a/Streaming/FFmpegDecoder.h b/Streaming/FFmpegDecoder.h index 38ea1baf..81a45406 100644 --- a/Streaming/FFmpegDecoder.h +++ b/Streaming/FFmpegDecoder.h @@ -30,13 +30,13 @@ class FFMpegDecoder { // Singleton accessor static FFMpegDecoder &instance(); - void CompleteInitialization(const std::shared_ptr &res, STREAM_CONFIGURATION *config, bool framePacingImmediate); + void CompleteInitialization(const std::shared_ptr &res, STREAM_CONFIGURATION *config, bool framePacingImmediate, int idrInterval); int Init(int videoFormat, int width, int height, int redrawRate, void *context, int drFlags); void Cleanup(); int SubmitDecodeUnit(PDECODE_UNIT decodeUnit); static FFMpegDecoder *getInstance(); static DECODER_RENDERER_CALLBACKS getDecoder(); - int videoFormat, width, height, fps; + int videoFormat, width, height, fps, idrInterval; std::recursive_mutex m_mutex; // locking helper @@ -74,5 +74,6 @@ class FFMpegDecoder { std::shared_ptr m_deviceResources; int m_LastFrameNumber; int64_t m_StreamEpochQpc; + int m_FramesSinceIDR; }; } // namespace moonlight_xbox_dx From 5a1fd3029be932ead7586e23967e0d9932d875fe Mon Sep 17 00:00:00 2001 From: "Jeremy N." Date: Sun, 10 May 2026 15:59:17 +0200 Subject: [PATCH 2/8] Fix indentation in FFmpegDecoder.cpp Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Streaming/FFmpegDecoder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Streaming/FFmpegDecoder.cpp b/Streaming/FFmpegDecoder.cpp index c4f58315..d4515f72 100644 --- a/Streaming/FFmpegDecoder.cpp +++ b/Streaming/FFmpegDecoder.cpp @@ -85,10 +85,10 @@ namespace moonlight_xbox_dx { Utils::Logf(shouldPrefixThisMessage ? "[ffmpeg] %s" : "%s", lineBuffer); } - void FFMpegDecoder::CompleteInitialization(const std::shared_ptr& res, STREAM_CONFIGURATION *config, bool framePacingImmediate, int idrInterval) { + void FFMpegDecoder::CompleteInitialization(const std::shared_ptr& res, STREAM_CONFIGURATION *config, bool framePacingImmediate, int idrInterval) { this->m_deviceResources = res; this->fps = config->fps; - this->idrInterval = idrInterval; + this->idrInterval = idrInterval; Pacer::instance().init(res, config->fps, res->GetRefreshRate(), framePacingImmediate); } From 72cf7a229437c28a7ab34c3edcd1001da3473a70 Mon Sep 17 00:00:00 2001 From: Jeremy Nieth Date: Sun, 10 May 2026 17:19:01 +0200 Subject: [PATCH 3/8] Set IdrIntervalSlider minimum when enabled --- Pages/HostSettingsPage.xaml.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Pages/HostSettingsPage.xaml.cpp b/Pages/HostSettingsPage.xaml.cpp index dc7be1a7..b63ed80b 100644 --- a/Pages/HostSettingsPage.xaml.cpp +++ b/Pages/HostSettingsPage.xaml.cpp @@ -124,9 +124,11 @@ void HostSettingsPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEv if (host->IdrInterval > 0) { EnableIdrIntervalCheckbox->IsChecked = true; + IdrIntervalSlider->Minimum = 2; IdrIntervalSlider->IsEnabled = true; } else { EnableIdrIntervalCheckbox->IsChecked = false; + IdrIntervalSlider->Minimum = 0; IdrIntervalSlider->Value = 0; IdrIntervalSlider->IsEnabled = false; } From 5e8ba614eed4606bc6145ebd37e1b761290f81b7 Mon Sep 17 00:00:00 2001 From: Jeremy Nieth Date: Sun, 10 May 2026 17:29:00 +0200 Subject: [PATCH 4/8] Clamp IdrInterval to valid values in the property setter --- State/MoonlightHost.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/State/MoonlightHost.h b/State/MoonlightHost.h index 620f6f6d..202dcd42 100644 --- a/State/MoonlightHost.h +++ b/State/MoonlightHost.h @@ -290,7 +290,7 @@ namespace moonlight_xbox_dx { { int get() { return this->idrInterval; } void set(int value) { - this->idrInterval = value; + this->idrInterval = (value <= 0) ? 0 : (value < 2 ? 2 : value); OnPropertyChanged("IdrInterval"); } } From a075b6593fb92ce094ef099840ddd2dd9d2fdd4a Mon Sep 17 00:00:00 2001 From: Jeremy Nieth Date: Sun, 10 May 2026 18:26:05 +0200 Subject: [PATCH 5/8] Detect key frames using AV_FRAME_FLAG_KEY --- Streaming/FFmpegDecoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming/FFmpegDecoder.cpp b/Streaming/FFmpegDecoder.cpp index d4515f72..57eab161 100644 --- a/Streaming/FFmpegDecoder.cpp +++ b/Streaming/FFmpegDecoder.cpp @@ -282,7 +282,7 @@ namespace moonlight_xbox_dx { // Request new IDR frames periodically to mitigate stream corruption. if (idrInterval > 0) { - if (frame->pict_type == AV_PICTURE_TYPE_I) { + if (frame->flags == AV_FRAME_FLAG_KEY) { m_FramesSinceIDR = 0; } else { m_FramesSinceIDR++; From 22c0f77e3b1563d5bc8f814b886598d78ff6e59b Mon Sep 17 00:00:00 2001 From: Jeremy Nieth Date: Sun, 10 May 2026 18:31:32 +0200 Subject: [PATCH 6/8] Use bitwise check for AV_FRAME_FLAG_KEY --- Streaming/FFmpegDecoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming/FFmpegDecoder.cpp b/Streaming/FFmpegDecoder.cpp index 57eab161..a384d0cf 100644 --- a/Streaming/FFmpegDecoder.cpp +++ b/Streaming/FFmpegDecoder.cpp @@ -282,7 +282,7 @@ namespace moonlight_xbox_dx { // Request new IDR frames periodically to mitigate stream corruption. if (idrInterval > 0) { - if (frame->flags == AV_FRAME_FLAG_KEY) { + if (frame->flags & AV_FRAME_FLAG_KEY) { m_FramesSinceIDR = 0; } else { m_FramesSinceIDR++; From efcd34582816f1a6ef3707f3a13d9fbd94ad553e Mon Sep 17 00:00:00 2001 From: Jeremy Nieth Date: Sun, 10 May 2026 18:34:01 +0200 Subject: [PATCH 7/8] Guard periodic IDR logic to valid FPS --- Streaming/FFmpegDecoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streaming/FFmpegDecoder.cpp b/Streaming/FFmpegDecoder.cpp index a384d0cf..0c351228 100644 --- a/Streaming/FFmpegDecoder.cpp +++ b/Streaming/FFmpegDecoder.cpp @@ -281,7 +281,7 @@ namespace moonlight_xbox_dx { } // Request new IDR frames periodically to mitigate stream corruption. - if (idrInterval > 0) { + if (idrInterval > 0 && fps > 0) { if (frame->flags & AV_FRAME_FLAG_KEY) { m_FramesSinceIDR = 0; } else { From e0038980968da3841054c0c0789c4fe6c32d13de Mon Sep 17 00:00:00 2001 From: Jeremy Nieth Date: Sun, 10 May 2026 18:49:12 +0200 Subject: [PATCH 8/8] Don't override IDR slider value when a value was already set --- Pages/HostSettingsPage.xaml.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Pages/HostSettingsPage.xaml.cpp b/Pages/HostSettingsPage.xaml.cpp index b63ed80b..1948d90a 100644 --- a/Pages/HostSettingsPage.xaml.cpp +++ b/Pages/HostSettingsPage.xaml.cpp @@ -204,8 +204,10 @@ void HostSettingsPage::FramePacing_SelectionChanged(Platform::Object^ sender, Wi void HostSettingsPage::EnableIdrIntervalCheckbox_Checked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) { IdrIntervalSlider->IsEnabled = true; + if (IdrIntervalSlider->Value < 2) { + IdrIntervalSlider->Value = 10; + } IdrIntervalSlider->Minimum = 2; - IdrIntervalSlider->Value = 10; } void HostSettingsPage::EnableIdrIntervalCheckbox_Unchecked(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e) {