mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-08-29 05:10:49 +00:00
Initial work on updating for GFE 2.0.1's new RTSP-based handshake protocol
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package com.limelight.nvstream.rtsp;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.limelight.nvstream.StreamConfiguration;
|
||||
import com.tinyrtsp.rtsp.message.RtspMessage;
|
||||
import com.tinyrtsp.rtsp.message.RtspRequest;
|
||||
import com.tinyrtsp.rtsp.message.RtspResponse;
|
||||
import com.tinyrtsp.rtsp.parser.RtspStream;
|
||||
|
||||
public class RtspConnection {
|
||||
public static final int PORT = 48010;
|
||||
public static final int RTSP_TIMEOUT = 5000;
|
||||
|
||||
// SHIELD Update 77
|
||||
public static final int CLIENT_VERSION = 9;
|
||||
|
||||
private int sequenceNumber = 1;
|
||||
private int sessionId = 0;
|
||||
|
||||
private String host;
|
||||
|
||||
public RtspConnection(InetAddress host) {
|
||||
if (host instanceof Inet6Address) {
|
||||
// RFC2732-formatted IPv6 address for use in URL
|
||||
this.host = "["+host.getHostAddress()+"]";
|
||||
}
|
||||
else {
|
||||
this.host = host.getHostAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private RtspRequest createRtspRequest(String command, String target) {
|
||||
RtspRequest m = new RtspRequest(command, target, "RTSP/1.0",
|
||||
sequenceNumber++, new HashMap<String, String>(), null);
|
||||
m.setOption("X-GS-ClientVersion", ""+CLIENT_VERSION);
|
||||
return m;
|
||||
}
|
||||
|
||||
private RtspResponse transactRtspMessage(RtspMessage m) throws IOException {
|
||||
Socket s = new Socket();
|
||||
try {
|
||||
s.setTcpNoDelay(true);
|
||||
s.connect(new InetSocketAddress(host, PORT), RTSP_TIMEOUT);
|
||||
|
||||
RtspStream rtspStream = new RtspStream(s.getInputStream(), s.getOutputStream());
|
||||
try {
|
||||
rtspStream.write(m);
|
||||
return (RtspResponse) rtspStream.read();
|
||||
} finally {
|
||||
rtspStream.close();
|
||||
}
|
||||
} finally {
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
||||
private RtspResponse requestOptions() throws IOException {
|
||||
RtspRequest m = createRtspRequest("OPTIONS", "rtsp://"+host);
|
||||
return transactRtspMessage(m);
|
||||
}
|
||||
|
||||
private RtspResponse requestDescribe() throws IOException {
|
||||
RtspRequest m = createRtspRequest("DESCRIBE", "rtsp://"+host);
|
||||
m.setOption("Accept", "application/sdp");
|
||||
m.setOption("If-Modified-Since", "Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
return transactRtspMessage(m);
|
||||
}
|
||||
|
||||
private RtspResponse setupStream(String streamName) throws IOException {
|
||||
RtspRequest m = createRtspRequest("SETUP", "streamid="+streamName);
|
||||
if (sessionId != 0) {
|
||||
m.setOption("Session", ""+sessionId);
|
||||
}
|
||||
m.setOption("Transport", " ");
|
||||
m.setOption("If-Modified-Since", "Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
return transactRtspMessage(m);
|
||||
}
|
||||
|
||||
private RtspResponse playStream(String streamName) throws IOException {
|
||||
RtspRequest m = createRtspRequest("PLAY", "streamid="+streamName);
|
||||
m.setOption("Session", ""+sessionId);
|
||||
return transactRtspMessage(m);
|
||||
}
|
||||
|
||||
private RtspResponse sendVideoAnnounce(StreamConfiguration sc) throws IOException {
|
||||
RtspRequest m = createRtspRequest("ANNOUNCE", "streamid=video");
|
||||
m.setOption("Session", ""+sessionId);
|
||||
m.setOption("Content-type", "application/sdp");
|
||||
// FIXME: IP jank
|
||||
m.setPayload(SdpGenerator.generateSdpFromConfig(InetAddress.getByName(host), sc));
|
||||
m.setOption("Content-length", ""+m.getPayload().length());
|
||||
return transactRtspMessage(m);
|
||||
}
|
||||
|
||||
public void doRtspHandshake(StreamConfiguration sc) throws IOException {
|
||||
RtspResponse r;
|
||||
|
||||
r = requestOptions();
|
||||
if (r.getStatusCode() != 200) {
|
||||
throw new IOException("RTSP OPTIONS request failed: "+r.getStatusCode());
|
||||
}
|
||||
|
||||
r = requestDescribe();
|
||||
if (r.getStatusCode() != 200) {
|
||||
throw new IOException("RTSP DESCRIBE request failed: "+r.getStatusCode());
|
||||
}
|
||||
|
||||
r = setupStream("audio");
|
||||
if (r.getStatusCode() != 200) {
|
||||
throw new IOException("RTSP SETUP request failed: "+r.getStatusCode());
|
||||
}
|
||||
|
||||
try {
|
||||
sessionId = Integer.parseInt(r.getOption("Session"));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("RTSP SETUP response was malformed");
|
||||
}
|
||||
|
||||
r = setupStream("video");
|
||||
if (r.getStatusCode() != 200) {
|
||||
throw new IOException("RTSP SETUP request failed: "+r.getStatusCode());
|
||||
}
|
||||
|
||||
r = sendVideoAnnounce(sc);
|
||||
if (r.getStatusCode() != 200) {
|
||||
throw new IOException("RTSP ANNOUNCE request failed: "+r.getStatusCode());
|
||||
}
|
||||
|
||||
r = playStream("video");
|
||||
if (r.getStatusCode() != 200) {
|
||||
throw new IOException("RTSP PLAY request failed: "+r.getStatusCode());
|
||||
}
|
||||
r = playStream("audio");
|
||||
if (r.getStatusCode() != 200) {
|
||||
throw new IOException("RTSP PLAY request failed: "+r.getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.limelight.nvstream.rtsp;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Inet6Address;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
|
||||
import com.limelight.nvstream.StreamConfiguration;
|
||||
|
||||
public class SdpGenerator {
|
||||
private static void addSessionAttributeBytes(StringBuilder config, String attribute, byte[] value) {
|
||||
try {
|
||||
addSessionAttribute(config, attribute, new String(value, "IBM437"));
|
||||
} catch (UnsupportedEncodingException e) {}
|
||||
}
|
||||
|
||||
private static void addSessionAttributeInts(StringBuilder config, String attribute, int[] value) {
|
||||
ByteBuffer b = ByteBuffer.allocate(value.length * 4).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int val : value) {
|
||||
b.putInt(val);
|
||||
}
|
||||
|
||||
addSessionAttributeBytes(config, attribute, b.array());
|
||||
}
|
||||
|
||||
private static void addSessionAttributeInt(StringBuilder config, String attribute, int value) {
|
||||
ByteBuffer b = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
|
||||
b.putInt(value);
|
||||
addSessionAttributeBytes(config, attribute, b.array());
|
||||
}
|
||||
|
||||
private static void addSessionAttribute(StringBuilder config, String attribute, String value) {
|
||||
config.append("a="+attribute+":"+value+" \r\n");
|
||||
}
|
||||
|
||||
public static String generateSdpFromConfig(InetAddress host, StreamConfiguration sc) {
|
||||
StringBuilder config = new StringBuilder();
|
||||
config.append("v=0").append("\r\n"); // SDP Version 0
|
||||
config.append("o=android 0 9 IN ");
|
||||
if (host instanceof Inet6Address) {
|
||||
config.append("IPv6 ");
|
||||
}
|
||||
else {
|
||||
config.append("IPv4 ");
|
||||
}
|
||||
config.append(host.getHostAddress());
|
||||
config.append("\r\n");
|
||||
config.append("s=NVIDIA Streaming Client").append("\r\n");
|
||||
|
||||
addSessionAttributeBytes(config, "x-nv-callbacks", new byte[] {
|
||||
0x50, 0x51, 0x49, 0x4a, 0x0d,
|
||||
(byte)0xad, 0x30, 0x4a, (byte)0xf1, (byte)0xbd, 0x30, 0x4a, (byte)0xd5,
|
||||
(byte)0xac, 0x30, 0x4a, 0x21, (byte)0xbc, 0x30, 0x4a, (byte)0xc1,
|
||||
(byte)0xbb, 0x30, 0x4a, 0x7d, (byte)0xbb, 0x30, 0x4a, 0x19,
|
||||
(byte)0xbb, 0x30, 0x4a, 0x00, 0x00, 0x00, 0x00
|
||||
});
|
||||
addSessionAttributeBytes(config, "x-nv-videoDecoder", new byte[] {
|
||||
0x50, 0x51, 0x49, 0x4a, 0x65, (byte)0xad, 0x30, 0x4a, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xd1, (byte)0xac, 0x30,
|
||||
0x4a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x4d, (byte)0xad, 0x30, 0x4a
|
||||
});
|
||||
addSessionAttributeBytes(config, "x-nv-audioRenderer", new byte[] {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
});
|
||||
|
||||
addSessionAttribute(config, "x-nv-general.serverAddress", host.getHostAddress());
|
||||
|
||||
addSessionAttributeInts(config, "x-nv-general.serverPorts", new int[] {
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
|
||||
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
|
||||
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
|
||||
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
|
||||
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
|
||||
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
|
||||
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff,
|
||||
|
||||
0x00000000, 0xffffffff, 0xffffffff, 0x00000000,
|
||||
0xffffffff, 0xffffffff, 0x00000000, 0xffffffff,
|
||||
0xffffffff, 0x00000000, 0xffffffff, 0xffffffff
|
||||
});
|
||||
|
||||
addSessionAttribute(config, "x-nv-general.videoSyncAudioDelayAdjust", "10000");
|
||||
addSessionAttribute(config, "x-nv-general.startTime", "0");
|
||||
addSessionAttributeInt(config, "x-nv-general.featureFlags", 0xffffffff);
|
||||
addSessionAttribute(config, "x-nv-general.userIdleWarningTimeout", "0");
|
||||
addSessionAttribute(config, "x-nv-general.userIdleSessionTimeout", "0");
|
||||
addSessionAttribute(config, "x-nv-general.serverCapture", "0");
|
||||
addSessionAttribute(config, "x-nv-general.clientCapture", "0");
|
||||
addSessionAttribute(config, "x-nv-general.rtpQueueMaxPackets", "16");
|
||||
addSessionAttribute(config, "x-nv-general.rtpQueueMaxDurationMs", "40");
|
||||
addSessionAttribute(config, "x-nv-general.useRtspClient", "257");
|
||||
|
||||
addSessionAttribute(config, "x-nv-video[0].clientViewportWd", ""+sc.getWidth());
|
||||
addSessionAttribute(config, "x-nv-video[0].clientViewportHt", ""+sc.getHeight());
|
||||
addSessionAttribute(config, "x-nv-video[0].adapterNumber", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].maxFPS", "30");
|
||||
addSessionAttribute(config, "x-nv-video[0].iFrameOnDemand", "1");
|
||||
|
||||
// FIXME: Handle other settings
|
||||
addSessionAttributeInt(config, "x-nv-video[0].transferProtocol", 1);
|
||||
addSessionAttributeInt(config, "x-nv-video[0].rateControlMode", 5);
|
||||
addSessionAttribute(config, "x-nv-video[0].averageBitrate", "7");
|
||||
addSessionAttribute(config, "x-nv-video[0].peakBitrate", "7");
|
||||
|
||||
addSessionAttribute(config, "x-nv-video[0].gopLength", "60");
|
||||
addSessionAttribute(config, "x-nv-video[0].vbvMultiplier", "100");
|
||||
addSessionAttribute(config, "x-nv-video[0].slicesPerFrame", "4");
|
||||
addSessionAttribute(config, "x-nv-video[0].numTemporalLayers", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].packetSize", "1024");
|
||||
addSessionAttribute(config, "x-nv-video[0].enableSubframeEncoding", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].refPicInvalidation", "1");
|
||||
addSessionAttribute(config, "x-nv-video[0].pingBackIntervalMs", "3000");
|
||||
addSessionAttribute(config, "x-nv-video[0].pingBackTimeoutMs", "10000");
|
||||
addSessionAttribute(config, "x-nv-video[0].timeoutLengthMs", "7000");
|
||||
addSessionAttribute(config, "x-nv-video[0].fullFrameAssembly", "1");
|
||||
addSessionAttribute(config, "x-nv-video[0].decodeIncompleteFrames", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].enableIntraRefresh", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].enableLongTermReferences", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].enableFrameRateCtrl", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].rtpDynamicPort", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].framesWithInvalidRefThreshold", "0");
|
||||
addSessionAttribute(config, "x-nv-video[0].consecutiveFrameLostThreshold", "0");
|
||||
|
||||
addSessionAttribute(config, "x-nv-vqos[0].ts.enable", "0");
|
||||
|
||||
// FIXME: Handle other settings
|
||||
addSessionAttribute(config, "x-nv-vqos[0].ts.averageBitrate", "8");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].ts.maximumBitrate", "10");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.flags", "823");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.maximumBitrate", "10000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.minimumBitrate", "2000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.statsTime", "50");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.zeroLossCount", "3000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.lossThreshold", "2");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.owdThreshold", "5000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.owdReference", "500");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.lossWaitTime", "75");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.rateDropMultiplier", "25");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.rateGainMultiplier", "10");
|
||||
|
||||
// FIXME: Other settings?
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.maxFps", "60");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.minFps", "30");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.fpsThreshold", "3");
|
||||
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.jitterThreshold", "1000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.jitterWaitTime", "5000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.noJitterWaitTime", "5000");
|
||||
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionEnableBitRatePercentThreshold", "110");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionEnableL1Threshold", "10");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionEnableL0Threshold", "6");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionDisableThreshold", "4");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionDisableWaitTime", "20000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionDisableWaitPercent", "100");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionLowerBoundRate", "1000");
|
||||
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionLowerBoundWidth", "720");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.earlyDetectionLowerBoundHeight", "480");
|
||||
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.pf.enableFlags", "3");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.pf.lowBitrate30FpsThreshold", "4000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.pf.lowBitrate60FpsThreshold", "5000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.pf.highBitrateThreshold", "6000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bw.pf.bitrateStepSize", "1000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bn.notifyUpBoundThreshold", "40");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bn.notifyLowBoundThreshold", "25");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].bn.notifyWaitTime", "3000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].fec.enable", "1");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].fec.numSrcPackets", "50");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].fec.numOutPackets", "60");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].fec.repairPercent", "20");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].pictureRefreshIntervalMs", "0");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].videoQualityScoreUpdateTime", "5000");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].invalidateThreshold", "3");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].invalidateSkipPercentage", "10");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].qosTrafficType", "7");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].videoQoSMaxRoundTripLatencyFrames", "12");
|
||||
addSessionAttribute(config, "x-nv-vqos[0].videoQoSMaxConsecutiveDrops", "3");
|
||||
addSessionAttributeInt(config, "x-nv-vqos[0].profile", 0);
|
||||
|
||||
addSessionAttributeInt(config, "x-nv-aqos.mode", 1);
|
||||
addSessionAttribute(config, "x-nv-aqos.enableAudioStats", "1");
|
||||
addSessionAttribute(config, "x-nv-aqos.audioStatsUpdateIntervalMs", "70");
|
||||
addSessionAttribute(config, "x-nv-aqos.enablePacketLossPercentage", "1");
|
||||
addSessionAttribute(config, "x-nv-aqos.bitRate", "96000");
|
||||
addSessionAttribute(config, "x-nv-aqos.packetDuration", "5");
|
||||
addSessionAttribute(config, "x-nv-aqos.packetLossPercentageUpdateIntervalMs", "100");
|
||||
addSessionAttribute(config, "x-nv-aqos.qosTrafficType", "4");
|
||||
|
||||
addSessionAttribute(config, "x-nv-runtime.recordClientStats", "8");
|
||||
addSessionAttribute(config, "x-nv-runtime.recordServerStats", "0");
|
||||
addSessionAttribute(config, "x-nv-runtime.clientNetworkCapture", "0");
|
||||
addSessionAttribute(config, "x-nv-runtime.clientTraceCapture", "0");
|
||||
addSessionAttribute(config, "x-nv-runtime.serverNetworkCapture", "0");
|
||||
addSessionAttribute(config, "x-nv-runtime.serverTraceCapture", "0");
|
||||
|
||||
addSessionAttributeInt(config, "x-nv-ri.protocol", 0);
|
||||
addSessionAttribute(config, "x-nv-ri.sendStatus", "0");
|
||||
addSessionAttributeInt(config, "x-nv-ri.securityProtocol", 0);
|
||||
addSessionAttributeBytes(config, "x-nv-ri.secInfo", new byte[0x20a]);
|
||||
addSessionAttribute(config, "x-nv-videoFrameDropIntervalNumber", "0");
|
||||
addSessionAttribute(config, "x-nv-videoFrameDropContinualNumber", "0");
|
||||
|
||||
config.append("t=0 0").append("\r\n");
|
||||
|
||||
config.append("m=video 47996 ").append("\r\n");
|
||||
|
||||
return config.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user