mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-19 11:03:01 +00:00
Add HDR support and tweak HEVC supported decoders
This commit is contained in:
parent
91a30ff6fe
commit
3f118dae93
@ -91,7 +91,7 @@ public class AppViewShortcutTrampoline extends Activity {
|
||||
// If a game is running, we'll make the stream the top level activity
|
||||
if (details.runningGameId != 0) {
|
||||
intentStack.add(ServerHelper.createStartIntent(AppViewShortcutTrampoline.this,
|
||||
new NvApp("app", details.runningGameId), details, managerBinder));
|
||||
new NvApp("app", details.runningGameId, false), details, managerBinder));
|
||||
}
|
||||
|
||||
// Now start the activities
|
||||
|
@ -125,6 +125,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
public static final String EXTRA_STREAMING_REMOTE = "Remote";
|
||||
public static final String EXTRA_PC_UUID = "UUID";
|
||||
public static final String EXTRA_PC_NAME = "PcName";
|
||||
public static final String EXTRA_APP_HDR = "HDR";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -191,7 +192,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
|
||||
// Warn the user if they're on a metered connection
|
||||
checkDataConnection();
|
||||
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (connMgr.isActiveNetworkMetered()) {
|
||||
displayTransientMessage(getResources().getString(R.string.conn_metered));
|
||||
}
|
||||
|
||||
// Make sure Wi-Fi is fully powered up
|
||||
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
|
||||
@ -206,6 +210,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
boolean remote = Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, false);
|
||||
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
|
||||
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME);
|
||||
boolean willStreamHdr = Game.this.getIntent().getBooleanExtra(EXTRA_APP_HDR, false);
|
||||
|
||||
if (appId == StreamConfiguration.INVALID_APP_ID) {
|
||||
finish();
|
||||
@ -219,6 +224,42 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Initialize the MediaCodec helper before creating the decoder
|
||||
MediaCodecHelper.initializeWithContext(this);
|
||||
|
||||
// Check if the user has enabled HDR
|
||||
if (prefConfig.enableHdr) {
|
||||
// Check if the app supports it
|
||||
if (!willStreamHdr) {
|
||||
Toast.makeText(this, "This game does not support HDR10", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
// It does, so start our HDR checklist
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// We already know the app supports HDR if willStreamHdr is set.
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
Display.HdrCapabilities hdrCaps = display.getHdrCapabilities();
|
||||
|
||||
// We must now ensure our display is compatible with HDR10
|
||||
boolean foundHdr10 = false;
|
||||
for (int hdrType : hdrCaps.getSupportedHdrTypes()) {
|
||||
if (hdrType == Display.HdrCapabilities.HDR_TYPE_HDR10) {
|
||||
LimeLog.info("Display supports HDR10");
|
||||
foundHdr10 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundHdr10) {
|
||||
// Nope, no HDR for us :(
|
||||
willStreamHdr = false;
|
||||
Toast.makeText(this, "Display does not support HDR10", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
else {
|
||||
Toast.makeText(this, "HDR requires Android 7.0 or later", Toast.LENGTH_LONG).show();
|
||||
willStreamHdr = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
willStreamHdr = false;
|
||||
}
|
||||
|
||||
decoderRenderer = new MediaCodecDecoderRenderer(prefConfig,
|
||||
new CrashListener() {
|
||||
@Override
|
||||
@ -230,7 +271,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
tombstonePrefs.edit().putInt("CrashCount", tombstonePrefs.getInt("CrashCount", 0) + 1).commit();
|
||||
}
|
||||
},
|
||||
tombstonePrefs.getInt("CrashCount", 0));
|
||||
tombstonePrefs.getInt("CrashCount", 0),
|
||||
connMgr.isActiveNetworkMetered(),
|
||||
willStreamHdr
|
||||
);
|
||||
|
||||
// Don't stream HDR if the decoder can't support it
|
||||
if (willStreamHdr && !decoderRenderer.isHevcMain10Hdr10Supported()) {
|
||||
willStreamHdr = false;
|
||||
Toast.makeText(this, "Decoder does not support HEVC Main10HDR10", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
||||
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
||||
@ -252,7 +302,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
StreamConfiguration config = new StreamConfiguration.Builder()
|
||||
.setResolution(prefConfig.width, prefConfig.height)
|
||||
.setRefreshRate(prefConfig.fps)
|
||||
.setApp(new NvApp(appName, appId))
|
||||
.setApp(new NvApp(appName, appId, willStreamHdr))
|
||||
.setBitrate(prefConfig.bitrate * 1000)
|
||||
.setEnableSops(prefConfig.enableSops)
|
||||
.enableLocalAudioPlayback(prefConfig.playHostAudio)
|
||||
@ -260,6 +310,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setRemote(remote)
|
||||
.setHevcBitratePercentageMultiplier(75)
|
||||
.setHevcSupported(decoderRenderer.isHevcSupported())
|
||||
.setEnableHdr(willStreamHdr)
|
||||
.setAudioConfiguration(prefConfig.enable51Surround ?
|
||||
MoonBridge.AUDIO_CONFIGURATION_51_SURROUND :
|
||||
MoonBridge.AUDIO_CONFIGURATION_STEREO)
|
||||
@ -409,14 +460,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDataConnection()
|
||||
{
|
||||
ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (mgr.isActiveNetworkMetered()) {
|
||||
displayTransientMessage(getResources().getString(R.string.conn_metered));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private final Runnable hideSystemUi = new Runnable() {
|
||||
@Override
|
||||
|
@ -543,7 +543,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
return true;
|
||||
}
|
||||
|
||||
ServerHelper.doStart(this, new NvApp("app", computer.details.runningGameId), computer.details, managerBinder);
|
||||
ServerHelper.doStart(this, new NvApp("app", computer.details.runningGameId, false), computer.details, managerBinder);
|
||||
return true;
|
||||
|
||||
case QUIT_ID:
|
||||
@ -558,7 +558,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
|
||||
public void run() {
|
||||
ServerHelper.doQuit(PcView.this,
|
||||
ServerHelper.getCurrentAddressFromComputer(computer.details),
|
||||
new NvApp("app", 0), managerBinder, null);
|
||||
new NvApp("app", 0, false), managerBinder, null);
|
||||
}
|
||||
}, null);
|
||||
return true;
|
||||
|
@ -80,9 +80,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
return decoder;
|
||||
}
|
||||
|
||||
private MediaCodecInfo findHevcDecoder(int videoFormat) {
|
||||
private MediaCodecInfo findHevcDecoder(PreferenceConfiguration prefs, boolean meteredNetwork, boolean requestedHdr) {
|
||||
// Don't return anything if H.265 is forced off
|
||||
if (videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_OFF) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -93,10 +93,10 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
// for even required levels of HEVC.
|
||||
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
|
||||
if (decoderInfo != null) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName())) {
|
||||
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork, requestedHdr)) {
|
||||
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
|
||||
|
||||
if (videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
|
||||
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
|
||||
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
|
||||
}
|
||||
else {
|
||||
@ -113,7 +113,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
|
||||
public MediaCodecDecoderRenderer(PreferenceConfiguration prefs,
|
||||
CrashListener crashListener, int consecutiveCrashCount) {
|
||||
CrashListener crashListener, int consecutiveCrashCount,
|
||||
boolean meteredData, boolean requestedHdr) {
|
||||
//dumpDecoders();
|
||||
|
||||
this.prefs = prefs;
|
||||
@ -136,7 +137,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
LimeLog.warning("No AVC decoder found");
|
||||
}
|
||||
|
||||
hevcDecoder = findHevcDecoder(prefs.videoFormat);
|
||||
hevcDecoder = findHevcDecoder(prefs, meteredData, requestedHdr);
|
||||
if (hevcDecoder != null) {
|
||||
LimeLog.info("Selected HEVC decoder: "+hevcDecoder.getName());
|
||||
}
|
||||
@ -175,6 +176,21 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
return avcDecoder != null;
|
||||
}
|
||||
|
||||
public boolean isHevcMain10Hdr10Supported() {
|
||||
if (hevcDecoder == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (MediaCodecInfo.CodecProfileLevel profileLevel : hevcDecoder.getCapabilitiesForType("video/hevc").profileLevels) {
|
||||
if (profileLevel.profile == MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10) {
|
||||
LimeLog.info("HEVC decoder "+hevcDecoder.getName()+" supports HEVC Main10 HDR10");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getActiveVideoFormat() {
|
||||
return this.videoFormat;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ public class MediaCodecHelper {
|
||||
private static final List<String> blacklistedDecoderPrefixes;
|
||||
private static final List<String> spsFixupBitstreamFixupDecoderPrefixes;
|
||||
private static final List<String> whitelistedAdaptiveResolutionPrefixes;
|
||||
private static final List<String> deprioritizedHevcDecoders;
|
||||
private static final List<String> baselineProfileHackPrefixes;
|
||||
private static final List<String> directSubmitPrefixes;
|
||||
private static final List<String> constrainedHighProfilePrefixes;
|
||||
@ -113,8 +114,14 @@ public class MediaCodecHelper {
|
||||
// Exynos seems to be the only HEVC decoder that works reliably
|
||||
whitelistedHevcDecoders.add("omx.exynos");
|
||||
|
||||
// On Darcy (Shield 2017), HEVC runs fine with no fixups required.
|
||||
// For some reason, other X1 implementations require bitstream fixups.
|
||||
if (Build.DEVICE.equalsIgnoreCase("darcy")) {
|
||||
whitelistedHevcDecoders.add("omx.nvidia");
|
||||
}
|
||||
else {
|
||||
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames
|
||||
//whitelistedHevcDecoders.add("omx.nvidia");
|
||||
}
|
||||
|
||||
// Sony ATVs have broken MediaTek codecs (decoder hangs after rendering the first frame).
|
||||
// I know the Fire TV 2 works, so I'll just whitelist Amazon devices which seem
|
||||
@ -132,6 +139,14 @@ public class MediaCodecHelper {
|
||||
// during initialization to avoid SoCs with broken HEVC decoders.
|
||||
}
|
||||
|
||||
static {
|
||||
deprioritizedHevcDecoders = new LinkedList<>();
|
||||
|
||||
// These are decoders that work but aren't used by default for various reasons.
|
||||
|
||||
// Qualcomm is currently the only decoders in this group.
|
||||
}
|
||||
|
||||
public static void initializeWithContext(Context context) {
|
||||
ActivityManager activityManager =
|
||||
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
@ -163,11 +178,12 @@ public class MediaCodecHelper {
|
||||
// Unfortunately, it's not that easy to get that information here, so I'll use an
|
||||
// approximation by checking the GLES level (<= 3.0 is bad).
|
||||
if (configInfo.reqGlEsVersion > 0x30000) {
|
||||
// FIXME: We prefer reference frame invalidation support (which is only doable on AVC on
|
||||
// We prefer reference frame invalidation support (which is only doable on AVC on
|
||||
// older Qualcomm chips) vs. enabling HEVC by default. The user can override using the settings
|
||||
// to force HEVC on.
|
||||
//LimeLog.info("Added omx.qcom to supported HEVC decoders based on GLES 3.1+ support");
|
||||
//whitelistedHevcDecoders.add("omx.qcom");
|
||||
// to force HEVC on. If HDR or mobile data will be used, we'll override this and use
|
||||
// HEVC anyway.
|
||||
LimeLog.info("Added omx.qcom to deprioritized HEVC decoders based on GLES 3.1+ support");
|
||||
deprioritizedHevcDecoders.add("omx.qcom");
|
||||
}
|
||||
else {
|
||||
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc");
|
||||
@ -245,7 +261,7 @@ public class MediaCodecHelper {
|
||||
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
|
||||
}
|
||||
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName) {
|
||||
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, boolean willStreamHdr) {
|
||||
// TODO: Shield Tablet K1/LTE?
|
||||
//
|
||||
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
|
||||
@ -277,6 +293,15 @@ public class MediaCodecHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Some devices have HEVC decoders that we prefer not to use
|
||||
// typically because it can't support reference frame invalidation.
|
||||
// However, we will use it for HDR and for streaming over mobile networks
|
||||
// since it works fine otherwise.
|
||||
if ((meteredData || willStreamHdr) && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
|
||||
LimeLog.info("Selected deprioritized decoder");
|
||||
return true;
|
||||
}
|
||||
|
||||
return isDecoderInList(whitelistedHevcDecoders, decoderName);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ public class PreferenceConfiguration {
|
||||
private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3";
|
||||
private static final String BATTERY_SAVER_PREF_STRING = "checkbox_battery_saver";
|
||||
private static final String DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop";
|
||||
private static final String ENABLE_HDR_PREF_STRING = "checkbox_enable_hdr";
|
||||
|
||||
private static final int BITRATE_DEFAULT_720_30 = 5;
|
||||
private static final int BITRATE_DEFAULT_720_60 = 10;
|
||||
@ -50,6 +51,7 @@ public class PreferenceConfiguration {
|
||||
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;
|
||||
|
||||
public static final int FORCE_H265_ON = -1;
|
||||
public static final int AUTOSELECT_H265 = 0;
|
||||
@ -66,6 +68,7 @@ public class PreferenceConfiguration {
|
||||
public boolean onlyL3R3;
|
||||
public boolean batterySaver;
|
||||
public boolean disableFrameDrop;
|
||||
public boolean enableHdr;
|
||||
|
||||
public static int getDefaultBitrate(String resFpsString) {
|
||||
if (resFpsString.equals("720p30")) {
|
||||
@ -209,6 +212,7 @@ public class PreferenceConfiguration {
|
||||
config.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
|
||||
config.batterySaver = prefs.getBoolean(BATTERY_SAVER_PREF_STRING, DEFAULT_BATTERY_SAVER);
|
||||
config.disableFrameDrop = prefs.getBoolean(DISABLE_FRAME_DROP_PREF_STRING, DEFAULT_DISABLE_FRAME_DROP);
|
||||
config.enableHdr = prefs.getBoolean(ENABLE_HDR_PREF_STRING, DEFAULT_ENABLE_HDR);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ public class ServerHelper {
|
||||
intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer));
|
||||
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
|
||||
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId());
|
||||
intent.putExtra(Game.EXTRA_APP_HDR, app.isHdrSupported());
|
||||
intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId());
|
||||
intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
|
||||
computer.reachability != ComputerDetails.Reachability.LOCAL);
|
||||
|
@ -148,5 +148,7 @@
|
||||
<string name="summary_disable_frame_drop">May reduce micro-stuttering on some devices, but can increase latency</string>
|
||||
<string name="title_video_format">Change H.265 settings</string>
|
||||
<string name="summary_video_format">H.265 lowers video bandwidth requirements but requires a very recent device</string>
|
||||
<string name="title_enable_hdr">Enable HDR (Experimental)</string>
|
||||
<string name="summary_enable_hdr">Stream HDR when the game, client display, decoder, and GPU support it</string>
|
||||
|
||||
</resources>
|
||||
|
@ -109,5 +109,10 @@
|
||||
android:title="@string/title_disable_frame_drop"
|
||||
android:summary="@string/summary_disable_frame_drop"
|
||||
android:defaultValue="false" />
|
||||
<CheckBoxPreference
|
||||
android:key="checkbox_enable_hdr"
|
||||
android:title="@string/title_enable_hdr"
|
||||
android:summary="@string/summary_enable_hdr"
|
||||
android:defaultValue="false" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a5a402981fc2524b0a6a71971b7cdf9f123ff457
|
||||
Subproject commit ad27fb7f2d05708facec18ab27dbaca484961fec
|
Loading…
x
Reference in New Issue
Block a user