From 6adc9dcb2dcc15f24be6de15fb27b35faffa00fb Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 23 Nov 2018 18:41:43 -0800 Subject: [PATCH] Add support for 90/120 FPS streaming and 1440p --- .../preferences/PreferenceConfiguration.java | 228 +++++++++++------- .../limelight/preferences/StreamSettings.java | 63 ++++- app/src/main/res/values/arrays.xml | 39 +-- app/src/main/res/values/strings.xml | 10 +- app/src/main/res/xml/preferences.xml | 11 +- 5 files changed, 234 insertions(+), 117 deletions(-) diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index b1b47b23..8f56cab1 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -7,7 +7,11 @@ import android.os.Build; import android.preference.PreferenceManager; public class PreferenceConfiguration { - static final String RES_FPS_PREF_STRING = "list_resolution_fps"; + private static final String LEGACY_RES_FPS_PREF_STRING = "list_resolution_fps"; + + + static final String RESOLUTION_PREF_STRING = "list_resolution"; + static final String FPS_PREF_STRING = "list_fps"; static final String BITRATE_PREF_STRING = "seekbar_bitrate_kbps"; private static final String BITRATE_PREF_OLD_STRING = "seekbar_bitrate"; private static final String STRETCH_PREF_STRING = "checkbox_stretch_video"; @@ -30,17 +34,8 @@ 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 int BITRATE_DEFAULT_360_30 = 1000; - private static final int BITRATE_DEFAULT_360_60 = 2000; - private static final int BITRATE_DEFAULT_720_30 = 5000; - private static final int BITRATE_DEFAULT_720_60 = 10000; - private static final int BITRATE_DEFAULT_1080_30 = 10000; - private static final int BITRATE_DEFAULT_1080_60 = 20000; - private static final int BITRATE_DEFAULT_4K_30 = 40000; - private static final int BITRATE_DEFAULT_4K_60 = 80000; - - private static final String DEFAULT_RES_FPS = "720p60"; - private static final int DEFAULT_BITRATE = BITRATE_DEFAULT_720_60; + static final String DEFAULT_RESOLUTION = "720p"; + static final String DEFAULT_FPS = "60"; private static final boolean DEFAULT_STRETCH = false; private static final boolean DEFAULT_SOPS = true; private static final boolean DEFAULT_DISABLE_TOASTS = false; @@ -54,7 +49,6 @@ public class PreferenceConfiguration { private static final String DEFAULT_VIDEO_FORMAT = "auto"; private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false; private static final boolean ONLY_L3_R3_DEFAULT = false; - private static final boolean DEFAULT_BATTERY_SAVER = false; private static final boolean DEFAULT_DISABLE_FRAME_DROP = false; private static final boolean DEFAULT_ENABLE_HDR = false; private static final boolean DEFAULT_ENABLE_PIP = false; @@ -80,34 +74,77 @@ public class PreferenceConfiguration { public boolean bindAllUsb; public boolean mouseEmulation; - public static int getDefaultBitrate(String resFpsString) { - if (resFpsString.equals("360p30")) { - return BITRATE_DEFAULT_360_30; + private static int getHeightFromResolutionString(String resString) { + if (resString.equalsIgnoreCase("360p")) { + return 360; } - else if (resFpsString.equals("360p60")) { - return BITRATE_DEFAULT_360_60; + else if (resString.equalsIgnoreCase("720p")) { + return 720; } - else if (resFpsString.equals("720p30")) { - return BITRATE_DEFAULT_720_30; + else if (resString.equalsIgnoreCase("1080p")) { + return 1080; } - else if (resFpsString.equals("720p60")) { - return BITRATE_DEFAULT_720_60; + else if (resString.equalsIgnoreCase("1440p")) { + return 1440; } - else if (resFpsString.equals("1080p30")) { - return BITRATE_DEFAULT_1080_30; - } - else if (resFpsString.equals("1080p60")) { - return BITRATE_DEFAULT_1080_60; - } - else if (resFpsString.equals("4K30")) { - return BITRATE_DEFAULT_4K_30; - } - else if (resFpsString.equals("4K60")) { - return BITRATE_DEFAULT_4K_60; + else if (resString.equalsIgnoreCase("4K")) { + return 3840; } else { - // Should never get here - return DEFAULT_BITRATE; + // Should be unreachable + return 720; + } + } + + private static int getWidthFromResolutionString(String resString) { + return (getHeightFromResolutionString(resString) * 16) / 9; + } + + private static String getResolutionString(int width, int height) { + switch (height) { + case 360: + return "360p"; + default: + case 720: + return "720p"; + case 1080: + return "1080p"; + case 1440: + return "1440p"; + case 3840: + return "4K"; + + } + } + + public static int getDefaultBitrate(String resString, String fpsString) { + int width = getWidthFromResolutionString(resString); + int height = getHeightFromResolutionString(resString); + int fps = Integer.parseInt(fpsString); + + // This table prefers 16:10 resolutions because they are + // only slightly more pixels than the 16:9 equivalents, so + // we don't want to bump those 16:10 resolutions up to the + // next 16:9 slot. + // + // This logic is shamelessly stolen from Moonlight Qt: + // https://github.com/moonlight-stream/moonlight-qt/blob/master/app/settings/streamingpreferences.cpp + + if (width * height <= 640 * 360) { + return (int)(1000 * (fps / 30.0)); + } + // This covers 1280x720 and 1280x800 too + else if (width * height <= 1366 * 768) { + return (int)(5000 * (fps / 30.0)); + } + else if (width * height <= 1920 * 1200) { + return (int)(10000 * (fps / 30.0)); + } + else if (width * height <= 2560 * 1600) { + return (int)(20000 * (fps / 30.0)); + } + else /* if (width * height <= 3840 * 2160) */ { + return (int)(40000 * (fps / 30.0)); } } @@ -133,7 +170,9 @@ public class PreferenceConfiguration { public static int getDefaultBitrate(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - return getDefaultBitrate(prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS)); + return getDefaultBitrate( + prefs.getString(RESOLUTION_PREF_STRING, DEFAULT_RESOLUTION), + prefs.getString(FPS_PREF_STRING, DEFAULT_FPS)); } private static int getVideoFormatValue(Context context) { @@ -161,7 +200,9 @@ public class PreferenceConfiguration { prefs.edit() .remove(BITRATE_PREF_STRING) .remove(BITRATE_PREF_OLD_STRING) - .remove(RES_FPS_PREF_STRING) + .remove(LEGACY_RES_FPS_PREF_STRING) + .remove(RESOLUTION_PREF_STRING) + .remove(FPS_PREF_STRING) .remove(VIDEO_FORMAT_PREF_STRING) .remove(ENABLE_HDR_PREF_STRING) .apply(); @@ -171,59 +212,76 @@ public class PreferenceConfiguration { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); PreferenceConfiguration config = new PreferenceConfiguration(); + // Migrate legacy preferences to the new locations + String str = prefs.getString(LEGACY_RES_FPS_PREF_STRING, null); + if (str != null) { + if (str.equals("360p30")) { + config.width = 640; + config.height = 360; + config.fps = 30; + } + else if (str.equals("360p60")) { + config.width = 640; + config.height = 360; + config.fps = 60; + } + else if (str.equals("720p30")) { + config.width = 1280; + config.height = 720; + config.fps = 30; + } + else if (str.equals("720p60")) { + config.width = 1280; + config.height = 720; + config.fps = 60; + } + else if (str.equals("1080p30")) { + config.width = 1920; + config.height = 1080; + config.fps = 30; + } + else if (str.equals("1080p60")) { + config.width = 1920; + config.height = 1080; + config.fps = 60; + } + else if (str.equals("4K30")) { + config.width = 3840; + config.height = 2160; + config.fps = 30; + } + else if (str.equals("4K60")) { + config.width = 3840; + config.height = 2160; + config.fps = 60; + } + else { + // Should never get here + config.width = 1280; + config.height = 720; + config.fps = 60; + } + + prefs.edit() + .remove(LEGACY_RES_FPS_PREF_STRING) + .putString(RESOLUTION_PREF_STRING, getResolutionString(config.width, config.height)) + .putString(FPS_PREF_STRING, ""+config.fps) + .apply(); + } + else { + // Use the new preference location + String resStr = prefs.getString(RESOLUTION_PREF_STRING, PreferenceConfiguration.DEFAULT_RESOLUTION); + config.width = PreferenceConfiguration.getWidthFromResolutionString(resStr); + config.height = PreferenceConfiguration.getHeightFromResolutionString(resStr); + config.fps = Integer.parseInt(prefs.getString(FPS_PREF_STRING, PreferenceConfiguration.DEFAULT_FPS)); + } + + // This must happen after the preferences migration to ensure the preferences are populated config.bitrate = prefs.getInt(BITRATE_PREF_STRING, prefs.getInt(BITRATE_PREF_OLD_STRING, 0) * 1000); if (config.bitrate == 0) { config.bitrate = getDefaultBitrate(context); } - String str = prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS); - if (str.equals("360p30")) { - config.width = 640; - config.height = 360; - config.fps = 30; - } - else if (str.equals("360p60")) { - config.width = 640; - config.height = 360; - config.fps = 60; - } - else if (str.equals("720p30")) { - config.width = 1280; - config.height = 720; - config.fps = 30; - } - else if (str.equals("720p60")) { - config.width = 1280; - config.height = 720; - config.fps = 60; - } - else if (str.equals("1080p30")) { - config.width = 1920; - config.height = 1080; - config.fps = 30; - } - else if (str.equals("1080p60")) { - config.width = 1920; - config.height = 1080; - config.fps = 60; - } - else if (str.equals("4K30")) { - config.width = 3840; - config.height = 2160; - config.fps = 30; - } - else if (str.equals("4K60")) { - config.width = 3840; - config.height = 2160; - config.fps = 60; - } - else { - // Should never get here - config.width = 1280; - config.height = 720; - config.fps = 60; - } - config.videoFormat = getVideoFormatValue(context); config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE); diff --git a/app/src/main/java/com/limelight/preferences/StreamSettings.java b/app/src/main/java/com/limelight/preferences/StreamSettings.java index 9135f8d1..e657c8ca 100644 --- a/app/src/main/java/com/limelight/preferences/StreamSettings.java +++ b/app/src/main/java/com/limelight/preferences/StreamSettings.java @@ -58,12 +58,12 @@ public class StreamSettings extends Activity { public static class SettingsFragment extends PreferenceFragment { - private static void removeResolution(ListPreference pref, String prefix) { + private static void removeValue(ListPreference pref, String value) { int matchingCount = 0; // Count the number of matching entries we'll be removing for (CharSequence seq : pref.getEntryValues()) { - if (seq.toString().startsWith(prefix)) { + if (seq.toString().equalsIgnoreCase(value)) { matchingCount++; } } @@ -73,8 +73,8 @@ public class StreamSettings extends Activity { CharSequence[] entryValues = new CharSequence[pref.getEntryValues().length-matchingCount]; int outIndex = 0; for (int i = 0; i < pref.getEntryValues().length; i++) { - if (pref.getEntryValues()[i].toString().startsWith(prefix)) { - // Skip matching prefixes + if (pref.getEntryValues()[i].toString().equalsIgnoreCase(value)) { + // Skip matching values continue; } @@ -110,6 +110,8 @@ public class StreamSettings extends Activity { category.removePreference(findPreference("checkbox_enable_pip")); } + int maxSupportedFps = 0; + // Hide non-supported resolution/FPS combinations if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Display display = getActivity().getWindowManager().getDefaultDisplay(); @@ -134,9 +136,16 @@ public class StreamSettings extends Activity { if ((width >= 3840 || height >= 2160) && maxSupportedResW < 3840) { maxSupportedResW = 3840; } + else if ((width >= 2560 || height >= 1440) && maxSupportedResW < 2560) { + maxSupportedResW = 2560; + } else if ((width >= 1920 || height >= 1080) && maxSupportedResW < 1920) { maxSupportedResW = 1920; } + + if (candidate.getRefreshRate() > maxSupportedFps) { + maxSupportedFps = (int)candidate.getRefreshRate(); + } } // This must be called to do runtime initialization before calling functions that evaluate @@ -186,20 +195,35 @@ public class StreamSettings extends Activity { LimeLog.info("Maximum resolution slot: "+maxSupportedResW); - ListPreference resPref = (ListPreference) findPreference("list_resolution_fps"); + ListPreference resPref = (ListPreference) findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING); if (maxSupportedResW != 0) { if (maxSupportedResW < 3840) { // 4K is unsupported - removeResolution(resPref, "4K"); + removeValue(resPref, "4K"); + } + if (maxSupportedResW < 2560) { + // 1440p is unsupported + removeValue(resPref, "1440p"); } if (maxSupportedResW < 1920) { // 1080p is unsupported - removeResolution(resPref, "1080p"); + removeValue(resPref, "1080p"); } // 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 (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) { LimeLog.info("Excluding HDR toggle based on OS"); @@ -232,8 +256,7 @@ public class StreamSettings extends Activity { // Add a listener to the FPS and resolution preference // so the bitrate can be auto-adjusted - Preference pref = findPreference(PreferenceConfiguration.RES_FPS_PREF_STRING); - pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + findPreference(PreferenceConfiguration.RESOLUTION_PREF_STRING).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); @@ -242,7 +265,27 @@ public class StreamSettings extends Activity { // Write the new bitrate value prefs.edit() .putInt(PreferenceConfiguration.BITRATE_PREF_STRING, - PreferenceConfiguration.getDefaultBitrate(valueStr)) + PreferenceConfiguration.getDefaultBitrate(valueStr, + prefs.getString(PreferenceConfiguration.FPS_PREF_STRING, + PreferenceConfiguration.DEFAULT_FPS))) + .apply(); + + // Allow the original preference change to take place + return true; + } + }); + findPreference(PreferenceConfiguration.FPS_PREF_STRING).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SettingsFragment.this.getActivity()); + 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(); // Allow the original preference change to take place diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 9a30df7d..4a98174f 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,24 +1,31 @@ - 360p 30 FPS - 360p 60 FPS - 720p 30 FPS - 720p 60 FPS - 1080p 30 FPS - 1080p 60 FPS - 4K 30 FPS - 4K 60 FPS + 360p + 720p + 1080p + 1440p + 4K - 360p30 - 360p60 - 720p30 - 720p60 - 1080p30 - 1080p60 - 4K30 - 4K60 + 360p + 720p + 1080p + 1440p + 4K + + + + 30 FPS + 60 FPS + 90 FPS + 120 FPS + + + 30 + 60 + 90 + 120 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 71e4561d..93181ade 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -114,10 +114,12 @@ Basic Settings - Select resolution and FPS target - Setting values too high for your device may cause lag or crashing - Select target video bitrate - Lower bitrate to reduce stuttering. Raise bitrate to increase image quality. + Video resolution + Increase to improve image clarity. Decrease for better performance on lower end devices and slower networks. + Video frame rate + Increase for a smoother video stream. Decrease for better performance on lower end devices. + Video bitrate + Increase for better image quality. Decrease to improve performance on slower connections. Kbps Stretch video to full-screen Disable warning messages diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8e6c7cd6..993c5048 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -5,12 +5,19 @@ + android:defaultValue="720p" /> +