Initial work on updating for GFE 2.0.1's new RTSP-based handshake protocol

This commit is contained in:
Cameron Gutman 2014-05-06 21:18:57 -04:00
parent 4ee99a78b2
commit d01a28c57f
12 changed files with 393 additions and 453 deletions

View File

@ -3,5 +3,6 @@
<classpathentry kind="src" path="src"/> <classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="lib" path="libs/xpp3-1.1.4c.jar"/> <classpathentry kind="lib" path="libs/xpp3-1.1.4c.jar"/>
<classpathentry kind="lib" path="libs/tinyrtsp.jar"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

Binary file not shown.

View File

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

View File

@ -22,6 +22,7 @@ import com.limelight.nvstream.http.GfeHttpResponseException;
import com.limelight.nvstream.http.NvApp; import com.limelight.nvstream.http.NvApp;
import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.http.NvHTTP;
import com.limelight.nvstream.input.NvController; import com.limelight.nvstream.input.NvController;
import com.limelight.nvstream.rtsp.RtspConnection;
public class NvConnection { public class NvConnection {
private String host; private String host;
@ -190,9 +191,16 @@ public class NvConnection {
return true; return true;
} }
private boolean doRtspHandshake() throws IOException
{
RtspConnection r = new RtspConnection(hostAddr);
r.doRtspHandshake(config);
return true;
}
private boolean startControlStream() throws IOException private boolean startControlStream() throws IOException
{ {
controlStream = new ControlStream(hostAddr, listener, config); controlStream = new ControlStream(hostAddr, listener);
controlStream.initialize(); controlStream.initialize();
controlStream.start(); controlStream.start();
return true; return true;
@ -237,8 +245,8 @@ public class NvConnection {
success = startSteamBigPicture(); success = startSteamBigPicture();
break; break;
case HANDSHAKE: case RTSP_HANDSHAKE:
success = Handshake.performHandshake(hostAddr); success = doRtspHandshake();
break; break;
case CONTROL_START: case CONTROL_START:

View File

@ -4,7 +4,7 @@ public interface NvConnectionListener {
public enum Stage { public enum Stage {
LAUNCH_APP("app"), LAUNCH_APP("app"),
HANDSHAKE("handshake"), RTSP_HANDSHAKE("RTSP handshake"),
CONTROL_START("control connection"), CONTROL_START("control connection"),
VIDEO_START("video stream"), VIDEO_START("video stream"),
AUDIO_START("audio stream"), AUDIO_START("audio stream"),

View File

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

View File

@ -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<ConfigTuple> 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<ConfigTuple> tupleSet)
{
int size = 0;
for (ConfigTuple t : tupleSet)
{
size += ConfigTuple.HEADER_LENGTH + t.payloadLength;
}
return size;
}
private ArrayList<ConfigTuple> generateTupleSet() {
ArrayList<ConfigTuple> tupleSet = new ArrayList<ConfigTuple>();
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<ConfigTuple> 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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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