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..1948d90a 100644
--- a/Pages/HostSettingsPage.xaml.cpp
+++ b/Pages/HostSettingsPage.xaml.cpp
@@ -121,6 +121,17 @@ void HostSettingsPage::OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEv
XboxOneGraphsNote->Visibility = Windows::UI::Xaml::Visibility::Collapsed;
}
}
+
+ 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;
+ }
}
void HostSettingsPage::backButton_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
@@ -191,6 +202,20 @@ 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;
+ if (IdrIntervalSlider->Value < 2) {
+ IdrIntervalSlider->Value = 10;
+ }
+ IdrIntervalSlider->Minimum = 2;
+}
+
+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..202dcd42 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 <= 0) ? 0 : (value < 2 ? 2 : 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..0c351228 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 && fps > 0) {
+ if (frame->flags & AV_FRAME_FLAG_KEY) {
+ 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