diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/ConnectionContext.java b/moonlight-common/src/main/java/com/limelight/nvstream/ConnectionContext.java index 36188faf..1ff5af6a 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/ConnectionContext.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/ConnectionContext.java @@ -15,6 +15,7 @@ public class ConnectionContext { public int negotiatedWidth, negotiatedHeight; public int negotiatedFps; + public boolean negotiatedHdr; public int videoCapabilities; } diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/NvConnection.java b/moonlight-common/src/main/java/com/limelight/nvstream/NvConnection.java index 65888cc6..3d96bdb9 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/NvConnection.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/NvConnection.java @@ -99,6 +99,12 @@ public class NvConnection { context.connListener.displayMessage("Device not paired with computer"); return false; } + + context.negotiatedHdr = context.streamConfig.getEnableHdr(); + if ((h.getServerCodecModeSupport(serverInfo) & 0x200) == 0 && context.negotiatedHdr) { + context.connListener.displayTransientMessage("Your GPU does not support streaming HDR. The stream will be SDR."); + context.negotiatedHdr = false; + } // // Decide on negotiated stream parameters now @@ -154,7 +160,7 @@ public class NvConnection { return false; } } else { - return quitAndLaunch(h, app); + return quitAndLaunch(h, context); } } catch (GfeHttpResponseException e) { if (e.getErrorCode() == 470) { @@ -178,11 +184,11 @@ public class NvConnection { return true; } else { - return launchNotRunningApp(h, app); + return launchNotRunningApp(h, context); } } - protected boolean quitAndLaunch(NvHTTP h, NvApp app) throws IOException, + protected boolean quitAndLaunch(NvHTTP h, ConnectionContext context) throws IOException, XmlPullParserException { try { if (!h.quitApp()) { @@ -201,13 +207,13 @@ public class NvConnection { } } - return launchNotRunningApp(h, app); + return launchNotRunningApp(h, context); } - private boolean launchNotRunningApp(NvHTTP h, NvApp app) + private boolean launchNotRunningApp(NvHTTP h, ConnectionContext context) throws IOException, XmlPullParserException { // Launch the app since it's not running - if (!h.launchApp(context, app.getAppId())) { + if (!h.launchApp(context, context.streamConfig.getApp().getAppId(), context.negotiatedHdr)) { context.connListener.displayMessage("Failed to launch application"); return false; } @@ -263,6 +269,7 @@ public class NvConnection { context.streamConfig.getMaxPacketSize(), context.streamConfig.getRemote(), context.streamConfig.getAudioConfiguration(), context.streamConfig.getHevcSupported(), + context.negotiatedHdr, context.streamConfig.getHevcBitratePercentageMultiplier(), context.riKey.getEncoded(), ib.array(), context.videoCapabilities); diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/StreamConfiguration.java b/moonlight-common/src/main/java/com/limelight/nvstream/StreamConfiguration.java index 1d3ad687..db835404 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/StreamConfiguration.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/StreamConfiguration.java @@ -26,6 +26,7 @@ public class StreamConfiguration { private int audioConfiguration; private boolean supportsHevc; private int hevcBitratePercentageMultiplier; + private boolean enableHdr; public static class Builder { private StreamConfiguration config = new StreamConfiguration(); @@ -80,6 +81,11 @@ public class StreamConfiguration { config.hevcBitratePercentageMultiplier = multiplier; return this; } + + public StreamConfiguration.Builder setEnableHdr(boolean enableHdr) { + config.enableHdr = enableHdr; + return this; + } public StreamConfiguration.Builder setAudioConfiguration(int audioConfig) { if (audioConfig == MoonBridge.AUDIO_CONFIGURATION_STEREO) { @@ -122,6 +128,7 @@ public class StreamConfiguration { this.audioChannelCount = CHANNEL_COUNT_STEREO; this.audioChannelMask = CHANNEL_MASK_STEREO; this.supportsHevc = false; + this.enableHdr = false; } public int getWidth() { @@ -183,4 +190,8 @@ public class StreamConfiguration { public int getHevcBitratePercentageMultiplier() { return hevcBitratePercentageMultiplier; } + + public boolean getEnableHdr() { + return enableHdr; + } } diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/http/NvApp.java b/moonlight-common/src/main/java/com/limelight/nvstream/http/NvApp.java index b870b0a3..0b247e2f 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/http/NvApp.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/http/NvApp.java @@ -6,6 +6,7 @@ public class NvApp { private String appName = ""; private int appId; private boolean initialized; + private boolean hdrSupported; public NvApp() {} @@ -13,9 +14,10 @@ public class NvApp { this.appName = appName; } - public NvApp(String appName, int appId) { + public NvApp(String appName, int appId, boolean hdrSupported) { this.appName = appName; this.appId = appId; + this.hdrSupported = hdrSupported; this.initialized = true; } @@ -36,6 +38,10 @@ public class NvApp { this.appId = appId; this.initialized = true; } + + public void setHdrSupported(boolean hdrSupported) { + this.hdrSupported = hdrSupported; + } public String getAppName() { return this.appName; @@ -44,6 +50,10 @@ public class NvApp { public int getAppId() { return this.appId; } + + public boolean isHdrSupported() { + return this.hdrSupported; + } public boolean isInitialized() { return this.initialized; diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/http/NvHTTP.java b/moonlight-common/src/main/java/com/limelight/nvstream/http/NvHTTP.java index cdb19f14..a24a4a28 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/http/NvHTTP.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/http/NvHTTP.java @@ -370,6 +370,27 @@ public class NvHTTP { return 0; } } + + // Possible meaning of bits + // Bit 0: H.264 Baseline + // Bit 1: H.264 High + // ---- + // Bit 8: HEVC Main + // Bit 9: HEVC Main10 + // Bit 10: HEVC Main10 4:4:4 + // Bit 11: ??? + public long getServerCodecModeSupport(String serverInfo) throws XmlPullParserException, IOException { + String str = getXmlString(serverInfo, "ServerCodecModeSupport"); + if (str != null) { + try { + return Long.parseLong(str); + } catch (NumberFormatException e) { + return 0; + } + } else { + return 0; + } + } public String getGpuType(String serverInfo) throws XmlPullParserException, IOException { return getXmlString(serverInfo, "gputype"); @@ -504,6 +525,8 @@ public class NvHTTP { app.setAppName(xpp.getText().trim()); } else if (currentTag.peek().equals("ID")) { app.setAppId(xpp.getText().trim()); + } else if (currentTag.peek().equals("IsHdrSupported")) { + app.setHdrSupported(xpp.getText().trim().equals("1")); } break; } @@ -601,7 +624,7 @@ public class NvHTTP { return new String(hexChars); } - public boolean launchApp(ConnectionContext context, int appId) throws IOException, XmlPullParserException { + public boolean launchApp(ConnectionContext context, int appId, boolean enableHdr) throws IOException, XmlPullParserException { String xmlStr = openHttpConnectionToString(baseUrlHttps + "/launch?" + buildUniqueIdUuidString() + "&appid=" + appId + @@ -609,6 +632,7 @@ public class NvHTTP { "&additionalStates=1&sops=" + (context.streamConfig.getSops() ? 1 : 0) + "&rikey="+bytesToHex(context.riKey.getEncoded()) + "&rikeyid="+context.riKeyId + + (!enableHdr ? "" : "&hdrMode=1&clientHdrCapVersion=0&clientHdrCapSupportedFlagsInUint32=0&clientHdrCapMetaDataId=NV_STATIC_METADATA_TYPE_1&clientHdrCapDisplayData=0x0x0x0x0x0x0x0x0x0x0") + "&localAudioPlayMode=" + (context.streamConfig.getPlayLocalAudio() ? 1 : 0) + "&surroundAudioInfo=" + ((context.streamConfig.getAudioChannelMask() << 16) + context.streamConfig.getAudioChannelCount()), false); diff --git a/moonlight-common/src/main/java/com/limelight/nvstream/jni/MoonBridge.java b/moonlight-common/src/main/java/com/limelight/nvstream/jni/MoonBridge.java index 522c3916..302db920 100644 --- a/moonlight-common/src/main/java/com/limelight/nvstream/jni/MoonBridge.java +++ b/moonlight-common/src/main/java/com/limelight/nvstream/jni/MoonBridge.java @@ -160,6 +160,7 @@ public class MoonBridge { int width, int height, int fps, int bitrate, int packetSize, boolean streamingRemotely, int audioConfiguration, boolean supportsHevc, + boolean enableHdr, int hevcBitratePercentageMultiplier, byte[] riAesKey, byte[] riAesIv, int videoCapabilities); diff --git a/moonlight-common/src/main/jni/moonlight-core/callbacks.c b/moonlight-common/src/main/jni/moonlight-core/callbacks.c index 5ce77e5e..48318323 100644 --- a/moonlight-common/src/main/jni/moonlight-core/callbacks.c +++ b/moonlight-common/src/main/jni/moonlight-core/callbacks.c @@ -382,6 +382,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jobject jint width, jint height, jint fps, jint bitrate, jint packetSize, jboolean streamingRemotely, jint audioConfiguration, jboolean supportsHevc, + jboolean enableHdr, jint hevcBitratePercentageMultiplier, jbyteArray riAesKey, jbyteArray riAesIv, jint videoCapabilities) { @@ -399,6 +400,7 @@ Java_com_limelight_nvstream_jni_MoonBridge_startConnection(JNIEnv *env, jobject .streamingRemotely = streamingRemotely, .audioConfiguration = audioConfiguration, .supportsHevc = supportsHevc, + .enableHdr = enableHdr, .hevcBitratePercentageMultiplier = hevcBitratePercentageMultiplier, };