Add implementation of http negotiation, handshake, control, and input packets

This commit is contained in:
Cameron Gutman 2013-09-21 18:51:48 -04:00
parent b263367528
commit fcdc23eeb7
16 changed files with 1012 additions and 35 deletions

View File

@ -7,7 +7,9 @@
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
@ -23,6 +25,18 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.limelight.Game"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/title_activity_game"
android:parentActivityName="com.limelight.Connection"
android:theme="@style/FullscreenTheme" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.limelight.Connection" />
</activity>
<service
android:name="com.limelight.RelayService"></service>
</application>
</manifest>

View File

@ -9,6 +9,17 @@ package com.limelight;
public final class R {
public static final class attr {
/** <p>Must be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
*/
public static final int buttonBarButtonStyle=0x7f010001;
/** <p>Must be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
*/
public static final int buttonBarStyle=0x7f010000;
}
public static final class color {
public static final int black_overlay=0x7f040000;
}
public static final class dimen {
/** Default screen margins, per the Android Design guidelines.
@ -17,17 +28,24 @@ public final class R {
screen margins) for sw720dp devices (e.g. 10" tablets) in landscape here.
*/
public static final int activity_horizontal_margin=0x7f040000;
public static final int activity_vertical_margin=0x7f040001;
public static final int activity_horizontal_margin=0x7f050000;
public static final int activity_vertical_margin=0x7f050001;
}
public static final class drawable {
public static final int ic_launcher=0x7f020000;
}
public static final class id {
public static final int videoView=0x7f080000;
}
public static final class layout {
public static final int activity_connection=0x7f030000;
public static final int activity_game=0x7f030001;
}
public static final class string {
public static final int app_name=0x7f050000;
public static final int app_name=0x7f060000;
public static final int dummy_button=0x7f060002;
public static final int dummy_content=0x7f060003;
public static final int title_activity_game=0x7f060001;
}
public static final class style {
/**
@ -35,14 +53,17 @@ public final class R {
by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
Theme customizations available in newer API levels can go in
res/values-vXX/styles.xml, while customizations related to
backward-compatibility can go here.
Base application theme for API 11+. This theme completely replaces
AppBaseTheme from res/values/styles.xml on API 11+ devices.
API 11 theme customizations can go here.
Base application theme for API 14+. This theme completely replaces
@ -51,10 +72,56 @@ public final class R {
API 14 theme customizations can go here.
*/
public static final int AppBaseTheme=0x7f060000;
public static final int AppBaseTheme=0x7f070000;
/** Application theme.
All customizations that are NOT specific to a particular API-level can go here.
*/
public static final int AppTheme=0x7f060001;
public static final int AppTheme=0x7f070001;
public static final int ButtonBar=0x7f070003;
public static final int ButtonBarButton=0x7f070004;
public static final int FullscreenActionBarStyle=0x7f070005;
public static final int FullscreenTheme=0x7f070002;
}
public static final class styleable {
/**
Declare custom theme attributes that allow changing which styles are
used for button bars depending on the API level.
?android:attr/buttonBarStyle is new as of API 11 so this is
necessary to support previous API levels.
<p>Includes the following attributes:</p>
<table>
<colgroup align="left" />
<colgroup align="left" />
<tr><th>Attribute</th><th>Description</th></tr>
<tr><td><code>{@link #ButtonBarContainerTheme_buttonBarButtonStyle com.limelight:buttonBarButtonStyle}</code></td><td></td></tr>
<tr><td><code>{@link #ButtonBarContainerTheme_buttonBarStyle com.limelight:buttonBarStyle}</code></td><td></td></tr>
</table>
@see #ButtonBarContainerTheme_buttonBarButtonStyle
@see #ButtonBarContainerTheme_buttonBarStyle
*/
public static final int[] ButtonBarContainerTheme = {
0x7f010000, 0x7f010001
};
/**
<p>This symbol is the offset where the {@link com.limelight.R.attr#buttonBarButtonStyle}
attribute's value can be found in the {@link #ButtonBarContainerTheme} array.
<p>Must be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
@attr name android:buttonBarButtonStyle
*/
public static final int ButtonBarContainerTheme_buttonBarButtonStyle = 1;
/**
<p>This symbol is the offset where the {@link com.limelight.R.attr#buttonBarStyle}
attribute's value can be found in the {@link #ButtonBarContainerTheme} array.
<p>Must be a reference to another resource, in the form "<code>@[+][<i>package</i>:]<i>type</i>:<i>name</i></code>"
or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>name</i></code>".
@attr name android:buttonBarStyle
*/
public static final int ButtonBarContainerTheme_buttonBarStyle = 0;
};
}

