Add HDR support and tweak HEVC supported decoders

This commit is contained in:
Cameron Gutman 2017-11-05 19:31:05 -08:00
parent 91a30ff6fe
commit 3f118dae93
10 changed files with 124 additions and 28 deletions

View File

@ -91,7 +91,7 @@ public class AppViewShortcutTrampoline extends Activity {
// If a game is running, we'll make the stream the top level activity // If a game is running, we'll make the stream the top level activity
if (details.runningGameId != 0) { if (details.runningGameId != 0) {
intentStack.add(ServerHelper.createStartIntent(AppViewShortcutTrampoline.this, 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 // Now start the activities

View File

@ -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_STREAMING_REMOTE = "Remote";
public static final String EXTRA_PC_UUID = "UUID"; public static final String EXTRA_PC_UUID = "UUID";
public static final String EXTRA_PC_NAME = "PcName"; public static final String EXTRA_PC_NAME = "PcName";
public static final String EXTRA_APP_HDR = "HDR";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { 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 // 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 // Make sure Wi-Fi is fully powered up
WifiManager wifiMgr = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); 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); boolean remote = Game.this.getIntent().getBooleanExtra(EXTRA_STREAMING_REMOTE, false);
String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID); String uuid = Game.this.getIntent().getStringExtra(EXTRA_PC_UUID);
String pcName = Game.this.getIntent().getStringExtra(EXTRA_PC_NAME); 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) { if (appId == StreamConfiguration.INVALID_APP_ID) {
finish(); finish();
@ -219,6 +224,42 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize the MediaCodec helper before creating the decoder // Initialize the MediaCodec helper before creating the decoder
MediaCodecHelper.initializeWithContext(this); 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, decoderRenderer = new MediaCodecDecoderRenderer(prefConfig,
new CrashListener() { new CrashListener() {
@Override @Override
@ -230,7 +271,16 @@ public class Game extends Activity implements SurfaceHolder.Callback,
tombstonePrefs.edit().putInt("CrashCount", tombstonePrefs.getInt("CrashCount", 0) + 1).commit(); 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 // 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()) { 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() StreamConfiguration config = new StreamConfiguration.Builder()
.setResolution(prefConfig.width, prefConfig.height) .setResolution(prefConfig.width, prefConfig.height)
.setRefreshRate(prefConfig.fps) .setRefreshRate(prefConfig.fps)
.setApp(new NvApp(appName, appId)) .setApp(new NvApp(appName, appId, willStreamHdr))
.setBitrate(prefConfig.bitrate * 1000) .setBitrate(prefConfig.bitrate * 1000)
.setEnableSops(prefConfig.enableSops) .setEnableSops(prefConfig.enableSops)
.enableLocalAudioPlayback(prefConfig.playHostAudio) .enableLocalAudioPlayback(prefConfig.playHostAudio)
@ -260,6 +310,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
.setRemote(remote) .setRemote(remote)
.setHevcBitratePercentageMultiplier(75) .setHevcBitratePercentageMultiplier(75)
.setHevcSupported(decoderRenderer.isHevcSupported()) .setHevcSupported(decoderRenderer.isHevcSupported())
.setEnableHdr(willStreamHdr)
.setAudioConfiguration(prefConfig.enable51Surround ? .setAudioConfiguration(prefConfig.enable51Surround ?
MoonBridge.AUDIO_CONFIGURATION_51_SURROUND : MoonBridge.AUDIO_CONFIGURATION_51_SURROUND :
MoonBridge.AUDIO_CONFIGURATION_STEREO) 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") @SuppressLint("InlinedApi")
private final Runnable hideSystemUi = new Runnable() { private final Runnable hideSystemUi = new Runnable() {
@Override @Override

View File

@ -543,7 +543,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
return true; 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; return true;
case QUIT_ID: case QUIT_ID:
@ -558,7 +558,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
public void run() { public void run() {
ServerHelper.doQuit(PcView.this, ServerHelper.doQuit(PcView.this,
ServerHelper.getCurrentAddressFromComputer(computer.details), ServerHelper.getCurrentAddressFromComputer(computer.details),
new NvApp("app", 0), managerBinder, null); new NvApp("app", 0, false), managerBinder, null);
} }
}, null); }, null);
return true; return true;

View File

@ -80,9 +80,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
return decoder; 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 // 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; return null;
} }
@ -93,10 +93,10 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// for even required levels of HEVC. // for even required levels of HEVC.
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1); MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
if (decoderInfo != null) { 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()); 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"); LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
} }
else { else {
@ -113,7 +113,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
} }
public MediaCodecDecoderRenderer(PreferenceConfiguration prefs, public MediaCodecDecoderRenderer(PreferenceConfiguration prefs,
CrashListener crashListener, int consecutiveCrashCount) { CrashListener crashListener, int consecutiveCrashCount,
boolean meteredData, boolean requestedHdr) {
//dumpDecoders(); //dumpDecoders();
this.prefs = prefs; this.prefs = prefs;
@ -136,7 +137,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
LimeLog.warning("No AVC decoder found"); LimeLog.warning("No AVC decoder found");
} }
hevcDecoder = findHevcDecoder(prefs.videoFormat); hevcDecoder = findHevcDecoder(prefs, meteredData, requestedHdr);
if (hevcDecoder != null) { if (hevcDecoder != null) {
LimeLog.info("Selected HEVC decoder: "+hevcDecoder.getName()); LimeLog.info("Selected HEVC decoder: "+hevcDecoder.getName());
} }
@ -175,6 +176,21 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
return avcDecoder != null; 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() { public int getActiveVideoFormat() {
return this.videoFormat; return this.videoFormat;
} }

View File

@ -28,6 +28,7 @@ public class MediaCodecHelper {
private static final List<String> blacklistedDecoderPrefixes; private static final List<String> blacklistedDecoderPrefixes;
private static final List<String> spsFixupBitstreamFixupDecoderPrefixes; private static final List<String> spsFixupBitstreamFixupDecoderPrefixes;
private static final List<String> whitelistedAdaptiveResolutionPrefixes; private static final List<String> whitelistedAdaptiveResolutionPrefixes;
private static final List<String> deprioritizedHevcDecoders;
private static final List<String> baselineProfileHackPrefixes; private static final List<String> baselineProfileHackPrefixes;
private static final List<String> directSubmitPrefixes; private static final List<String> directSubmitPrefixes;
private static final List<String> constrainedHighProfilePrefixes; private static final List<String> constrainedHighProfilePrefixes;
@ -113,8 +114,14 @@ public class MediaCodecHelper {
// Exynos seems to be the only HEVC decoder that works reliably // Exynos seems to be the only HEVC decoder that works reliably
whitelistedHevcDecoders.add("omx.exynos"); whitelistedHevcDecoders.add("omx.exynos");
// TODO: This needs a similar fixup to the Tegra 3 otherwise it buffers 16 frames // On Darcy (Shield 2017), HEVC runs fine with no fixups required.
//whitelistedHevcDecoders.add("omx.nvidia"); // 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
}
// Sony ATVs have broken MediaTek codecs (decoder hangs after rendering the first frame). // 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 // 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. // 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) { public static void initializeWithContext(Context context) {
ActivityManager activityManager = ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); (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 // 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). // approximation by checking the GLES level (<= 3.0 is bad).
if (configInfo.reqGlEsVersion > 0x30000) { 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 // older Qualcomm chips) vs. enabling HEVC by default. The user can override using the settings
// to force HEVC on. // to force HEVC on. If HDR or mobile data will be used, we'll override this and use
//LimeLog.info("Added omx.qcom to supported HEVC decoders based on GLES 3.1+ support"); // HEVC anyway.
//whitelistedHevcDecoders.add("omx.qcom"); LimeLog.info("Added omx.qcom to deprioritized HEVC decoders based on GLES 3.1+ support");
deprioritizedHevcDecoders.add("omx.qcom");
} }
else { else {
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc"); blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevc");
@ -245,7 +261,7 @@ public class MediaCodecHelper {
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName); 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? // TODO: Shield Tablet K1/LTE?
// //
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know // NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
@ -277,6 +293,15 @@ public class MediaCodecHelper {
return false; 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); return isDecoderInList(whitelistedHevcDecoders, decoderName);
} }

