Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5efcd606e3 | |||
| 3524cdd764 | |||
| 368cd8808d | |||
| b52a6ce93c | |||
| 7ab4e5d0a5 | |||
| 095dfd8035 | |||
| f7c33ef975 | |||
| 57b0bce5a4 | |||
| 7a017d7b97 | |||
| d2773be32e | |||
| 93a7d9f181 | |||
| 9703cf4ffe | |||
| 41ec64e87c | |||
| aca92a5056 | |||
| fc9c4d9aaa | |||
| d8c6a544f0 | |||
| 7e100f2c9c | |||
| 643b644e17 | |||
| 48a9d3ac12 | |||
| efdd1e2046 | |||
| 8a40892865 | |||
| 20635a3012 | |||
| b908c5cec3 |
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.limelight"
|
package="com.limelight"
|
||||||
android:versionCode="8"
|
android:versionCode="13"
|
||||||
android:versionName="2.1.2" >
|
android:versionName="2.2" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="16"
|
android:minSdkVersion="16"
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.limelight.Game"
|
android:name="com.limelight.Game"
|
||||||
android:screenOrientation="sensorLandscape"
|
android:screenOrientation="sensorLandscape"
|
||||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|screenLayout|fontScale|uiMode|orientation|screenSize|smallestScreenSize|layoutDirection"
|
||||||
android:label="@string/title_activity_game"
|
android:label="@string/title_activity_game"
|
||||||
android:parentActivityName="com.limelight.Connection"
|
android:parentActivityName="com.limelight.Connection"
|
||||||
android:theme="@style/FullscreenTheme" >
|
android:theme="@style/FullscreenTheme" >
|
||||||
|
|||||||
@@ -3,10 +3,8 @@
|
|||||||
Limelight is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
Limelight is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||||
We reverse engineered the Shield streaming software, and created a version that can be run on any Android device.
|
We reverse engineered the Shield streaming software, and created a version that can be run on any Android device.
|
||||||
|
|
||||||
Limelight will allow you to stream your full collection of Steam games from your Windows PC to your Android device on the same network.
|
Limelight will allow you to stream your full collection of Steam games from your Windows PC to your Android device,
|
||||||
|
in your own home, or over the internet.
|
||||||
Streaming can be done remotely using the [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481)
|
|
||||||
application.
|
|
||||||
|
|
||||||
[Limelight-pc](https://github.com/limelight-stream/limelight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/limelight-stream/limelight-ios) and [Windows Phone](https://github.com/limelight-stream/limelight-wp) are also in development.
|
[Limelight-pc](https://github.com/limelight-stream/limelight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/limelight-stream/limelight-ios) and [Windows Phone](https://github.com/limelight-stream/limelight-wp) are also in development.
|
||||||
|
|
||||||
@@ -36,9 +34,10 @@ application.
|
|||||||
|
|
||||||
##Usage
|
##Usage
|
||||||
|
|
||||||
* Ensure your Android device and your PC are on the same network or you're running [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481).
|
|
||||||
* Turn on Shield Streaming in the GFE settings
|
* Turn on Shield Streaming in the GFE settings
|
||||||
* In Limelight, enter your PC's IP or Hostname and click "Pair".
|
* If you are connecting from outside the same network, turn on internet
|
||||||
|
streaming
|
||||||
|
* In Limelight, enter your PC's IP or Hostname and click "Pair"
|
||||||
* Accept the pairing confirmation on your PC
|
* Accept the pairing confirmation on your PC
|
||||||
* In Limelight, click "Start Streaming"
|
* In Limelight, click "Start Streaming"
|
||||||
* Play games!
|
* Play games!
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"SupportedControllers" : {
|
||||||
|
"Gamepad" : {},
|
||||||
|
"Remote" : "false",
|
||||||
|
"SecondScreen" : {
|
||||||
|
"DPad" : "false",
|
||||||
|
"AnalogSticks" : "0",
|
||||||
|
"DigitalButtons" : "0",
|
||||||
|
"Mouse" : "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,18 +35,21 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
|||||||
public static final int ic_launcher=0x7f020000;
|
public static final int ic_launcher=0x7f020000;
|
||||||
}
|
}
|
||||||
public static final class id {
|
public static final class id {
|
||||||
public static final int autoDec=0x7f080005;
|
public static final int autoDec=0x7f080006;
|
||||||
public static final int config1080p30Selected=0x7f080009;
|
public static final int bitrateLabel=0x7f08000c;
|
||||||
public static final int config1080p60Selected=0x7f08000a;
|
public static final int bitrateSeekBar=0x7f08000d;
|
||||||
public static final int config720p30Selected=0x7f080007;
|
public static final int config1080p30Selected=0x7f08000a;
|
||||||
public static final int config720p60Selected=0x7f080008;
|
public static final int config1080p60Selected=0x7f08000b;
|
||||||
public static final int hardwareDec=0x7f080006;
|
public static final int config720p30Selected=0x7f080008;
|
||||||
|
public static final int config720p60Selected=0x7f080009;
|
||||||
|
public static final int decoderConfigGroup=0x7f080003;
|
||||||
|
public static final int hardwareDec=0x7f080007;
|
||||||
public static final int hostTextView=0x7f080000;
|
public static final int hostTextView=0x7f080000;
|
||||||
public static final int pairButton=0x7f080002;
|
public static final int pairButton=0x7f080002;
|
||||||
public static final int softwareDec=0x7f080004;
|
public static final int softwareDec=0x7f080005;
|
||||||
public static final int statusButton=0x7f080001;
|
public static final int statusButton=0x7f080001;
|
||||||
public static final int streamConfigGroup=0x7f080003;
|
public static final int streamConfigGroup=0x7f080004;
|
||||||
public static final int surfaceView=0x7f08000b;
|
public static final int surfaceView=0x7f08000e;
|
||||||
}
|
}
|
||||||
public static final class layout {
|
public static final class layout {
|
||||||
public static final int activity_connection=0x7f030000;
|
public static final int activity_connection=0x7f030000;
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -45,12 +45,13 @@
|
|||||||
android:text="Pair with PC" />
|
android:text="Pair with PC" />
|
||||||
|
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
|
android:id="@+id/decoderConfigGroup"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_below="@+id/streamConfigGroup"
|
android:layout_below="@+id/streamConfigGroup"
|
||||||
android:layout_marginTop="25dp"
|
android:layout_marginTop="15dp"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_below="@+id/pairButton"
|
android:layout_below="@+id/pairButton"
|
||||||
android:layout_marginTop="25dp"
|
android:layout_marginTop="10dp"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<RadioButton
|
<RadioButton
|
||||||
@@ -110,6 +111,24 @@
|
|||||||
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bitrateLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="5dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup" />
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/bitrateSeekBar"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_below="@+id/decoderConfigGroup"
|
||||||
|
android:layout_toLeftOf="@+id/bitrateLabel" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import android.widget.Button;
|
|||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.SeekBar.OnSeekBarChangeListener;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
@@ -33,6 +35,8 @@ public class Connection extends Activity {
|
|||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
||||||
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
||||||
|
private SeekBar bitrateSlider;
|
||||||
|
private TextView bitrateLabel;
|
||||||
|
|
||||||
private static final String DEFAULT_HOST = "";
|
private static final String DEFAULT_HOST = "";
|
||||||
public static final String HOST_KEY = "hostText";
|
public static final String HOST_KEY = "hostText";
|
||||||
@@ -42,6 +46,7 @@ public class Connection extends Activity {
|
|||||||
SharedPreferences.Editor editor = prefs.edit();
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
|
||||||
editor.putString(Connection.HOST_KEY, this.hostText.getText().toString());
|
editor.putString(Connection.HOST_KEY, this.hostText.getText().toString());
|
||||||
|
editor.putInt(Game.BITRATE_PREF_STRING, bitrateSlider.getProgress());
|
||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
super.onPause();
|
super.onPause();
|
||||||
@@ -66,12 +71,18 @@ public class Connection extends Activity {
|
|||||||
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
||||||
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
||||||
this.forceHardDec = (RadioButton) findViewById(R.id.hardwareDec);
|
this.forceHardDec = (RadioButton) findViewById(R.id.hardwareDec);
|
||||||
|
this.bitrateLabel = (TextView) findViewById(R.id.bitrateLabel);
|
||||||
|
this.bitrateSlider = (SeekBar) findViewById(R.id.bitrateSeekBar);
|
||||||
|
|
||||||
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
||||||
|
|
||||||
boolean res720p = prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720;
|
boolean res720p = prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720;
|
||||||
boolean fps30 = prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30;
|
boolean fps30 = prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30;
|
||||||
|
|
||||||
|
bitrateSlider.setMax(Game.BITRATE_CEILING);
|
||||||
|
bitrateSlider.setProgress(prefs.getInt(Game.BITRATE_PREF_STRING, Game.DEFAULT_BITRATE));
|
||||||
|
updateBitrateLabel();
|
||||||
|
|
||||||
rbutton720p30.setChecked(false);
|
rbutton720p30.setChecked(false);
|
||||||
rbutton720p60.setChecked(false);
|
rbutton720p60.setChecked(false);
|
||||||
@@ -124,22 +135,30 @@ public class Connection extends Activity {
|
|||||||
if (buttonView == rbutton720p30) {
|
if (buttonView == rbutton720p30) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_30).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_720_30);
|
||||||
}
|
}
|
||||||
else if (buttonView == rbutton720p60) {
|
else if (buttonView == rbutton720p60) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_720_60).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_720_60);
|
||||||
}
|
}
|
||||||
else if (buttonView == rbutton1080p30) {
|
else if (buttonView == rbutton1080p30) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 30).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_30).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_1080_30);
|
||||||
}
|
}
|
||||||
else if (buttonView == rbutton1080p60) {
|
else if (buttonView == rbutton1080p60) {
|
||||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).commit();
|
putInt(Game.REFRESH_RATE_PREF_STRING, 60).
|
||||||
|
putInt(Game.BITRATE_PREF_STRING, Game.BITRATE_DEFAULT_1080_60).commit();
|
||||||
|
bitrateSlider.setProgress(Game.BITRATE_DEFAULT_1080_60);
|
||||||
}
|
}
|
||||||
else if (buttonView == forceSoftDec) {
|
else if (buttonView == forceSoftDec) {
|
||||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
||||||
@@ -160,6 +179,45 @@ public class Connection extends Activity {
|
|||||||
forceHardDec.setOnCheckedChangeListener(occl);
|
forceHardDec.setOnCheckedChangeListener(occl);
|
||||||
autoDec.setOnCheckedChangeListener(occl);
|
autoDec.setOnCheckedChangeListener(occl);
|
||||||
|
|
||||||
|
this.bitrateSlider.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress,
|
||||||
|
boolean fromUser) {
|
||||||
|
|
||||||
|
// Verify the user's selection
|
||||||
|
if (fromUser) {
|
||||||
|
int floor;
|
||||||
|
if (rbutton720p30.isChecked()) {
|
||||||
|
floor = Game.BITRATE_FLOOR_720_30;
|
||||||
|
}
|
||||||
|
else if (rbutton720p60.isChecked()){
|
||||||
|
floor = Game.BITRATE_FLOOR_720_60;
|
||||||
|
}
|
||||||
|
else if (rbutton1080p30.isChecked()){
|
||||||
|
floor = Game.BITRATE_FLOOR_1080_30;
|
||||||
|
}
|
||||||
|
else /*if (rbutton1080p60.isChecked())*/ {
|
||||||
|
floor = Game.BITRATE_FLOOR_1080_60;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress < floor) {
|
||||||
|
seekBar.setProgress(floor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateBitrateLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.statusButton.setOnClickListener(new OnClickListener() {
|
this.statusButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View arg0) {
|
public void onClick(View arg0) {
|
||||||
@@ -177,6 +235,11 @@ public class Connection extends Activity {
|
|||||||
this.pairButton.setOnClickListener(new OnClickListener() {
|
this.pairButton.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View arg0) {
|
public void onClick(View arg0) {
|
||||||
|
if (Connection.this.hostText.getText().length() == 0) {
|
||||||
|
Toast.makeText(Connection.this, "Please enter the target PC's IP address in the text box at the top of the screen.", Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_LONG).show();
|
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_LONG).show();
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -235,4 +298,7 @@ public class Connection extends Activity {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateBitrateLabel() {
|
||||||
|
bitrateLabel.setText(bitrateSlider.getProgress()+" Mbps");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import android.view.WindowManager;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
|
||||||
public class Game extends Activity implements OnGenericMotionListener, OnTouchListener, NvConnectionListener {
|
public class Game extends Activity implements SurfaceHolder.Callback, OnGenericMotionListener, OnTouchListener, NvConnectionListener {
|
||||||
private int lastMouseX = Integer.MIN_VALUE;
|
private int lastMouseX = Integer.MIN_VALUE;
|
||||||
private int lastMouseY = Integer.MIN_VALUE;
|
private int lastMouseY = Integer.MIN_VALUE;
|
||||||
private int lastButtonState = 0;
|
private int lastButtonState = 0;
|
||||||
@@ -52,6 +52,10 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
private NvConnection conn;
|
private NvConnection conn;
|
||||||
private SpinnerDialog spinner;
|
private SpinnerDialog spinner;
|
||||||
private boolean displayedFailureDialog = false;
|
private boolean displayedFailureDialog = false;
|
||||||
|
private boolean connecting = false;
|
||||||
|
private boolean connected = false;
|
||||||
|
|
||||||
|
private int drFlags = 0;
|
||||||
|
|
||||||
public static final String PREFS_FILE_NAME = "gameprefs";
|
public static final String PREFS_FILE_NAME = "gameprefs";
|
||||||
|
|
||||||
@@ -59,11 +63,25 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
public static final String HEIGHT_PREF_STRING = "ResV";
|
public static final String HEIGHT_PREF_STRING = "ResV";
|
||||||
public static final String REFRESH_RATE_PREF_STRING = "FPS";
|
public static final String REFRESH_RATE_PREF_STRING = "FPS";
|
||||||
public static final String DECODER_PREF_STRING = "Decoder";
|
public static final String DECODER_PREF_STRING = "Decoder";
|
||||||
|
public static final String BITRATE_PREF_STRING = "Bitrate";
|
||||||
|
|
||||||
|
public static final int BITRATE_FLOOR_720_30 = 4;
|
||||||
|
public static final int BITRATE_FLOOR_720_60 = 8;
|
||||||
|
public static final int BITRATE_FLOOR_1080_30 = 10;
|
||||||
|
public static final int BITRATE_FLOOR_1080_60 = 20;
|
||||||
|
|
||||||
|
public static final int BITRATE_DEFAULT_720_30 = 7;
|
||||||
|
public static final int BITRATE_DEFAULT_720_60 = 10;
|
||||||
|
public static final int BITRATE_DEFAULT_1080_30 = 16;
|
||||||
|
public static final int BITRATE_DEFAULT_1080_60 = 30;
|
||||||
|
|
||||||
|
public static final int BITRATE_CEILING = 50;
|
||||||
|
|
||||||
public static final int DEFAULT_WIDTH = 1280;
|
public static final int DEFAULT_WIDTH = 1280;
|
||||||
public static final int DEFAULT_HEIGHT = 720;
|
public static final int DEFAULT_HEIGHT = 720;
|
||||||
public static final int DEFAULT_REFRESH_RATE = 60;
|
public static final int DEFAULT_REFRESH_RATE = 60;
|
||||||
public static final int DEFAULT_DECODER = 0;
|
public static final int DEFAULT_DECODER = 0;
|
||||||
|
public static final int DEFAULT_BITRATE = BITRATE_DEFAULT_720_60;
|
||||||
|
|
||||||
public static final int FORCE_HARDWARE_DECODER = -1;
|
public static final int FORCE_HARDWARE_DECODER = -1;
|
||||||
public static final int AUTOSELECT_DECODER = 0;
|
public static final int AUTOSELECT_DECODER = 0;
|
||||||
@@ -101,7 +119,6 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
|
|
||||||
// Read the stream preferences
|
// Read the stream preferences
|
||||||
SharedPreferences prefs = getSharedPreferences(PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
SharedPreferences prefs = getSharedPreferences(PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||||
int drFlags = 0;
|
|
||||||
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
||||||
case Game.FORCE_SOFTWARE_DECODER:
|
case Game.FORCE_SOFTWARE_DECODER:
|
||||||
drFlags |= VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING;
|
drFlags |= VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING;
|
||||||
@@ -113,10 +130,11 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int refreshRate;
|
int refreshRate, bitrate;
|
||||||
width = prefs.getInt(WIDTH_PREF_STRING, DEFAULT_WIDTH);
|
width = prefs.getInt(WIDTH_PREF_STRING, DEFAULT_WIDTH);
|
||||||
height = prefs.getInt(HEIGHT_PREF_STRING, DEFAULT_HEIGHT);
|
height = prefs.getInt(HEIGHT_PREF_STRING, DEFAULT_HEIGHT);
|
||||||
refreshRate = prefs.getInt(REFRESH_RATE_PREF_STRING, DEFAULT_REFRESH_RATE);
|
refreshRate = prefs.getInt(REFRESH_RATE_PREF_STRING, DEFAULT_REFRESH_RATE);
|
||||||
|
bitrate = prefs.getInt(BITRATE_PREF_STRING, DEFAULT_BITRATE);
|
||||||
sh.setFixedSize(width, height);
|
sh.setFixedSize(width, height);
|
||||||
|
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
@@ -127,11 +145,12 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
|
|
||||||
// Start the connection
|
// Start the connection
|
||||||
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this,
|
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this,
|
||||||
new StreamConfiguration(width, height, refreshRate));
|
new StreamConfiguration(width, height, refreshRate, bitrate * 1000));
|
||||||
keybTranslator = new KeyboardTranslator(conn);
|
keybTranslator = new KeyboardTranslator(conn);
|
||||||
controllerHandler = new ControllerHandler(conn);
|
controllerHandler = new ControllerHandler(conn);
|
||||||
conn.start(PlatformBinding.getDeviceName(), sv.getHolder(), drFlags,
|
|
||||||
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer());
|
// The connection will be started when the surface gets created
|
||||||
|
sh.addCallback(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkDataConnection()
|
private void checkDataConnection()
|
||||||
@@ -351,7 +370,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
@Override
|
@Override
|
||||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
|
||||||
controllerHandler.handleMotionEvent(event);
|
if (controllerHandler.handleMotionEvent(event)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||||
{
|
{
|
||||||
@@ -418,6 +439,7 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true);
|
Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true);
|
||||||
conn.stop();
|
conn.stop();
|
||||||
|
connecting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,6 +450,7 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true);
|
Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true);
|
||||||
conn.stop();
|
conn.stop();
|
||||||
|
connected = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -436,6 +459,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
spinner.dismiss();
|
spinner.dismiss();
|
||||||
spinner = null;
|
spinner = null;
|
||||||
|
|
||||||
|
connecting = false;
|
||||||
|
connected = true;
|
||||||
|
|
||||||
hideSystemUi();
|
hideSystemUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,4 +484,25 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
if (!connected && !connecting) {
|
||||||
|
connecting = true;
|
||||||
|
conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
|
||||||
|
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
if (connected) {
|
||||||
|
conn.stop();
|
||||||
|
connected = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.limelight.binding.input;
|
package com.limelight.binding.input;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
import android.view.InputDevice;
|
import android.view.InputDevice;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
@@ -16,28 +18,148 @@ public class ControllerHandler {
|
|||||||
private short leftStickX = 0x0000;
|
private short leftStickX = 0x0000;
|
||||||
private short leftStickY = 0x0000;
|
private short leftStickY = 0x0000;
|
||||||
|
|
||||||
|
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
|
||||||
|
|
||||||
private NvConnection conn;
|
private NvConnection conn;
|
||||||
|
|
||||||
public ControllerHandler(NvConnection conn) {
|
public ControllerHandler(NvConnection conn) {
|
||||||
this.conn = conn;
|
this.conn = conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControllerMapping createMappingForDevice(InputDevice dev) {
|
||||||
|
ControllerMapping mapping = new ControllerMapping();
|
||||||
|
|
||||||
|
mapping.leftStickXAxis = MotionEvent.AXIS_X;
|
||||||
|
mapping.leftStickYAxis = MotionEvent.AXIS_Y;
|
||||||
|
|
||||||
|
InputDevice.MotionRange leftTriggerRange = dev.getMotionRange(MotionEvent.AXIS_LTRIGGER);
|
||||||
|
InputDevice.MotionRange rightTriggerRange = dev.getMotionRange(MotionEvent.AXIS_RTRIGGER);
|
||||||
|
InputDevice.MotionRange brakeRange = dev.getMotionRange(MotionEvent.AXIS_BRAKE);
|
||||||
|
InputDevice.MotionRange gasRange = dev.getMotionRange(MotionEvent.AXIS_GAS);
|
||||||
|
if (leftTriggerRange != null && rightTriggerRange != null)
|
||||||
|
{
|
||||||
|
// Some controllers use LTRIGGER and RTRIGGER (like Ouya)
|
||||||
|
mapping.leftTriggerAxis = MotionEvent.AXIS_LTRIGGER;
|
||||||
|
mapping.rightTriggerAxis = MotionEvent.AXIS_RTRIGGER;
|
||||||
|
}
|
||||||
|
else if (brakeRange != null && gasRange != null)
|
||||||
|
{
|
||||||
|
// Others use GAS and BRAKE (like Moga)
|
||||||
|
mapping.leftTriggerAxis = MotionEvent.AXIS_BRAKE;
|
||||||
|
mapping.rightTriggerAxis = MotionEvent.AXIS_GAS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InputDevice.MotionRange rxRange = dev.getMotionRange(MotionEvent.AXIS_RX);
|
||||||
|
InputDevice.MotionRange ryRange = dev.getMotionRange(MotionEvent.AXIS_RY);
|
||||||
|
if (rxRange != null && ryRange != null) {
|
||||||
|
String devName = dev.getName();
|
||||||
|
if (devName.contains("Xbox") || devName.contains("XBox") || devName.contains("X-Box")) {
|
||||||
|
// Xbox controllers use RX and RY for right stick
|
||||||
|
mapping.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||||
|
mapping.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||||
|
|
||||||
|
// Xbox controllers use Z and RZ for triggers
|
||||||
|
mapping.leftTriggerAxis = MotionEvent.AXIS_Z;
|
||||||
|
mapping.rightTriggerAxis = MotionEvent.AXIS_RZ;
|
||||||
|
mapping.triggersIdleNegative = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// DS4 controller uses RX and RY for triggers
|
||||||
|
mapping.leftTriggerAxis = MotionEvent.AXIS_RX;
|
||||||
|
mapping.rightTriggerAxis = MotionEvent.AXIS_RY;
|
||||||
|
mapping.triggersIdleNegative = true;
|
||||||
|
|
||||||
|
mapping.isDualShock4 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapping.rightStickXAxis == -1 && mapping.rightStickYAxis == -1) {
|
||||||
|
InputDevice.MotionRange zRange = dev.getMotionRange(MotionEvent.AXIS_Z);
|
||||||
|
InputDevice.MotionRange rzRange = dev.getMotionRange(MotionEvent.AXIS_RZ);
|
||||||
|
|
||||||
|
// Most other controllers use Z and RZ for the right stick
|
||||||
|
if (zRange != null && rzRange != null) {
|
||||||
|
mapping.rightStickXAxis = MotionEvent.AXIS_Z;
|
||||||
|
mapping.rightStickYAxis = MotionEvent.AXIS_RZ;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
InputDevice.MotionRange rxRange = dev.getMotionRange(MotionEvent.AXIS_RX);
|
||||||
|
InputDevice.MotionRange ryRange = dev.getMotionRange(MotionEvent.AXIS_RY);
|
||||||
|
|
||||||
|
// Try RX and RY now
|
||||||
|
if (rxRange != null && ryRange != null) {
|
||||||
|
mapping.rightStickXAxis = MotionEvent.AXIS_RX;
|
||||||
|
mapping.rightStickYAxis = MotionEvent.AXIS_RY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some devices have "hats" for d-pads
|
||||||
|
InputDevice.MotionRange hatXRange = dev.getMotionRange(MotionEvent.AXIS_HAT_X);
|
||||||
|
InputDevice.MotionRange hatYRange = dev.getMotionRange(MotionEvent.AXIS_HAT_Y);
|
||||||
|
if (hatXRange != null && hatYRange != null) {
|
||||||
|
mapping.hatXAxis = MotionEvent.AXIS_HAT_X;
|
||||||
|
mapping.hatYAxis = MotionEvent.AXIS_HAT_Y;
|
||||||
|
|
||||||
|
mapping.hatXDeadzone = hatXRange.getFlat();
|
||||||
|
mapping.hatYDeadzone = hatYRange.getFlat();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||||
|
InputDevice.MotionRange lsXRange = dev.getMotionRange(mapping.leftStickXAxis);
|
||||||
|
InputDevice.MotionRange lsYRange = dev.getMotionRange(mapping.leftStickYAxis);
|
||||||
|
if (lsXRange != null) {
|
||||||
|
mapping.leftStickXAxisDeadzone = lsXRange.getFlat();
|
||||||
|
}
|
||||||
|
if (lsYRange != null) {
|
||||||
|
mapping.leftStickYAxisDeadzone = lsYRange.getFlat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
|
||||||
|
InputDevice.MotionRange rsXRange = dev.getMotionRange(mapping.rightStickXAxis);
|
||||||
|
InputDevice.MotionRange rsYRange = dev.getMotionRange(mapping.rightStickYAxis);
|
||||||
|
if (rsXRange != null) {
|
||||||
|
mapping.rightStickXAxisDeadzone = rsXRange.getFlat();
|
||||||
|
}
|
||||||
|
if (rsYRange != null) {
|
||||||
|
mapping.rightStickYAxisDeadzone = rsYRange.getFlat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ControllerMapping getMappingForDevice(InputDevice dev) {
|
||||||
|
// Unknown devices can't be handled
|
||||||
|
if (dev == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String descriptor = dev.getDescriptor();
|
||||||
|
|
||||||
|
// Return the existing mapping if it exists
|
||||||
|
ControllerMapping mapping = mappings.get(descriptor);
|
||||||
|
if (mapping != null) {
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create a new mapping
|
||||||
|
mapping = createMappingForDevice(dev);
|
||||||
|
mappings.put(descriptor, mapping);
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
private void sendControllerInputPacket() {
|
private void sendControllerInputPacket() {
|
||||||
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
|
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
|
||||||
leftStickX, leftStickY, rightStickX, rightStickY);
|
leftStickX, leftStickY, rightStickX, rightStickY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isDualShock4(InputDevice dev) {
|
private int handleRemapping(ControllerMapping mapping, int keyCode) {
|
||||||
return (dev.getMotionRange(MotionEvent.AXIS_RX) != null &&
|
if (mapping.isDualShock4) {
|
||||||
dev.getMotionRange(MotionEvent.AXIS_RY) != null &&
|
|
||||||
dev.getMotionRange(MotionEvent.AXIS_HAT_X) != null &&
|
|
||||||
dev.getMotionRange(MotionEvent.AXIS_HAT_Y) != null &&
|
|
||||||
dev.getMotionRange(MotionEvent.AXIS_RZ) != null &&
|
|
||||||
dev.getMotionRange(MotionEvent.AXIS_RY) != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int handleRemapping(InputDevice dev, int keyCode) {
|
|
||||||
if (isDualShock4(dev)) {
|
|
||||||
switch (keyCode) {
|
switch (keyCode) {
|
||||||
case KeyEvent.KEYCODE_BUTTON_Y:
|
case KeyEvent.KEYCODE_BUTTON_Y:
|
||||||
return KeyEvent.KEYCODE_BUTTON_L1;
|
return KeyEvent.KEYCODE_BUTTON_L1;
|
||||||
@@ -73,8 +195,12 @@ public class ControllerHandler {
|
|||||||
case KeyEvent.KEYCODE_BUTTON_R1:
|
case KeyEvent.KEYCODE_BUTTON_R1:
|
||||||
case KeyEvent.KEYCODE_BUTTON_L1:
|
case KeyEvent.KEYCODE_BUTTON_L1:
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
// These are duplicate dpad events
|
}
|
||||||
|
|
||||||
|
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
|
||||||
|
switch (keyCode) {
|
||||||
|
// These are duplicate dpad events for hat input
|
||||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||||
@@ -88,106 +214,86 @@ public class ControllerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleMotionEvent(MotionEvent event) {
|
public boolean handleMotionEvent(MotionEvent event) {
|
||||||
InputDevice dev = event.getDevice();
|
ControllerMapping mapping = getMappingForDevice(event.getDevice());
|
||||||
if (dev == null) {
|
if (mapping == null) {
|
||||||
System.err.println("Unknown device");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float LS_X = event.getAxisValue(MotionEvent.AXIS_X);
|
// Handle left stick events outside of the deadzone
|
||||||
float LS_Y = event.getAxisValue(MotionEvent.AXIS_Y);
|
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
|
||||||
|
float LS_X = event.getAxisValue(mapping.leftStickXAxis);
|
||||||
float RS_X, RS_Y, L2, R2;
|
float LS_Y = event.getAxisValue(mapping.leftStickYAxis);
|
||||||
|
if (LS_X >= -mapping.leftStickXAxisDeadzone && LS_X <= mapping.leftStickXAxisDeadzone) {
|
||||||
InputDevice.MotionRange leftTriggerRange = dev.getMotionRange(MotionEvent.AXIS_LTRIGGER);
|
LS_X = 0;
|
||||||
InputDevice.MotionRange rightTriggerRange = dev.getMotionRange(MotionEvent.AXIS_RTRIGGER);
|
}
|
||||||
if (leftTriggerRange != null && rightTriggerRange != null)
|
if (LS_Y >= -mapping.leftStickYAxisDeadzone && LS_Y <= mapping.leftStickYAxisDeadzone) {
|
||||||
{
|
LS_Y = 0;
|
||||||
// Ouya controller
|
}
|
||||||
L2 = event.getAxisValue(MotionEvent.AXIS_LTRIGGER);
|
leftStickX = (short)Math.round(LS_X * 0x7FFF);
|
||||||
R2 = event.getAxisValue(MotionEvent.AXIS_RTRIGGER);
|
leftStickY = (short)Math.round(-LS_Y * 0x7FFF);
|
||||||
RS_X = event.getAxisValue(MotionEvent.AXIS_Z);
|
|
||||||
RS_Y = event.getAxisValue(MotionEvent.AXIS_RZ);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
// Handle right stick events outside of the deadzone
|
||||||
InputDevice.MotionRange brakeRange = dev.getMotionRange(MotionEvent.AXIS_BRAKE);
|
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
|
||||||
InputDevice.MotionRange gasRange = dev.getMotionRange(MotionEvent.AXIS_GAS);
|
float RS_X = event.getAxisValue(mapping.rightStickXAxis);
|
||||||
InputDevice.MotionRange rxRange = dev.getMotionRange(MotionEvent.AXIS_RX);
|
float RS_Y = event.getAxisValue(mapping.rightStickYAxis);
|
||||||
InputDevice.MotionRange ryRange = dev.getMotionRange(MotionEvent.AXIS_RY);
|
if (RS_X >= -mapping.rightStickXAxisDeadzone && RS_X <= mapping.rightStickXAxisDeadzone) {
|
||||||
if (brakeRange != null && gasRange != null)
|
RS_X = 0;
|
||||||
{
|
|
||||||
// Moga controller
|
|
||||||
RS_X = event.getAxisValue(MotionEvent.AXIS_Z);
|
|
||||||
RS_Y = event.getAxisValue(MotionEvent.AXIS_RZ);
|
|
||||||
L2 = event.getAxisValue(MotionEvent.AXIS_BRAKE);
|
|
||||||
R2 = event.getAxisValue(MotionEvent.AXIS_GAS);
|
|
||||||
}
|
}
|
||||||
else if (rxRange != null && ryRange != null)
|
if (RS_Y >= -mapping.rightStickYAxisDeadzone && RS_Y <= mapping.rightStickYAxisDeadzone) {
|
||||||
{
|
RS_Y = 0;
|
||||||
// DS4 controller
|
|
||||||
RS_X = event.getAxisValue(MotionEvent.AXIS_Z);
|
|
||||||
RS_Y = event.getAxisValue(MotionEvent.AXIS_RZ);
|
|
||||||
L2 = (event.getAxisValue(MotionEvent.AXIS_RX) + 1) / 2;
|
|
||||||
R2 = (event.getAxisValue(MotionEvent.AXIS_RY) + 1) / 2;
|
|
||||||
}
|
}
|
||||||
else
|
rightStickX = (short)Math.round(RS_X * 0x7FFF);
|
||||||
{
|
rightStickY = (short)Math.round(-RS_Y * 0x7FFF);
|
||||||
// Xbox controller
|
}
|
||||||
RS_X = event.getAxisValue(MotionEvent.AXIS_RX);
|
|
||||||
RS_Y = event.getAxisValue(MotionEvent.AXIS_RY);
|
// Handle controllers with analog triggers
|
||||||
L2 = (event.getAxisValue(MotionEvent.AXIS_Z) + 1) / 2;
|
if (mapping.leftTriggerAxis != -1 && mapping.rightTriggerAxis != -1) {
|
||||||
R2 = (event.getAxisValue(MotionEvent.AXIS_RZ) + 1) / 2;
|
float L2 = event.getAxisValue(mapping.leftTriggerAxis);
|
||||||
|
float R2 = event.getAxisValue(mapping.rightTriggerAxis);
|
||||||
|
|
||||||
|
if (mapping.triggersIdleNegative) {
|
||||||
|
L2 = (L2 + 1) / 2;
|
||||||
|
R2 = (R2 + 1) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leftTrigger = (byte)Math.round(L2 * 0xFF);
|
||||||
|
rightTrigger = (byte)Math.round(R2 * 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
InputDevice.MotionRange hatXRange = dev.getMotionRange(MotionEvent.AXIS_HAT_X);
|
// Hats emulate d-pad events
|
||||||
InputDevice.MotionRange hatYRange = dev.getMotionRange(MotionEvent.AXIS_HAT_Y);
|
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
|
||||||
if (hatXRange != null && hatYRange != null)
|
float hatX = event.getAxisValue(MotionEvent.AXIS_HAT_X);
|
||||||
{
|
float hatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
|
||||||
// Xbox and DS4 D-pad
|
|
||||||
float hatX, hatY;
|
|
||||||
|
|
||||||
hatX = event.getAxisValue(MotionEvent.AXIS_HAT_X);
|
|
||||||
hatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
|
|
||||||
|
|
||||||
inputMap &= ~(ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG);
|
inputMap &= ~(ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG);
|
||||||
inputMap &= ~(ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG);
|
if (hatX < -(0.5 + mapping.hatXDeadzone)) {
|
||||||
if (hatX < -0.5) {
|
|
||||||
inputMap |= ControllerPacket.LEFT_FLAG;
|
inputMap |= ControllerPacket.LEFT_FLAG;
|
||||||
}
|
}
|
||||||
if (hatX > 0.5) {
|
else if (hatX > (0.5 + mapping.hatXDeadzone)) {
|
||||||
inputMap |= ControllerPacket.RIGHT_FLAG;
|
inputMap |= ControllerPacket.RIGHT_FLAG;
|
||||||
}
|
}
|
||||||
if (hatY < -0.5) {
|
|
||||||
|
inputMap &= ~(ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG);
|
||||||
|
if (hatY < -(0.5 + mapping.hatYDeadzone)) {
|
||||||
inputMap |= ControllerPacket.UP_FLAG;
|
inputMap |= ControllerPacket.UP_FLAG;
|
||||||
}
|
}
|
||||||
if (hatY > 0.5) {
|
else if (hatY > (0.5 + mapping.hatYDeadzone)) {
|
||||||
inputMap |= ControllerPacket.DOWN_FLAG;
|
inputMap |= ControllerPacket.DOWN_FLAG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leftStickX = (short)Math.round(LS_X * 0x7FFF);
|
|
||||||
leftStickY = (short)Math.round(-LS_Y * 0x7FFF);
|
|
||||||
|
|
||||||
rightStickX = (short)Math.round(RS_X * 0x7FFF);
|
|
||||||
rightStickY = (short)Math.round(-RS_Y * 0x7FFF);
|
|
||||||
|
|
||||||
leftTrigger = (byte)Math.round(L2 * 0xFF);
|
|
||||||
rightTrigger = (byte)Math.round(R2 * 0xFF);
|
|
||||||
|
|
||||||
sendControllerInputPacket();
|
sendControllerInputPacket();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleButtonUp(int keyCode, KeyEvent event) {
|
public boolean handleButtonUp(int keyCode, KeyEvent event) {
|
||||||
InputDevice dev = event.getDevice();
|
ControllerMapping mapping = getMappingForDevice(event.getDevice());
|
||||||
if (dev == null) {
|
if (mapping == null) {
|
||||||
System.err.println("Unknown device");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyCode = handleRemapping(dev, keyCode);
|
keyCode = handleRemapping(mapping, keyCode);
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -219,6 +325,7 @@ public class ControllerHandler {
|
|||||||
case KeyEvent.KEYCODE_BUTTON_B:
|
case KeyEvent.KEYCODE_BUTTON_B:
|
||||||
inputMap &= ~ControllerPacket.B_FLAG;
|
inputMap &= ~ControllerPacket.B_FLAG;
|
||||||
break;
|
break;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||||
case KeyEvent.KEYCODE_BUTTON_A:
|
case KeyEvent.KEYCODE_BUTTON_A:
|
||||||
inputMap &= ~ControllerPacket.A_FLAG;
|
inputMap &= ~ControllerPacket.A_FLAG;
|
||||||
break;
|
break;
|
||||||
@@ -240,6 +347,12 @@ public class ControllerHandler {
|
|||||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||||
inputMap &= ~ControllerPacket.RS_CLK_FLAG;
|
inputMap &= ~ControllerPacket.RS_CLK_FLAG;
|
||||||
break;
|
break;
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||||
|
leftTrigger = 0;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||||
|
rightTrigger = 0;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -256,13 +369,12 @@ public class ControllerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean handleButtonDown(int keyCode, KeyEvent event) {
|
public boolean handleButtonDown(int keyCode, KeyEvent event) {
|
||||||
InputDevice dev = event.getDevice();
|
ControllerMapping mapping = getMappingForDevice(event.getDevice());
|
||||||
if (dev == null) {
|
if (mapping == null) {
|
||||||
System.err.println("Unknown device");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyCode = handleRemapping(dev, keyCode);
|
keyCode = handleRemapping(mapping, keyCode);
|
||||||
if (keyCode == 0) {
|
if (keyCode == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -294,6 +406,7 @@ public class ControllerHandler {
|
|||||||
case KeyEvent.KEYCODE_BUTTON_B:
|
case KeyEvent.KEYCODE_BUTTON_B:
|
||||||
inputMap |= ControllerPacket.B_FLAG;
|
inputMap |= ControllerPacket.B_FLAG;
|
||||||
break;
|
break;
|
||||||
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||||
case KeyEvent.KEYCODE_BUTTON_A:
|
case KeyEvent.KEYCODE_BUTTON_A:
|
||||||
inputMap |= ControllerPacket.A_FLAG;
|
inputMap |= ControllerPacket.A_FLAG;
|
||||||
break;
|
break;
|
||||||
@@ -315,6 +428,12 @@ public class ControllerHandler {
|
|||||||
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
case KeyEvent.KEYCODE_BUTTON_THUMBR:
|
||||||
inputMap |= ControllerPacket.RS_CLK_FLAG;
|
inputMap |= ControllerPacket.RS_CLK_FLAG;
|
||||||
break;
|
break;
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_L2:
|
||||||
|
leftTrigger = (byte)0xFF;
|
||||||
|
break;
|
||||||
|
case KeyEvent.KEYCODE_BUTTON_R2:
|
||||||
|
rightTrigger = (byte)0xFF;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -330,4 +449,29 @@ public class ControllerHandler {
|
|||||||
sendControllerInputPacket();
|
sendControllerInputPacket();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ControllerMapping {
|
||||||
|
public int leftStickXAxis = -1;
|
||||||
|
public float leftStickXAxisDeadzone;
|
||||||
|
|
||||||
|
public int leftStickYAxis = -1;
|
||||||
|
public float leftStickYAxisDeadzone;
|
||||||
|
|
||||||
|
public int rightStickXAxis = -1;
|
||||||
|
public float rightStickXAxisDeadzone;
|
||||||
|
|
||||||
|
public int rightStickYAxis = -1;
|
||||||
|
public float rightStickYAxisDeadzone;
|
||||||
|
|
||||||
|
public int leftTriggerAxis = -1;
|
||||||
|
public int rightTriggerAxis = -1;
|
||||||
|
public boolean triggersIdleNegative;
|
||||||
|
|
||||||
|
public int hatXAxis = -1;
|
||||||
|
public int hatYAxis = -1;
|
||||||
|
public float hatXDeadzone;
|
||||||
|
public float hatYDeadzone;
|
||||||
|
|
||||||
|
public boolean isDualShock4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,23 +24,27 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
private MediaCodec videoDecoder;
|
private MediaCodec videoDecoder;
|
||||||
private Thread rendererThread;
|
private Thread rendererThread;
|
||||||
private int redrawRate;
|
private int redrawRate;
|
||||||
private boolean needsSpsFixup;
|
private boolean needsSpsBitstreamFixup;
|
||||||
|
private boolean needsSpsNumRefFixup;
|
||||||
private boolean fastInputQueueing;
|
private boolean fastInputQueueing;
|
||||||
|
|
||||||
public static final List<String> blacklistedDecoderPrefixes;
|
public static final List<String> blacklistedDecoderPrefixes;
|
||||||
public static final List<String> spsFixupDecoderPrefixes;
|
public static final List<String> spsFixupBitsreamFixupDecoderPrefixes;
|
||||||
|
public static final List<String> spsFixupNumRefFixupDecoderPrefixes;
|
||||||
public static final List<String> fastInputQueueingPrefixes;
|
public static final List<String> fastInputQueueingPrefixes;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
blacklistedDecoderPrefixes = new LinkedList<String>();
|
blacklistedDecoderPrefixes = new LinkedList<String>();
|
||||||
|
|
||||||
// TI's decoder technically supports high profile but doesn't work for some reason
|
// Nothing here right now :)
|
||||||
blacklistedDecoderPrefixes.add("omx.TI");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
spsFixupDecoderPrefixes = new LinkedList<String>();
|
spsFixupBitsreamFixupDecoderPrefixes = new LinkedList<String>();
|
||||||
spsFixupDecoderPrefixes.add("omx.nvidia");
|
spsFixupBitsreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||||
|
|
||||||
|
spsFixupNumRefFixupDecoderPrefixes = new LinkedList<String>();
|
||||||
|
spsFixupNumRefFixupDecoderPrefixes.add("omx.TI");
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -128,9 +132,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
MediaCodecInfo safeDecoder = findSafeDecoder();
|
MediaCodecInfo safeDecoder = findSafeDecoder();
|
||||||
if (safeDecoder != null) {
|
if (safeDecoder != null) {
|
||||||
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());
|
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());
|
||||||
needsSpsFixup = isDecoderInList(spsFixupDecoderPrefixes, safeDecoder.getName());
|
needsSpsBitstreamFixup = isDecoderInList(spsFixupBitsreamFixupDecoderPrefixes, safeDecoder.getName());
|
||||||
if (needsSpsFixup) {
|
needsSpsNumRefFixup = isDecoderInList(spsFixupNumRefFixupDecoderPrefixes, safeDecoder.getName());
|
||||||
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS fixup");
|
if (needsSpsBitstreamFixup) {
|
||||||
|
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS bitstream restrictions fixup");
|
||||||
|
}
|
||||||
|
if (needsSpsNumRefFixup) {
|
||||||
|
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS ref num fixup");
|
||||||
}
|
}
|
||||||
fastInputQueueing = isDecoderInList(fastInputQueueingPrefixes, safeDecoder.getName());
|
fastInputQueueing = isDecoderInList(fastInputQueueingPrefixes, safeDecoder.getName());
|
||||||
if (fastInputQueueing) {
|
if (fastInputQueueing) {
|
||||||
@@ -139,7 +147,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
videoDecoder = MediaCodec.createDecoderByType("video/avc");
|
videoDecoder = MediaCodec.createDecoderByType("video/avc");
|
||||||
needsSpsFixup = false;
|
needsSpsBitstreamFixup = false;
|
||||||
|
needsSpsNumRefFixup = false;
|
||||||
fastInputQueueing = false;
|
fastInputQueueing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,39 +265,49 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
// Clear old input data
|
// Clear old input data
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
if (needsSpsBitstreamFixup || needsSpsNumRefFixup) {
|
||||||
// or max_dec_frame_buffering which increases decoding latency on Tegra.
|
|
||||||
// We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it.
|
|
||||||
if (needsSpsFixup) {
|
|
||||||
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
|
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
|
||||||
// Check for SPS NALU type
|
|
||||||
if (header.data[header.offset+4] == 0x67) {
|
if (header.data[header.offset+4] == 0x67) {
|
||||||
int spsLength;
|
// TI OMAP4 requires a reference frame count of 1 to decode successfully
|
||||||
|
if (needsSpsNumRefFixup) {
|
||||||
|
LimeLog.info("Fixing up num ref frames");
|
||||||
|
this.replace(header, 80, 9, new byte[] {0x40}, 3);
|
||||||
|
}
|
||||||
|
|
||||||
switch (header.length) {
|
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
||||||
case 26:
|
// or max_dec_frame_buffering which increases decoding latency on Tegra.
|
||||||
LimeLog.info("Modifying SPS (26)");
|
// We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it.
|
||||||
buf.put(header.data, header.offset, 24);
|
int spsLength;
|
||||||
buf.put((byte) 0x11);
|
if (needsSpsBitstreamFixup) {
|
||||||
buf.put((byte) 0xe3);
|
switch (header.length) {
|
||||||
buf.put((byte) 0x06);
|
case 26:
|
||||||
buf.put((byte) 0x50);
|
LimeLog.info("Adding bitstream restrictions to SPS (26)");
|
||||||
spsLength = header.length + 2;
|
buf.put(header.data, header.offset, 24);
|
||||||
break;
|
buf.put((byte) 0x11);
|
||||||
case 27:
|
buf.put((byte) 0xe3);
|
||||||
LimeLog.info("Modifying SPS (27)");
|
buf.put((byte) 0x06);
|
||||||
buf.put(header.data, header.offset, 25);
|
buf.put((byte) 0x50);
|
||||||
buf.put((byte) 0x04);
|
spsLength = header.length + 2;
|
||||||
buf.put((byte) 0x78);
|
break;
|
||||||
buf.put((byte) 0xc1);
|
case 27:
|
||||||
buf.put((byte) 0x94);
|
LimeLog.info("Adding bitstream restrictions to SPS (27)");
|
||||||
spsLength = header.length + 2;
|
buf.put(header.data, header.offset, 25);
|
||||||
break;
|
buf.put((byte) 0x04);
|
||||||
default:
|
buf.put((byte) 0x78);
|
||||||
LimeLog.warning("Unknown SPS of length "+header.length);
|
buf.put((byte) 0xc1);
|
||||||
|
buf.put((byte) 0x94);
|
||||||
|
spsLength = header.length + 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LimeLog.warning("Unknown SPS of length "+header.length);
|
||||||
|
buf.put(header.data, header.offset, header.length);
|
||||||
|
spsLength = header.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
buf.put(header.data, header.offset, header.length);
|
buf.put(header.data, header.offset, header.length);
|
||||||
spsLength = header.length;
|
spsLength = header.length;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
videoDecoder.queueInputBuffer(inputIndex,
|
videoDecoder.queueInputBuffer(inputIndex,
|
||||||
@@ -316,4 +335,83 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
|||||||
public int getCapabilities() {
|
public int getCapabilities() {
|
||||||
return fastInputQueueing ? VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT : 0;
|
return fastInputQueueing ? VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace bits in array
|
||||||
|
* @param source array in which bits should be replaced
|
||||||
|
* @param srcOffset offset in bits where replacement should take place
|
||||||
|
* @param srcLength length in bits of data that should be replaced
|
||||||
|
* @param data data array with the the replacement data
|
||||||
|
* @param dataLength length of replacement data in bits
|
||||||
|
*/
|
||||||
|
public void replace(ByteBufferDescriptor source, int srcOffset, int srcLength, byte[] data, int dataLength) {
|
||||||
|
//Add 7 to always round up
|
||||||
|
int length = (source.length*8-srcLength+dataLength+7)/8;
|
||||||
|
|
||||||
|
int bitOffset = srcOffset%8;
|
||||||
|
int byteOffset = srcOffset/8;
|
||||||
|
|
||||||
|
byte dest[] = null;
|
||||||
|
int offset = 0;
|
||||||
|
if (length>source.length) {
|
||||||
|
dest = new byte[length];
|
||||||
|
|
||||||
|
//Copy the first bytes
|
||||||
|
System.arraycopy(source.data, source.offset, dest, offset, byteOffset);
|
||||||
|
} else {
|
||||||
|
dest = source.data;
|
||||||
|
offset = source.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int byteLength = (bitOffset+dataLength+7)/8;
|
||||||
|
int bitTrailing = 8 - (srcOffset+dataLength) % 8;
|
||||||
|
for (int i=0;i<byteLength;i++) {
|
||||||
|
byte result = 0;
|
||||||
|
if (i != 0)
|
||||||
|
result = (byte) (data[i-1] << 8-bitOffset);
|
||||||
|
else if (bitOffset > 0)
|
||||||
|
result = (byte) (source.data[byteOffset+source.offset] & (0xFF << 8-bitOffset));
|
||||||
|
|
||||||
|
if (i == 0 || i != byteLength-1) {
|
||||||
|
byte moved = (byte) ((data[i]&0xFF) >>> bitOffset);
|
||||||
|
result |= moved;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == byteLength-1 && bitTrailing > 0) {
|
||||||
|
int sourceOffset = srcOffset+srcLength/8;
|
||||||
|
int bitMove = (dataLength-srcLength)%8;
|
||||||
|
if (bitMove<0) {
|
||||||
|
result |= (byte) (source.data[sourceOffset+source.offset] << -bitMove & (0xFF >>> bitTrailing));
|
||||||
|
result |= (byte) (source.data[sourceOffset+1+source.offset] << -bitMove & (0xFF >>> 8+bitMove));
|
||||||
|
} else {
|
||||||
|
byte moved = (byte) ((source.data[sourceOffset+source.offset]&0xFF) >>> bitOffset);
|
||||||
|
result |= moved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dest[i+byteOffset+offset] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Source offset
|
||||||
|
byteOffset += srcLength/8;
|
||||||
|
bitOffset = (srcOffset+dataLength-srcLength)%8;
|
||||||
|
|
||||||
|
//Offset in destination
|
||||||
|
int destOffset = (srcOffset+dataLength)/8;
|
||||||
|
|
||||||
|
for (int i=1;i<source.length-byteOffset;i++) {
|
||||||
|
int diff = destOffset >= byteOffset-1?i:source.length-byteOffset-i;
|
||||||
|
|
||||||
|
byte result = 0;
|
||||||
|
result = (byte) (source.data[byteOffset+diff-1+source.offset] << 8-bitOffset);
|
||||||
|
byte moved = (byte) ((source.data[byteOffset+diff+source.offset]&0xFF) >>> bitOffset);
|
||||||
|
result ^= moved;
|
||||||
|
|
||||||
|
dest[diff+destOffset+offset] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.data = dest;
|
||||||
|
source.offset = offset;
|
||||||
|
source.length = length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user