View File

@ -0,0 +1,13 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0099cc"
tools:context=".Game" >
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
@ -8,4 +9,16 @@
<!-- API 11 theme customizations can go here. -->
</style>
<style name="FullscreenTheme" parent="android:Theme.Holo">
<item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item>
<item name="android:windowActionBarOverlay">true</item>
<item name="android:windowBackground">@null</item>
<item name="buttonBarStyle">?android:attr/buttonBarStyle</item>
<item name="buttonBarButtonStyle">?android:attr/buttonBarButtonStyle</item>
</style>
<style name="FullscreenActionBarStyle" parent="android:Widget.Holo.ActionBar">
<item name="android:background">@color/black_overlay</item>
</style>
</resources>

14
res/values/attrs.xml Normal file
View File

@ -0,0 +1,14 @@
<resources>
<!--
Declare custom theme attributes that allow changing which styles are
used for button bars depending on the API level.
?android:attr/buttonBarStyle is new as of API 11 so this is
necessary to support previous API levels.
-->
<declare-styleable name="ButtonBarContainerTheme">
<attr name="buttonBarStyle" format="reference" />
<attr name="buttonBarButtonStyle" format="reference" />
</declare-styleable>
</resources>

5
res/values/colors.xml Normal file
View File

@ -0,0 +1,5 @@
<resources>
<color name="black_overlay">#66000000</color>
</resources>

View File

@ -2,5 +2,8 @@
<resources>
<string name="app_name">Limelight</string>
<string name="title_activity_game">Game</string>
<string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string>
</resources>

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
@ -17,4 +18,21 @@
<!-- All customizations that are NOT specific to a particular API-level can go here. -->
</style>
<style name="FullscreenTheme" parent="android:Theme.NoTitleBar">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowBackground">@null</item>
<item name="buttonBarStyle">@style/ButtonBar</item>
<item name="buttonBarButtonStyle">@style/ButtonBarButton</item>
</style>
<style name="ButtonBar">
<item name="android:paddingLeft">2dp</item>
<item name="android:paddingTop">5dp</item>
<item name="android:paddingRight">2dp</item>
<item name="android:paddingBottom">0dp</item>
<item name="android:background">@android:drawable/bottom_bar</item>
</style>
<style name="ButtonBarButton" />
</resources>

View File

@ -0,0 +1,30 @@
package com.limelight;
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.VideoView;
public class Game extends Activity {
private VideoView vv;
private MediaController mc;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
vv = (VideoView) findViewById(R.id.videoView);
mc = new MediaController(this);
mc.setAnchorView(vv);
Uri video = Uri.parse("rtsp://141.213.191.236:47902/nvstream");
vv.setMediaController(mc);
vv.setVideoURI(video);
vv.start();
}
}

View File

