From 4c67631ea5405e0928444c1ca26736fb90c9e80b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 12 Dec 2015 17:18:15 -0800 Subject: [PATCH] Add negotiation logic for 4K and H.265 --- .../limelight/nvstream/ConnectionContext.java | 2 + .../com/limelight/nvstream/NvConnection.java | 42 +++++++++++++++ .../av/video/VideoDecoderRenderer.java | 1 + .../nvstream/av/video/VideoStream.java | 4 +- .../com/limelight/nvstream/http/NvHTTP.java | 51 ++++++++++++++++++- .../limelight/nvstream/rtsp/SdpGenerator.java | 6 +-- 6 files changed, 100 insertions(+), 6 deletions(-) diff --git a/moonlight-common/src/com/limelight/nvstream/ConnectionContext.java b/moonlight-common/src/com/limelight/nvstream/ConnectionContext.java index ac5a939a..6e9a0d3e 100644 --- a/moonlight-common/src/com/limelight/nvstream/ConnectionContext.java +++ b/moonlight-common/src/com/limelight/nvstream/ConnectionContext.java @@ -24,4 +24,6 @@ public class ConnectionContext { public int serverGeneration; public VideoFormat negotiatedVideoFormat; + public int negotiatedWidth, negotiatedHeight; + public int negotiatedFps; } diff --git a/moonlight-common/src/com/limelight/nvstream/NvConnection.java b/moonlight-common/src/com/limelight/nvstream/NvConnection.java index 0c87e7dc..b36f6d9e 100644 --- a/moonlight-common/src/com/limelight/nvstream/NvConnection.java +++ b/moonlight-common/src/com/limelight/nvstream/NvConnection.java @@ -15,6 +15,7 @@ import com.limelight.LimeLog; import com.limelight.nvstream.av.audio.AudioStream; import com.limelight.nvstream.av.audio.AudioRenderer; import com.limelight.nvstream.av.video.VideoDecoderRenderer; +import com.limelight.nvstream.av.video.VideoDecoderRenderer.VideoFormat; import com.limelight.nvstream.av.video.VideoStream; import com.limelight.nvstream.control.ControlStream; import com.limelight.nvstream.http.GfeHttpResponseException; @@ -61,6 +62,8 @@ public class NvConnection { } this.context.riKeyId = generateRiKeyId(); + + this.context.negotiatedVideoFormat = VideoFormat.Unknown; } private static SecretKey generateRiAesKey() throws NoSuchAlgorithmException { @@ -139,6 +142,41 @@ public class NvConnection { return false; } + // + // Decide on negotiated stream parameters now + // + + // Check for a supported stream resolution + if (context.streamConfig.getHeight() >= 2160 && !h.supports4K(serverInfo)) { + // Client wants 4K but the server can't do it + context.connListener.displayTransientMessage("Your PC does not have a supported GPU or GFE version for 4K streaming. The stream will be 1080p."); + + // Lower resolution to 1080p + context.negotiatedWidth = 1920; + context.negotiatedHeight = 1080; + } + else { + // Take what the client wanted + context.negotiatedWidth = context.streamConfig.getWidth(); + context.negotiatedHeight = context.streamConfig.getHeight(); + } + + // For now, always take the client's FPS request + context.negotiatedFps = context.streamConfig.getRefreshRate(); + + // Determine whether we should request H.265 video + String gpuType = h.getGpuType(serverInfo); + if (context.streamConfig.getHevcSupported() && // Client wants it + h.getMaxLumaPixelsHEVC(serverInfo) > 0 && gpuType != null && // Check if GFE version supports it + gpuType.contains("GTX 9")) // Check if GPU can do it (only 900-series) - TODO: Find a better way to detect this + { + context.negotiatedVideoFormat = VideoFormat.H265; + } + else + { + context.negotiatedVideoFormat = VideoFormat.H264; + } + NvApp app = context.streamConfig.getApp(); // If the client did not provide an exact app ID, do a lookup with the applist @@ -407,4 +445,8 @@ public class NvConnection { inputStream.sendMouseScroll(scrollClicks); } + + public VideoFormat getActiveVideoFormat() { + return context.negotiatedVideoFormat; + } } diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java index 8fe73f22..12191154 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java @@ -4,6 +4,7 @@ import com.limelight.nvstream.av.DecodeUnit; public abstract class VideoDecoderRenderer { public enum VideoFormat { + Unknown, H264, H265 }; diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java index 9d1d1743..64e56193 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java @@ -122,8 +122,8 @@ public class VideoStream { if (decRend != null) { try { - if (!decRend.setup(context.negotiatedVideoFormat, context.streamConfig.getWidth(), - context.streamConfig.getHeight(), context.streamConfig.getRefreshRate(), + if (!decRend.setup(context.negotiatedVideoFormat, context.negotiatedWidth, + context.negotiatedHeight, context.negotiatedFps, renderTarget, drFlags)) { return false; } diff --git a/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java b/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java index d39fba26..a2c2a216 100644 --- a/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java +++ b/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java @@ -334,6 +334,55 @@ public class NvHTTP { public PairingManager.PairState getPairState(String serverInfo) throws IOException, XmlPullParserException { return pm.getPairState(serverInfo); } + + public long getMaxLumaPixelsH264(String serverInfo) throws XmlPullParserException, IOException { + String str = getXmlString(serverInfo, "MaxLumaPixelsH264"); + if (str != null) { + try { + return Long.parseLong(str); + } catch (NumberFormatException e) { + return 0; + } + } else { + return 0; + } + } + + public long getMaxLumaPixelsHEVC(String serverInfo) throws XmlPullParserException, IOException { + String str = getXmlString(serverInfo, "MaxLumaPixelsHEVC"); + 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"); + } + + public boolean supports4K(String serverInfo) throws XmlPullParserException, IOException { + // serverinfo returns supported resolutions in descending order, so getting the first + // height will give us whether we support 4K. If this is not present, we don't support + // 4K. + String heightStr = getXmlString(serverInfo, "Height"); + if (heightStr == null) { + return false; + } + + try { + if (Integer.parseInt(heightStr) >= 2160) { + // Found a 4K resolution in the list + return true; + } + } catch (NumberFormatException ignored) {} + + return false; + } public int getCurrentGame(String serverInfo) throws IOException, XmlPullParserException { // GFE 2.8 started keeping currentgame set to the last game played. As a result, it no longer @@ -481,7 +530,7 @@ public class NvHTTP { String xmlStr = openHttpConnectionToString(baseUrlHttps + "/launch?uniqueid=" + uniqueId + "&appid=" + appId + - "&mode=" + context.streamConfig.getWidth() + "x" + context.streamConfig.getHeight() + "x" + context.streamConfig.getRefreshRate() + + "&mode=" + context.negotiatedWidth + "x" + context.negotiatedHeight + "x" + context.negotiatedFps + "&additionalStates=1&sops=" + (context.streamConfig.getSops() ? 1 : 0) + "&rikey="+bytesToHex(context.riKey.getEncoded()) + "&rikeyid="+context.riKeyId + diff --git a/moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java b/moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java index 373b65cb..5f919c80 100644 --- a/moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java +++ b/moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java @@ -95,9 +95,9 @@ public class SdpGenerator { config.append("\r\n"); config.append("s=NVIDIA Streaming Client").append("\r\n"); - addSessionAttribute(config, "x-nv-video[0].clientViewportWd", ""+context.streamConfig.getWidth()); - addSessionAttribute(config, "x-nv-video[0].clientViewportHt", ""+context.streamConfig.getHeight()); - addSessionAttribute(config, "x-nv-video[0].maxFPS", ""+context.streamConfig.getRefreshRate()); + addSessionAttribute(config, "x-nv-video[0].clientViewportWd", ""+context.negotiatedWidth); + addSessionAttribute(config, "x-nv-video[0].clientViewportHt", ""+context.negotiatedHeight); + addSessionAttribute(config, "x-nv-video[0].maxFPS", ""+context.negotiatedFps); addSessionAttribute(config, "x-nv-video[0].packetSize", ""+context.streamConfig.getMaxPacketSize());