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