mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-19 19:13:03 +00:00
Add implementation of http negotiation, handshake, control, and input packets
This commit is contained in:
parent
b263367528
commit
fcdc23eeb7
@ -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>
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
13
res/layout/activity_game.xml
Normal file
13
res/layout/activity_game.xml
Normal 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>
|
@ -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
14
res/values/attrs.xml
Normal 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
5
res/values/colors.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
|
||||
<color name="black_overlay">#66000000</color>
|
||||
|
||||
</resources>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
30
src/com/limelight/Game.java
Normal file
30
src/com/limelight/Game.java
Normal 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();
|
||||
}
|
||||
}
|
73
src/com/limelight/nvstream/NvAudioStream.java
Normal file
73
src/com/limelight/nvstream/NvAudioStream.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
384
src/com/limelight/nvstream/NvControl.java
Normal file
384
src/com/limelight/nvstream/NvControl.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
40
src/com/limelight/nvstream/NvController.java
Normal file
40
src/com/limelight/nvstream/NvController.java
Normal 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());
|
||||
}
|
||||
}
|
107
src/com/limelight/nvstream/NvHandshake.java
Normal file
107
src/com/limelight/nvstream/NvHandshake.java
Normal 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();
|
||||
}
|
||||
}
|
78
src/com/limelight/nvstream/NvInputPacket.java
Normal file
78
src/com/limelight/nvstream/NvInputPacket.java
Normal 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();
|
||||
}
|
||||
}
|
71
src/com/limelight/nvstream/NvVideoStream.java
Normal file
71
src/com/limelight/nvstream/NvVideoStream.java
Normal 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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user