From ce1494895e862598a9951243e054fde1b7942d6c Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 5 Dec 2013 12:57:49 -0500 Subject: [PATCH] Commit of common limelight core with bindings based on HEAD of RenderScript-Renderer --- moonlight-common/.classpath | 7 + moonlight-common/.project | 17 + .../.settings/org.eclipse.jdt.core.prefs | 11 + moonlight-common/libs/xpp3-1.1.4c.jar | Bin 0 -> 120069 bytes .../com/limelight/nvstream/ControlStream.java | 505 ++++++++++++++++++ .../src/com/limelight/nvstream/Handshake.java | 133 +++++ .../com/limelight/nvstream/NvConnection.java | 321 +++++++++++ .../nvstream/NvConnectionListener.java | 32 ++ .../nvstream/av/ByteBufferDescriptor.java | 46 ++ .../nvstream/av/ConnectionStatusListener.java | 7 + .../com/limelight/nvstream/av/DecodeUnit.java | 42 ++ .../com/limelight/nvstream/av/RtpPacket.java | 46 ++ .../nvstream/av/ShortBufferDescriptor.java | 28 + .../nvstream/av/audio/AudioDepacketizer.java | 65 +++ .../nvstream/av/audio/AudioRenderer.java | 9 + .../nvstream/av/audio/AudioStream.java | 223 ++++++++ .../nvstream/av/audio/OpusDecoder.java | 14 + .../av/video/VideoDecoderRenderer.java | 17 + .../nvstream/av/video/VideoDepacketizer.java | 213 ++++++++ .../nvstream/av/video/VideoPacket.java | 56 ++ .../nvstream/av/video/VideoStream.java | 290 ++++++++++ .../nvstream/av/video/cpu/AvcDecoder.java | 44 ++ .../com/limelight/nvstream/http/NvApp.java | 31 ++ .../com/limelight/nvstream/http/NvHTTP.java | 145 +++++ .../nvstream/input/ControllerPacket.java | 89 +++ .../limelight/nvstream/input/InputPacket.java | 26 + .../nvstream/input/MouseButtonPacket.java | 36 ++ .../nvstream/input/MouseMovePacket.java | 42 ++ .../nvstream/input/NvController.java | 65 +++ 29 files changed, 2560 insertions(+) create mode 100644 moonlight-common/.classpath create mode 100644 moonlight-common/.project create mode 100644 moonlight-common/.settings/org.eclipse.jdt.core.prefs create mode 100644 moonlight-common/libs/xpp3-1.1.4c.jar create mode 100644 moonlight-common/src/com/limelight/nvstream/ControlStream.java create mode 100644 moonlight-common/src/com/limelight/nvstream/Handshake.java create mode 100644 moonlight-common/src/com/limelight/nvstream/NvConnection.java create mode 100644 moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/ConnectionStatusListener.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/RtpPacket.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/ShortBufferDescriptor.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/audio/AudioDepacketizer.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/audio/AudioRenderer.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/audio/OpusDecoder.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java create mode 100644 moonlight-common/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java create mode 100644 moonlight-common/src/com/limelight/nvstream/http/NvApp.java create mode 100644 moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java create mode 100644 moonlight-common/src/com/limelight/nvstream/input/ControllerPacket.java create mode 100644 moonlight-common/src/com/limelight/nvstream/input/InputPacket.java create mode 100644 moonlight-common/src/com/limelight/nvstream/input/MouseButtonPacket.java create mode 100644 moonlight-common/src/com/limelight/nvstream/input/MouseMovePacket.java create mode 100644 moonlight-common/src/com/limelight/nvstream/input/NvController.java diff --git a/moonlight-common/.classpath b/moonlight-common/.classpath new file mode 100644 index 00000000..62423bd8 --- /dev/null +++ b/moonlight-common/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/moonlight-common/.project b/moonlight-common/.project new file mode 100644 index 00000000..aff885f2 --- /dev/null +++ b/moonlight-common/.project @@ -0,0 +1,17 @@ + + + limelight-common + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/moonlight-common/.settings/org.eclipse.jdt.core.prefs b/moonlight-common/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..8000cd6c --- /dev/null +++ b/moonlight-common/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/moonlight-common/libs/xpp3-1.1.4c.jar b/moonlight-common/libs/xpp3-1.1.4c.jar new file mode 100644 index 0000000000000000000000000000000000000000..451ac82af4a30c845cac77137cd9f64f5b5d60a3 GIT binary patch literal 120069 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

