Compare commits

...

40 Commits

Author SHA1 Message Date
Cameron Gutman 51a630995a Update common for GFE 2.2.2+ support 2015-01-22 15:29:58 -05:00
Cameron Gutman 3a74f0726c Updated libs 2015-01-22 15:29:46 -05:00
Cameron Gutman efa6c7bba0 Fix build of root package 2015-01-22 15:29:16 -05:00
Michelle Bergeron b8141542f8 Add wiki link 2015-01-17 18:44:31 -08:00
Cameron Gutman 8fc9a90207 Switch back to Maven repos of ion and androidasync packages 2014-12-13 20:41:32 -08:00
Cameron Gutman 13d707d98d Use final release of Gradle 1.0 2014-12-09 01:13:13 -08:00
Cameron Gutman aae0ff6e7a Migrate the project to Android Studio 1.0 RC4 2014-12-08 00:12:37 -08:00
Cameron Gutman 69c7b5a0d5 Update version 2014-12-03 20:52:52 -08:00
Cameron Gutman d1ad3115fa Add remote to stream config 2014-12-02 00:55:46 -08:00
Cameron Gutman 770af402a4 Reduce default 1080p60 bitrate to 20 Mbps 2014-12-02 00:55:31 -08:00
Cameron Gutman 3236c0b93a Lower the level_idc of the SPS to the minimum required for streaming at a given resolution 2014-12-01 22:58:52 -08:00
Cameron Gutman 51aacc3f38 Remove extra newlines 2014-12-01 22:39:17 -08:00
Cameron Gutman 397c6f46f9 Fix a security issue which caused input devices to remain world readable after the stream is ended 2014-12-01 22:29:16 -08:00
Cameron Gutman d00f78f859 Revert square to circle analog work since it seems to be handled correctly already 2014-12-01 22:27:02 -08:00
Cameron Gutman 29fec2e0de Add initial support for rooted devices running Lollipop with SELinux set to enforcing. This should really be improved in the future since we're modifying policies for untrusted_app. 2014-12-01 22:26:35 -08:00
Cameron Gutman 88d28665ef Attempt to fix IndexOutOfBoundsException (index 0 size 0) reported by a couple users 2014-11-30 18:34:34 -06:00
Cameron Gutman de1f4da258 Apply the square to circle plane mapping before evaluating the deadzone. Cleanup some dead code. 2014-11-30 15:52:49 -06:00
Cameron Gutman 7985be57ab Translate the analog stick values of controllers with "square" analog stick planes (DS3, DS4, and others) to the circular plane that XInput programs expect 2014-11-30 15:35:20 -06:00
Cameron Gutman a835e7aaa2 Increase DS4 controller responsiveness by ignoring historical values again 2014-11-30 12:34:30 -06:00
Cameron Gutman c4dc5eb9e1 Update common for faster IDR recovery 2014-11-28 22:17:42 -06:00
Cameron Gutman db758f386e Comment out unused variable 2014-11-28 22:16:46 -06:00
Cameron Gutman 3fb3eefa94 Fix Nyko Playpad input issue 2014-11-28 22:16:33 -06:00
Cameron Gutman 2d6c756e70 Always consider a PC to be remote if localIP == remoteIP 2014-11-27 21:56:20 -06:00
Cameron Gutman 03e965d449 Merge pull request #34 from Ansa89/italian-translation
Italian translation: better wording
2014-11-27 20:35:57 -06:00
Cameron Gutman 34f72544d8 Increment version 2014-11-25 14:56:40 -08:00
Cameron Gutman d839ea9781 Increase deadzone on triggers to Xinput defaults and add special handling of the Nexus Player Controller and Nexus Remote 2014-11-25 14:54:36 -08:00
Cameron Gutman 2b7f13fdbb Increase max frame time to improve accuracy of latency counter 2014-11-25 13:34:00 -08:00
Cameron Gutman 7557a3a4ae Don't capture the back button on remotes 2014-11-25 11:16:47 -08:00
Cameron Gutman fcecba484f Fix a crash caught by Monkey 2014-11-25 02:05:24 -08:00
Cameron Gutman fa85a0a0bd Improve CPU decoder frame latency when rendering speed is less than decoding speed 2014-11-25 02:04:51 -08:00
Cameron Gutman dc64bfeba2 Slightly reduce max packet size in an attempt to cut packet losses 2014-11-25 01:05:55 -08:00
Cameron Gutman 871b73c48d Fix PC duplication issue when multiple machines report the same remote IP address 2014-11-24 20:10:02 -08:00
Cameron Gutman 5dcff91d27 Only grab Fire TV remotes if a gamepad isn't attached 2014-11-24 18:43:08 -08:00
Cameron Gutman 0041fc1dab Fix broken de-duplication of computers 2014-11-24 18:25:58 -08:00
Cameron Gutman 314242ab08 Update to Ion with fixes for SSLContext and self-signed certificates 2014-11-24 18:10:23 -08:00
Cameron Gutman 09e8ddfd74 Use the bitstream restrictions fixup on Broadcom VideoCore IV devices 2014-11-24 18:03:47 -08:00
Ansa89 4cea483a87 Italian translation: better wording 2014-11-24 11:53:18 +01:00
Cameron Gutman 444c4602c1 Update libraries. Seems to improve image caching behavior with Ion. 2014-11-23 23:39:20 -08:00
Cameron Gutman 5b6eac7140 Update build.gradle for re-release 2014-11-23 02:07:03 -08:00
Cameron Gutman 7cdd184197 Fix null pointer exception on ATV emulator 2014-11-23 02:06:49 -08:00
24 changed files with 363 additions and 169 deletions
+2
View File
@@ -8,6 +8,8 @@ whether in your own home or over the internet.
[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 and Windows Phone](https://github.com/limelight-stream/limelight-windows) are also in development.
Check our [wiki](https://github.com/limelight-stream/limelight-android/wiki) for more detailed information or a troubleshooting guide.
##Features
* Streams any of your games from your PC to your Android device
+8 -6
View File
@@ -31,6 +31,7 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/nonRoot/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/nonRoot/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRootDebug/assets" type="java-resource" />
@@ -43,6 +44,7 @@
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/test/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/test/nonRoot/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/test/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/test/nonRoot/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/nonRoot/assets" type="java-resource" />
@@ -101,18 +103,18 @@
</content>
<orderEntry type="jdk" jdkName="Android API 21 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="okhttp-2.1.0-RC1" level="project" />
<orderEntry type="library" exported="" name="bcprov-jdk15on-1.51" level="project" />
<orderEntry type="library" exported="" name="androidasync-2.0.5" level="project" />
<orderEntry type="library" exported="" name="gson-2.3.1" level="project" />
<orderEntry type="library" exported="" name="androidasync-1.3.7" level="project" />
<orderEntry type="library" exported="" name="support-v4-r7" level="project" />
<orderEntry type="library" exported="" name="jmdns-fixed" level="project" />
<orderEntry type="library" exported="" name="jcodec-0.1.6-3" level="project" />
<orderEntry type="library" exported="" name="bcpkix-jdk15on-1.51" level="project" />
<orderEntry type="library" exported="" name="tinyrtsp" level="project" />
<orderEntry type="library" exported="" name="limelight-common" level="project" />
<orderEntry type="library" exported="" name="support-v4-r6" level="project" />
<orderEntry type="library" exported="" name="okio-1.0.1" level="project" />
<orderEntry type="library" exported="" name="ion-1.3.7" level="project" />
<orderEntry type="library" exported="" name="ion-2.0.5" level="project" />
<orderEntry type="library" exported="" name="okhttp-2.2.0" level="project" />
<orderEntry type="library" exported="" name="jcodec-0.1.9" level="project" />
<orderEntry type="library" exported="" name="okio-1.2.0" level="project" />
</component>
</module>
+16 -11
View File
@@ -5,14 +5,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "21.0.2"
buildToolsVersion "21.1.2"
defaultConfig {
minSdkVersion 16
targetSdkVersion 21
versionName "3.0"
versionCode = 45
versionName "3.0.3"
versionCode = 49
}
productFlavors {
@@ -27,7 +27,7 @@ android {
buildTypes {
release {
runProguard false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
@@ -62,13 +62,18 @@ android {
}
dependencies {
compile group: 'org.jcodec', name: 'jcodec', version: '0.1.6-3'
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.51'
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.51'
compile group: 'com.google.android', name: 'support-v4', version:'r6'
compile group: 'com.koushikdutta.ion', name: 'ion', version:'1.3.7'
compile group: 'com.squareup.okhttp', name: 'okhttp', version:'2.1.0-RC1'
compile group: 'com.squareup.okio', name:'okio', version:'1.0.1'
compile group: 'org.jcodec', name: 'jcodec', version: '+'
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '+'
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '+'
compile group: 'com.google.android', name: 'support-v4', version:'+'
compile group: 'com.koushikdutta.ion', name: 'ion', version:'+'
compile group: 'com.google.code.gson', name: 'gson', version:'+'
compile group: 'com.squareup.okhttp', name: 'okhttp', version:'+'
compile group: 'com.squareup.okio', name:'okio', version:'+'
compile files('libs/jmdns-fixed.jar')
compile files('libs/limelight-common.jar')
compile files('libs/tinyrtsp.jar')
Binary file not shown.
+2 -1
View File
@@ -170,7 +170,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
.enableAdaptiveResolution((decoderRenderer.getCapabilities() &
VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION) != 0)
.enableLocalAudioPlayback(prefConfig.playHostAudio)
.setMaxPacketSize(remote ? 1024 : 1392)
.setMaxPacketSize(remote ? 1024 : 1292)
.setRemote(remote)
.build();
// Initialize the connection
@@ -527,6 +527,7 @@ public class PcView extends Activity {
if (details.equals(computer.details)) {
pcGridAdapter.removeComputer(computer);
pcGridAdapter.notifyDataSetChanged();
break;
}
}
@@ -40,13 +40,13 @@ public class ControllerHandler {
private static final int EMULATED_SELECT_UP_DELAY_MS = 30;
private Vector2d inputVector = new Vector2d();
private Vector2d normalizedInputVector = new Vector2d();
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
private NvConnection conn;
private double stickDeadzone;
private final ControllerMapping defaultMapping = new ControllerMapping();
private boolean hasGameController;
public ControllerHandler(NvConnection conn, int deadzonePercentage) {
this.conn = conn;
@@ -55,6 +55,20 @@ public class ControllerHandler {
// is required for controller batching support to work.
deadzonePercentage = 10;
int[] ids = InputDevice.getDeviceIds();
for (int i = 0; i < ids.length; i++) {
InputDevice dev = InputDevice.getDevice(ids[i]);
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 ||
(dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) {
// This looks like a gamepad, but we'll check X and Y to be sure
if (getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_X) != null &&
getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Y) != null) {
// This is a gamepad
hasGameController = true;
}
}
}
// 1% is the lowest possible deadzone we support
if (deadzonePercentage <= 0) {
deadzonePercentage = 1;
@@ -94,6 +108,12 @@ public class ControllerHandler {
mapping.leftStickXAxis = MotionEvent.AXIS_X;
mapping.leftStickYAxis = MotionEvent.AXIS_Y;
if (getMotionRangeForJoystickAxis(dev, mapping.leftStickXAxis) != null &&
getMotionRangeForJoystickAxis(dev, mapping.leftStickYAxis) != null) {
// This is a gamepad
hasGameController = true;
mapping.hasJoystickAxes = true;
}
InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER);
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
@@ -182,27 +202,48 @@ public class ControllerHandler {
// It's important to have a valid deadzone so controller packet batching works properly
mapping.triggerDeadzone = Math.max(Math.abs(ltRange.getFlat()), Math.abs(rtRange.getFlat()));
// For triggers without (valid) deadzones, we'll use 10%
if (mapping.triggerDeadzone <= 0.02 ||
mapping.triggerDeadzone > 0.30)
// For triggers without (valid) deadzones, we'll use 13% (around XInput's default)
if (mapping.triggerDeadzone < 0.13f ||
mapping.triggerDeadzone > 0.30f)
{
mapping.triggerDeadzone = 0.1f;
mapping.triggerDeadzone = 0.13f;
}
}
// For the Nexus Player (and probably other ATV devices), we should
// use the back button as start since it doesn't have a start/menu button
// on the controller
if (devName != null && devName.contains("ASUS Gamepad")) {
// We can only do this check on KitKat or higher, but it doesn't matter since ATV
// is Android 5.0 anyway
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
boolean[] hasStartKey = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_MENU, 0);
if (!hasStartKey[0] && !hasStartKey[1]) {
mapping.backIsStart = true;
if (devName != null) {
// For the Nexus Player (and probably other ATV devices), we should
// use the back button as start since it doesn't have a start/menu button
// on the controller
if (devName.contains("ASUS Gamepad")) {
// We can only do this check on KitKat or higher, but it doesn't matter since ATV
// is Android 5.0 anyway
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
boolean[] hasStartKey = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_MENU, 0);
if (!hasStartKey[0] && !hasStartKey[1]) {
mapping.backIsStart = true;
}
}
// The ASUS Gamepad has triggers that sit far forward and are prone to false presses
// so we increase the deadzone on them to minimize this
mapping.triggerDeadzone = 0.30f;
}
// Classify this device as a remote by name
else if (devName.contains("Fire TV Remote") || devName.contains("Nexus Remote")) {
// It's only a remote if it doesn't any sticks
if (!mapping.hasJoystickAxes) {
mapping.isRemote = true;
}
}
// NYKO Playpad has a fake hat that mimics the left stick for some reason
else if (devName.contains("NYKO PLAYPAD")) {
mapping.hatXAxis = -1;
mapping.hatYAxis = -1;
}
}
LimeLog.info("Analog stick deadzone: "+mapping.leftStickDeadzoneRadius+" "+mapping.rightStickDeadzoneRadius);
LimeLog.info("Trigger deadzone: "+mapping.triggerDeadzone);
return mapping;
}
@@ -232,9 +273,18 @@ public class ControllerHandler {
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
private static int handleRemapping(ControllerMapping mapping, KeyEvent event) {
if (mapping.isDualShock4) {
// Return a valid keycode, 0 to consume, or -1 to not consume the event
// Device MAY BE NULL
private int handleRemapping(ControllerMapping mapping, KeyEvent event) {
// For remotes, don't capture the back button
if (mapping.isRemote) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
return -1;
}
}
if (mapping.isDualShock4) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BUTTON_Y:
return KeyEvent.KEYCODE_BUTTON_L1;
@@ -328,60 +378,40 @@ public class ControllerHandler {
return keyCode;
}
private Vector2d populateCachedVector(float x, float y) {
// Reinitialize our cached Vector2d object
inputVector.initialize(x, y);
return inputVector;
}
private Vector2d handleDeadZone(float x, float y, float deadzoneRadius) {
// Reinitialize our cached Vector2d object
inputVector.initialize(x, y);
if (inputVector.getMagnitude() <= deadzoneRadius) {
// Deadzone -- return the zero vector
return Vector2d.ZERO;
private void handleDeadZone(Vector2d stickVector, float deadzoneRadius) {
if (stickVector.getMagnitude() <= deadzoneRadius) {
// Deadzone
stickVector.initialize(0, 0);
}
else {
/*
FIXME: We're not normalizing here because we let the computer handle the deadzones.
Normalizing can make the deadzones larger than they should be after the computer also
evaluates the deadzone
// Scale the input based on the distance from the deadzone
inputVector.getNormalized(normalizedInputVector);
normalizedInputVector.scalarMultiply((inputVector.getMagnitude() - deadzoneRadius) / (1.0f - deadzoneRadius));
// Bound the X value to -1.0 to 1.0
if (normalizedInputVector.getX() > 1.0f) {
normalizedInputVector.setX(1.0f);
}
else if (normalizedInputVector.getX() < -1.0f) {
normalizedInputVector.setX(-1.0f);
}
// Bound the Y value to -1.0 to 1.0
if (normalizedInputVector.getY() > 1.0f) {
normalizedInputVector.setY(1.0f);
}
else if (normalizedInputVector.getY() < -1.0f) {
normalizedInputVector.setY(-1.0f);
}
return normalizedInputVector;
*/
return inputVector;
}
}
// We're not normalizing here because we let the computer handle the deadzones.
// Normalizing can make the deadzones larger than they should be after the computer also
// evaluates the deadzone.
}
private void handleAxisSet(ControllerMapping mapping, float lsX, float lsY, float rsX,
float rsY, float lt, float rt, float hatX, float hatY) {
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
Vector2d leftStickVector = handleDeadZone(lsX, lsY, mapping.leftStickDeadzoneRadius);
Vector2d leftStickVector = populateCachedVector(lsX, lsY);
handleDeadZone(leftStickVector, mapping.leftStickDeadzoneRadius);
leftStickX = (short) (leftStickVector.getX() * 0x7FFE);
leftStickY = (short) (-leftStickVector.getY() * 0x7FFE);
}
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
Vector2d rightStickVector = handleDeadZone(rsX, rsY, mapping.rightStickDeadzoneRadius);
Vector2d rightStickVector = populateCachedVector(rsX, rsY);
handleDeadZone(rightStickVector, mapping.rightStickDeadzoneRadius);
rightStickX = (short) (rightStickVector.getX() * 0x7FFE);
rightStickY = (short) (-rightStickVector.getY() * 0x7FFE);
@@ -429,32 +459,9 @@ public class ControllerHandler {
ControllerMapping mapping = getMappingForDevice(event.getDevice());
float lsX = 0, lsY = 0, rsX = 0, rsY = 0, rt = 0, lt = 0, hatX = 0, hatY = 0;
// Replay the full history before getting the current values
for (int i = 0; i < event.getHistorySize(); i++) {
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
lsX = event.getHistoricalAxisValue(mapping.leftStickXAxis, i);
lsY = event.getHistoricalAxisValue(mapping.leftStickYAxis, i);
}
// We purposefully ignore the historical values in the motion event as it makes
// the controller feel sluggish for some users.
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
rsX = event.getHistoricalAxisValue(mapping.rightStickXAxis, i);
rsY = event.getHistoricalAxisValue(mapping.rightStickYAxis, i);
}
if (mapping.leftTriggerAxis != -1 && mapping.rightTriggerAxis != -1) {
lt = event.getHistoricalAxisValue(mapping.leftTriggerAxis, i);
rt = event.getHistoricalAxisValue(mapping.rightTriggerAxis, i);
}
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
hatX = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_X, i);
hatY = event.getHistoricalAxisValue(MotionEvent.AXIS_HAT_Y, i);
}
handleAxisSet(mapping, lsX, lsY, rsX, rsY, lt, rt, hatX, hatY);
}
// Now handle the current set of values
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
lsX = event.getAxisValue(mapping.leftStickXAxis);
lsY = event.getAxisValue(mapping.leftStickYAxis);
@@ -718,5 +725,7 @@ public class ControllerHandler {
public boolean isDualShock4;
public boolean isXboxController;
public boolean backIsStart;
public boolean isRemote;
public boolean hasJoystickAxes;
}
}
@@ -1,7 +1,7 @@
package com.limelight.binding.input.evdev;
import java.io.IOException;
import java.io.OutputStream;
import android.os.Build;
import java.nio.ByteBuffer;
import java.util.Locale;
@@ -11,31 +11,33 @@ public class EvdevReader {
static {
System.loadLibrary("evdev_reader");
}
public static void patchSeLinuxPolicies() {
//
// FIXME: We REALLY shouldn't being changing permissions on the input devices like this.
// We should probably do something clever with a separate daemon and talk via a localhost
// socket. We don't return the SELinux policies back to default after we're done which I feel
// bad about, but we do chmod the input devices back so I don't think any additional attack surface
// remains opened after streaming other than listing the /dev/input directory which you wouldn't
// normally be able to do with SELinux enforcing on Lollipop.
//
// We need to modify SELinux policies to allow us to capture input devices on Lollipop and possibly other
// more restrictive ROMs. Per Chainfire's SuperSU documentation, the supolicy binary is provided on
// 4.4 and later to do live SELinux policy changes.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
EvdevShell shell = EvdevShell.getInstance();
shell.runCommand("supolicy --live \"allow untrusted_app input_device dir getattr\" " +
"\"allow untrusted_app input_device chr_file { open read write ioctl }\"");
}
}
// Requires root to chmod /dev/input/eventX
public static boolean setPermissions(String[] files, int octalPermissions) {
ProcessBuilder builder = new ProcessBuilder("su");
try {
Process p = builder.start();
OutputStream stdin = p.getOutputStream();
for (String file : files) {
stdin.write(String.format((Locale)null, "chmod %o %s\n", octalPermissions, file).getBytes("UTF-8"));
}
stdin.write("exit\n".getBytes("UTF-8"));
stdin.flush();
p.waitFor();
p.destroy();
return true;
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
public static void setPermissions(String[] files, int octalPermissions) {
EvdevShell shell = EvdevShell.getInstance();
for (String file : files) {
shell.runCommand(String.format((Locale)null, "chmod %o %s", octalPermissions, file));
}
}
// Returns the fd to be passed to other function or -1 on error
@@ -0,0 +1,116 @@
package com.limelight.binding.input.evdev;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Scanner;
import java.util.UUID;
public class EvdevShell {
private OutputStream stdin;
private InputStream stdout;
private Process shell;
private final String uuidString = UUID.randomUUID().toString();
private static final EvdevShell globalShell = new EvdevShell();
public static EvdevShell getInstance() {
return globalShell;
}
public void startShell() {
ProcessBuilder builder = new ProcessBuilder("su");
try {
// Redirect stderr to stdout
builder.redirectErrorStream(true);
shell = builder.start();
stdin = shell.getOutputStream();
stdout = shell.getInputStream();
} catch (IOException e) {
// This is unexpected
e.printStackTrace();
// Kill the shell if it spawned
if (stdin != null) {
try {
stdin.close();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
stdin = null;
}
}
if (stdout != null) {
try {
stdout.close();
} catch (IOException e1) {
e1.printStackTrace();
} finally {
stdout = null;
}
}
if (shell != null) {
shell.destroy();
shell = null;
}
}
}
public void runCommand(String command) {
if (shell == null) {
// Shell never started
return;
}
try {
// Write the command followed by an echo with our UUID
stdin.write((command+'\n').getBytes("UTF-8"));
stdin.write(("echo "+uuidString+'\n').getBytes("UTF-8"));
stdin.flush();
// This is the only command in flight so we can use a scanner
// without worrying about it eating too many characters
Scanner scanner = new Scanner(stdout);
while (scanner.hasNext()) {
if (scanner.next().contains(uuidString)) {
// Our command ran
return;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void stopShell() throws InterruptedException {
boolean exitWritten = false;
if (shell == null) {
// Shell never started
return;
}
try {
stdin.write("exit\n".getBytes("UTF-8"));
exitWritten = true;
} catch (IOException e) {
// We'll destroy the process without
// waiting for it to terminate since
// we don't know whether our exit command made it
e.printStackTrace();
}
if (exitWritten) {
try {
shell.waitFor();
} finally {
shell.destroy();
}
}
else {
shell.destroy();
}
}
}
@@ -19,6 +19,8 @@ public class EvdevWatcher {
private boolean ungrabbed = false;
private EvdevListener listener;
private Thread startThread;
private static boolean patchedSeLinuxPolicies = false;
private FileObserver observer = new FileObserver(PATH, FileObserver.CREATE | FileObserver.DELETE) {
@Override
@@ -117,6 +119,15 @@ public class EvdevWatcher {
startThread = new Thread() {
@Override
public void run() {
// Initialize the root shell
EvdevShell.getInstance().startShell();
// Patch SELinux policies (if needed)
if (!patchedSeLinuxPolicies) {
EvdevReader.patchSeLinuxPolicies();
patchedSeLinuxPolicies = true;
}
// List all files and allow us access
File[] files = rundownWithPermissionsChange(0666);
@@ -139,7 +150,12 @@ public class EvdevWatcher {
}
// Giveup eventX permissions
rundownWithPermissionsChange(066);
rundownWithPermissionsChange(0660);
// Kill the root shell
try {
EvdevShell.getInstance().stopShell();
} catch (InterruptedException e) {}
}
};
startThread.start();
@@ -141,7 +141,9 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
throw new IllegalStateException("AVC decoder initialization failure: "+err);
}
AvcDecoder.setRenderTarget(sh.getSurface());
if (!AvcDecoder.setRenderTarget(sh.getSurface())) {
return false;
}
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
@@ -252,7 +254,7 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
// Add delta time to the totals (excluding probable outliers)
long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp();
if (delta >= 0 && delta < 300) {
if (delta >= 0 && delta < 1000) {
totalTimeMs += delta;
totalFrames++;
}
@@ -273,7 +273,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Add delta time to the totals (excluding probable outliers)
long delta = System.currentTimeMillis()-(presentationTimeUs/1000);
if (delta >= 0 && delta < 300) {
if (delta >= 0 && delta < 1000) {
decoderTimeMs += delta;
totalTimeMs += delta;
}
@@ -371,7 +371,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) {
long currentTime = System.currentTimeMillis();
long delta = currentTime-decodeUnit.getReceiveTimestamp();
if (delta >= 0 && delta < 300) {
if (delta >= 0 && delta < 1000) {
totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp();
totalFrames++;
}
@@ -410,6 +410,23 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
spsBuf.position(header.offset+5);
SeqParameterSet sps = SeqParameterSet.read(spsBuf);
// Some decoders rely on H264 level to decide how many buffers are needed
// Since we only need one frame buffered, we'll set the level as low as we can
// for known resolution combinations
if (initialWidth == 1280 && initialHeight == 720) {
// Max 5 buffered frames at 1280x720x60
LimeLog.info("Patching level_idc to 32");
sps.level_idc = 32;
}
else if (initialWidth == 1920 && initialHeight == 1080) {
// Max 4 buffered frames at 1920x1080x64
LimeLog.info("Patching level_idc to 42");
sps.level_idc = 42;
}
else {
// Leave the profile alone (currently 5.0)
}
// TI OMAP4 requires a reference frame count of 1 to decode successfully. Exynos 4
// also requires this fixup.
@@ -44,6 +44,7 @@ public class MediaCodecHelper {
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
baselineProfileHackPrefixes = new LinkedList<String>();
baselineProfileHackPrefixes.add("omx.intel");
@@ -244,9 +244,12 @@ public class ComputerManagerService extends Service {
for (PollingTuple tuple : pollingTuples) {
// Check if this is the same computer
if (tuple.computer == details ||
tuple.computer.localIp.equals(details.localIp) ||
tuple.computer.remoteIp.equals(details.remoteIp) ||
tuple.computer.name.equals(details.name)) {
// If there's no name on one of these computers, compare with the local IP
((details.name.isEmpty() || tuple.computer.name.isEmpty()) &&
tuple.computer.localIp.equals(details.localIp)) ||
// If there is a name on both computers, compare with name
((!details.name.isEmpty() && !tuple.computer.name.isEmpty()) &&
tuple.computer.name.equals(details.name))) {
// Start a polling thread if polling is active
if (pollingActive && tuple.thread == null) {
@@ -329,12 +332,22 @@ public class ComputerManagerService extends Service {
}
}
private ComputerDetails tryPollIp(InetAddress ipAddr) {
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
try {
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
return http.getComputerDetails();
ComputerDetails newDetails = http.getComputerDetails();
// Check if this is the PC we expected
if (details.uuid != null && newDetails.uuid != null &&
!details.uuid.equals(newDetails.uuid)) {
// We got the wrong PC!
LimeLog.info("Polling returned the wrong PC!");
return null;
}
return newDetails;
} catch (Exception e) {
return null;
}
@@ -342,21 +355,27 @@ public class ComputerManagerService extends Service {
private boolean pollComputer(ComputerDetails details, boolean localFirst) {
ComputerDetails polledDetails;
// If the local address is routable across the Internet,
// always consider this PC remote to be conservative
if (details.localIp.equals(details.remoteIp)) {
localFirst = false;
}
if (localFirst) {
polledDetails = tryPollIp(details.localIp);
polledDetails = tryPollIp(details, details.localIp);
}
else {
polledDetails = tryPollIp(details.remoteIp);
polledDetails = tryPollIp(details, details.remoteIp);
}
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
// Failed, so let's try the fallback
if (!localFirst) {
polledDetails = tryPollIp(details.localIp);
polledDetails = tryPollIp(details, details.localIp);
}
else {
polledDetails = tryPollIp(details.remoteIp);
polledDetails = tryPollIp(details, details.remoteIp);
}
// The fallback poll worked
@@ -414,8 +433,8 @@ public class ComputerManagerService extends Service {
}
for (ComputerDetails computer : dbManager.getAllComputers()) {
// Add this computer without a thread
pollingTuples.add(new PollingTuple(computer, null));
// Add tuples for each computer
addTuple(computer);
}
releaseLocalDatabaseReference();
@@ -142,8 +142,9 @@ public class AddComputerManually extends Activity {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE ||
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
(keyEvent != null &&
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
if (hostText.getText().length() == 0) {
Toast.makeText(AddComputerManually.this, getResources().getString(R.string.addpc_enter_ip), Toast.LENGTH_LONG).show();
return true;
@@ -17,7 +17,7 @@ public class PreferenceConfiguration {
private static final int BITRATE_DEFAULT_720_30 = 5;
private static final int BITRATE_DEFAULT_720_60 = 10;
private static final int BITRATE_DEFAULT_1080_30 = 10;
private static final int BITRATE_DEFAULT_1080_60 = 30;
private static final int BITRATE_DEFAULT_1080_60 = 20;
private static final String DEFAULT_RES_FPS = "720p60";
private static final String DEFAULT_DECODER = "auto";
+12 -12
View File
@@ -69,9 +69,6 @@ int nv_avc_init(int width, int height, int perf_lvl, int thread_count) {
return -1;
}
// Show frames even before a reference frame
decoder_ctx->flags2 |= CODEC_FLAG2_SHOW_ALL;
if (perf_lvl & DISABLE_LOOP_FILTER) {
// Skip the loop filter for performance reasons
decoder_ctx->skip_loop_filter = AVDISCARD_ALL;
@@ -370,17 +367,20 @@ int nv_avc_decode(unsigned char* indata, int inlen) {
// Only copy the picture at the end of decoding the packet
if (got_pic) {
// Clone the current decode frame outside of the mutex
AVFrame* new_frame = av_frame_clone(dec_frame);
AVFrame* old_frame;
// Swap it in under lock
pthread_mutex_lock(&mutex);
// Only clone this frame if the last frame was taken.
// This saves on extra copies for frames that don't get
// rendered.
if (yuv_frame == NULL) {
// Clone a new frame
yuv_frame = av_frame_clone(dec_frame);
}
old_frame = yuv_frame;
yuv_frame = new_frame;
pthread_mutex_unlock(&mutex);
// Free the old frame outside of the mutex
if (old_frame != NULL) {
av_frame_free(&old_frame);
}
}
return err < 0 ? err : 0;
+2 -2
View File
@@ -87,7 +87,7 @@
<string name="summary_seekbar_bitrate">Abbassa il bitrate per ridurre lo stuttering; alza il bitrate per aumenteare la qualità dell\'immagine</string>
<string name="suffix_seekbar_bitrate">Mbps</string>
<string name="title_checkbox_stretch_video">Forza video in full-screen</string>
<string name="title_checkbox_disable_warnings">Disabilita i messaggi di warning</string>
<string name="title_checkbox_disable_warnings">Disabilita messaggi di warning</string>
<string name="summary_checkbox_disable_warnings">Disabilita i messaggi di warning sullo schermo durante lo streaming</string>
<string name="category_gamepad_settings">Impostazioni Gamepad</string>
@@ -102,5 +102,5 @@
<string name="category_advanced_settings">Impostazioni Avanzate</string>
<string name="title_decoder_list">Cambia decoder</string>
<string name="summary_decoder_list">Il decoder software può aumentare la latenza video quando si usano impostazioni streaming basse</string>
<string name="summary_decoder_list">Il decoder software può ridurre la latenza video quando si usano impostazioni streaming basse</string>
</resources>
+1 -2
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.limelight" >
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Non-root application name -->
<application android:label="Limelight" />
+1 -2
View File
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.limelight.root" >
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Root permissions -->
<uses-permission android:name="android.permission.ACCESS_SUPERUSER" />
+1 -1
View File
@@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.13.2'
classpath 'com.android.tools.build:gradle:1.0.0'
}
}
+2 -2
View File
@@ -4,12 +4,12 @@ This file serves to document some of the decoder errata when using MediaCodec ha
- Affected decoders: TI OMAP4, Allwinner A20
2. Some decoders have a huge per-frame latency with the unmodified SPS sent from NVENC. Setting max_dec_frame_buffering fixes this latency issue.
- Affected decoders: NVIDIA Tegra 3 and 4
- Affected decoders: NVIDIA Tegra 3 and 4, Broadcom VideoCore IV
3. Some decoders strictly require that you pass BUFFER_FLAG_CODEC_CONFIG and crash upon the IDR frame if you don't
- Affected decoders: TI OMAP4
4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS or first I-frame
4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS on first I-frame
- Affected decoders: Qualcomm in GS3 on 4.3+, Exynos 4 at 1080p only
5. Some decoders will hang if max_dec_frame_buffering is not present
+2 -2
View File
@@ -1,6 +1,6 @@
#Wed Apr 10 15:27:10 PDT 2013
#Sun Dec 07 22:52:07 PST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
+3 -1
View File
@@ -7,7 +7,9 @@
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/classes/main" />
<output-test url="file://$MODULE_DIR$/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" />