Compare commits

..

18 Commits

Author SHA1 Message Date
Cameron Gutman d2773be32e Separate the different SPS fixups by decoder. Go back to the old way of doing bitstream restrictions because the new way seems to be broken. TI OMAP4's Ducati decoder works now :) 2014-04-14 13:24:52 -04:00
Cameron Gutman 93a7d9f181 Fix crash on devices that re-create the surface when we set the format 2014-04-14 12:46:59 -04:00
Cameron Gutman 9703cf4ffe Merge branch 'master' of github.com:cgutman/limelight 2014-04-14 11:38:16 -04:00
Cameron Gutman 41ec64e87c Add controllers.json for the Fire TV 2014-04-14 11:37:55 -04:00
Cameron Gutman aca92a5056 Increment version to 2.1.5 2014-04-13 21:01:58 -04:00
Cameron Gutman fc9c4d9aaa Use a much better method for adding bitstream restrictions to the SPS. Fix a violation in H.264 spec after adding bitstream restrictions. Credit to irtimmer for the changes. 2014-04-13 20:47:37 -04:00
Cameron Gutman d8c6a544f0 Fix race condition with the destruction of the rendering surface and stopping the renderer to fix a random crash on exit 2014-04-13 20:24:38 -04:00
Cameron Gutman 7e100f2c9c Update common 2014-04-13 20:23:15 -04:00
Cameron Gutman 643b644e17 Update limelight common and increment version to 2.1.4 2014-04-07 19:29:37 -04:00
Cameron Gutman 48a9d3ac12 Increment version number to 2.1.3 2014-03-31 18:39:05 -04:00
Cameron Gutman efdd1e2046 Fix unacknowledged motion events causing phantom d-pad events 2014-03-31 18:37:53 -04:00
Cameron Gutman 8a40892865 Rewrite controller code to better handle previously unseen controllers 2014-03-31 00:30:36 -04:00
Cameron Gutman 20635a3012 Prevent the Game activity from being relaunch due to config changes that weren't handled before 2014-03-30 18:26:33 -04:00
Cameron Gutman b908c5cec3 Add support for digital L2 and R2 buttons 2014-03-30 16:28:54 -04:00
Cameron Gutman 1cb0b723f6 Increment version to 2.1.2 2014-03-29 10:24:19 -04:00
Cameron Gutman 3f3c573c79 Move controller handling into a new class. Implement translation for DualShock 4 controllers. 2014-03-28 23:53:28 -04:00
Cameron Gutman e1253bbb59 Move KeyboardTranslator into its own package 2014-03-28 23:52:34 -04:00
Cameron Gutman b2eb953f45 Don't dump decoders on start because some devices will throw a spurious IllegalStateException when querying capabilities 2014-03-28 20:06:53 -04:00
7 changed files with 672 additions and 254 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.limelight"
android:versionCode="7"
android:versionName="2.1.1" >
android:versionCode="11"
android:versionName="2.1.5" >
<uses-sdk
android:minSdkVersion="16"
@@ -29,7 +29,7 @@
<activity
android:name="com.limelight.Game"
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:parentActivityName="com.limelight.Connection"
android:theme="@style/FullscreenTheme" >
+12
View File
@@ -0,0 +1,12 @@
{
"SupportedControllers" : {
"Gamepad" : {},
"Remote" : "false",
"SecondScreen" : {
"DPad" : "false",
"AnalogSticks" : "0",
"DigitalButtons" : "0",
"Mouse" : "false"
}
}
}
Binary file not shown.
+44 -211
View File
@@ -1,12 +1,13 @@
package com.limelight;
import com.limelight.binding.PlatformBinding;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.KeyboardTranslator;
import com.limelight.binding.video.ConfigurableDecoderRenderer;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.NvConnectionListener;
import com.limelight.nvstream.StreamConfiguration;
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.input.ControllerPacket;
import com.limelight.nvstream.input.KeyboardPacket;
import com.limelight.utils.Dialog;
import com.limelight.utils.SpinnerDialog;
@@ -33,14 +34,7 @@ import android.view.WindowManager;
import android.widget.Toast;
public class Game extends Activity implements OnGenericMotionListener, OnTouchListener, NvConnectionListener {
private short inputMap = 0x0000;
private byte leftTrigger = 0x00;
private byte rightTrigger = 0x00;
private short rightStickX = 0x0000;
private short rightStickY = 0x0000;
private short leftStickX = 0x0000;
private short leftStickY = 0x0000;
public class Game extends Activity implements SurfaceHolder.Callback, OnGenericMotionListener, OnTouchListener, NvConnectionListener {
private int lastMouseX = Integer.MIN_VALUE;
private int lastMouseY = Integer.MIN_VALUE;
private int lastButtonState = 0;
@@ -48,6 +42,7 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
private int lastTouchY = 0;
private boolean hasMoved = false;
private ControllerHandler controllerHandler;
private KeyboardTranslator keybTranslator;
private int height;
@@ -57,6 +52,10 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
private NvConnection conn;
private SpinnerDialog spinner;
private boolean displayedFailureDialog = false;
private boolean connecting = false;
private boolean connected = false;
private int drFlags = 0;
public static final String PREFS_FILE_NAME = "gameprefs";
@@ -106,7 +105,6 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
// Read the stream preferences
SharedPreferences prefs = getSharedPreferences(PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
int drFlags = 0;
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
case Game.FORCE_SOFTWARE_DECODER:
drFlags |= VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING;
@@ -134,8 +132,10 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this,
new StreamConfiguration(width, height, refreshRate));
keybTranslator = new KeyboardTranslator(conn);
conn.start(PlatformBinding.getDeviceName(), sv.getHolder(), drFlags,
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer());
controllerHandler = new ControllerHandler(conn);
// The connection will be started when the surface gets created
sh.addCallback(this);
}
private void checkDataConnection()
@@ -210,64 +210,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
getModifierState(event));
}
else {
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU:
inputMap |= ControllerPacket.PLAY_FLAG;
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
inputMap |= ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
inputMap |= ControllerPacket.LEFT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
inputMap |= ControllerPacket.RIGHT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_UP:
inputMap |= ControllerPacket.UP_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
inputMap |= ControllerPacket.DOWN_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_B:
inputMap |= ControllerPacket.B_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_A:
inputMap |= ControllerPacket.A_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_X:
inputMap |= ControllerPacket.X_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_Y:
inputMap |= ControllerPacket.Y_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L1:
inputMap |= ControllerPacket.LB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_R1:
inputMap |= ControllerPacket.RB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
inputMap |= ControllerPacket.LS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
inputMap |= ControllerPacket.RS_CLK_FLAG;
break;
default:
if (!controllerHandler.handleButtonDown(keyCode, event)) {
return super.onKeyDown(keyCode, event);
}
// We detect back+start as the special button combo
if ((inputMap & ControllerPacket.BACK_FLAG) != 0 &&
(inputMap & ControllerPacket.PLAY_FLAG) != 0)
{
inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG);
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
}
sendControllerInputPacket();
}
return true;
@@ -286,63 +231,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
getModifierState(event));
}
else {
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU:
inputMap &= ~ControllerPacket.PLAY_FLAG;
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
inputMap &= ~ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
inputMap &= ~ControllerPacket.LEFT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
inputMap &= ~ControllerPacket.RIGHT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_UP:
inputMap &= ~ControllerPacket.UP_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
inputMap &= ~ControllerPacket.DOWN_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_B:
inputMap &= ~ControllerPacket.B_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_A:
inputMap &= ~ControllerPacket.A_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_X:
inputMap &= ~ControllerPacket.X_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_Y:
inputMap &= ~ControllerPacket.Y_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L1:
inputMap &= ~ControllerPacket.LB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_R1:
inputMap &= ~ControllerPacket.RB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
inputMap &= ~ControllerPacket.LS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
inputMap &= ~ControllerPacket.RS_CLK_FLAG;
break;
default:
if (!controllerHandler.handleButtonUp(keyCode, event)) {
return super.onKeyUp(keyCode, event);
}
// If one of the two is up, the special button comes up too
if ((inputMap & ControllerPacket.BACK_FLAG) == 0 ||
(inputMap & ControllerPacket.PLAY_FLAG) == 0)
{
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
}
sendControllerInputPacket();
}
return true;
@@ -460,92 +351,13 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
return super.onTouchEvent(event);
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
InputDevice dev = event.getDevice();
if (dev == null) {
System.err.println("Unknown device");
return false;
}
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
float LS_X = event.getAxisValue(MotionEvent.AXIS_X);
float LS_Y = event.getAxisValue(MotionEvent.AXIS_Y);
float RS_X, RS_Y, L2, R2;
InputDevice.MotionRange leftTriggerRange = dev.getMotionRange(MotionEvent.AXIS_LTRIGGER);
InputDevice.MotionRange rightTriggerRange = dev.getMotionRange(MotionEvent.AXIS_RTRIGGER);
if (leftTriggerRange != null && rightTriggerRange != null)
{
// Ouya controller
L2 = event.getAxisValue(MotionEvent.AXIS_LTRIGGER);
R2 = event.getAxisValue(MotionEvent.AXIS_RTRIGGER);
RS_X = event.getAxisValue(MotionEvent.AXIS_Z);
RS_Y = event.getAxisValue(MotionEvent.AXIS_RZ);
}
else
{
InputDevice.MotionRange brakeRange = dev.getMotionRange(MotionEvent.AXIS_BRAKE);
InputDevice.MotionRange gasRange = dev.getMotionRange(MotionEvent.AXIS_GAS);
if (brakeRange != null && gasRange != null)
{
// 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
{
// Xbox controller
RS_X = event.getAxisValue(MotionEvent.AXIS_RX);
RS_Y = event.getAxisValue(MotionEvent.AXIS_RY);
L2 = (event.getAxisValue(MotionEvent.AXIS_Z) + 1) / 2;
R2 = (event.getAxisValue(MotionEvent.AXIS_RZ) + 1) / 2;
}
}
InputDevice.MotionRange hatXRange = dev.getMotionRange(MotionEvent.AXIS_HAT_X);
InputDevice.MotionRange hatYRange = dev.getMotionRange(MotionEvent.AXIS_HAT_Y);
if (hatXRange != null && hatYRange != null)
{
// Xbox controller 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.UP_FLAG | ControllerPacket.DOWN_FLAG);
if (hatX < -0.5) {
inputMap |= ControllerPacket.LEFT_FLAG;
}
if (hatX > 0.5) {
inputMap |= ControllerPacket.RIGHT_FLAG;
}
if (hatY < -0.5) {
inputMap |= ControllerPacket.UP_FLAG;
}
if (hatY > 0.5) {
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();
return true;
if (controllerHandler.handleMotionEvent(event)) {
return true;
}
}
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
{
@@ -579,11 +391,6 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
lastMouseX = eventX;
lastMouseY = eventY;
}
private void sendControllerInputPacket() {
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
@Override
public boolean onGenericMotion(View v, MotionEvent event) {
@@ -617,6 +424,7 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
displayedFailureDialog = true;
Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true);
conn.stop();
connecting = false;
}
}
@@ -627,6 +435,7 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
e.printStackTrace();
Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true);
conn.stop();
connected = false;
}
}
@@ -635,6 +444,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
spinner.dismiss();
spinner = null;
connecting = false;
connected = true;
hideSystemUi();
}
@@ -657,4 +469,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;
}
}
}
@@ -0,0 +1,475 @@
package com.limelight.binding.input;
import java.util.HashMap;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.ControllerPacket;
public class ControllerHandler {
private short inputMap = 0x0000;
private byte leftTrigger = 0x00;
private byte rightTrigger = 0x00;
private short rightStickX = 0x0000;
private short rightStickY = 0x0000;
private short leftStickX = 0x0000;
private short leftStickY = 0x0000;
private HashMap<String, ControllerMapping> mappings = new HashMap<String, ControllerMapping>();
private NvConnection conn;
public ControllerHandler(NvConnection 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() {
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
private int handleRemapping(ControllerMapping mapping, int keyCode) {
if (mapping.isDualShock4) {
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_Y:
return KeyEvent.KEYCODE_BUTTON_L1;
case KeyEvent.KEYCODE_BUTTON_Z:
return KeyEvent.KEYCODE_BUTTON_R1;
case KeyEvent.KEYCODE_BUTTON_C:
return KeyEvent.KEYCODE_BUTTON_B;
case KeyEvent.KEYCODE_BUTTON_X:
return KeyEvent.KEYCODE_BUTTON_Y;
case KeyEvent.KEYCODE_BUTTON_B:
return KeyEvent.KEYCODE_BUTTON_A;
case KeyEvent.KEYCODE_BUTTON_A:
return KeyEvent.KEYCODE_BUTTON_X;
case KeyEvent.KEYCODE_BUTTON_SELECT:
return KeyEvent.KEYCODE_BUTTON_THUMBL;
case KeyEvent.KEYCODE_BUTTON_START:
return KeyEvent.KEYCODE_BUTTON_THUMBR;
case KeyEvent.KEYCODE_BUTTON_L2:
return KeyEvent.KEYCODE_BUTTON_SELECT;
case KeyEvent.KEYCODE_BUTTON_R2:
return KeyEvent.KEYCODE_BUTTON_START;
// These are duplicate trigger events
case KeyEvent.KEYCODE_BUTTON_R1:
case KeyEvent.KEYCODE_BUTTON_L1:
return 0;
}
}
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_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
return 0;
}
}
return keyCode;
}
public boolean handleMotionEvent(MotionEvent event) {
ControllerMapping mapping = getMappingForDevice(event.getDevice());
if (mapping == null) {
return false;
}
// Handle left stick events outside of the deadzone
if (mapping.leftStickXAxis != -1 && mapping.leftStickYAxis != -1) {
float LS_X = event.getAxisValue(mapping.leftStickXAxis);
float LS_Y = event.getAxisValue(mapping.leftStickYAxis);
if (LS_X >= -mapping.leftStickXAxisDeadzone && LS_X <= mapping.leftStickXAxisDeadzone) {
LS_X = 0;
}
if (LS_Y >= -mapping.leftStickYAxisDeadzone && LS_Y <= mapping.leftStickYAxisDeadzone) {
LS_Y = 0;
}
leftStickX = (short)Math.round(LS_X * 0x7FFF);
leftStickY = (short)Math.round(-LS_Y * 0x7FFF);
}
// Handle right stick events outside of the deadzone
if (mapping.rightStickXAxis != -1 && mapping.rightStickYAxis != -1) {
float RS_X = event.getAxisValue(mapping.rightStickXAxis);
float RS_Y = event.getAxisValue(mapping.rightStickYAxis);
if (RS_X >= -mapping.rightStickXAxisDeadzone && RS_X <= mapping.rightStickXAxisDeadzone) {
RS_X = 0;
}
if (RS_Y >= -mapping.rightStickYAxisDeadzone && RS_Y <= mapping.rightStickYAxisDeadzone) {
RS_Y = 0;
}
rightStickX = (short)Math.round(RS_X * 0x7FFF);
rightStickY = (short)Math.round(-RS_Y * 0x7FFF);
}
// Handle controllers with analog triggers
if (mapping.leftTriggerAxis != -1 && mapping.rightTriggerAxis != -1) {
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);
}
// Hats emulate d-pad events
if (mapping.hatXAxis != -1 && mapping.hatYAxis != -1) {
float hatX = event.getAxisValue(MotionEvent.AXIS_HAT_X);
float hatY = event.getAxisValue(MotionEvent.AXIS_HAT_Y);
inputMap &= ~(ControllerPacket.LEFT_FLAG | ControllerPacket.RIGHT_FLAG);
if (hatX < -(0.5 + mapping.hatXDeadzone)) {
inputMap |= ControllerPacket.LEFT_FLAG;
}
else if (hatX > (0.5 + mapping.hatXDeadzone)) {
inputMap |= ControllerPacket.RIGHT_FLAG;
}
inputMap &= ~(ControllerPacket.UP_FLAG | ControllerPacket.DOWN_FLAG);
if (hatY < -(0.5 + mapping.hatYDeadzone)) {
inputMap |= ControllerPacket.UP_FLAG;
}
else if (hatY > (0.5 + mapping.hatYDeadzone)) {
inputMap |= ControllerPacket.DOWN_FLAG;
}
}
sendControllerInputPacket();
return true;
}
public boolean handleButtonUp(int keyCode, KeyEvent event) {
ControllerMapping mapping = getMappingForDevice(event.getDevice());
if (mapping == null) {
return false;
}
keyCode = handleRemapping(mapping, keyCode);
if (keyCode == 0) {
return true;
}
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE:
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU:
inputMap &= ~ControllerPacket.PLAY_FLAG;
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
inputMap &= ~ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
inputMap &= ~ControllerPacket.LEFT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
inputMap &= ~ControllerPacket.RIGHT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_UP:
inputMap &= ~ControllerPacket.UP_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
inputMap &= ~ControllerPacket.DOWN_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_B:
inputMap &= ~ControllerPacket.B_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_A:
inputMap &= ~ControllerPacket.A_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_X:
inputMap &= ~ControllerPacket.X_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_Y:
inputMap &= ~ControllerPacket.Y_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L1:
inputMap &= ~ControllerPacket.LB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_R1:
inputMap &= ~ControllerPacket.RB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
inputMap &= ~ControllerPacket.LS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
inputMap &= ~ControllerPacket.RS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L2:
leftTrigger = 0;
break;
case KeyEvent.KEYCODE_BUTTON_R2:
rightTrigger = 0;
break;
default:
return false;
}
// If one of the two is up, the special button comes up too
if ((inputMap & ControllerPacket.BACK_FLAG) == 0 ||
(inputMap & ControllerPacket.PLAY_FLAG) == 0)
{
inputMap &= ~ControllerPacket.SPECIAL_BUTTON_FLAG;
}
sendControllerInputPacket();
return true;
}
public boolean handleButtonDown(int keyCode, KeyEvent event) {
ControllerMapping mapping = getMappingForDevice(event.getDevice());
if (mapping == null) {
return false;
}
keyCode = handleRemapping(mapping, keyCode);
if (keyCode == 0) {
return true;
}
switch (keyCode) {
case KeyEvent.KEYCODE_BUTTON_MODE:
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_START:
case KeyEvent.KEYCODE_MENU:
inputMap |= ControllerPacket.PLAY_FLAG;
break;
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_BUTTON_SELECT:
inputMap |= ControllerPacket.BACK_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
inputMap |= ControllerPacket.LEFT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
inputMap |= ControllerPacket.RIGHT_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_UP:
inputMap |= ControllerPacket.UP_FLAG;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
inputMap |= ControllerPacket.DOWN_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_B:
inputMap |= ControllerPacket.B_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_A:
inputMap |= ControllerPacket.A_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_X:
inputMap |= ControllerPacket.X_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_Y:
inputMap |= ControllerPacket.Y_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L1:
inputMap |= ControllerPacket.LB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_R1:
inputMap |= ControllerPacket.RB_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
inputMap |= ControllerPacket.LS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
inputMap |= ControllerPacket.RS_CLK_FLAG;
break;
case KeyEvent.KEYCODE_BUTTON_L2:
leftTrigger = (byte)0xFF;
break;
case KeyEvent.KEYCODE_BUTTON_R2:
rightTrigger = (byte)0xFF;
break;
default:
return false;
}
// We detect back+start as the special button combo
if ((inputMap & ControllerPacket.BACK_FLAG) != 0 &&
(inputMap & ControllerPacket.PLAY_FLAG) != 0)
{
inputMap &= ~(ControllerPacket.BACK_FLAG | ControllerPacket.PLAY_FLAG);
inputMap |= ControllerPacket.SPECIAL_BUTTON_FLAG;
}
sendControllerInputPacket();
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;
}
}
@@ -1,4 +1,4 @@
package com.limelight;
package com.limelight.binding.input;
import android.view.KeyEvent;
@@ -24,23 +24,27 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
private MediaCodec videoDecoder;
private Thread rendererThread;
private int redrawRate;
private boolean needsSpsFixup;
private boolean needsSpsBitstreamFixup;
private boolean needsSpsNumRefFixup;
private boolean fastInputQueueing;
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;
static {
blacklistedDecoderPrefixes = new LinkedList<String>();
// TI's decoder technically supports high profile but doesn't work for some reason
blacklistedDecoderPrefixes.add("omx.TI");
// Nothing here right now :)
}
static {
spsFixupDecoderPrefixes = new LinkedList<String>();
spsFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitsreamFixupDecoderPrefixes = new LinkedList<String>();
spsFixupBitsreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupNumRefFixupDecoderPrefixes = new LinkedList<String>();
spsFixupNumRefFixupDecoderPrefixes.add("omx.TI");
}
static {
@@ -123,14 +127,18 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.redrawRate = redrawRate;
dumpDecoders();
//dumpDecoders();
MediaCodecInfo safeDecoder = findSafeDecoder();
if (safeDecoder != null) {
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());
needsSpsFixup = isDecoderInList(spsFixupDecoderPrefixes, safeDecoder.getName());
if (needsSpsFixup) {
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS fixup");
needsSpsBitstreamFixup = isDecoderInList(spsFixupBitsreamFixupDecoderPrefixes, safeDecoder.getName());
needsSpsNumRefFixup = isDecoderInList(spsFixupNumRefFixupDecoderPrefixes, safeDecoder.getName());
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());
if (fastInputQueueing) {
@@ -139,7 +147,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
}
else {
videoDecoder = MediaCodec.createDecoderByType("video/avc");
needsSpsFixup = false;
needsSpsBitstreamFixup = false;
needsSpsNumRefFixup = false;
fastInputQueueing = false;
}
@@ -256,39 +265,49 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
// Clear old input data
buf.clear();
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
// 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) {
if (needsSpsBitstreamFixup || needsSpsNumRefFixup) {
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
// Check for SPS NALU type
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) {
case 26:
LimeLog.info("Modifying SPS (26)");
buf.put(header.data, header.offset, 24);
buf.put((byte) 0x11);
buf.put((byte) 0xe3);
buf.put((byte) 0x06);
buf.put((byte) 0x50);
spsLength = header.length + 2;
break;
case 27:
LimeLog.info("Modifying SPS (27)");
buf.put(header.data, header.offset, 25);
buf.put((byte) 0x04);
buf.put((byte) 0x78);
buf.put((byte) 0xc1);
buf.put((byte) 0x94);
spsLength = header.length + 2;
break;
default:
LimeLog.warning("Unknown SPS of length "+header.length);
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
// 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.
int spsLength;
if (needsSpsBitstreamFixup) {
switch (header.length) {
case 26:
LimeLog.info("Adding bitstream restrictions to SPS (26)");
buf.put(header.data, header.offset, 24);
buf.put((byte) 0x11);
buf.put((byte) 0xe3);
buf.put((byte) 0x06);
buf.put((byte) 0x50);
spsLength = header.length + 2;
break;
case 27:
LimeLog.info("Adding bitstream restrictions to SPS (27)");
buf.put(header.data, header.offset, 25);
buf.put((byte) 0x04);
buf.put((byte) 0x78);
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);
spsLength = header.length;
break;
}
videoDecoder.queueInputBuffer(inputIndex,
@@ -316,4 +335,83 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
public int getCapabilities() {
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;
}
}