mbXUm5C3Jtl%Pae2Y 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 MM-?{X6#a_y50f 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 literal 0 HcmV?d00001 diff --git a/moonlight-common/src/com/limelight/nvstream/ControlStream.java b/moonlight-common/src/com/limelight/nvstream/ControlStream.java new file mode 100644 index 00000000..75bde0e4 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/ControlStream.java @@ -0,0 +1,505 @@ +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; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.limelight.nvstream.av.ConnectionStatusListener; + +public class ControlStream implements ConnectionStatusListener { + + public static final int PORT = 47995; + + public static final int CONTROL_TIMEOUT = 5000; + + 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_RESYNC = 0x1404; + public static final short PPAYLEN_RESYNC = 16; + + 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 NvConnectionListener listener; + private InetAddress host; + + private Socket s; + private InputStream in; + private OutputStream out; + + private Thread heartbeatThread; + private Thread jitterThread; + private Thread resyncThread; + private Object resyncNeeded = new Object(); + private boolean aborting = false; + + public ControlStream(InetAddress host, NvConnectionListener listener) + { + this.listener = listener; + this.host = host; + } + + public void initialize() throws IOException + { + s = new Socket(); + s.setSoTimeout(CONTROL_TIMEOUT); + s.setTcpNoDelay(true); + s.connect(new InetSocketAddress(host, PORT), CONTROL_TIMEOUT); + in = s.getInputStream(); + out = s.getOutputStream(); + } + + private void sendPacket(NvCtlPacket packet) throws IOException + { + out.write(packet.toWire()); + out.flush(); + } + + private ControlStream.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 requestResync() throws IOException + { + System.out.println("CTL: Requesting IDR frame"); + sendResync(); + } + + public void start() throws IOException + { + sendHello(); + sendConfig(); + pingPong(); + send1405AndGetResponse(); + + heartbeatThread = new Thread() { + @Override + public void run() { + while (!isInterrupted()) + { + try { + sendHeartbeat(); + } catch (IOException e) { + listener.connectionTerminated(e); + return; + } + + + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + listener.connectionTerminated(e); + return; + } + } + } + }; + heartbeatThread.start(); + + resyncThread = new Thread() { + @Override + public void run() { + while (!isInterrupted()) + { + try { + // Wait for notification of a resync needed + synchronized (resyncNeeded) { + resyncNeeded.wait(); + } + } catch (InterruptedException e) { + listener.connectionTerminated(e); + return; + } + + try { + requestResync(); + } catch (IOException e) { + listener.connectionTerminated(e); + return; + } + } + } + }; + resyncThread.start(); + } + + public void startJitterPackets() + { + jitterThread = new Thread() { + @Override + public void run() { + while (!isInterrupted()) + { + try { + sendJitter(); + } catch (IOException e) { + listener.connectionTerminated(e); + return; + } + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + listener.connectionTerminated(e); + return; + } + } + } + }; + jitterThread.start(); + } + + private ControlStream.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 sendResync() throws IOException + { + ByteBuffer conf = ByteBuffer.wrap(new byte[PPAYLEN_RESYNC]).order(ByteOrder.LITTLE_ENDIAN); + + conf.putLong(0); + conf.putLong(0xFFFF); + + sendAndGetReply(new NvCtlPacket(PTYPE_RESYNC, PPAYLEN_RESYNC, conf.array())); + } + + 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 ControlStream.NvCtlResponse pingPong() throws IOException + { + sendPacket(new NvCtlPacket(PTYPE_KEEPALIVE, PPAYLEN_KEEPALIVE)); + return new ControlStream.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; + } + } + + @Override + public void connectionTerminated() { + abort(); + } + + @Override + public void connectionNeedsResync() { + synchronized (resyncNeeded) { + // Wake up the resync thread + resyncNeeded.notify(); + } + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/Handshake.java b/moonlight-common/src/com/limelight/nvstream/Handshake.java new file mode 100644 index 00000000..e3249af9 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/Handshake.java @@ -0,0 +1,133 @@ +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 new file mode 100644 index 00000000..54ed4f3b --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/NvConnection.java @@ -0,0 +1,321 @@ +package com.limelight.nvstream; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.xmlpull.v1.XmlPullParserException; + +import com.limelight.nvstream.av.audio.AudioStream; +import com.limelight.nvstream.av.audio.AudioRenderer; +import com.limelight.nvstream.av.video.VideoDecoderRenderer; +import com.limelight.nvstream.av.video.VideoStream; +import com.limelight.nvstream.http.NvHTTP; +import com.limelight.nvstream.input.NvController; + +public class NvConnection { + private String host; + private NvConnectionListener listener; + + private InetAddress hostAddr; + private ControlStream controlStream; + private NvController inputStream; + private VideoStream videoStream; + private AudioStream audioStream; + + // Start parameters + private int drFlags; + private Object videoRenderTarget; + private VideoDecoderRenderer videoDecoderRenderer; + private AudioRenderer audioRenderer; + private String localDeviceName; + + private ThreadPoolExecutor threadPool; + + public NvConnection(String host, NvConnectionListener listener) + { + this.host = host; + this.listener = listener; + this.threadPool = new ThreadPoolExecutor(1, 1, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue()); + } + + public static String getMacAddressString() throws SocketException { + Enumeration ifaceList; + NetworkInterface selectedIface = null; + + // First look for a WLAN interface (since those generally aren't removable) + ifaceList = NetworkInterface.getNetworkInterfaces(); + while (selectedIface == null && ifaceList.hasMoreElements()) { + NetworkInterface iface = ifaceList.nextElement(); + + if (iface.getName().startsWith("wlan") && + iface.getHardwareAddress() != null) { + selectedIface = iface; + } + } + + // If we didn't find that, look for an Ethernet interface + ifaceList = NetworkInterface.getNetworkInterfaces(); + while (selectedIface == null && ifaceList.hasMoreElements()) { + NetworkInterface iface = ifaceList.nextElement(); + + if (iface.getName().startsWith("eth") && + iface.getHardwareAddress() != null) { + selectedIface = iface; + } + } + + // Now just find something with a MAC address + ifaceList = NetworkInterface.getNetworkInterfaces(); + while (selectedIface == null && ifaceList.hasMoreElements()) { + NetworkInterface iface = ifaceList.nextElement(); + + if (iface.getHardwareAddress() != null) { + selectedIface = ifaceList.nextElement(); + break; + } + } + + if (selectedIface == null) { + return null; + } + + byte[] macAddress = selectedIface.getHardwareAddress(); + if (macAddress != null) { + StringBuilder addrStr = new StringBuilder(); + for (int i = 0; i < macAddress.length; i++) { + addrStr.append(String.format("%02x", macAddress[i])); + if (i != macAddress.length - 1) { + addrStr.append(':'); + } + } + return addrStr.toString(); + } + + return null; + } + + public void stop() + { + threadPool.shutdownNow(); + + if (videoStream != null) { + videoStream.abort(); + } + if (audioStream != null) { + audioStream.abort(); + } + + if (controlStream != null) { + controlStream.abort(); + } + + if (inputStream != null) { + inputStream.close(); + inputStream = null; + } + } + + private boolean startSteamBigPicture() throws XmlPullParserException, IOException + { + NvHTTP h = new NvHTTP(hostAddr, getMacAddressString(), localDeviceName); + + if (!h.getPairState()) { + listener.displayMessage("Device not paired with computer"); + return false; + } + + int sessionId = h.getSessionId(); + int appId = h.getSteamAppId(sessionId); + + h.launchApp(sessionId, appId); + + return true; + } + + private boolean startControlStream() throws IOException + { + controlStream = new ControlStream(hostAddr, listener); + controlStream.initialize(); + controlStream.start(); + return true; + } + + private boolean startVideoStream() throws IOException + { + videoStream = new VideoStream(hostAddr, listener, controlStream); + videoStream.startVideoStream(videoDecoderRenderer, videoRenderTarget, drFlags); + return true; + } + + private boolean startAudioStream() throws IOException + { + audioStream = new AudioStream(hostAddr, listener, audioRenderer); + audioStream.startAudioStream(); + return true; + } + + private boolean startInputConnection() throws IOException + { + inputStream = new NvController(hostAddr); + inputStream.initialize(); + return true; + } + + private void establishConnection() { + for (NvConnectionListener.Stage currentStage : NvConnectionListener.Stage.values()) + { + boolean success = false; + + listener.stageStarting(currentStage); + try { + switch (currentStage) + { + case LAUNCH_APP: + success = startSteamBigPicture(); + break; + + case HANDSHAKE: + success = Handshake.performHandshake(hostAddr); + break; + + case CONTROL_START: + success = startControlStream(); + break; + + case VIDEO_START: + success = startVideoStream(); + break; + + case AUDIO_START: + success = startAudioStream(); + break; + + case CONTROL_START2: + controlStream.startJitterPackets(); + success = true; + break; + + case INPUT_START: + success = startInputConnection(); + break; + } + } catch (Exception e) { + e.printStackTrace(); + success = false; + } + + if (success) { + listener.stageComplete(currentStage); + } + else { + listener.stageFailed(currentStage); + return; + } + } + + listener.connectionStarted(); + } + + public void start(String localDeviceName, Object videoRenderTarget, int drFlags, AudioRenderer audioRenderer, VideoDecoderRenderer videoDecoderRenderer) + { + this.localDeviceName = localDeviceName; + this.drFlags = drFlags; + this.audioRenderer = audioRenderer; + this.videoRenderTarget = videoRenderTarget; + this.videoDecoderRenderer = videoDecoderRenderer; + + new Thread(new Runnable() { + @Override + public void run() { + try { + hostAddr = InetAddress.getByName(host); + } catch (UnknownHostException e) { + listener.connectionTerminated(e); + return; + } + + establishConnection(); + } + }).start(); + } + + public void sendMouseMove(final short deltaX, final short deltaY) + { + if (inputStream == null) + return; + + threadPool.execute(new Runnable() { + @Override + public void run() { + try { + inputStream.sendMouseMove(deltaX, deltaY); + } catch (IOException e) { + listener.connectionTerminated(e); + } + } + }); + } + + public void sendMouseButtonDown() + { + if (inputStream == null) + return; + + threadPool.execute(new Runnable() { + @Override + public void run() { + try { + inputStream.sendMouseButtonDown(); + } catch (IOException e) { + listener.connectionTerminated(e); + } + } + }); + } + + public void sendMouseButtonUp() + { + if (inputStream == null) + return; + + threadPool.execute(new Runnable() { + @Override + public void run() { + try { + inputStream.sendMouseButtonUp(); + } catch (IOException e) { + listener.connectionTerminated(e); + } + } + }); + } + + public void sendControllerInput(final short buttonFlags, + final byte leftTrigger, final byte rightTrigger, + final short leftStickX, final short leftStickY, + final short rightStickX, final short rightStickY) + { + if (inputStream == null) + return; + + threadPool.execute(new Runnable() { + @Override + public void run() { + try { + inputStream.sendControllerInput(buttonFlags, leftTrigger, + rightTrigger, leftStickX, leftStickY, + rightStickX, rightStickY); + } catch (IOException e) { + listener.connectionTerminated(e); + } + } + }); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java b/moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java new file mode 100644 index 00000000..0fb63dc2 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/NvConnectionListener.java @@ -0,0 +1,32 @@ +package com.limelight.nvstream; + +public interface NvConnectionListener { + + public enum Stage { + LAUNCH_APP("app"), + HANDSHAKE("handshake"), + CONTROL_START("control connection"), + VIDEO_START("video stream"), + AUDIO_START("audio stream"), + CONTROL_START2("control connection"), + INPUT_START("input connection"); + + private String name; + private Stage(String name) { + this.name = name; + } + + public String getName() { + return name; + } + }; + + public void stageStarting(Stage stage); + public void stageComplete(Stage stage); + public void stageFailed(Stage stage); + + public void connectionStarted(); + public void connectionTerminated(Exception e); + + public void displayMessage(String message); +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java b/moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java new file mode 100644 index 00000000..83fa0b46 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/ByteBufferDescriptor.java @@ -0,0 +1,46 @@ +package com.limelight.nvstream.av; + +public class ByteBufferDescriptor { + public byte[] data; + public int offset; + public int length; + + public ByteBufferDescriptor(byte[] data, int offset, int length) + { + this.data = data; + this.offset = offset; + this.length = length; + } + + public ByteBufferDescriptor(ByteBufferDescriptor desc) + { + this.data = desc.data; + this.offset = desc.offset; + this.length = desc.length; + } + + public void reinitialize(byte[] data, int offset, int length) + { + this.data = data; + this.offset = offset; + this.length = length; + } + + public void print() + { + print(offset, length); + } + + public void print(int length) + { + print(this.offset, length); + } + + public void print(int offset, int length) + { + for (int i = offset; i < offset+length; i++) { + System.out.printf("%d: %02x \n", i, data[i]); + } + System.out.println(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/ConnectionStatusListener.java b/moonlight-common/src/com/limelight/nvstream/av/ConnectionStatusListener.java new file mode 100644 index 00000000..35262ddf --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/ConnectionStatusListener.java @@ -0,0 +1,7 @@ +package com.limelight.nvstream.av; + +public interface ConnectionStatusListener { + public void connectionTerminated(); + + public void connectionNeedsResync(); +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java b/moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java new file mode 100644 index 00000000..f58510e0 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/DecodeUnit.java @@ -0,0 +1,42 @@ +package com.limelight.nvstream.av; + +import java.util.List; + +public class DecodeUnit { + public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_H264 = 1; + public static final int TYPE_OPUS = 2; + + private int type; + private List bufferList; + private int dataLength; + private int flags; + + public DecodeUnit(int type, List bufferList, int dataLength, int flags) + { + this.type = type; + this.bufferList = bufferList; + this.dataLength = dataLength; + this.flags = flags; + } + + public int getType() + { + return type; + } + + public int getFlags() + { + return flags; + } + + public List getBufferList() + { + return bufferList; + } + + public int getDataLength() + { + return dataLength; + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/RtpPacket.java b/moonlight-common/src/com/limelight/nvstream/av/RtpPacket.java new file mode 100644 index 00000000..d1fc429e --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/RtpPacket.java @@ -0,0 +1,46 @@ +package com.limelight.nvstream.av; + +import java.nio.ByteBuffer; + +public class RtpPacket { + + private byte packetType; + private short seqNum; + private ByteBufferDescriptor buffer; + + public RtpPacket(ByteBufferDescriptor buffer) + { + this.buffer = new ByteBufferDescriptor(buffer); + + ByteBuffer bb = ByteBuffer.wrap(buffer.data, buffer.offset, buffer.length); + + // Discard the first byte + bb.position(bb.position()+1); + + // Get the packet type + packetType = bb.get(); + + // Get the sequence number + seqNum = bb.getShort(); + } + + public byte getPacketType() + { + return packetType; + } + + public short getSequenceNumber() + { + return seqNum; + } + + public byte[] getBackingBuffer() + { + return buffer.data; + } + + public ByteBufferDescriptor getNewPayloadDescriptor() + { + return new ByteBufferDescriptor(buffer.data, buffer.offset+12, buffer.length-12); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/ShortBufferDescriptor.java b/moonlight-common/src/com/limelight/nvstream/av/ShortBufferDescriptor.java new file mode 100644 index 00000000..44cd2d8e --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/ShortBufferDescriptor.java @@ -0,0 +1,28 @@ +package com.limelight.nvstream.av; + +public class ShortBufferDescriptor { + public short[] data; + public int offset; + public int length; + + public ShortBufferDescriptor(short[] data, int offset, int length) + { + this.data = data; + this.offset = offset; + this.length = length; + } + + public ShortBufferDescriptor(ShortBufferDescriptor desc) + { + this.data = desc.data; + this.offset = desc.offset; + this.length = desc.length; + } + + public void reinitialize(short[] data, int offset, int length) + { + this.data = data; + this.offset = offset; + this.length = length; + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/audio/AudioDepacketizer.java b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioDepacketizer.java new file mode 100644 index 00000000..c8fd728d --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioDepacketizer.java @@ -0,0 +1,65 @@ +package com.limelight.nvstream.av.audio; + +import java.util.concurrent.LinkedBlockingQueue; + +import com.limelight.nvstream.av.ByteBufferDescriptor; +import com.limelight.nvstream.av.RtpPacket; +import com.limelight.nvstream.av.ShortBufferDescriptor; + +public class AudioDepacketizer { + + private static final int DU_LIMIT = 15; + private LinkedBlockingQueue decodedUnits = + new LinkedBlockingQueue(DU_LIMIT); + + // Sequencing state + private short lastSequenceNumber; + + private void decodeData(byte[] data, int off, int len) + { + // Submit this data to the decoder + short[] pcmData = new short[OpusDecoder.getMaxOutputShorts()]; + int decodeLen = OpusDecoder.decode(data, off, len, pcmData); + + if (decodeLen > 0) { + // Return value of decode is frames decoded per channel + decodeLen *= OpusDecoder.getChannelCount(); + + // Put it on the decoded queue + if (!decodedUnits.offer(new ShortBufferDescriptor(pcmData, 0, decodeLen))) { + // Clear out the queue + decodedUnits.clear(); + } + } + } + + public void decodeInputData(RtpPacket packet) + { + short seq = packet.getSequenceNumber(); + + if (packet.getPacketType() != 97) { + // Only type 97 is audio + return; + } + + // Toss out the current NAL if we receive a packet that is + // out of sequence + if (lastSequenceNumber != 0 && + (short)(lastSequenceNumber + 1) != seq) + { + System.out.println("Received OOS audio data (expected "+(lastSequenceNumber + 1)+", got "+seq+")"); + decodeData(null, 0, 0); + } + + lastSequenceNumber = seq; + + // This is all the depacketizing we need to do + ByteBufferDescriptor rtpPayload = packet.getNewPayloadDescriptor(); + decodeData(rtpPayload.data, rtpPayload.offset, rtpPayload.length); + } + + public ShortBufferDescriptor getNextDecodedData() throws InterruptedException + { + return decodedUnits.take(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/audio/AudioRenderer.java b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioRenderer.java new file mode 100644 index 00000000..ebffda58 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioRenderer.java @@ -0,0 +1,9 @@ +package com.limelight.nvstream.av.audio; + +public interface AudioRenderer { + public void streamInitialized(int channelCount, int sampleRate); + + public void playDecodedAudio(short[] audioData, int offset, int length); + + public void streamClosing(); +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java new file mode 100644 index 00000000..19a2a1cb --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/audio/AudioStream.java @@ -0,0 +1,223 @@ +package com.limelight.nvstream.av.audio; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingQueue; + +import com.limelight.nvstream.NvConnectionListener; +import com.limelight.nvstream.av.ByteBufferDescriptor; +import com.limelight.nvstream.av.RtpPacket; +import com.limelight.nvstream.av.ShortBufferDescriptor; + +public class AudioStream { + public static final int RTP_PORT = 48000; + public static final int RTCP_PORT = 47999; + + private LinkedBlockingQueue packets = new LinkedBlockingQueue(100); + + private DatagramSocket rtp; + + private AudioDepacketizer depacketizer = new AudioDepacketizer(); + + private LinkedList threads = new LinkedList(); + + private boolean aborting = false; + + private InetAddress host; + private NvConnectionListener connListener; + private AudioRenderer streamListener; + + public AudioStream(InetAddress host, NvConnectionListener connListener, AudioRenderer streamListener) + { + this.host = host; + this.connListener = connListener; + this.streamListener = streamListener; + } + + public void abort() + { + if (aborting) { + return; + } + + aborting = true; + + for (Thread t : threads) { + t.interrupt(); + } + + // Close the socket to interrupt the receive thread + if (rtp != null) { + rtp.close(); + } + + // Wait for threads to terminate + for (Thread t : threads) { + try { + t.join(); + } catch (InterruptedException e) { } + } + + streamListener.streamClosing(); + + threads.clear(); + } + + public void startAudioStream() throws SocketException + { + setupRtpSession(); + + setupAudio(); + + startReceiveThread(); + + startDepacketizerThread(); + + startDecoderThread(); + + startUdpPingThread(); + } + + private void setupRtpSession() throws SocketException + { + rtp = new DatagramSocket(RTP_PORT); + } + + private void setupAudio() + { + int err; + + err = OpusDecoder.init(); + if (err != 0) { + throw new IllegalStateException("Opus decoder failed to initialize"); + } + + streamListener.streamInitialized(OpusDecoder.getChannelCount(), OpusDecoder.getSampleRate()); + } + + private void startDepacketizerThread() + { + // This thread lessens the work on the receive thread + // so it can spend more time waiting for data + Thread t = new Thread() { + @Override + public void run() { + while (!isInterrupted()) + { + RtpPacket packet; + + try { + packet = packets.take(); + } catch (InterruptedException e) { + connListener.connectionTerminated(e); + return; + } + + depacketizer.decodeInputData(packet); + } + } + }; + threads.add(t); + t.setName("Audio - Depacketizer"); + t.start(); + } + + private void startDecoderThread() + { + // Decoder thread + Thread t = new Thread() { + @Override + public void run() { + while (!isInterrupted()) + { + ShortBufferDescriptor samples; + + try { + samples = depacketizer.getNextDecodedData(); + } catch (InterruptedException e) { + connListener.connectionTerminated(e); + return; + } + + streamListener.playDecodedAudio(samples.data, samples.offset, samples.length); + } + } + }; + threads.add(t); + t.setName("Audio - Player"); + t.start(); + } + + private void startReceiveThread() + { + // Receive thread + Thread t = new Thread() { + @Override + public void run() { + ByteBufferDescriptor desc = new ByteBufferDescriptor(new byte[1500], 0, 1500); + DatagramPacket packet = new DatagramPacket(desc.data, desc.length); + + while (!isInterrupted()) + { + try { + rtp.receive(packet); + } catch (IOException e) { + connListener.connectionTerminated(e); + return; + } + + // Give the packet to the depacketizer thread + desc.length = packet.getLength(); + if (packets.offer(new RtpPacket(desc))) { + desc.reinitialize(new byte[1500], 0, 1500); + packet.setData(desc.data, desc.offset, desc.length); + } + } + } + }; + threads.add(t); + t.setName("Audio - Receive"); + t.start(); + } + + private void startUdpPingThread() + { + // Ping thread + Thread t = new Thread() { + @Override + public void run() { + // PING in ASCII + final byte[] pingPacketData = new byte[] {0x50, 0x49, 0x4E, 0x47}; + DatagramPacket pingPacket = new DatagramPacket(pingPacketData, pingPacketData.length); + pingPacket.setSocketAddress(new InetSocketAddress(host, RTP_PORT)); + + // Send PING every 100 ms + while (!isInterrupted()) + { + try { + rtp.send(pingPacket); + } catch (IOException e) { + connListener.connectionTerminated(e); + return; + } + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + connListener.connectionTerminated(e); + return; + } + } + } + }; + threads.add(t); + t.setPriority(Thread.MIN_PRIORITY); + t.setName("Audio - Ping"); + t.start(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/audio/OpusDecoder.java b/moonlight-common/src/com/limelight/nvstream/av/audio/OpusDecoder.java new file mode 100644 index 00000000..c01f7fa5 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/audio/OpusDecoder.java @@ -0,0 +1,14 @@ +package com.limelight.nvstream.av.audio; + +public class OpusDecoder { + static { + System.loadLibrary("nv_opus_dec"); + } + + public static native int init(); + public static native void destroy(); + public static native int getChannelCount(); + public static native int getMaxOutputShorts(); + public static native int getSampleRate(); + public static native int decode(byte[] indata, int inoff, int inlen, short[] outpcmdata); +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java new file mode 100644 index 00000000..733d23fd --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDecoderRenderer.java @@ -0,0 +1,17 @@ +package com.limelight.nvstream.av.video; + +import com.limelight.nvstream.av.DecodeUnit; + +public interface VideoDecoderRenderer { + public static int FLAG_PREFER_QUALITY = 0x1; + + public void setup(int width, int height, Object renderTarget, int drFlags); + + public void start(); + + public void stop(); + + public void release(); + + public boolean submitDecodeUnit(DecodeUnit decodeUnit); +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java new file mode 100644 index 00000000..d1936a34 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoDepacketizer.java @@ -0,0 +1,213 @@ +package com.limelight.nvstream.av.video; + +import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingQueue; + +import com.limelight.nvstream.av.ByteBufferDescriptor; +import com.limelight.nvstream.av.DecodeUnit; +import com.limelight.nvstream.av.RtpPacket; +import com.limelight.nvstream.av.ConnectionStatusListener; + +public class VideoDepacketizer { + + // Current NAL state + private LinkedList avcNalDataChain = null; + private int avcNalDataLength = 0; + + // Sequencing state + private short lastSequenceNumber; + + private ConnectionStatusListener controlListener; + + private static final int DU_LIMIT = 15; + private LinkedBlockingQueue decodedUnits = new LinkedBlockingQueue(DU_LIMIT); + + public VideoDepacketizer(ConnectionStatusListener controlListener) + { + this.controlListener = controlListener; + } + + private void clearAvcNalState() + { + avcNalDataChain = null; + avcNalDataLength = 0; + } + + private void reassembleAvcNal() + { + // This is the start of a new NAL + if (avcNalDataChain != null && avcNalDataLength != 0) { + // Construct the H264 decode unit + DecodeUnit du = new DecodeUnit(DecodeUnit.TYPE_H264, avcNalDataChain, avcNalDataLength, 0); + if (!decodedUnits.offer(du)) { + // We need a new IDR frame since we're discarding data now + decodedUnits.clear(); + controlListener.connectionNeedsResync(); + } + + // Clear old state + clearAvcNalState(); + } + } + + public void addInputData(VideoPacket packet) + { + ByteBufferDescriptor location = packet.getNewPayloadDescriptor(); + + // SPS and PPS packet doesn't have standard headers, so submit it as is + if (location.length < 968) { + avcNalDataChain = new LinkedList(); + avcNalDataLength = 0; + + avcNalDataChain.add(location); + avcNalDataLength += location.length; + + reassembleAvcNal(); + } + else { + int packetIndex = packet.getPacketIndex(); + int packetsInFrame = packet.getTotalPackets(); + + // Check if this is the first packet for a frame + if (packetIndex == 0) { + // Setup state for the new frame + avcNalDataChain = new LinkedList(); + avcNalDataLength = 0; + } + + // Check if this packet falls in the range of packets in frame + if (packetIndex >= packetsInFrame) { + // This isn't H264 frame data + return; + } + + // Adjust the length to only contain valid data + location.length = packet.getPayloadLength(); + + // Add the payload data to the chain + if (avcNalDataChain != null) { + avcNalDataChain.add(location); + avcNalDataLength += location.length; + } + + // Reassemble the NALs if this was the last packet for this frame + if (packetIndex + 1 == packetsInFrame) { + reassembleAvcNal(); + } + } + } + + public void addInputData(RtpPacket packet) + { + short seq = packet.getSequenceNumber(); + + // Toss out the current NAL if we receive a packet that is + // out of sequence + if (lastSequenceNumber != 0 && + (short)(lastSequenceNumber + 1) != seq) + { + System.out.println("Received OOS video data (expected "+(lastSequenceNumber + 1)+", got "+seq+")"); + + // Reset the depacketizer state + clearAvcNalState(); + + // Request an IDR frame + controlListener.connectionNeedsResync(); + } + + lastSequenceNumber = seq; + + // Pass the payload to the non-sequencing parser + ByteBufferDescriptor rtpPayload = packet.getNewPayloadDescriptor(); + addInputData(new VideoPacket(rtpPayload)); + } + + public DecodeUnit getNextDecodeUnit() throws InterruptedException + { + return decodedUnits.take(); + } +} + +class NAL { + + // This assumes that the buffer passed in is already a special sequence + public static boolean isAvcStartSequence(ByteBufferDescriptor specialSeq) + { + // The start sequence is 00 00 01 or 00 00 00 01 + return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x01); + } + + // This assumes that the buffer passed in is already a special sequence + public static boolean isAvcFrameStart(ByteBufferDescriptor specialSeq) + { + if (specialSeq.length != 4) + return false; + + // The frame start sequence is 00 00 00 01 + return (specialSeq.data[specialSeq.offset+specialSeq.length-1] == 0x01); + } + + // Returns a buffer descriptor describing the start sequence + public static boolean getSpecialSequenceDescriptor(ByteBufferDescriptor buffer, ByteBufferDescriptor outputDesc) + { + // NAL start sequence is 00 00 00 01 or 00 00 01 + if (buffer.length < 3) + return false; + + // 00 00 is magic + if (buffer.data[buffer.offset] == 0x00 && + buffer.data[buffer.offset+1] == 0x00) + { + // Another 00 could be the end of the special sequence + // 00 00 00 or the middle of 00 00 00 01 + if (buffer.data[buffer.offset+2] == 0x00) + { + if (buffer.length >= 4 && + buffer.data[buffer.offset+3] == 0x01) + { + // It's the AVC start sequence 00 00 00 01 + outputDesc.reinitialize(buffer.data, buffer.offset, 4); + } + else + { + // It's 00 00 00 + outputDesc.reinitialize(buffer.data, buffer.offset, 3); + } + return true; + } + else if (buffer.data[buffer.offset+2] == 0x01 || + buffer.data[buffer.offset+2] == 0x02) + { + // These are easy: 00 00 01 or 00 00 02 + outputDesc.reinitialize(buffer.data, buffer.offset, 3); + return true; + } + else if (buffer.data[buffer.offset+2] == 0x03) + { + // 00 00 03 is special because it's a subsequence of the + // NAL wrapping substitute for 00 00 00, 00 00 01, 00 00 02, + // or 00 00 03 in the RBSP sequence. We need to check the next + // byte to see whether it's 00, 01, 02, or 03 (a valid RBSP substitution) + // or whether it's something else + + if (buffer.length < 4) + return false; + + if (buffer.data[buffer.offset+3] >= 0x00 && + buffer.data[buffer.offset+3] <= 0x03) + { + // It's not really a special sequence after all + return false; + } + else + { + // It's not a standard replacement so it's a special sequence + outputDesc.reinitialize(buffer.data, buffer.offset, 3); + return true; + } + } + } + + return false; + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java new file mode 100644 index 00000000..a74da573 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoPacket.java @@ -0,0 +1,56 @@ +package com.limelight.nvstream.av.video; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import com.limelight.nvstream.av.ByteBufferDescriptor; + +public class VideoPacket { + private ByteBufferDescriptor buffer; + + private int frameIndex; + private int packetIndex; + private int totalPackets; + private int payloadLength; + + public VideoPacket(ByteBufferDescriptor rtpPayload) + { + buffer = new ByteBufferDescriptor(rtpPayload); + + ByteBuffer bb = ByteBuffer.wrap(buffer.data).order(ByteOrder.LITTLE_ENDIAN); + bb.position(buffer.offset); + + frameIndex = bb.getInt(); + packetIndex = bb.getInt(); + totalPackets = bb.getInt(); + + bb.position(bb.position()+4); + + payloadLength = bb.getInt(); + } + + public int getFrameIndex() + { + return frameIndex; + } + + public int getPacketIndex() + { + return packetIndex; + } + + public int getPayloadLength() + { + return payloadLength; + } + + public int getTotalPackets() + { + return totalPackets; + } + + public ByteBufferDescriptor getNewPayloadDescriptor() + { + return new ByteBufferDescriptor(buffer.data, buffer.offset+56, buffer.length-56); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java new file mode 100644 index 00000000..0abd3bd1 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/video/VideoStream.java @@ -0,0 +1,290 @@ +package com.limelight.nvstream.av.video; + +import java.io.IOException; +import java.io.InputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.LinkedList; +import java.util.concurrent.LinkedBlockingQueue; + +import com.limelight.nvstream.NvConnectionListener; +import com.limelight.nvstream.av.ByteBufferDescriptor; +import com.limelight.nvstream.av.DecodeUnit; +import com.limelight.nvstream.av.RtpPacket; +import com.limelight.nvstream.av.ConnectionStatusListener; + +public class VideoStream { + public static final int RTP_PORT = 47998; + public static final int RTCP_PORT = 47999; + public static final int FIRST_FRAME_PORT = 47996; + + public static final int FIRST_FRAME_TIMEOUT = 5000; + + private LinkedBlockingQueue packets = new LinkedBlockingQueue(100); + + private InetAddress host; + private DatagramSocket rtp; + private Socket firstFrameSocket; + + private LinkedList threads = new LinkedList(); + + private NvConnectionListener listener; + private VideoDepacketizer depacketizer; + + private VideoDecoderRenderer decRend; + private boolean startedRendering; + + private boolean aborting = false; + + public VideoStream(InetAddress host, NvConnectionListener listener, ConnectionStatusListener avConnListener) + { + this.host = host; + this.listener = listener; + this.depacketizer = new VideoDepacketizer(avConnListener); + } + + public void abort() + { + if (aborting) { + return; + } + + aborting = true; + + // Interrupt threads + for (Thread t : threads) { + t.interrupt(); + } + + // Close the socket to interrupt the receive thread + if (rtp != null) { + rtp.close(); + } + if (firstFrameSocket != null) { + try { + firstFrameSocket.close(); + } catch (IOException e) {} + } + + // Wait for threads to terminate + for (Thread t : threads) { + try { + t.join(); + } catch (InterruptedException e) { } + } + + if (startedRendering) { + decRend.stop(); + } + + if (decRend != null) { + decRend.release(); + } + + threads.clear(); + } + + private void readFirstFrame() throws IOException + { + byte[] firstFrame = new byte[1500]; + + firstFrameSocket = new Socket(); + firstFrameSocket.setSoTimeout(FIRST_FRAME_TIMEOUT); + + try { + firstFrameSocket.connect(new InetSocketAddress(host, FIRST_FRAME_PORT), FIRST_FRAME_TIMEOUT); + InputStream firstFrameStream = firstFrameSocket.getInputStream(); + + int offset = 0; + for (;;) + { + int bytesRead = firstFrameStream.read(firstFrame, offset, firstFrame.length-offset); + + if (bytesRead == -1) + break; + + offset += bytesRead; + } + + depacketizer.addInputData(new VideoPacket(new ByteBufferDescriptor(firstFrame, 0, offset))); + } finally { + firstFrameSocket.close(); + firstFrameSocket = null; + } + } + + public void setupRtpSession() throws SocketException + { + rtp = new DatagramSocket(RTP_PORT); + } + + public void setupDecoderRenderer(VideoDecoderRenderer decRend, Object renderTarget, int drFlags) { + this.decRend = decRend; + if (decRend != null) { + decRend.setup(1280, 720, renderTarget, drFlags); + } + } + + public void startVideoStream(VideoDecoderRenderer decRend, Object renderTarget, int drFlags) throws IOException + { + // Setup the decoder and renderer + setupDecoderRenderer(decRend, renderTarget, drFlags); + + // Open RTP sockets and start session + setupRtpSession(); + + // Start pinging before reading the first frame + // so Shield Proxy knows we're here and sends us + // the reference frame + startUdpPingThread(); + + // Read the first frame to start the UDP video stream + // This MUST be called before the normal UDP receive thread + // starts in order to avoid state corruption caused by two + // threads simultaneously adding input data. + readFirstFrame(); + + if (decRend != null) { + // Start the receive thread early to avoid missing + // early packets + startReceiveThread(); + + // Start the depacketizer thread to deal with the RTP data + startDepacketizerThread(); + + // Start decoding the data we're receiving + startDecoderThread(); + + // Start the renderer + decRend.start(); + startedRendering = true; + } + } + + private void startDecoderThread() + { + Thread t = new Thread() { + @Override + public void run() { + // Read the decode units generated from the RTP stream + while (!isInterrupted()) + { + DecodeUnit du; + + try { + du = depacketizer.getNextDecodeUnit(); + } catch (InterruptedException e) { + listener.connectionTerminated(e); + return; + } + + decRend.submitDecodeUnit(du); + } + } + }; + threads.add(t); + t.setName("Video - Decoder"); + t.setPriority(Thread.MAX_PRIORITY); + t.start(); + } + + private void startDepacketizerThread() + { + // This thread lessens the work on the receive thread + // so it can spend more time waiting for data + Thread t = new Thread() { + @Override + public void run() { + while (!isInterrupted()) + { + RtpPacket packet; + + try { + packet = packets.take(); + } catch (InterruptedException e) { + listener.connectionTerminated(e); + return; + } + + // !!! We no longer own the data buffer at this point !!! + depacketizer.addInputData(packet); + } + } + }; + threads.add(t); + t.setName("Video - Depacketizer"); + t.start(); + } + + private void startReceiveThread() + { + // Receive thread + Thread t = new Thread() { + @Override + public void run() { + ByteBufferDescriptor desc = new ByteBufferDescriptor(new byte[1500], 0, 1500); + DatagramPacket packet = new DatagramPacket(desc.data, desc.length); + + while (!isInterrupted()) + { + try { + rtp.receive(packet); + } catch (IOException e) { + listener.connectionTerminated(e); + return; + } + + // Give the packet to the depacketizer thread + desc.length = packet.getLength(); + if (packets.offer(new RtpPacket(desc))) { + desc.reinitialize(new byte[1500], 0, 1500); + packet.setData(desc.data, desc.offset, desc.length); + } + } + } + }; + threads.add(t); + t.setName("Video - Receive"); + t.start(); + } + + private void startUdpPingThread() + { + // Ping thread + Thread t = new Thread() { + @Override + public void run() { + // PING in ASCII + final byte[] pingPacketData = new byte[] {0x50, 0x49, 0x4E, 0x47}; + DatagramPacket pingPacket = new DatagramPacket(pingPacketData, pingPacketData.length); + pingPacket.setSocketAddress(new InetSocketAddress(host, RTP_PORT)); + + // Send PING every 100 ms + while (!isInterrupted()) + { + try { + rtp.send(pingPacket); + } catch (IOException e) { + listener.connectionTerminated(e); + return; + } + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + listener.connectionTerminated(e); + return; + } + } + } + }; + threads.add(t); + t.setName("Video - Ping"); + t.setPriority(Thread.MIN_PRIORITY); + t.start(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java b/moonlight-common/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java new file mode 100644 index 00000000..874a645e --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/av/video/cpu/AvcDecoder.java @@ -0,0 +1,44 @@ +package com.limelight.nvstream.av.video.cpu; + +public class AvcDecoder { + static { + // FFMPEG dependencies + System.loadLibrary("avutil-52"); + System.loadLibrary("swresample-0"); + System.loadLibrary("swscale-2"); + System.loadLibrary("avcodec-55"); + System.loadLibrary("avformat-55"); + System.loadLibrary("avfilter-3"); + + System.loadLibrary("nv_avc_dec"); + } + + /** Disables the deblocking filter at the cost of image quality */ + public static final int DISABLE_LOOP_FILTER = 0x1; + /** Uses the low latency decode flag (disables multithreading) */ + public static final int LOW_LATENCY_DECODE = 0x2; + /** Threads process each slice, rather than each frame */ + public static final int SLICE_THREADING = 0x4; + /** Uses nonstandard speedup tricks */ + public static final int FAST_DECODE = 0x8; + /** Uses bilinear filtering instead of bicubic */ + public static final int BILINEAR_FILTERING = 0x10; + /** Uses a faster bilinear filtering with lower image quality */ + public static final int FAST_BILINEAR_FILTERING = 0x20; + /** Disables color conversion (output is NV21) */ + public static final int NO_COLOR_CONVERSION = 0x40; + + public static native int init(int width, int height, int perflvl, int threadcount); + public static native void destroy(); + + // Rendering API when NO_COLOR_CONVERSION == 0 + public static native boolean setRenderTarget(Object androidSurface); + public static native boolean getRgbFrame(byte[] rgbFrame, int bufferSize); + public static native boolean redraw(); + + // Rendering API when NO_COLOR_CONVERSION == 1 + public static native boolean getRawFrame(byte[] yuvFrame, int bufferSize); + + public static native int getInputPaddingSize(); + public static native int decode(byte[] indata, int inoff, int inlen); +} diff --git a/moonlight-common/src/com/limelight/nvstream/http/NvApp.java b/moonlight-common/src/com/limelight/nvstream/http/NvApp.java new file mode 100644 index 00000000..70ef03ea --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/http/NvApp.java @@ -0,0 +1,31 @@ +package com.limelight.nvstream.http; + +public class NvApp { + private String appName; + private int appId; + private boolean isRunning; + + public void setAppName(String appName) { + this.appName = appName; + } + + public void setAppId(String appId) { + this.appId = Integer.parseInt(appId); + } + + public void setIsRunning(String isRunning) { + this.isRunning = isRunning.equals("1"); + } + + public String getAppName() { + return this.appName; + } + + public int getAppId() { + return this.appId; + } + + public boolean getIsRunning() { + return this.isRunning; + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java b/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java new file mode 100644 index 00000000..374bd650 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/http/NvHTTP.java @@ -0,0 +1,145 @@ +package com.limelight.nvstream.http; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.URL; +import java.net.URLConnection; +import java.util.LinkedList; +import java.util.Stack; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + + +public class NvHTTP { + private String macAddress; + private String deviceName; + + public static final int PORT = 47989; + public static final int CONNECTION_TIMEOUT = 5000; + + public String baseUrl; + + public NvHTTP(InetAddress host, String macAddress, String deviceName) { + this.macAddress = macAddress; + this.deviceName = deviceName; + this.baseUrl = "http://" + host.getHostAddress() + ":" + PORT; + } + + private String getXmlString(InputStream in, String tagname) + throws XmlPullParserException, IOException { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput(new InputStreamReader(in)); + int eventType = xpp.getEventType(); + Stack currentTag = new Stack(); + + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case (XmlPullParser.START_TAG): + currentTag.push(xpp.getName()); + break; + case (XmlPullParser.END_TAG): + currentTag.pop(); + break; + case (XmlPullParser.TEXT): + if (currentTag.peek().equals(tagname)) { + return xpp.getText(); + } + break; + } + eventType = xpp.next(); + } + + return null; + } + + private InputStream openHttpConnection(String url) throws IOException { + URLConnection conn = new URL(url).openConnection(); + conn.setConnectTimeout(CONNECTION_TIMEOUT); + conn.setDefaultUseCaches(false); + conn.connect(); + return conn.getInputStream(); + } + + public String getAppVersion() throws XmlPullParserException, IOException { + InputStream in = openHttpConnection(baseUrl + "/appversion"); + return getXmlString(in, "appversion"); + } + + public boolean getPairState() throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/pairstate?mac=" + macAddress); + String paired = getXmlString(in, "paired"); + return Integer.valueOf(paired) != 0; + } + + public int getSessionId() throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/pair?mac=" + macAddress + + "&devicename=" + deviceName); + String sessionId = getXmlString(in, "sessionid"); + return Integer.parseInt(sessionId); + } + + public int getSteamAppId(int sessionId) throws IOException, + XmlPullParserException { + LinkedList appList = getAppList(sessionId); + for (NvApp app : appList) { + if (app.getAppName().equals("Steam")) { + return app.getAppId(); + } + } + return 0; + } + + public LinkedList getAppList(int sessionId) throws IOException, XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/applist?session=" + sessionId); + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + factory.setNamespaceAware(true); + XmlPullParser xpp = factory.newPullParser(); + + xpp.setInput(new InputStreamReader(in)); + int eventType = xpp.getEventType(); + LinkedList appList = new LinkedList(); + Stack currentTag = new Stack(); + + while (eventType != XmlPullParser.END_DOCUMENT) { + switch (eventType) { + case (XmlPullParser.START_TAG): + currentTag.push(xpp.getName()); + if (xpp.getName().equals("App")) { + appList.addLast(new NvApp()); + } + break; + case (XmlPullParser.END_TAG): + currentTag.pop(); + break; + case (XmlPullParser.TEXT): + NvApp app = appList.getLast(); + if (currentTag.peek().equals("AppTitle")) { + app.setAppName(xpp.getText()); + } else if (currentTag.peek().equals("ID")) { + app.setAppId(xpp.getText()); + } else if (currentTag.peek().equals("IsRunning")) { + app.setIsRunning(xpp.getText()); + } + break; + } + eventType = xpp.next(); + } + return appList; + } + + // Returns gameSession XML attribute + public int launchApp(int sessionId, int appId) throws IOException, + XmlPullParserException { + InputStream in = openHttpConnection(baseUrl + "/launch?session=" + + sessionId + "&appid=" + appId); + String gameSession = getXmlString(in, "gamesession"); + return Integer.parseInt(gameSession); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/input/ControllerPacket.java b/moonlight-common/src/com/limelight/nvstream/input/ControllerPacket.java new file mode 100644 index 00000000..b36810bf --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/input/ControllerPacket.java @@ -0,0 +1,89 @@ +package com.limelight.nvstream.input; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class ControllerPacket extends InputPacket { + public static final byte[] HEADER = + { + 0x0A, + 0x00, + 0x00, + 0x00, + 0x00, + 0x14 + }; + + public static final byte[] TAIL = + { + (byte)0x9C, + 0x00, + 0x00, + 0x00, + 0x55, + 0x00 + }; + + public static final int PACKET_TYPE = 0x18; + + public static final short A_FLAG = 0x1000; + public static final short B_FLAG = 0x2000; + public static final short X_FLAG = 0x4000; + public static final short Y_FLAG = (short)0x8000; + public static final short UP_FLAG = 0x0001; + public static final short DOWN_FLAG = 0x0002; + public static final short LEFT_FLAG = 0x0004; + public static final short RIGHT_FLAG = 0x0008; + public static final short LB_FLAG = 0x0100; + public static final short RB_FLAG = 0x0200; + public static final short PLAY_FLAG = 0x0010; + public static final short BACK_FLAG = 0x0020; + public static final short LS_CLK_FLAG = 0x0040; + public static final short RS_CLK_FLAG = 0x0080; + public static final short SPECIAL_BUTTON_FLAG = 0x0400; + + public static final short PAYLOAD_LENGTH = 24; + public static final short PACKET_LENGTH = PAYLOAD_LENGTH + + InputPacket.HEADER_LENGTH; + + private short buttonFlags; + private byte leftTrigger; + private byte rightTrigger; + private short leftStickX; + private short leftStickY; + private short rightStickX; + private short rightStickY; + + public ControllerPacket(short buttonFlags, byte leftTrigger, byte rightTrigger, + short leftStickX, short leftStickY, + short rightStickX, short rightStickY) + { + super(PACKET_TYPE); + + this.buttonFlags = buttonFlags; + this.leftTrigger = leftTrigger; + this.rightTrigger = rightTrigger; + this.leftStickX = leftStickX; + this.leftStickY = leftStickY; + this.rightStickX = rightStickX; + this.rightStickY = rightStickY; + } + + public byte[] toWire() + { + ByteBuffer bb = ByteBuffer.allocate(PACKET_LENGTH).order(ByteOrder.LITTLE_ENDIAN); + + bb.put(toWireHeader()); + bb.put(HEADER); + bb.putShort(buttonFlags); + bb.put(leftTrigger); + bb.put(rightTrigger); + bb.putShort(leftStickX); + bb.putShort(leftStickY); + bb.putShort(rightStickX); + bb.putShort(rightStickY); + bb.put(TAIL); + + return bb.array(); + } + } \ No newline at end of file diff --git a/moonlight-common/src/com/limelight/nvstream/input/InputPacket.java b/moonlight-common/src/com/limelight/nvstream/input/InputPacket.java new file mode 100644 index 00000000..dec3ff11 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/input/InputPacket.java @@ -0,0 +1,26 @@ +package com.limelight.nvstream.input; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public abstract class InputPacket { + public static final int HEADER_LENGTH = 0x4; + + protected int packetType; + + public InputPacket(int packetType) + { + this.packetType = packetType; + } + + public abstract byte[] toWire(); + + public byte[] toWireHeader() + { + ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN); + + bb.putInt(packetType); + + return bb.array(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/input/MouseButtonPacket.java b/moonlight-common/src/com/limelight/nvstream/input/MouseButtonPacket.java new file mode 100644 index 00000000..70cea01c --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/input/MouseButtonPacket.java @@ -0,0 +1,36 @@ +package com.limelight.nvstream.input; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class MouseButtonPacket extends InputPacket { + + private byte buttonEventType; + + public static final int PACKET_TYPE = 0x5; + public static final int PAYLOAD_LENGTH = 5; + public static final int PACKET_LENGTH = PAYLOAD_LENGTH + + InputPacket.HEADER_LENGTH; + + public static final byte PRESS_EVENT = 0x07; + public static final byte RELEASE_EVENT = 0x08; + + public MouseButtonPacket(boolean leftButtonDown) + { + super(PACKET_TYPE); + + buttonEventType = leftButtonDown ? + PRESS_EVENT : RELEASE_EVENT; + } + + @Override + public byte[] toWire() { + ByteBuffer bb = ByteBuffer.allocate(PACKET_LENGTH).order(ByteOrder.BIG_ENDIAN); + + bb.put(toWireHeader()); + bb.put(buttonEventType); + bb.putInt(1); // FIXME: button index? + + return bb.array(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/input/MouseMovePacket.java b/moonlight-common/src/com/limelight/nvstream/input/MouseMovePacket.java new file mode 100644 index 00000000..dba6f27b --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/input/MouseMovePacket.java @@ -0,0 +1,42 @@ +package com.limelight.nvstream.input; + +import java.nio.ByteBuffer; + +public class MouseMovePacket extends InputPacket { + + private static final byte[] HEADER = + { + 0x06, + 0x00, + 0x00, + 0x00 + }; + + public static final int PACKET_TYPE = 0x8; + public static final int PAYLOAD_LENGTH = 8; + public static final int PACKET_LENGTH = PAYLOAD_LENGTH + + InputPacket.HEADER_LENGTH; + + private short deltaX; + private short deltaY; + + public MouseMovePacket(short deltaX, short deltaY) + { + super(PACKET_TYPE); + + this.deltaX = deltaX; + this.deltaY = deltaY; + } + + @Override + public byte[] toWire() { + ByteBuffer bb = ByteBuffer.allocate(PACKET_LENGTH); + + bb.put(toWireHeader()); + bb.put(HEADER); + bb.putShort(deltaX); + bb.putShort(deltaY); + + return bb.array(); + } +} diff --git a/moonlight-common/src/com/limelight/nvstream/input/NvController.java b/moonlight-common/src/com/limelight/nvstream/input/NvController.java new file mode 100644 index 00000000..b0b85984 --- /dev/null +++ b/moonlight-common/src/com/limelight/nvstream/input/NvController.java @@ -0,0 +1,65 @@ +package com.limelight.nvstream.input; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; + +public class NvController { + + public final static int PORT = 35043; + + public final static int CONTROLLER_TIMEOUT = 3000; + + private InetAddress host; + private Socket s; + private OutputStream out; + + public NvController(InetAddress host) + { + this.host = host; + } + + public void initialize() throws IOException + { + s = new Socket(); + s.connect(new InetSocketAddress(host, PORT), CONTROLLER_TIMEOUT); + s.setTcpNoDelay(true); + out = s.getOutputStream(); + } + + public void close() + { + try { + s.close(); + } catch (IOException e) {} + } + + public void sendControllerInput(short buttonFlags, byte leftTrigger, byte rightTrigger, + short leftStickX, short leftStickY, short rightStickX, short rightStickY) throws IOException + { + out.write(new ControllerPacket(buttonFlags, leftTrigger, + rightTrigger, leftStickX, leftStickY, + rightStickX, rightStickY).toWire()); + out.flush(); + } + + public void sendMouseButtonDown() throws IOException + { + out.write(new MouseButtonPacket(true).toWire()); + out.flush(); + } + + public void sendMouseButtonUp() throws IOException + { + out.write(new MouseButtonPacket(false).toWire()); + out.flush(); + } + + public void sendMouseMove(short deltaX, short deltaY) throws IOException + { + out.write(new MouseMovePacket(deltaX, deltaY).toWire()); + out.flush(); + } +}