View File

@ -25,6 +25,7 @@ public class PreferenceConfiguration {
private static final String ONLY_L3_R3_PREF_STRING = "checkbox_only_show_L3R3"; 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 BATTERY_SAVER_PREF_STRING = "checkbox_battery_saver";
private static final String DISABLE_FRAME_DROP_PREF_STRING = "checkbox_disable_frame_drop"; 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_30 = 5;
private static final int BITRATE_DEFAULT_720_60 = 10; 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 ONLY_L3_R3_DEFAULT = false;
private static final boolean DEFAULT_BATTERY_SAVER = false; private static final boolean DEFAULT_BATTERY_SAVER = false;
private static final boolean DEFAULT_DISABLE_FRAME_DROP = 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 FORCE_H265_ON = -1;
public static final int AUTOSELECT_H265 = 0; public static final int AUTOSELECT_H265 = 0;
@ -66,6 +68,7 @@ public class PreferenceConfiguration {
public boolean onlyL3R3; public boolean onlyL3R3;
public boolean batterySaver; public boolean batterySaver;
public boolean disableFrameDrop; public boolean disableFrameDrop;
public boolean enableHdr;
public static int getDefaultBitrate(String resFpsString) { public static int getDefaultBitrate(String resFpsString) {
if (resFpsString.equals("720p30")) { 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.onlyL3R3 = prefs.getBoolean(ONLY_L3_R3_PREF_STRING, ONLY_L3_R3_DEFAULT);
config.batterySaver = prefs.getBoolean(BATTERY_SAVER_PREF_STRING, DEFAULT_BATTERY_SAVER); 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.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; return config;
} }

View File

@ -28,6 +28,7 @@ public class ServerHelper {
intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer)); intent.putExtra(Game.EXTRA_HOST, getCurrentAddressFromComputer(computer));
intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName()); intent.putExtra(Game.EXTRA_APP_NAME, app.getAppName());
intent.putExtra(Game.EXTRA_APP_ID, app.getAppId()); 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_UNIQUEID, managerBinder.getUniqueId());
intent.putExtra(Game.EXTRA_STREAMING_REMOTE, intent.putExtra(Game.EXTRA_STREAMING_REMOTE,
computer.reachability != ComputerDetails.Reachability.LOCAL); computer.reachability != ComputerDetails.Reachability.LOCAL);

View File

@ -148,5 +148,7 @@
<string name="summary_disable_frame_drop">May reduce micro-stuttering on some devices, but can increase latency</string> <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="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="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> </resources>

View File

@ -109,5 +109,10 @@
android:title="@string/title_disable_frame_drop" android:title="@string/title_disable_frame_drop"
android:summary="@string/summary_disable_frame_drop" android:summary="@string/summary_disable_frame_drop"
android:defaultValue="false" /> 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> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

@ -1 +1 @@
Subproject commit a5a402981fc2524b0a6a71971b7cdf9f123ff457 Subproject commit ad27fb7f2d05708facec18ab27dbaca484961fec