From d01a28c57f1b2087b915a792d947c33fe52e1353 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Tue, 6 May 2014 21:18:57 -0400 Subject: [PATCH] Initial work on updating for GFE 2.0.1's new RTSP-based handshake protocol --- moonlight-common/.classpath | 1 + moonlight-common/libs/tinyrtsp.jar | Bin 0 -> 9019 bytes .../src/com/limelight/nvstream/Handshake.java | 133 ---------- .../com/limelight/nvstream/NvConnection.java | 14 +- .../nvstream/NvConnectionListener.java | 2 +- .../nvstream/control/ByteConfigTuple.java | 17 -- .../limelight/nvstream/control/Config.java | 200 --------------- .../nvstream/control/ConfigTuple.java | 53 ---- .../nvstream/control/IntConfigTuple.java | 23 -- .../nvstream/control/ShortConfigTuple.java | 23 -- .../nvstream/rtsp/RtspConnection.java | 144 +++++++++++ .../limelight/nvstream/rtsp/SdpGenerator.java | 236 ++++++++++++++++++ 12 files changed, 393 insertions(+), 453 deletions(-) create mode 100644 moonlight-common/libs/tinyrtsp.jar delete mode 100644 moonlight-common/src/com/limelight/nvstream/Handshake.java delete mode 100644 moonlight-common/src/com/limelight/nvstream/control/ByteConfigTuple.java delete mode 100644 moonlight-common/src/com/limelight/nvstream/control/Config.java delete mode 100644 moonlight-common/src/com/limelight/nvstream/control/ConfigTuple.java delete mode 100644 moonlight-common/src/com/limelight/nvstream/control/IntConfigTuple.java delete mode 100644 moonlight-common/src/com/limelight/nvstream/control/ShortConfigTuple.java create mode 100644 moonlight-common/src/com/limelight/nvstream/rtsp/RtspConnection.java create mode 100644 moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java diff --git a/moonlight-common/.classpath b/moonlight-common/.classpath index 62423bd8..db1e0181 100644 --- a/moonlight-common/.classpath +++ b/moonlight-common/.classpath @@ -3,5 +3,6 @@ + diff --git a/moonlight-common/libs/tinyrtsp.jar b/moonlight-common/libs/tinyrtsp.jar new file mode 100644 index 0000000000000000000000000000000000000000..45086d583770a0d8739ea897c606e00594a4e697 GIT binary patch literal 9019 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 VpLtaX2!x-n2>=9y9{I0t{{hZo;uZh^ literal 0 HcmV?d00001 diff --git a/moonlight-common/src/com/limelight/nvstream/Handshake.java b/moonlight-common/src/com/limelight/nvstream/Handshake.java deleted file mode 100644 index e3249af9..00000000 --- a/moonlight-common/src/com/limelight/nvstream/Handshake.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.limelight.nvstream; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; - -public class Handshake { - public static final int PORT = 47991; - - public static final int HANDSHAKE_TIMEOUT = 5000; - - public static final byte[] PLATFORM_HELLO = - { - (byte)0x07, - (byte)0x00, - (byte)0x00, - (byte)0x00, - - // android in ASCII - (byte)0x61, - (byte)0x6e, - (byte)0x64, - (byte)0x72, - (byte)0x6f, - (byte)0x69, - (byte)0x64, - - (byte)0x03, - (byte)0x01, - (byte)0x00, - (byte)0x00 - }; - - public static final byte[] PACKET_2 = - { - (byte)0x01, - (byte)0x03, - (byte)0x02, - (byte)0x00, - (byte)0x08, - (byte)0x00 - }; - - public static final byte[] PACKET_3 = - { - (byte)0x04, - (byte)0x01, - (byte)0x00, - (byte)0x00, - - (byte)0x00, - (byte)0x00, - (byte)0x00, - (byte)0x00 - }; - - public static final byte[] PACKET_4 = - { - (byte)0x01, - (byte)0x01, - (byte)0x00, - (byte)0x00 - }; - - private static boolean waitAndDiscardResponse(InputStream in) - { - // Wait for response and discard response - try { - in.read(); - - // Wait for the full response to come in - Thread.sleep(250); - - for (int i = 0; i < in.available(); i++) - in.read(); - - } catch (IOException e1) { - return false; - } catch (InterruptedException e) { - return false; - } - - return true; - } - - public static boolean performHandshake(InetAddress host) throws IOException - { - Socket s = new Socket(); - s.connect(new InetSocketAddress(host, PORT), HANDSHAKE_TIMEOUT); - s.setSoTimeout(HANDSHAKE_TIMEOUT); - OutputStream out = s.getOutputStream(); - InputStream in = s.getInputStream(); - - // First packet - out.write(PLATFORM_HELLO); - out.flush(); - - if (!waitAndDiscardResponse(in)) { - s.close(); - return false; - } - - // Second packet - out.write(PACKET_2); - out.flush(); - - if (!waitAndDiscardResponse(in)) { - s.close(); - return false; - } - - // Third packet - out.write(PACKET_3); - out.flush(); - - if (!waitAndDiscardResponse(in)) { - s.close(); - return false; - } - - // Fourth packet - out.write(PACKET_4); - out.flush(); - - // Done - s.close(); - - return true; - } -} diff --git a/moonlight-common/src/com/limelight/nvstream/NvConnection.java b/moonlight-common/src/com/limelight/nvstream/NvConnection.java index e0cac6ca..ee0073a0 100644 --- a/moonlight-common/src/com/limelight/nvstream/NvConnection.java +++ b/moonlight-common/src/com/limelight/nvstream/NvConnection.java @@ -22,6 +22,7 @@ import com.limelight.nvstream.http.GfeHttpResponseException; import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.input.NvController; +import com.limelight.nvstream.rtsp.RtspConnection; public class NvConnection { private String host; @@ -190,9 +191,16 @@ public class NvConnection { return true; } + private boolean doRtspHandshake() throws IOException + { + RtspConnection r = new RtspConnection(hostAddr); + r.doRtspHandshake(config); + return true; + } + private boolean startControlStream() throws IOException { - controlStream = new ControlStream(hostAddr, listener, config); + controlStream = new ControlStream(hostAddr, listener); controlStream.initialize(); controlStream.start(); return true; @@ -237,8 +245,8 @@ public class NvConnection { success = startSteamBigPicture(); break; - case HANDSHAKE: - success = Handshake.performHandshake(hostAddr); + case RTSP_HANDSHAKE: + success = doRtspHandshake(); break; case CONTROL_START: diff --git a/moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java b/moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java index f7b80f82..c96f1d0a 100644 --- a/moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java +++ b/moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java @@ -4,7 +4,7 @@ public interface NvConnectionListener { public enum Stage { LAUNCH_APP("app"), - HANDSHAKE("handshake"), + RTSP_HANDSHAKE("RTSP handshake"), CONTROL_START("control connection"), VIDEO_START("video stream"), AUDIO_START("audio stream"), diff --git a/moonlight-common/src/com/limelight/nvstream/control/ByteConfigTuple.java b/moonlight-common/src/com/limelight/nvstream/control/ByteConfigTuple.java deleted file mode 100644 index 78b6a253..00000000 --- a/moonlight-common/src/com/limelight/nvstream/control/ByteConfigTuple.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.limelight.nvstream.control; - -public class ByteConfigTuple extends ConfigTuple { - public static final short PAYLOAD_LENGTH = 1; - - public byte payload; - - public ByteConfigTuple(short packetType, byte payload) { - super(packetType, PAYLOAD_LENGTH); - this.payload = payload; - } - - @Override - public byte[] payloadToWire() { - return new byte[] {payload}; - } -} diff --git a/moonlight-common/src/com/limelight/nvstream/control/Config.java b/moonlight-common/src/com/limelight/nvstream/control/Config.java deleted file mode 100644 index 3339125a..00000000 --- a/moonlight-common/src/com/limelight/nvstream/control/Config.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.limelight.nvstream.control; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; - -import com.limelight.nvstream.StreamConfiguration; - -public class Config { - - public static final ConfigTuple[] CONFIG_720_60 = - { - new ByteConfigTuple((short)0x1207, (byte)1), //iFrameOnDemand - new IntConfigTuple((short)0x120b, 7), //averageBitrate - new IntConfigTuple((short)0x120c, 7), //peakBitrate - new IntConfigTuple((short)0x120d, 60), //gopLength - new IntConfigTuple((short)0x120e, 100), //vbvMultiplier - new IntConfigTuple((short)0x120f, 5), //rateControlMode - new IntConfigTuple((short)0x1210, 4), //slicesPerFrame - new IntConfigTuple((short)0x1202, 1024), //packetSize - new ByteConfigTuple((short)0x1203, (byte)0), //recordServerStats - new ByteConfigTuple((short)0x1201, (byte)0), //serverCapture - new ByteConfigTuple((short)0x1234, (byte)0), //serverNetworkCapture - new ByteConfigTuple((short)0x1248, (byte)0), - new ByteConfigTuple((short)0x1208, (byte)1), //refPicInvalidation - new ByteConfigTuple((short)0x1209, (byte)0), //enableFrameRateCtrl - new IntConfigTuple((short)0x1212, 3000), //pingBackIntervalMs - new IntConfigTuple((short)0x1238, 10000), //pingBackTimeoutMs - new ByteConfigTuple((short)0x1211, (byte)0), //enableSubframeEncoding - new ByteConfigTuple((short)0x1213, (byte)1), //videoQoSFecEnable - new IntConfigTuple((short)0x1214, 50), //videoQoSFecNumSrcPackets - new IntConfigTuple((short)0x1215, 60), //videoQoSFecNumOutPackets - new IntConfigTuple((short)0x1216, 20), //videoQoSFecRepairPercent - new IntConfigTuple((short)0x1217, 0), //videoQoSTsEnable - new IntConfigTuple((short)0x1218, 8), //videoQoSTsAverageBitrate - new IntConfigTuple((short)0x1219, 10), //videoQoSTsMaximumBitrate - new IntConfigTuple((short)0x121a, 311), //videoQoSBwFlags - new IntConfigTuple((short)0x121b, 10000), //videoQoSBwMaximumBitrate - new IntConfigTuple((short)0x121c, 2000), //videoQoSBwMinimumBitrate - new IntConfigTuple((short)0x121d, 50), //videoQoSBwStatsTime - new IntConfigTuple((short)0x121e, 3000), //videoQoSBwZeroLossCount - new IntConfigTuple((short)0x121f, 2), //videoQoSBwLossThreshold - new IntConfigTuple((short)0x122a, 5000), //videoQoSBwOwdThreshold - new IntConfigTuple((short)0x122b, 500), //videoQoSBwOwdReference - new IntConfigTuple((short)0x1220, 75), //videoQoSBwLossWaitTime - new IntConfigTuple((short)0x1221, 25), //videoQoSBwRateDropMultiplier - new IntConfigTuple((short)0x1222, 10), //videoQoSBwRateGainMultiplier - new IntConfigTuple((short)0x1223, 60), //videoQoSBwMaxFps - new IntConfigTuple((short)0x1224, 30), //videoQoSBwMinFps - new IntConfigTuple((short)0x1225, 3), //videoQoSBwFpsThreshold - new IntConfigTuple((short)0x1226, 1000), //videoQoSBwJitterThreshold - new IntConfigTuple((short)0x1227, 5000), //videoQoSBwJitterWaitTime - new IntConfigTuple((short)0x1228, 5000), //videoQoSBwNoJitterWaitTime - new IntConfigTuple((short)0x124e, 110), - new IntConfigTuple((short)0x1237, 10), //videoQoSBwEarlyDetectionEnableL1Threshold - new IntConfigTuple((short)0x1236, 6), //videoQoSBwEarlyDetectionEnableL0Threshold - new IntConfigTuple((short)0x1235, 4), //videoQoSBwEarlyDetectionDisableThreshold - new IntConfigTuple((short)0x1242, 20000), //videoQoSBwEarlyDetectionWaitTime - new IntConfigTuple((short)0x1244, 100), - new IntConfigTuple((short)0x1245, 1000), - new IntConfigTuple((short)0x1246, 720), - new IntConfigTuple((short)0x1247, 480), - new IntConfigTuple((short)0x1229, 5000), //videoQosVideoQualityScoreUpdateTime - new ByteConfigTuple((short)0x122e, (byte)7), //videoQosTrafficType - new IntConfigTuple((short)0x1231, 40), //videoQosBnNotifyUpBoundThreshold - new IntConfigTuple((short)0x1232, 25), //videoQosBnNotifyLowBoundThreshold - new IntConfigTuple((short)0x1233, 3000), //videoQosBnNotifyWaitTime - new IntConfigTuple((short)0x122c, 3), //videoQosInvalidateThreshold - new IntConfigTuple((short)0x122d, 10), //videoQosInvalidateSkipPercentage - /*new IntConfigTuple((short)0x123b, 12), - new IntConfigTuple((short)0x123c, 3), - new IntConfigTuple((short)0x1249, 0), - new IntConfigTuple((short)0x124a, 4000), - new IntConfigTuple((short)0x124b, 5000), - new IntConfigTuple((short)0x124c, 6000), - new IntConfigTuple((short)0x124d, 1000),*/ - new IntConfigTuple((short)0x122f, 0), //riSecurityProtocol - new ShortConfigTuple((short)0x1230, (short)0), //riSecInfoUsePredefinedCert - new IntConfigTuple((short)0x1239, 0), //videoFrameDropIntervalNumber - new IntConfigTuple((short)0x123a, 0), //videoFrameDropContinualNumber - new IntConfigTuple((short)0x123d, 96000), //audioQosBitRate - new IntConfigTuple((short)0x123e, 5), //audioQosPacketDuration - new IntConfigTuple((short)0x123f, 1), //audioQosEnablePacketLossPercentage - new IntConfigTuple((short)0x1243, 100) //audioQosPacketLossPercentageUpdateInterval - }; - - public static final ConfigTuple[] CONFIG_1080_30_DIFF = - { - new IntConfigTuple((short)0x120b, 10), //averageBitrate - new IntConfigTuple((short)0x120c, 10), //peakBitrate - - // HACK: Streaming 1080p30 without these options causes the encoder - // to step down to 720p which breaks the CPU decoder - new IntConfigTuple((short)0x121b, 25000), //videoQoSBwMaximumBitrate - new IntConfigTuple((short)0x121c, 25000), //videoQoSBwMinimumBitrate - - new IntConfigTuple((short)0x1246, 1280), - new IntConfigTuple((short)0x1247, 720), - /*new IntConfigTuple((short)0x124a, 5000), - new IntConfigTuple((short)0x124c, 7000),*/ - }; - - public static final ConfigTuple[] CONFIG_1080_60_DIFF = - { - new IntConfigTuple((short)0x120b, 30), //averageBitrate - new IntConfigTuple((short)0x120c, 30), //peakBitrate - new IntConfigTuple((short)0x120f, 4), //rateControlMode - new IntConfigTuple((short)0x121b, 30000), //videoQoSBwMaximumBitrate - new IntConfigTuple((short)0x121c, 25000), //videoQoSBwMinimumBitrate - new IntConfigTuple((short)0x1245, 3000), - new IntConfigTuple((short)0x1246, 1280), - new IntConfigTuple((short)0x1247, 720), - /*new IntConfigTuple((short)0x124a, 5000), - new IntConfigTuple((short)0x124c, 7000),*/ - }; - - private StreamConfiguration streamConfig; - - public Config(StreamConfiguration streamConfig) { - this.streamConfig = streamConfig; - } - - private void updateSetWithConfig(ArrayList set, ConfigTuple[] config) - { - for (ConfigTuple tuple : config) - { - int i; - - for (i = 0; i < set.size(); i++) { - ConfigTuple existingTuple = set.get(i); - if (existingTuple.packetType == tuple.packetType) { - set.remove(i); - set.add(i, tuple); - break; - } - } - - if (i == set.size()) { - set.add(tuple); - } - } - } - - private int getConfigOnWireSize(ArrayList tupleSet) - { - int size = 0; - - for (ConfigTuple t : tupleSet) - { - size += ConfigTuple.HEADER_LENGTH + t.payloadLength; - } - - return size; - } - - private ArrayList generateTupleSet() { - ArrayList tupleSet = new ArrayList(); - - tupleSet.add(new IntConfigTuple((short)0x1204, streamConfig.getWidth())); - tupleSet.add(new IntConfigTuple((short)0x1205, streamConfig.getHeight())); - tupleSet.add(new IntConfigTuple((short)0x1206, 1)); //videoTransferProtocol - tupleSet.add(new IntConfigTuple((short)0x120A, streamConfig.getRefreshRate())); - - // Start with the initial config for 720p60 - updateSetWithConfig(tupleSet, CONFIG_720_60); - - if (streamConfig.getWidth() >= 1920 && - streamConfig.getHeight() >= 1080) - { - if (streamConfig.getRefreshRate() >= 60) - { - // Update the initial set with the changed 1080p60 options - updateSetWithConfig(tupleSet, CONFIG_1080_60_DIFF); - } - else - { - // Update the initial set with the changed 1080p30 options - updateSetWithConfig(tupleSet, CONFIG_1080_30_DIFF); - } - } - - return tupleSet; - } - - public byte[] toWire() { - ArrayList tupleSet = generateTupleSet(); - ByteBuffer bb = ByteBuffer.allocate(getConfigOnWireSize(tupleSet) + 4).order(ByteOrder.LITTLE_ENDIAN); - - for (ConfigTuple t : tupleSet) - { - bb.put(t.toWire()); - } - - // Config tail - bb.putShort((short) 0x13fe); - bb.putShort((short) 0x00); - - return bb.array(); - } -} diff --git a/moonlight-common/src/com/limelight/nvstream/control/ConfigTuple.java b/moonlight-common/src/com/limelight/nvstream/control/ConfigTuple.java deleted file mode 100644 index ffd4457a..00000000 --- a/moonlight-common/src/com/limelight/nvstream/control/ConfigTuple.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.limelight.nvstream.control; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public abstract class ConfigTuple { - public short packetType; - public short payloadLength; - - public static final short HEADER_LENGTH = 4; - - public ConfigTuple(short packetType, short payloadLength) - { - this.packetType = packetType; - this.payloadLength = payloadLength; - } - - public abstract byte[] payloadToWire(); - - public byte[] toWire() - { - byte[] payload = payloadToWire(); - ByteBuffer bb = ByteBuffer.allocate(HEADER_LENGTH + (payload != null ? payload.length : 0)) - .order(ByteOrder.LITTLE_ENDIAN); - - bb.putShort(packetType); - bb.putShort(payloadLength); - - if (payload != null) { - bb.put(payload); - } - - return bb.array(); - } - - @Override - public int hashCode() - { - return packetType; - } - - @Override - public boolean equals(Object o) - { - // We only compare the packet types on purpose - if (o instanceof ConfigTuple) { - return ((ConfigTuple)o).packetType == packetType; - } - else { - return false; - } - } -} diff --git a/moonlight-common/src/com/limelight/nvstream/control/IntConfigTuple.java b/moonlight-common/src/com/limelight/nvstream/control/IntConfigTuple.java deleted file mode 100644 index 9fe43c14..00000000 --- a/moonlight-common/src/com/limelight/nvstream/control/IntConfigTuple.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.limelight.nvstream.control; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class IntConfigTuple extends ConfigTuple { - - public static final short PAYLOAD_LENGTH = 4; - - public int payload; - - public IntConfigTuple(short packetType, int payload) { - super(packetType, PAYLOAD_LENGTH); - this.payload = payload; - } - - @Override - public byte[] payloadToWire() { - ByteBuffer bb = ByteBuffer.allocate(PAYLOAD_LENGTH).order(ByteOrder.LITTLE_ENDIAN); - bb.putInt(payload); - return bb.array(); - } -} diff --git a/moonlight-common/src/com/limelight/nvstream/control/ShortConfigTuple.java b/moonlight-common/src/com/limelight/nvstream/control/ShortConfigTuple.java deleted file mode 100644 index 7f684ca0..00000000 --- a/moonlight-common/src/com/limelight/nvstream/control/ShortConfigTuple.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.limelight.nvstream.control; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class ShortConfigTuple extends ConfigTuple { - - public static final short PAYLOAD_LENGTH = 2; - - public short payload; - - public ShortConfigTuple(short packetType, short payload) { - super(packetType, PAYLOAD_LENGTH); - this.payload = payload; - } - - @Override - public byte[] payloadToWire() { - ByteBuffer bb = ByteBuffer.allocate(PAYLOAD_LENGTH).order(ByteOrder.LITTLE_ENDIAN); - bb.putShort(payload); - return bb.array(); - } -} \ No newline at end of file diff --git a/moonlight-common/src/com/limelight/nvstream/rtsp/RtspConnection.java b/moonlight-common/src/com/limelight/nvstream/rtsp/RtspConnection.java new file mode 100644 index 00000000..fdca46db --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/rtsp/RtspConnection.java @@ -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(), 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()); + } + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java b/moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java new file mode 100644 index 00000000..e1796d85 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/rtsp/SdpGenerator.java @@ -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(); + } +}