From 4701c22b67d38be26e24e7ff6ab15e3cb6a2bae8 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 19 Dec 2013 04:24:45 -0500 Subject: [PATCH] Create a StreamConfiguration class and use it to send correct information about the requested resolution and refresh rate to the various streaming components and the target PC. --- .../com/limelight/nvstream/NvConnection.java | 41 ++++- .../nvstream/StreamConfiguration.java | 24 +++ .../nvstream/av/video/VideoStream.java | 7 +- .../limelight/nvstream/control/Config.java | 143 ++++++++++++++++++ .../nvstream/{ => control}/ControlStream.java | 132 ++-------------- .../com/limelight/nvstream/http/NvHTTP.java | 59 +++++--- 6 files changed, 256 insertions(+), 150 deletions(-) create mode 100644 moonlight-common/src/com/limelight/nvstream/StreamConfiguration.java create mode 100644 moonlight-common/src/com/limelight/nvstream/control/Config.java rename moonlight-common/src/com/limelight/nvstream/{ => control}/ControlStream.java (82%) diff --git a/moonlight-common/src/com/limelight/nvstream/NvConnection.java b/moonlight-common/src/com/limelight/nvstream/NvConnection.java index 739f3d03..edbe0598 100644 --- a/moonlight-common/src/com/limelight/nvstream/NvConnection.java +++ b/moonlight-common/src/com/limelight/nvstream/NvConnection.java @@ -16,12 +16,15 @@ 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.VideoStream; +import com.limelight.nvstream.control.ControlStream; +import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.input.NvController; public class NvConnection { private String host; private NvConnectionListener listener; + private StreamConfiguration config; private InetAddress hostAddr; private ControlStream controlStream; @@ -38,10 +41,12 @@ public class NvConnection { private ThreadPoolExecutor threadPool; - public NvConnection(String host, NvConnectionListener listener) + public NvConnection(String host, NvConnectionListener listener, StreamConfiguration config) { this.host = host; this.listener = listener; + this.config = config; + this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue()); } @@ -132,16 +137,42 @@ public class NvConnection { } int sessionId = h.getSessionId(); - int appId = h.getSteamAppId(sessionId); + if (sessionId == 0) { + listener.displayMessage("Invalid session ID"); + return false; + } - h.launchApp(sessionId, appId); + NvApp app = h.getSteamApp(); + if (app == null) { + listener.displayMessage("Steam not found in GFE app list"); + return false; + } + + // If there's a game running, resume it + if (h.getCurrentGame() != 0) { + if (!h.resumeApp()) { + listener.displayMessage("Failing to resume existing session"); + return false; + } + System.out.println("Resumed existing game session"); + } + else { + // Launch the app since it's not running + int gameSessionId = h.launchApp(app.getAppId(), config.getWidth(), + config.getHeight(), config.getRefreshRate()); + if (gameSessionId == 0) { + listener.displayMessage("Failed to launch application"); + return false; + } + System.out.println("Launched new game session"); + } return true; } private boolean startControlStream() throws IOException { - controlStream = new ControlStream(hostAddr, listener); + controlStream = new ControlStream(hostAddr, listener, config); controlStream.initialize(); controlStream.start(); return true; @@ -149,7 +180,7 @@ public class NvConnection { private boolean startVideoStream() throws IOException { - videoStream = new VideoStream(hostAddr, listener, controlStream); + videoStream = new VideoStream(hostAddr, listener, controlStream, config); videoStream.startVideoStream(videoDecoderRenderer, videoRenderTarget, drFlags); return true; } diff --git a/moonlight-common/src/com/limelight/nvstream/StreamConfiguration.java b/moonlight-common/src/com/limelight/nvstream/StreamConfiguration.java new file mode 100644 index 00000000..cdb9247b --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/StreamConfiguration.java @@ -0,0 +1,24 @@ +package com.limelight.nvstream; + +public class StreamConfiguration { + private int width, height; + private int refreshRate; + + public StreamConfiguration(int width, int height, int refreshRate) { + this.width = width; + this.height = height; + this.refreshRate = refreshRate; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getRefreshRate() { + return refreshRate; + } +} 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 c9f7b85e..f15e32ee 100644 --- a/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java @@ -12,6 +12,7 @@ import java.util.LinkedList; import java.util.concurrent.LinkedBlockingQueue; import com.limelight.nvstream.NvConnectionListener; +import com.limelight.nvstream.StreamConfiguration; import com.limelight.nvstream.av.ByteBufferDescriptor; import com.limelight.nvstream.av.DecodeUnit; import com.limelight.nvstream.av.RtpPacket; @@ -34,17 +35,19 @@ public class VideoStream { private NvConnectionListener listener; private VideoDepacketizer depacketizer; + private StreamConfiguration streamConfig; private VideoDecoderRenderer decRend; private boolean startedRendering; private boolean aborting = false; - public VideoStream(InetAddress host, NvConnectionListener listener, ConnectionStatusListener avConnListener) + public VideoStream(InetAddress host, NvConnectionListener listener, ConnectionStatusListener avConnListener, StreamConfiguration streamConfig) { this.host = host; this.listener = listener; this.depacketizer = new VideoDepacketizer(avConnListener); + this.streamConfig = streamConfig; } public void abort() @@ -127,7 +130,7 @@ public class VideoStream { public void setupDecoderRenderer(VideoDecoderRenderer decRend, Object renderTarget, int drFlags) { this.decRend = decRend; if (decRend != null) { - decRend.setup(1280, 720, renderTarget, drFlags); + decRend.setup(streamConfig.getWidth(), streamConfig.getHeight(), renderTarget, drFlags); } } diff --git a/moonlight-common/src/com/limelight/nvstream/control/Config.java b/moonlight-common/src/com/limelight/nvstream/control/Config.java new file mode 100644 index 00000000..476d8650 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/control/Config.java @@ -0,0 +1,143 @@ +package com.limelight.nvstream.control; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.limelight.nvstream.StreamConfiguration; + +public class Config { + + public static final int[] UNKNOWN_CONFIG = + { + 70151, + 68291329, + 1280, + 68291584, + 1280, + 68291840, + 15360, + 68292096, + 25600, + 68292352, + 2048, + 68292608, + 1024, + 68289024, + 262144, + 17957632, + 302055424, + 134217729, + 16777490, + 70153, + 68293120, + 768000, + 17961216, + 303235072, + 335609857, + 838861842, + 352321536, + 1006634002, + 369098752, + 335545362, + 385875968, + 1042, + 402653184, + 134218770, + 419430400, + 167773202, + 436207616, + 855638290, + 266779, + 7000, + 266780, + 2000, + 266781, + 50, + 266782, + 3000, + 266783, + 2, + 266794, + 5000, + 266795, + 500, + 266784, + 75, + 266785, + 25, + 266786, + 10, + 266787, + 60, + 266788, + 30, + 266789, + 3, + 266790, + 1000, + 266791, + 5000, + 266792, + 5000, + 266793, + 5000, + 70190, + 68301063, + 10240, + 68301312, + 6400, + 68301568, + 768000, + 68299776, + 768, + 68300032, + 2560, + 68300544, + 0, + 34746368, + (int)0xFE000000 + }; + + public static final int CONFIG_SIZE = ((8 + UNKNOWN_CONFIG.length) * 4) + 3; + + private StreamConfiguration streamConfig; + + public Config(StreamConfiguration streamConfig) { + this.streamConfig = streamConfig; + } + + public byte[] toWire() { + ByteBuffer bb = ByteBuffer.allocate(CONFIG_SIZE).order(ByteOrder.LITTLE_ENDIAN); + + // Width + bb.putShort((short) 0x1204); + bb.putShort((short) 0x0004); + bb.putInt(streamConfig.getWidth()); + + // Height + bb.putShort((short) 0x1205); + bb.putShort((short) 0x0004); + bb.putInt(streamConfig.getHeight()); + + // Unknown + bb.putShort((short) 0x1206); + bb.putShort((short) 0x0004); + bb.putInt(1); + + // Refresh rate + bb.putShort((short) 0x120A); + bb.putShort((short) 0x0004); + bb.putInt(streamConfig.getRefreshRate()); + + // The rest are hardcoded + for (int i : UNKNOWN_CONFIG) { + bb.putInt(i); + } + + // Config tail + bb.putShort((short) 0x0013); + bb.put((byte) 0x00); + + return bb.array(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/ControlStream.java b/moonlight-common/src/com/limelight/nvstream/control/ControlStream.java similarity index 82% rename from moonlight-common/src/com/limelight/nvstream/ControlStream.java rename to moonlight-common/src/com/limelight/nvstream/control/ControlStream.java index 75bde0e4..00d87753 100644 --- a/moonlight-common/src/com/limelight/nvstream/ControlStream.java +++ b/moonlight-common/src/com/limelight/nvstream/control/ControlStream.java @@ -1,4 +1,4 @@ -package com.limelight.nvstream; +package com.limelight.nvstream.control; import java.io.IOException; import java.io.InputStream; @@ -9,6 +9,8 @@ import java.net.Socket; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import com.limelight.nvstream.NvConnectionListener; +import com.limelight.nvstream.StreamConfiguration; import com.limelight.nvstream.av.ConnectionStatusListener; public class ControlStream implements ConnectionStatusListener { @@ -17,16 +19,6 @@ public class ControlStream implements ConnectionStatusListener { public static final int CONTROL_TIMEOUT = 5000; - public static final short PTYPE_HELLO = 0x1204; - public static final short PPAYLEN_HELLO = 0x0004; - public static final byte[] PPAYLOAD_HELLO = - { - (byte)0x00, - (byte)0x05, - (byte)0x00, - (byte)0x00 - }; - public static final short PTYPE_KEEPALIVE = 0x13ff; public static final short PPAYLEN_KEEPALIVE = 0x0000; @@ -41,102 +33,7 @@ public class ControlStream implements ConnectionStatusListener { public static final short PTYPE_CONFIG = 0x1205; public static final short PPAYLEN_CONFIG = 0x0004; - public static final int[] PPAYLOAD_CONFIG = - { - 720, - 266758, - 1, - 266762, - 30, - 70151, - 68291329, - 1280, - 68291584, - 1280, - 68291840, - 15360, - 68292096, - 25600, - 68292352, - 2048, - 68292608, - 1024, - 68289024, - 262144, - 17957632, - 302055424, - 134217729, - 16777490, - 70153, - 68293120, - 768000, - 17961216, - 303235072, - 335609857, - 838861842, - 352321536, - 1006634002, - 369098752, - 335545362, - 385875968, - 1042, - 402653184, - 134218770, - 419430400, - 167773202, - 436207616, - 855638290, - 266779, - 7000, - 266780, - 2000, - 266781, - 50, - 266782, - 3000, - 266783, - 2, - 266794, - 5000, - 266795, - 500, - 266784, - 75, - 266785, - 25, - 266786, - 10, - 266787, - 60, - 266788, - 30, - 266789, - 3, - 266790, - 1000, - 266791, - 5000, - 266792, - 5000, - 266793, - 5000, - 70190, - 68301063, - 10240, - 68301312, - 6400, - 68301568, - 768000, - 68299776, - 768, - 68300032, - 2560, - 68300544, - 0, - 34746368, - (int)0xFE000000 - }; - + public static final short PTYPE_JITTER = 0x140c; public static final short PPAYLEN_JITTER = 0x10; @@ -145,6 +42,7 @@ public class ControlStream implements ConnectionStatusListener { private NvConnectionListener listener; private InetAddress host; + private Config config; private Socket s; private InputStream in; @@ -156,10 +54,11 @@ public class ControlStream implements ConnectionStatusListener { private Object resyncNeeded = new Object(); private boolean aborting = false; - public ControlStream(InetAddress host, NvConnectionListener listener) + public ControlStream(InetAddress host, NvConnectionListener listener, StreamConfiguration streamConfig) { this.listener = listener; this.host = host; + this.config = new Config(streamConfig); } public void initialize() throws IOException @@ -225,7 +124,6 @@ public class ControlStream implements ConnectionStatusListener { public void start() throws IOException { - sendHello(); sendConfig(); pingPong(); send1405AndGetResponse(); @@ -312,11 +210,6 @@ public class ControlStream implements ConnectionStatusListener { return sendAndGetReply(new NvCtlPacket(PTYPE_1405, PPAYLEN_1405)); } - private void sendHello() throws IOException - { - sendPacket(new NvCtlPacket(PTYPE_HELLO, PPAYLEN_HELLO, PPAYLOAD_HELLO)); - } - private void sendResync() throws IOException { ByteBuffer conf = ByteBuffer.wrap(new byte[PPAYLEN_RESYNC]).order(ByteOrder.LITTLE_ENDIAN); @@ -329,15 +222,8 @@ public class ControlStream implements ConnectionStatusListener { private void sendConfig() throws IOException { - ByteBuffer conf = ByteBuffer.wrap(new byte[PPAYLOAD_CONFIG.length * 4 + 3]).order(ByteOrder.LITTLE_ENDIAN); - - for (int i : PPAYLOAD_CONFIG) - conf.putInt(i); - - conf.putShort((short)0x0013); - conf.put((byte) 0x00); - - sendPacket(new NvCtlPacket(PTYPE_CONFIG, PPAYLEN_CONFIG, conf.array())); + out.write(config.toWire()); + out.flush(); } private void sendHeartbeat() throws IOException diff --git a/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java b/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java index 374bd650..210910c2 100644 --- a/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java +++ b/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java @@ -15,7 +15,7 @@ import org.xmlpull.v1.XmlPullParserFactory; public class NvHTTP { - private String macAddress; + private String uniqueId; private String deviceName; public static final int PORT = 47989; @@ -23,8 +23,8 @@ public class NvHTTP { public String baseUrl; - public NvHTTP(InetAddress host, String macAddress, String deviceName) { - this.macAddress = macAddress; + public NvHTTP(InetAddress host, String uniqueId, String deviceName) { + this.uniqueId = uniqueId; this.deviceName = deviceName; this.baseUrl = "http://" + host.getHostAddress() + ":" + PORT; } @@ -73,31 +73,37 @@ public class NvHTTP { } public boolean getPairState() throws IOException, XmlPullParserException { - InputStream in = openHttpConnection(baseUrl + "/pairstate?mac=" + macAddress); + InputStream in = openHttpConnection(baseUrl + "/pairstate?uniqueid=" + uniqueId); String paired = getXmlString(in, "paired"); return Integer.valueOf(paired) != 0; } public int getSessionId() throws IOException, XmlPullParserException { - InputStream in = openHttpConnection(baseUrl + "/pair?mac=" + macAddress + InputStream in = openHttpConnection(baseUrl + "/pair?uniqueid=" + uniqueId + "&devicename=" + deviceName); String sessionId = getXmlString(in, "sessionid"); return Integer.parseInt(sessionId); } - public int getSteamAppId(int sessionId) throws IOException, - XmlPullParserException { - LinkedList appList = getAppList(sessionId); - for (NvApp app : appList) { - if (app.getAppName().equals("Steam")) { - return app.getAppId(); - } - } - return 0; + public int getCurrentGame() throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/serverinfo?uniqueid=" + uniqueId); + String game = getXmlString(in, "currentgame"); + return Integer.parseInt(game); } - public LinkedList getAppList(int sessionId) throws IOException, XmlPullParserException { - InputStream in = openHttpConnection(baseUrl + "/applist?session=" + sessionId); + public NvApp getSteamApp() throws IOException, + XmlPullParserException { + LinkedList appList = getAppList(); + for (NvApp app : appList) { + if (app.getAppName().equals("Steam")) { + return app; + } + } + return null; + } + + public LinkedList getAppList() throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/applist?uniqueid=" + uniqueId); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); XmlPullParser xpp = factory.newPullParser(); @@ -135,11 +141,24 @@ public class NvHTTP { } // Returns gameSession XML attribute - public int launchApp(int sessionId, int appId) throws IOException, - XmlPullParserException { - InputStream in = openHttpConnection(baseUrl + "/launch?session=" - + sessionId + "&appid=" + appId); + public int launchApp(int appId, int width, int height, int refreshRate) throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + + "/launch?uniqueid=" + uniqueId + + "&appid=" + appId + + "&mode=" + width + "x" + height + "x" + refreshRate); String gameSession = getXmlString(in, "gamesession"); return Integer.parseInt(gameSession); } + + public boolean resumeApp() throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/resume?uniqueid=" + uniqueId); + String resume = getXmlString(in, "resume"); + return Integer.parseInt(resume) != 0; + } + + public boolean quitApp() throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/cancel?uniqueid=" + uniqueId); + String cancel = getXmlString(in, "cancel"); + return Integer.parseInt(cancel) != 0; + } }