Compare commits

..

17 Commits

Author SHA1 Message Date
Cameron Gutman 9638c15c93 I'll take a mulligan on v2.1. Here comes v2.1.1 2014-03-26 01:37:34 -04:00
Cameron Gutman 8ce972ea7a Properly distinguish between keyboard and controller events 2014-03-26 01:36:52 -04:00
Cameron Gutman 968557d3a8 Fix mouse dragging 2014-03-26 01:28:02 -04:00
Cameron Gutman cf80a7380c Increment version to 2.1 2014-03-26 00:14:24 -04:00
Cameron Gutman ceb957e7f3 Add a toast if the IP field isn't filled 2014-03-26 00:13:46 -04:00
Cameron Gutman 669691d8e8 Remove the annoying focus highlight on the streaming button. Hide the keyboard on the connect screen. 2014-03-26 00:13:23 -04:00
Cameron Gutman ea57e48ed5 Add keyboard support 2014-03-25 23:59:48 -04:00
Cameron Gutman f744c7d9c4 Make the cursor on the target track more true to the client input by scaling based on screen resolution 2014-03-25 20:43:45 -04:00
Cameron Gutman 0d42beca93 Add support for secondary and tertiary mouse buttons 2014-03-25 20:16:23 -04:00
Cameron Gutman bafb9e6230 Make streaming button focusable so the keyboard doesn't appear when starting limelight 2014-03-21 21:36:07 -04:00
Cameron Gutman 5d30a3f4ab Increment version code for redeployment 2014-03-21 21:23:02 -04:00
Cameron Gutman 70a1dd56b4 A bit more cleanup 2014-03-21 21:16:53 -04:00
Cameron Gutman 9857017b22 Dump decoders before starting for logging purposes. Remove the software decoders from the blacklist since they are properly detected by the high profile check now. 2014-03-21 20:54:33 -04:00
Cameron Gutman 53474c7d28 Check that the decoder supports high profile H.264 before using it 2014-03-21 20:29:22 -04:00
Cameron Gutman 283c1060a3 Increment version 2014-03-21 19:32:42 -04:00
Cameron Gutman eb7c54b95c Fix some handling of the Android activity lifecycle that upset the window manager 2014-03-21 19:32:24 -04:00
Cameron Gutman 735ad02fb4 The surface format must be RGBX if we're doing CPU rendering. If the surface is RGB565, the renderer will not properly format the output and the decoder will crash. 2014-03-21 18:20:55 -04:00
7 changed files with 536 additions and 159 deletions
+2 -2
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="3"
android:versionName="2.0.1" >
android:versionCode="7"
android:versionName="2.1.1" >
<uses-sdk
android:minSdkVersion="16"
+2 -2
View File
@@ -10,7 +10,7 @@
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content" >
<EditText
android:id="@+id/hostTextView"
@@ -34,7 +34,7 @@
<requestFocus />
</Button>
</Button>
<Button
android:id="@+id/pairButton"
+9
View File
@@ -15,6 +15,7 @@ import com.limelight.nvstream.http.NvHTTP;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
@@ -52,6 +53,9 @@ public class Connection extends Activity {
setContentView(R.layout.activity_connection);
// Hide the keyboard by default
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
this.statusButton = (Button) findViewById(R.id.statusButton);
this.pairButton = (Button) findViewById(R.id.pairButton);
this.hostText = (TextView) findViewById(R.id.hostTextView);
@@ -159,6 +163,11 @@ public class Connection extends Activity {
this.statusButton.setOnClickListener(new OnClickListener() {
@Override
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;
}
Intent intent = new Intent(Connection.this, Game.class);
intent.putExtra("host", Connection.this.hostText.getText().toString());
Connection.this.startActivity(intent);
+215 -135
View File
@@ -7,6 +7,7 @@ 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;
@@ -14,9 +15,11 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Point;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -40,10 +43,17 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
private short leftStickY = 0x0000;
private int lastMouseX = Integer.MIN_VALUE;
private int lastMouseY = Integer.MIN_VALUE;
private int lastButtonState = 0;
private int lastTouchX = 0;
private int lastTouchY = 0;
private boolean hasMoved = false;
private KeyboardTranslator keybTranslator;
private int height;
private int width;
private Point screenSize = new Point(0, 0);
private NvConnection conn;
private SpinnerDialog spinner;
private boolean displayedFailureDialog = false;
@@ -108,18 +118,22 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
break;
}
int width, height, refreshRate;
int refreshRate;
width = prefs.getInt(WIDTH_PREF_STRING, DEFAULT_WIDTH);
height = prefs.getInt(HEIGHT_PREF_STRING, DEFAULT_HEIGHT);
refreshRate = prefs.getInt(REFRESH_RATE_PREF_STRING, DEFAULT_REFRESH_RATE);
sh.setFixedSize(width, height);
Display display = getWindowManager().getDefaultDisplay();
display.getSize(screenSize);
// Warn the user if they're on a metered connection
checkDataConnection();
// Start the connection
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());
}
@@ -157,142 +171,180 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
}
@Override
public void onPause() {
displayedFailureDialog = true;
conn.stop();
finish();
super.onPause();
}
@Override
protected void onDestroy() {
protected void onStop() {
super.onStop();
SpinnerDialog.closeDialogs();
Dialog.closeDialogs();
super.onDestroy();
displayedFailureDialog = true;
conn.stop();
finish();
}
private static byte getModifierState(KeyEvent event) {
byte modifier = 0;
if (event.isShiftPressed()) {
modifier |= KeyboardPacket.MODIFIER_SHIFT;
}
if (event.isCtrlPressed()) {
modifier |= KeyboardPacket.MODIFIER_CTRL;
}
if (event.isAltPressed()) {
modifier |= KeyboardPacket.MODIFIER_ALT;
}
return modifier;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
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:
return super.onKeyDown(keyCode, event);
if (event.getDevice() != null &&
(event.getDevice().getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC)) {
short translated = keybTranslator.translate(event.getKeyCode());
if (translated == 0) {
return super.onKeyDown(keyCode, event);
}
keybTranslator.sendKeyDown(translated,
getModifierState(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;
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:
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();
}
sendControllerInputPacket();
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
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:
return super.onKeyUp(keyCode, event);
if (event.getDevice() != null &&
(event.getDevice().getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC)) {
short translated = keybTranslator.translate(event.getKeyCode());
if (translated == 0) {
return super.onKeyUp(keyCode, event);
}
keybTranslator.sendKeyUp(translated,
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:
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();
}
// 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;
}
@@ -365,17 +417,38 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
// This case is for mice
else if (event.getSource() == InputDevice.SOURCE_MOUSE)
{
switch (event.getActionMasked())
{
case MotionEvent.ACTION_DOWN:
conn.sendMouseButtonDown((byte) 0x01);
break;
case MotionEvent.ACTION_UP:
conn.sendMouseButtonUp((byte) 0x01);
break;
default:
return super.onTouchEvent(event);
int changedButtons = event.getButtonState() ^ lastButtonState;
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
conn.sendMouseButtonDown((byte) 0x01);
}
else {
conn.sendMouseButtonUp((byte) 0x01);
}
}
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
conn.sendMouseButtonDown((byte) 0x03);
}
else {
conn.sendMouseButtonUp((byte) 0x03);
}
}
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
conn.sendMouseButtonDown((byte) 0x02);
}
else {
conn.sendMouseButtonUp((byte) 0x02);
}
}
updateMousePosition((int)event.getX(), (int)event.getY());
lastButtonState = event.getButtonState();
}
else
{
@@ -491,8 +564,15 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
lastMouseY != Integer.MIN_VALUE &&
!(lastMouseX == eventX && lastMouseY == eventY))
{
conn.sendMouseMove((short)(eventX - lastMouseX),
(short)(eventY - lastMouseY));
int deltaX = eventX - lastMouseX;
int deltaY = eventY - lastMouseY;
// Scale the deltas if the device resolution is different
// than the stream resolution
deltaX = (int)Math.round((double)deltaX * ((double)width / (double)screenSize.x));
deltaY = (int)Math.round((double)deltaY * ((double)height / (double)screenSize.y));
conn.sendMouseMove((short)deltaX, (short)deltaY);
}
// Update pointer location for delta calculation next time
+260
View File
@@ -0,0 +1,260 @@
package com.limelight;
import android.view.KeyEvent;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.KeycodeTranslator;
/**
* Class to translate a Android key code into the codes GFE is expecting
* @author Diego Waxemberg
* @author Cameron Gutman
*/
public class KeyboardTranslator extends KeycodeTranslator {
/**
* GFE's prefix for every key code
*/
public static final short KEY_PREFIX = (short) 0x80;
public static final int VK_0 = 48;
public static final int VK_9 = 57;
public static final int VK_A = 65;
public static final int VK_Z = 90;
public static final int VK_ALT = 18;
public static final int VK_NUMPAD0 = 96;
public static final int VK_BACK_SLASH = 92;
public static final int VK_CAPS_LOCK = 20;
public static final int VK_CLEAR = 12;
public static final int VK_COMMA = 44;
public static final int VK_CONTROL = 17;
public static final int VK_BACK_SPACE = 8;
public static final int VK_EQUALS = 61;
public static final int VK_ESCAPE = 27;
public static final int VK_F1 = 112;
public static final int VK_PERIOD = 46;
public static final int VK_INSERT = 155;
public static final int VK_OPEN_BRACKET = 91;
public static final int VK_WINDOWS = 524;
public static final int VK_MINUS = 45;
public static final int VK_END = 35;
public static final int VK_HOME = 36;
public static final int VK_NUM_LOCK = 144;
public static final int VK_PAGE_UP = 33;
public static final int VK_PAGE_DOWN = 34;
public static final int VK_PLUS = 521;
public static final int VK_CLOSE_BRACKET = 93;
public static final int VK_SCROLL_LOCK = 145;
public static final int VK_SEMICOLON = 59;
public static final int VK_SHIFT = 16;
public static final int VK_SLASH = 47;
public static final int VK_SPACE = 32;
public static final int VK_PRINTSCREEN = 154;
public static final int VK_TAB = 9;
public static final int VK_LEFT = 37;
public static final int VK_RIGHT = 39;
public static final int VK_UP = 38;
public static final int VK_DOWN = 40;
public static final int VK_BACK_QUOTE = 192;
public static final int VK_QUOTE = 222;
public static final int VK_PAUSE = 19;
/**
* Constructs a new translator for the specified connection
* @param conn the connection to which the translated codes are sent
*/
public KeyboardTranslator(NvConnection conn) {
super(conn);
}
/**
* Translates the given keycode and returns the GFE keycode
* @param keycode the code to be translated
* @return a GFE keycode for the given keycode
*/
@Override
public short translate(int keycode) {
int translated;
/* There seems to be no clean mapping between Android key codes
* and what Nvidia sends over the wire. If someone finds one,
* I'll happily delete this code :)
*/
if (keycode >= KeyEvent.KEYCODE_0 &&
keycode <= KeyEvent.KEYCODE_9) {
translated = (keycode - KeyEvent.KEYCODE_0) + VK_0;
}
else if (keycode >= KeyEvent.KEYCODE_A &&
keycode <= KeyEvent.KEYCODE_Z) {
translated = (keycode - KeyEvent.KEYCODE_A) + VK_A;
}
else if (keycode >= KeyEvent.KEYCODE_NUMPAD_0 &&
keycode <= KeyEvent.KEYCODE_NUMPAD_9) {
translated = (keycode - KeyEvent.KEYCODE_NUMPAD_0) + VK_NUMPAD0;
}
else if (keycode >= KeyEvent.KEYCODE_F1 &&
keycode <= KeyEvent.KEYCODE_F12) {
translated = (keycode - KeyEvent.KEYCODE_F1) + VK_F1;
}
else {
switch (keycode) {
case KeyEvent.KEYCODE_ALT_LEFT:
case KeyEvent.KEYCODE_ALT_RIGHT:
translated = VK_ALT;
break;
case KeyEvent.KEYCODE_BACKSLASH:
translated = 0xdc;
break;
case KeyEvent.KEYCODE_CAPS_LOCK:
translated = VK_CAPS_LOCK;
break;
case KeyEvent.KEYCODE_CLEAR:
translated = VK_CLEAR;
break;
case KeyEvent.KEYCODE_COMMA:
translated = 0xbc;
break;
case KeyEvent.KEYCODE_CTRL_LEFT:
case KeyEvent.KEYCODE_CTRL_RIGHT:
translated = VK_CONTROL;
break;
case KeyEvent.KEYCODE_DEL:
translated = VK_BACK_SPACE;
break;
case KeyEvent.KEYCODE_ENTER:
translated = 0x0d;
break;
case KeyEvent.KEYCODE_EQUALS:
translated = 0xbb;
break;
case KeyEvent.KEYCODE_ESCAPE:
translated = VK_ESCAPE;
break;
case KeyEvent.KEYCODE_FORWARD_DEL:
// Nvidia maps period to delete
translated = VK_PERIOD;
break;
case KeyEvent.KEYCODE_INSERT:
translated = -1;
break;
case KeyEvent.KEYCODE_LEFT_BRACKET:
translated = 0xdb;
break;
case KeyEvent.KEYCODE_META_LEFT:
case KeyEvent.KEYCODE_META_RIGHT:
translated = VK_WINDOWS;
break;
case KeyEvent.KEYCODE_MINUS:
translated = 0xbd;
break;
case KeyEvent.KEYCODE_MOVE_END:
translated = VK_END;
break;
case KeyEvent.KEYCODE_MOVE_HOME:
translated = VK_HOME;
break;
case KeyEvent.KEYCODE_NUM_LOCK:
translated = VK_NUM_LOCK;
break;
case KeyEvent.KEYCODE_PAGE_DOWN:
translated = VK_PAGE_DOWN;
break;
case KeyEvent.KEYCODE_PAGE_UP:
translated = VK_PAGE_UP;
break;
case KeyEvent.KEYCODE_PERIOD:
translated = 0xbe;
break;
case KeyEvent.KEYCODE_RIGHT_BRACKET:
translated = 0xdd;
break;
case KeyEvent.KEYCODE_SCROLL_LOCK:
translated = VK_SCROLL_LOCK;
break;
case KeyEvent.KEYCODE_SEMICOLON:
translated = 0xba;
break;
case KeyEvent.KEYCODE_SHIFT_LEFT:
case KeyEvent.KEYCODE_SHIFT_RIGHT:
translated = VK_SHIFT;
break;
case KeyEvent.KEYCODE_SLASH:
translated = 0xbf;
break;
case KeyEvent.KEYCODE_SPACE:
translated = VK_SPACE;
break;
case KeyEvent.KEYCODE_SYSRQ:
// Android defines this as SysRq/PrntScrn
translated = VK_PRINTSCREEN;
break;
case KeyEvent.KEYCODE_TAB:
translated = VK_TAB;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
translated = VK_LEFT;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
translated = VK_RIGHT;
break;
case KeyEvent.KEYCODE_DPAD_UP:
translated = VK_UP;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
translated = VK_DOWN;
break;
case KeyEvent.KEYCODE_GRAVE:
translated = VK_BACK_QUOTE;
break;
case KeyEvent.KEYCODE_APOSTROPHE:
translated = 0xde;
break;
case KeyEvent.KEYCODE_BREAK:
translated = VK_PAUSE;
break;
default:
System.out.println("No key for "+keycode);
return 0;
}
}
return (short) ((KEY_PREFIX << 8) | translated);
}
}
@@ -6,6 +6,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import android.graphics.PixelFormat;
import android.view.SurfaceHolder;
import com.limelight.LimeLog;
@@ -124,12 +125,15 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
LimeLog.info("Using high quality decoding");
}
SurfaceHolder sh = (SurfaceHolder)renderTarget;
sh.setFormat(PixelFormat.RGBX_8888);
int err = AvcDecoder.init(width, height, avcFlags, threadCount);
if (err != 0) {
throw new IllegalStateException("AVC decoder initialization failure: "+err);
}
AvcDecoder.setRenderTarget(((SurfaceHolder)renderTarget).getSurface());
AvcDecoder.setRenderTarget(sh.getSurface());
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
@@ -11,6 +11,8 @@ import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaCodec.BufferInfo;
@@ -31,9 +33,9 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
static {
blacklistedDecoderPrefixes = new LinkedList<String>();
blacklistedDecoderPrefixes.add("omx.google");
// TI's decoder technically supports high profile but doesn't work for some reason
blacklistedDecoderPrefixes.add("omx.TI");
blacklistedDecoderPrefixes.add("AVCDecoder");
}
static {
@@ -58,38 +60,58 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
return false;
}
public static MediaCodecInfo findSafeDecoder() {
public static void dumpDecoders() {
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
boolean badCodec = false;
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
for (String badPrefix : blacklistedDecoderPrefixes) {
String name = codecInfo.getName();
if (name.length() >= badPrefix.length()) {
String prefix = name.substring(0, badPrefix.length());
if (prefix.equalsIgnoreCase(badPrefix)) {
badCodec = true;
break;
}
LimeLog.info("Decoder: "+codecInfo.getName());
for (String type : codecInfo.getSupportedTypes()) {
LimeLog.info("\t"+type);
CodecCapabilities caps = codecInfo.getCapabilitiesForType(type);
for (CodecProfileLevel profile : caps.profileLevels) {
LimeLog.info("\t\t"+profile.profile+" "+profile.level);
}
}
if (badCodec) {
LimeLog.info("Blacklisted decoder: "+codecInfo.getName());
}
}
public static MediaCodecInfo findSafeDecoder() {
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
// Skip encoders
if (codecInfo.isEncoder()) {
continue;
}
// Check for explicitly blacklisted decoders
if (isDecoderInList(blacklistedDecoderPrefixes, codecInfo.getName())) {
LimeLog.info("Skipping blacklisted decoder: "+codecInfo.getName());
continue;
}
// Find a decoder that supports H.264 high profile
for (String mime : codecInfo.getSupportedTypes()) {
if (mime.equalsIgnoreCase("video/avc")) {
LimeLog.info("Selected decoder: "+codecInfo.getName());
return codecInfo;
LimeLog.info("Examining decoder capabilities of "+codecInfo.getName());
CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
for (CodecProfileLevel profile : caps.profileLevels) {
if (profile.profile == CodecProfileLevel.AVCProfileHigh) {
LimeLog.info("Decoder "+codecInfo.getName()+" supports high profile");
LimeLog.info("Selected decoder: "+codecInfo.getName());
return codecInfo;
}
}
LimeLog.info("Decoder "+codecInfo.getName()+" does NOT support high profile");
}
}
}
@@ -101,6 +123,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.redrawRate = redrawRate;
dumpDecoders();
MediaCodecInfo safeDecoder = findSafeDecoder();
if (safeDecoder != null) {
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());