@ -0,0 +1,73 @@
package com.limelight.nvstream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import android.net.rtp.AudioCodec;
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
public class NvAudioStream {
private AudioGroup group;
private AudioStream stream;
public static final int PORT = 48000;
/*public void startStream(String host) throws SocketException, UnknownHostException
{
System.out.println("Starting audio group");
group = new AudioGroup();
group.setMode(AudioGroup.MODE_NORMAL);
System.out.println("Starting audio stream");
stream = new AudioStream(InetAddress.getByAddress(new byte[]{0,0,0,0}));
stream.setMode(AudioStream.MODE_NORMAL);
stream.associate(InetAddress.getByName(host), PORT);
stream.setCodec(AudioCodec.PCMA);
stream.join(group);
for (AudioCodec c : AudioCodec.getCodecs())
System.out.println(c.type + " " + c.fmtp + " " + c.rtpmap);
System.out.println("Joined");
}*/
public void start()
{
new Thread(new Runnable() {
@Override
public void run() {
DatagramSocket ds;
try {
ds = new DatagramSocket(PORT);
} catch (SocketException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return;
}
for (;;)
{
DatagramPacket dp = new DatagramPacket(new byte[1500], 1500);
try {
ds.receive(dp);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
//System.out.println("Got UDP 48000: "+dp.getLength());
}
}
}).start();
}
}

View File

@ -12,20 +12,67 @@ public class NvConnection {
this.host = host;
}
private void delay(int ms)
{
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
return;
}
}
public void doShit() throws XmlPullParserException, IOException
{
NvHttp h = new NvHttp(host, "b0:ee:45:57:5d:5f");
System.out.println("Shield Shit");
System.out.println("Begin Shield Action");
System.out.println(h.getAppVersion());
System.out.println(h.getPairState());
int sessionId = h.getSessionId();
System.out.println("Session ID: "+sessionId);
int appId = h.getSteamAppId(sessionId);
System.out.println("Steam app ID: "+appId);
int gameSession = h.launchApp(sessionId, appId);
System.out.println("Started game session: "+gameSession);
System.out.println("Starting handshake");
NvHandshake.performHandshake(host);
System.out.println("Handshake complete");
NvControl nvC = new NvControl(host);
System.out.println("Starting control");
nvC.beginControl();
System.out.println("Startup controller");
NvController controller = new NvController(host);
// Wait 3 seconds to start input
delay(3000);
System.out.println("Beginning controller input");
controller.sendLeftButton();
delay(100);
controller.clearButtons();
delay(250);
controller.sendRightButton();
delay(100);
controller.clearButtons();
delay(250);
controller.sendRightButton();
delay(100);
controller.clearButtons();
delay(250);
controller.sendRightButton();
delay(100);
controller.clearButtons();
delay(250);
controller.sendLeftButton();
delay(100);
controller.clearButtons();
new NvAudioStream().start();
new NvVideoStream().start(host);
}
}

View File

@ -0,0 +1,384 @@
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_1 = 0x1204;
public static final short PPAYLEN_1 = 0x0004;
public static final byte[] PPAYLOAD_1 =
{
(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,
167773203,
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_HELLO = 0x1204;
public static final short PPAYLEN_HELLO = 0x0004;
public static final byte[] PPAYLOAD_HELLO = new byte[] {
0x04, 0x00, 0x00, 0x05
};
private Socket s;
private InputStream in;
private OutputStream out;
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);
}
public void beginControl() 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");
new Thread(new Runnable() {
@Override
public void run() {
for (;;)
{
try {
sendHeartbeat();
} catch (IOException e1) {
e1.printStackTrace();
break;
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
break;
}
}
}
}).start();
}
public void endControl() throws IOException
{
s.close();
}
private NvControl.NvCtlResponse send1405AndGetResponse() throws IOException
{
return sendAndGetReply(new NvCtlPacket(PTYPE_1405, PPAYLEN_1405));
}
private void send1404() throws IOException
{
sendPacket(new NvCtlPacket(PTYPE_1404, PPAYLEN_1404));
}
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
{
offset = in.read(header, offset, header.length - offset);
} while (offset != header.length);
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
{
offset = in.read(payload, offset, payload.length - offset);
} while (offset != payload.length);
}
}
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;
}
}
}

View File

@ -0,0 +1,40 @@
package com.limelight.nvstream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class NvController {
public final static int PORT = 35043;
private Socket s;
private OutputStream out;
public NvController(String host) throws UnknownHostException, IOException
{
s = new Socket(host, PORT);
out = s.getOutputStream();
}
// Example
public void sendLeftButton() throws IOException
{
out.write(new NvInputPacket(NvInputPacket.LEFT_FLAG, (byte)0, (byte)0, (short)0, (short)0).toWire());
}
// Example
public void sendRightButton() throws IOException
{
out.write(new NvInputPacket(NvInputPacket.RIGHT_FLAG, (byte)0, (byte)0, (short)0, (short)0).toWire());
}
// Example
public void clearButtons() throws IOException
{
out.write(new NvInputPacket((short)0, (byte)0, (byte)0, (short)0, (short)0).toWire());
}
}

