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.

This commit is contained in:
Cameron Gutman 2013-12-19 04:24:45 -05:00
parent 7f841c1fca
commit 4701c22b67
6 changed files with 256 additions and 150 deletions

View File

@ -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<Runnable>());
}
@ -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;
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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<NvApp> 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<NvApp> getAppList(int sessionId) throws IOException, XmlPullParserException {
InputStream in = openHttpConnection(baseUrl + "/applist?session=" + sessionId);
public NvApp getSteamApp() throws IOException,
XmlPullParserException {
LinkedList<NvApp> appList = getAppList();
for (NvApp app : appList) {
if (app.getAppName().equals("Steam")) {
return app;
}
}
return null;
}
public LinkedList<NvApp> 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;
}
}