From 9cd2ce1309f67958a3f597603953afcb8342f267 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 1 Dec 2018 14:19:29 -0800 Subject: [PATCH] Add option to unlock FPS --- app/src/main/java/com/limelight/Game.java | 9 +- .../preferences/PreferenceConfiguration.java | 5 + .../limelight/preferences/StreamSettings.java | 134 ++++++++++++++---- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 5 + 5 files changed, 123 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 87b9bbad..e0f3a62c 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -344,8 +344,13 @@ public class Game extends Activity implements SurfaceHolder.Callback, // Hopefully, we can get rid of this once someone comes up with a better way // to track the state of the pipeline and time frames. int roundedRefreshRate = Math.round(displayRefreshRate); - if (!prefConfig.disableFrameDrop && prefConfig.fps >= roundedRefreshRate) { - if (roundedRefreshRate <= 49) { + if ((!prefConfig.disableFrameDrop || prefConfig.unlockFps) && prefConfig.fps >= roundedRefreshRate) { + if (prefConfig.unlockFps) { + // Use frame drops when rendering above the screen frame rate + decoderRenderer.enableLegacyFrameDropRendering(); + LimeLog.info("Using drop mode for FPS > Hz"); + } + else if (roundedRefreshRate <= 49) { // Let's avoid clearly bogus refresh rates and fall back to legacy rendering decoderRenderer.enableLegacyFrameDropRendering(); LimeLog.info("Bogus refresh rate: "+roundedRefreshRate); diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index b7a53b85..397c978a 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -34,6 +34,7 @@ public class PreferenceConfiguration { private static final String BIND_ALL_USB_STRING = "checkbox_usb_bind_all"; private static final String MOUSE_EMULATION_STRING = "checkbox_mouse_emulation"; private static final String MOUSE_NAV_BUTTONS_STRING = "checkbox_mouse_nav_buttons"; + static final String UNLOCK_FPS_STRING = "checkbox_unlock_fps"; static final String DEFAULT_RESOLUTION = "720p"; static final String DEFAULT_FPS = "60"; @@ -56,6 +57,7 @@ public class PreferenceConfiguration { private static final boolean DEFAULT_BIND_ALL_USB = false; private static final boolean DEFAULT_MOUSE_EMULATION = true; private static final boolean DEFAULT_MOUSE_NAV_BUTTONS = false; + private static final boolean DEFAULT_UNLOCK_FPS = false; public static final int FORCE_H265_ON = -1; public static final int AUTOSELECT_H265 = 0; @@ -76,6 +78,7 @@ public class PreferenceConfiguration { public boolean bindAllUsb; public boolean mouseEmulation; public boolean mouseNavButtons; + public boolean unlockFps; private static int getHeightFromResolutionString(String resString) { if (resString.equalsIgnoreCase("360p")) { @@ -208,6 +211,7 @@ public class PreferenceConfiguration { .remove(FPS_PREF_STRING) .remove(VIDEO_FORMAT_PREF_STRING) .remove(ENABLE_HDR_PREF_STRING) + .remove(UNLOCK_FPS_STRING) .apply(); } @@ -309,6 +313,7 @@ public class PreferenceConfiguration { config.bindAllUsb = prefs.getBoolean(BIND_ALL_USB_STRING, DEFAULT_BIND_ALL_USB); config.mouseEmulation = prefs.getBoolean(MOUSE_EMULATION_STRING, DEFAULT_MOUSE_EMULATION); config.mouseNavButtons = prefs.getBoolean(MOUSE_NAV_BUTTONS_STRING, DEFAULT_MOUSE_NAV_BUTTONS); + config.unlockFps = prefs.getBoolean(UNLOCK_FPS_STRING, DEFAULT_UNLOCK_FPS); return config; } diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java index e657c8ca..8f9a3481 100644 --- a/app/src/main/java/com/limelight/preferences/StreamSettings.java +++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java @@ -6,6 +6,7 @@ import android.media.MediaCodecInfo; import android.os.Build; import android.os.Bundle; import android.app.Activity; +import android.os.Handler; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceCategory; @@ -24,6 +25,12 @@ import com.limelight.utils.UiHelper; public class StreamSettings extends Activity { private PreferenceConfiguration previousPrefs; + void reloadSettings() { + getFragmentManager().beginTransaction().replace( + R.id.stream_settings, new SettingsFragment() + ).commit(); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -33,9 +40,7 @@ public class StreamSettings extends Activity { UiHelper.setLocale(this); setContentView(R.layout.activity_stream_settings); - getFragmentManager().beginTransaction().replace( - R.id.stream_settings, new SettingsFragment() - ).commit(); + reloadSettings(); UiHelper.notifyNewRootView(this); } @@ -58,9 +63,17 @@ public class StreamSettings extends Activity { public static class SettingsFragment extends PreferenceFragment { - private static void removeValue(ListPreference pref, String value) { + private void setValue(String preferenceKey, String value) { + ListPreference pref = (ListPreference) findPreference(preferenceKey); + + pref.setValue(value); + } + + private void removeValue(String preferenceKey, String value, Runnable onMatched) { int matchingCount = 0; + ListPreference pref = (ListPreference) findPreference(preferenceKey); + // Count the number of matching entries we'll be removing for (CharSequence seq : pref.getEntryValues()) { if (seq.toString().equalsIgnoreCase(value)) { @@ -83,15 +96,34 @@ public class StreamSettings extends Activity { outIndex++; } + if (pref.getValue().equalsIgnoreCase(value)) { + onMatched.run(); + } + // Update the preference with the new list pref.setEntries(entries); pref.setEntryValues(entryValues); } + + + private void resetBitrateToDefault(SharedPreferences prefs, String res, String fps) { + if (res == null) { + res = prefs.getString(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION); + } + if (fps == null) { + fps = prefs.getString(PreferenceConfiguration.FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS); + } + + prefs.edit() + .putInt(PreferenceConfiguration.BITRATE_PREF_STRING, + PreferenceConfiguration.getDefaultBitrate(res, fps)) + .apply(); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.preferences); PreferenceScreen screen = getPreferenceScreen(); @@ -195,34 +227,69 @@ public class StreamSettings extends Activity { LimeLog.info("Maximum resolution slot: "+maxSupportedResW); - ListPreference resPref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING); if (maxSupportedResW != 0) { if (maxSupportedResW < 3840) { // 4K is unsupported - removeValue(resPref, "4K"); + removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "4K", new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p"); + resetBitrateToDefault(prefs, null, null); + } + }); } if (maxSupportedResW < 2560) { // 1440p is unsupported - removeValue(resPref, "1440p"); + removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1440p", new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p"); + resetBitrateToDefault(prefs, null, null); + } + }); } if (maxSupportedResW < 1920) { // 1080p is unsupported - removeValue(resPref, "1080p"); + removeValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "1080p", new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.RESOLUTION_PREF_STRING, "720p"); + resetBitrateToDefault(prefs, null, null); + } + }); } // Never remove 720p } } - ListPreference fpsPref = (ListPreference) findPreference(PreferenceConfiguration.FPS_PREF_STRING); - // We give some extra room in case the FPS is rounded down - if (maxSupportedFps < 118) { - removeValue(fpsPref, "120"); + if (!PreferenceConfiguration.readPreferences(this.getActivity()).unlockFps) { + // We give some extra room in case the FPS is rounded down + if (maxSupportedFps < 118) { + removeValue(PreferenceConfiguration.FPS_PREF_STRING, "120", new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.FPS_PREF_STRING, "90"); + resetBitrateToDefault(prefs, null, null); + } + }); + } + if (maxSupportedFps < 88) { + // 1080p is unsupported + removeValue(PreferenceConfiguration.FPS_PREF_STRING, "90", new Runnable() { + @Override + public void run() { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + setValue(PreferenceConfiguration.FPS_PREF_STRING, "60"); + resetBitrateToDefault(prefs, null, null); + } + }); + } + // Never remove 30 FPS or 60 FPS } - if (maxSupportedFps < 88) { - // 1080p is unsupported - removeValue(fpsPref, "90"); - } - // Never remove 30 FPS or 60 FPS // Remove HDR preference for devices below Nougat if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { @@ -263,12 +330,7 @@ public class StreamSettings extends Activity { String valueStr = (String) newValue; // Write the new bitrate value - prefs.edit() - .putInt(PreferenceConfiguration.BITRATE_PREF_STRING, - PreferenceConfiguration.getDefaultBitrate(valueStr, - prefs.getString(PreferenceConfiguration.FPS_PREF_STRING, - PreferenceConfiguration.DEFAULT_FPS))) - .apply(); + resetBitrateToDefault(prefs, valueStr, null); // Allow the original preference change to take place return true; @@ -281,12 +343,24 @@ public class StreamSettings extends Activity { String valueStr = (String) newValue; // Write the new bitrate value - prefs.edit() - .putInt(PreferenceConfiguration.BITRATE_PREF_STRING, - PreferenceConfiguration.getDefaultBitrate( - prefs.getString(PreferenceConfiguration.RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION), - valueStr)) - .apply(); + resetBitrateToDefault(prefs, null, valueStr); + + // Allow the original preference change to take place + return true; + } + }); + findPreference(PreferenceConfiguration.UNLOCK_FPS_STRING).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + // HACK: We need to let the preference change succeed before reinitializing to ensure + // it's reflected in the new layout. + final Handler h = new Handler(); + h.postDelayed(new Runnable() { + @Override + public void run() { + ((StreamSettings)SettingsFragment.this.getActivity()).reloadSettings(); + } + }, 500); // Allow the original preference change to take place return true; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a01ed908..ed6f78a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -175,6 +175,8 @@ May reduce micro-stuttering on some devices, but can increase latency Change H.265 settings H.265 lowers video bandwidth requirements but requires a very recent device + Unlock all FPS values + Streaming at 90 or 120 FPS may reduce latency on high-end devices but can cause lag or crashes on devices that can\'t support it Enable HDR (Experimental) Stream HDR when the game and PC GPU support it. HDR requires a GTX 1000 series GPU or later. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 1f450b80..619b098f 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -140,6 +140,11 @@ android:entryValues="@array/video_format_values" android:summary="@string/summary_video_format" android:defaultValue="auto" /> +