457 lines
8.1 KiB
Java

package com.limelight.nvstream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class NvControl {
public static final int PORT = 47995;
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;
public static final short PTYPE_HEARTBEAT = 0x1401;
public static final short PPAYLEN_HEARTBEAT = 0x0000;
public static final short PTYPE_1405 = 0x1405;
public static final short PPAYLEN_1405 = 0x0000;
public static final short PTYPE_1404 = 0x1404;
public static final short PPAYLEN_1404 = 0x0010;
public static final byte[] PPAYLOAD_1404 = new byte[]
{
0x02,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x02,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
};
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;
private int seqNum;
private Socket s;
private InputStream in;
private OutputStream out;
private Thread heartbeatThread;
private Thread jitterThread;
private boolean aborting = false;
public NvControl(String host) throws UnknownHostException, IOException
{
s = new Socket(host, PORT);
in = s.getInputStream();
out = s.getOutputStream();
}
public void sendPacket(NvCtlPacket packet) throws IOException
{
out.write(packet.toWire());
out.flush();
}
public NvControl.NvCtlResponse sendAndGetReply(NvCtlPacket packet) throws IOException
{
sendPacket(packet);
return new NvCtlResponse(in);
}
private void sendJitter() throws IOException
{
ByteBuffer bb = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(0);
bb.putInt(77);
bb.putInt(888);
bb.putInt(seqNum += 2);
sendPacket(new NvCtlPacket(PTYPE_JITTER, PPAYLEN_JITTER, bb.array()));
}
public void abort()
{
if (aborting) {
return;
}
aborting = true;
if (jitterThread != null) {
jitterThread.interrupt();
}
if (heartbeatThread != null) {
heartbeatThread.interrupt();
}
try {
s.close();
} catch (IOException e) {}
}
public void start() throws IOException
{
System.out.println("CTL: Sending hello");
sendHello();
System.out.println("CTL: Sending config");
sendConfig();
System.out.println("CTL: Initial ping/pong");
pingPong();
System.out.println("CTL: Sending and waiting for 1405");
send1405AndGetResponse();
//System.out.println("CTL: Sending 1404");
//send1404();
System.out.println("CTL: Launching heartbeat thread");
heartbeatThread = new Thread() {
@Override
public void run() {
while (!isInterrupted())
{
try {
sendHeartbeat();
} catch (IOException e1) {
abort();
return;
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
abort();
return;
}
}
}
};
heartbeatThread.start();
}
public void startJitterPackets()
{
jitterThread = new Thread() {
@Override
public void run() {
while (!isInterrupted())
{
try {
sendJitter();
} catch (IOException e1) {
abort();
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
abort();
return;
}
}
}
};
jitterThread.start();
}
private NvControl.NvCtlResponse send1405AndGetResponse() throws IOException
{
return sendAndGetReply(new NvCtlPacket(PTYPE_1405, PPAYLEN_1405));
}
private void sendHello() throws IOException
{
sendPacket(new NvCtlPacket(PTYPE_HELLO, PPAYLEN_HELLO, PPAYLOAD_HELLO));
}
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()));
}
private void sendHeartbeat() throws IOException
{
sendPacket(new NvCtlPacket(PTYPE_HEARTBEAT, PPAYLEN_HEARTBEAT));
}
private NvControl.NvCtlResponse pingPong() throws IOException
{
sendPacket(new NvCtlPacket(PTYPE_KEEPALIVE, PPAYLEN_KEEPALIVE));
return new NvControl.NvCtlResponse(in);
}
class NvCtlPacket {
public short type;
public short paylen;
public byte[] payload;
public NvCtlPacket(InputStream in) throws IOException
{
byte[] header = new byte[4];
int offset = 0;
do
{
int bytesRead = in.read(header, offset, header.length - offset);
if (bytesRead < 0) {
break;
}
offset += bytesRead;
} while (offset != header.length);
if (offset != header.length) {
throw new IOException("Socket closed prematurely");
}
ByteBuffer bb = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN);
type = bb.getShort();
paylen = bb.getShort();
if (paylen != 0)
{
payload = new byte[paylen];
offset = 0;
do
{
int bytesRead = in.read(payload, offset, payload.length - offset);
if (bytesRead < 0) {
break;
}
offset += bytesRead;
} while (offset != payload.length);
if (offset != payload.length) {
throw new IOException("Socket closed prematurely");
}
}
}
public NvCtlPacket(byte[] payload)
{
ByteBuffer bb = ByteBuffer.wrap(payload).order(ByteOrder.LITTLE_ENDIAN);
type = bb.getShort();
paylen = bb.getShort();
if (bb.hasRemaining())
{
payload = new byte[bb.remaining()];
bb.get(payload);
}
}
public NvCtlPacket(short type, short paylen)
{
this.type = type;
this.paylen = paylen;
}
public NvCtlPacket(short type, short paylen, byte[] payload)
{
this.type = type;
this.paylen = paylen;
this.payload = payload;
}
public short getType()
{
return type;
}
public short getPaylen()
{
return paylen;
}
public void setType(short type)
{
this.type = type;
}
public void setPaylen(short paylen)
{
this.paylen = paylen;
}
public byte[] toWire()
{
ByteBuffer bb = ByteBuffer.allocate(4 + (payload != null ? payload.length : 0)).order(ByteOrder.LITTLE_ENDIAN);
bb.putShort(type);
bb.putShort(paylen);
if (payload != null)
bb.put(payload);
return bb.array();
}
}
class NvCtlResponse extends NvCtlPacket {
public short status;
public NvCtlResponse(InputStream in) throws IOException {
super(in);
}
public NvCtlResponse(short type, short paylen) {
super(type, paylen);
}
public NvCtlResponse(short type, short paylen, byte[] payload) {
super(type, paylen, payload);
}
public NvCtlResponse(byte[] payload) {
super(payload);
}
public void setStatusCode(short status)
{
this.status = status;
}
public short getStatusCode()
{
return status;
}
}
}