View File

@ -0,0 +1,107 @@
package com.limelight.nvstream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;;
public class NvHandshake {
public static final int PORT = 47991;
// android
public static final byte[] PLATFORM_HELLO =
{
(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 void waitAndDiscardResponse(InputStream in) throws IOException
{
// Wait for response and discard response
in.read();
try {
Thread.sleep(250);
} catch (InterruptedException e) { }
for (int i = 0; i < in.available(); i++)
in.read();
}
public static void performHandshake(String host) throws UnknownHostException, IOException
{
Socket s = new Socket(host, PORT);
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();
// First packet
out.write(new byte[]{0x07, 0x00, 0x00, 0x00});
out.write(PLATFORM_HELLO);
System.out.println("HS: Waiting for hello response");
waitAndDiscardResponse(in);
// Second packet
out.write(PACKET_2);
System.out.println("HS: Waiting stage 2 response");
waitAndDiscardResponse(in);
// Third packet
out.write(PACKET_3);
System.out.println("HS: Waiting for stage 3 response");
waitAndDiscardResponse(in);
// Fourth packet
out.write(PACKET_4);
out.flush();
// Done
s.close();
}
}

View File

@ -0,0 +1,78 @@
package com.limelight.nvstream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class NvInputPacket {
public static final byte[] HEADER =
{
0x00,
0x00,
0x00,
0x18,
0x0A,
0x00,
0x00,
0x00,
0x00,
0x14
};
public static final byte[] TAIL =
{
(byte)0x9C,
0x00,
0x00,
0x00,
0x55,
0x00
};
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 RB_FLAG = 0x0100;
public static final short LB_FLAG = 0x0200;
public static final short LS_CLK_FLAG = 0x0004;
public static final short RS_CLK_FLAG = 0x0008;
public static final short PLAY_FLAG = 0x0001;
public static final short BACK_FLAG = 0x0002;
public static final short PACKET_LENGTH = 28;
private short buttonFlags;
private byte leftTrigger;
private byte rightTrigger;
private short leftStick;
private short rightStick;
public NvInputPacket(short buttonFlags, byte leftTrigger, byte rightTrigger,
short leftStick, short rightStick)
{
this.buttonFlags = buttonFlags;
this.leftTrigger = leftTrigger;
this.rightTrigger = rightTrigger;
this.leftStick = leftStick;
this.rightStick = rightStick;
}
public byte[] toWire()
{
ByteBuffer bb = ByteBuffer.allocate(PACKET_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
bb.put(HEADER);
bb.putShort(buttonFlags);
bb.put(leftTrigger);
bb.put(rightTrigger);
bb.putShort(leftStick);
bb.putShort(rightStick);
bb.put(TAIL);
return bb.array();
}
}

View File

@ -0,0 +1,71 @@
package com.limelight.nvstream;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
public class NvVideoStream {
public static final int PORT = 47998;
public static final int FIRST_FRAME_PORT = 47996;
private InputStream getFirstFrame(String host) throws UnknownHostException, IOException
{
Socket s = new Socket(host, FIRST_FRAME_PORT);
return s.getInputStream();
}
public void start(final String host)
{
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("VID: Waiting for first frame");
InputStream firstFrameStream = getFirstFrame(host);
firstFrameStream.read();
System.out.println("VID: First frame: "+firstFrameStream.available()+1);
firstFrameStream.close();
System.out.println("VID: Got first frame");
} catch (UnknownHostException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
return;
} catch (IOException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
return;
}
DatagramSocket ds;
try {
ds = new DatagramSocket(PORT);
} catch (SocketException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return;
}
for (;;)
{
DatagramPacket dp = new DatagramPacket(new byte[1500], 1500);
try {
ds.receive(dp);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
break;
}
System.out.println("Got UDP 47998: "+dp.getLength());
}
}
}).start();
}
}