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