diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 042bffaf..bc3c30de 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -411,11 +411,14 @@ public class Game extends Activity implements SurfaceHolder.Callback, } // Display a message to the user if HEVC was forced on but we still didn't find a decoder - if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) { - Toast.makeText(this, "No HEVC decoder found.\nFalling back to H.264.", Toast.LENGTH_LONG).show(); + if (prefConfig.hevcFormat == PreferenceConfiguration.FormatOption.FORCE_ON && !decoderRenderer.isHevcSupported()) { + Toast.makeText(this, "No HEVC decoder found", Toast.LENGTH_LONG).show(); } - // TODO: Display a message to the user if HEVC was forced on but we still didn't find a decoder + // Display a message to the user if AV1 was forced on but we still didn't find a decoder + if (prefConfig.av1Format == PreferenceConfiguration.FormatOption.FORCE_ON && !decoderRenderer.isAv1Supported()) { + Toast.makeText(this, "No AV1 decoder found", Toast.LENGTH_LONG).show(); + } // H.264 is always supported int supportedVideoFormats = MoonBridge.VIDEO_FORMAT_H264; diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java index fb6eb8a9..b6e6a4bc 100644 --- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java +++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java @@ -193,7 +193,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) { // Don't return anything if HEVC is forced off - if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) { + if (prefs.hevcFormat == PreferenceConfiguration.FormatOption.FORCE_OFF) { return null; } @@ -208,7 +208,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+hevcDecoderInfo.getName()); // Force HEVC enabled if the user asked for it - if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) { + if (prefs.hevcFormat == PreferenceConfiguration.FormatOption.FORCE_ON) { LimeLog.info("Forcing HEVC enabled despite non-whitelisted decoder"); } // HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR. @@ -233,7 +233,27 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C } private MediaCodecInfo findAv1Decoder(PreferenceConfiguration prefs) { - return MediaCodecHelper.findProbableSafeDecoder("video/av01", -1); + // Don't return anything if AV1 is forced off + if (prefs.av1Format == PreferenceConfiguration.FormatOption.FORCE_OFF) { + return null; + } + + MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/av01", -1); + if (decoderInfo != null) { + if (!MediaCodecHelper.isDecoderWhitelistedForAv1(decoderInfo)) { + LimeLog.info("Found AV1 decoder, but it's not whitelisted - "+decoderInfo.getName()); + + // Force HEVC enabled if the user asked for it + if (prefs.av1Format == PreferenceConfiguration.FormatOption.FORCE_ON) { + LimeLog.info("Forcing AV1 enabled despite non-whitelisted decoder"); + } + else { + return null; + } + } + } + + return decoderInfo; } public void setRenderTarget(SurfaceHolder renderTarget) { diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java b/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java index a80afb0c..78f07e7d 100644 --- a/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java +++ b/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java @@ -747,7 +747,33 @@ public class MediaCodecHelper { // Otherwise, we use our list of known working HEVC decoders return isDecoderInList(whitelistedHevcDecoders, decoderInfo.getName()); } - + + public static boolean isDecoderWhitelistedForAv1(MediaCodecInfo decoderInfo) { + // Google didn't have official support for AV1 (or more importantly, a CTS test) until + // Android 10, so don't use any decoder before then. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return false; + } + + // + // Software decoders are terrible and we never want to use them. + // We want to catch decoders like: + // OMX.qcom.video.decoder.hevcswvdec + // OMX.SEC.hevc.sw.dec + // + if (decoderInfo.getName().contains("sw")) { + LimeLog.info("Disallowing AV1 on software decoder: " + decoderInfo.getName()); + return false; + } + else if (!decoderInfo.isHardwareAccelerated() || decoderInfo.isSoftwareOnly()) { + LimeLog.info("Disallowing AV1 on software decoder: " + decoderInfo.getName()); + return false; + } + + // TODO: Test some AV1 decoders + return false; + } + @SuppressWarnings("deprecation") @SuppressLint("NewApi") private static LinkedList getMediaCodecList() { diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index 36c26483..5b3d1881 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -10,6 +10,12 @@ import android.view.Display; import com.limelight.nvstream.jni.MoonBridge; public class PreferenceConfiguration { + public enum FormatOption { + AUTO, + FORCE_ON, + FORCE_OFF + }; + private static final String LEGACY_RES_FPS_PREF_STRING = "list_resolution_fps"; private static final String LEGACY_ENABLE_51_SURROUND_PREF_STRING = "checkbox_51_surround"; @@ -28,7 +34,8 @@ public class PreferenceConfiguration { private static final String MULTI_CONTROLLER_PREF_STRING = "checkbox_multi_controller"; static final String AUDIO_CONFIG_PREF_STRING = "list_audio_config"; private static final String USB_DRIVER_PREF_SRING = "checkbox_usb_driver"; - private static final String VIDEO_FORMAT_PREF_STRING = "video_format"; + private static final String HEVC_FORMAT_PREF_STRING = "video_format"; + private static final String AV1_FORMAT_PREF_STRING = "av1_format"; private static final String ONSCREEN_CONTROLLER_PREF_STRING = "checkbox_show_onscreen_controls"; private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3"; private static final String LEGACY_DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop"; @@ -61,7 +68,9 @@ public class PreferenceConfiguration { public static final String DEFAULT_LANGUAGE = "default"; private static final boolean DEFAULT_MULTI_CONTROLLER = true; private static final boolean DEFAULT_USB_DRIVER = true; - private static final String DEFAULT_VIDEO_FORMAT = "auto"; + private static final String DEFAULT_HEVC_FORMAT = "auto"; + private static final String DEFAULT_AV1_FORMAT = "never"; + private static final boolean ONSCREEN_CONTROLLER_DEFAULT = false; private static final boolean ONLY_L3_R3_DEFAULT = false; private static final boolean DEFAULT_ENABLE_HDR = false; @@ -83,10 +92,6 @@ public class PreferenceConfiguration { private static final boolean DEFAULT_REDUCE_REFRESH_RATE = false; private static final boolean DEFAULT_FULL_RANGE = false; - public static final int FORCE_H265_ON = -1; - public static final int AUTOSELECT_H265 = 0; - public static final int FORCE_H265_OFF = 1; - public static final int FRAME_PACING_MIN_LATENCY = 0; public static final int FRAME_PACING_BALANCED = 1; public static final int FRAME_PACING_CAP_FPS = 2; @@ -102,7 +107,8 @@ public class PreferenceConfiguration { public int width, height, fps; public int bitrate; - public int videoFormat; + public FormatOption hevcFormat; + public FormatOption av1Format; public int deadzonePercentage; public int oscOpacity; public boolean stretchVideo, enableSops, playHostAudio, disableWarnings; @@ -289,22 +295,41 @@ public class PreferenceConfiguration { prefs.getString(FPS_PREF_STRING, DEFAULT_FPS)); } - private static int getVideoFormatValue(Context context) { + private static FormatOption getHevcFormatValue(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - String str = prefs.getString(VIDEO_FORMAT_PREF_STRING, DEFAULT_VIDEO_FORMAT); + String str = prefs.getString(HEVC_FORMAT_PREF_STRING, DEFAULT_HEVC_FORMAT); if (str.equals("auto")) { - return AUTOSELECT_H265; + return FormatOption.AUTO; } else if (str.equals("forceh265")) { - return FORCE_H265_ON; + return FormatOption.FORCE_ON; } else if (str.equals("neverh265")) { - return FORCE_H265_OFF; + return FormatOption.FORCE_OFF; } else { // Should never get here - return AUTOSELECT_H265; + return FormatOption.AUTO; + } + } + + private static FormatOption getAV1FormatValue(Context context) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + String str = prefs.getString(AV1_FORMAT_PREF_STRING, DEFAULT_AV1_FORMAT); + if (str.equals("auto")) { + return FormatOption.AUTO; + } + else if (str.equals("force")) { + return FormatOption.FORCE_ON; + } + else if (str.equals("never")) { + return FormatOption.FORCE_OFF; + } + else { + // Should never get here + return FormatOption.AUTO; } } @@ -348,7 +373,8 @@ public class PreferenceConfiguration { .remove(LEGACY_RES_FPS_PREF_STRING) .remove(RESOLUTION_PREF_STRING) .remove(FPS_PREF_STRING) - .remove(VIDEO_FORMAT_PREF_STRING) + .remove(HEVC_FORMAT_PREF_STRING) + .remove(AV1_FORMAT_PREF_STRING) .remove(ENABLE_HDR_PREF_STRING) .remove(UNLOCK_FPS_STRING) .remove(FULL_RANGE_PREF_STRING) @@ -475,7 +501,8 @@ public class PreferenceConfiguration { config.audioConfiguration = MoonBridge.AUDIO_CONFIGURATION_STEREO; } - config.videoFormat = getVideoFormatValue(context); + config.hevcFormat = getHevcFormatValue(context); + config.av1Format = getAV1FormatValue(context); config.framePacing = getFramePacingValue(context); config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE); diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 2ac57142..6dab59ca 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -100,6 +100,17 @@ neverh265 + + @string/av1_format_auto + @string/av1_format_always + @string/av1_format_never + + + auto + force + never + + @string/pacing_latency @string/pacing_balanced diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8d77846c..3deffe62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -232,6 +232,8 @@ May reduce micro-stuttering on some devices, but can increase latency Change HEVC settings HEVC lowers video bandwidth requirements but requires a newer device + Change AV1 settings (Experimental) + AV1 lowers video bandwidth requirements more than HEVC, but is currently experimental. Enable HDR (Experimental) Stream HDR when the game and PC GPU support it. HDR requires a GPU with HEVC Main 10 encoding support. Force full range video (Experimental) @@ -270,6 +272,10 @@ Prefer HEVC Never use HEVC + Automatic (Recommended) + Prefer AV1 + Never use AV1 + Video frame pacing Specify how to balance video latency and smoothness Prefer lowest latency diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 7c1becf8..25d68e90 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -206,6 +206,13 @@ android:entryValues="@array/video_format_values" android:summary="@string/summary_video_format" android:defaultValue="auto" /> +