mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-19 19:13:03 +00:00
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:
parent
7f841c1fca
commit
4701c22b67
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
143
moonlight-common/src/com/limelight/nvstream/control/Config.java
Normal file
143
moonlight-common/src/com/limelight/nvstream/control/Config.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user