Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9638c15c93 | |||
| 8ce972ea7a | |||
| 968557d3a8 | |||
| cf80a7380c | |||
| ceb957e7f3 | |||
| 669691d8e8 | |||
| ea57e48ed5 | |||
| f744c7d9c4 | |||
| 0d42beca93 | |||
| bafb9e6230 | |||
| 5d30a3f4ab | |||
| 70a1dd56b4 | |||
| 9857017b22 | |||
| 53474c7d28 | |||
| 283c1060a3 | |||
| eb7c54b95c | |||
| 735ad02fb4 | |||
| c1ad6f83b9 | |||
| 209b649eb8 | |||
| 9bf7bd366f | |||
| cfd788bec0 | |||
| 5284db761c | |||
| 07b5e9224c | |||
| f31736ee2d | |||
| 47a3a959e5 | |||
| c2a679e5ef | |||
| 71b4980553 | |||
| 0b360f8f1d | |||
| 52823df62f | |||
| 39472bdc7b | |||
| 49ae5a6d0c | |||
| 1c426417f3 | |||
| 2cd41b5141 | |||
| 74d9afd685 | |||
| e13241eebf | |||
| 700ec361b0 | |||
| c7f26a179d | |||
| 4b2552ed64 | |||
| ddc70e39c9 | |||
| 61c0de6ca1 | |||
| b0c4d47962 | |||
| 6fc848ef56 | |||
| c537af2273 | |||
| 839540dc74 | |||
| 22ecdd9cf8 | |||
| bf1c7dd675 | |||
| 426b40ae82 | |||
| 6683c25a39 |
@@ -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="1"
|
||||
android:versionName="1.0" >
|
||||
android:versionCode="7"
|
||||
android:versionName="2.1.1" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
@@ -10,9 +10,10 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
|
||||
<application
|
||||
android:largeHeap="true"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
@@ -22,7 +23,6 @@
|
||||
android:label="@string/app_name" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
@@ -3,22 +3,21 @@
|
||||
Limelight is an open source implementation of NVIDIA's GameStream, as used by the NVIDIA Shield.
|
||||
We reverse engineered the Shield streaming software, and created a version that can be run on any Android device.
|
||||
|
||||
Limelight will allow you to stream your full collection of Steam games from your
|
||||
Windows PC to your Android device on the same network.
|
||||
Limelight will allow you to stream your full collection of Steam games from your Windows PC to your Android device on the same network.
|
||||
|
||||
Streaming can be done remotely using the [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481)
|
||||
application.
|
||||
|
||||
[Limelight-pc](https://github.com/limelight-stream/limelight-pc) is also currently in development for Windows, OSX and Linux.
|
||||
[Limelight-pc](https://github.com/limelight-stream/limelight-pc) is also currently in development for Windows, OS X and Linux. Versions for [iOS](https://github.com/limelight-stream/limelight-ios) and [Windows Phone](https://github.com/limelight-stream/limelight-wp) are also in development.
|
||||
|
||||
##Features
|
||||
|
||||
* Streams Steam and all of your games from your PC to your Android device
|
||||
* Full gamepad support for MOGA, XBOX 360, PS3, OUYA, and Shield
|
||||
* Full gamepad support for MOGA, Xbox 360, PS3, OUYA, and Shield
|
||||
|
||||
##Features in development
|
||||
|
||||
* Use mDNS to scan for compatible GFE machines on the network
|
||||
* Use mDNS to scan for compatible GeForce Experience (GFE) machines on the network
|
||||
* Choose from the list of available games instead of just launching Steam
|
||||
* Keyboard input
|
||||
|
||||
@@ -37,8 +36,7 @@ application.
|
||||
|
||||
##Usage
|
||||
|
||||
* Ensure your Android device and your PC are on the same network or you're
|
||||
running [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481).
|
||||
* Ensure your Android device and your PC are on the same network or you're running [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481).
|
||||
* Turn on Shield Streaming in the GFE settings
|
||||
* In Limelight, enter your PC's IP or Hostname and click "Pair".
|
||||
* Accept the pairing confirmation on your PC
|
||||
@@ -47,12 +45,14 @@ application.
|
||||
|
||||
##Contribute
|
||||
|
||||
This project is being actively developed at [XDA](http://forum.xda-developers.com/showthread.php?t=2505510)
|
||||
This project is being actively developed at [XDA Developers](http://forum.xda-developers.com/showthread.php?t=2505510)
|
||||
|
||||
1. Fork us
|
||||
2. Write code
|
||||
3. Send Pull Requests
|
||||
|
||||
Check out our [website](http://limelight-stream.com) for project links and information.
|
||||
|
||||
##Authors
|
||||
|
||||
* [Cameron Gutman](https://github.com/cgutman)
|
||||
|
||||
@@ -36,19 +36,17 @@ or to a theme attribute in the form "<code>?[<i>package</i>:][<i>type</i>:]<i>na
|
||||
}
|
||||
public static final class id {
|
||||
public static final int autoDec=0x7f080005;
|
||||
public static final int config1080p30Selected=0x7f080009;
|
||||
public static final int config1080p60Selected=0x7f08000a;
|
||||
public static final int config720p30Selected=0x7f080007;
|
||||
public static final int config720p60Selected=0x7f080008;
|
||||
public static final int hardwareDec=0x7f080006;
|
||||
public static final int hostTextView=0x7f080000;
|
||||
public static final int imageQualityCheckbox=0x7f08000a;
|
||||
public static final int pairButton=0x7f080002;
|
||||
public static final int res1080pSelected=0x7f08000c;
|
||||
public static final int res720pSelected=0x7f08000b;
|
||||
public static final int resolutionGroup=0x7f080007;
|
||||
public static final int rr30Selected=0x7f080008;
|
||||
public static final int rr60Selected=0x7f080009;
|
||||
public static final int rrGroup=0x7f080003;
|
||||
public static final int softwareDec=0x7f080004;
|
||||
public static final int statusButton=0x7f080001;
|
||||
public static final int surfaceView=0x7f08000d;
|
||||
public static final int streamConfigGroup=0x7f080003;
|
||||
public static final int surfaceView=0x7f08000b;
|
||||
}
|
||||
public static final class layout {
|
||||
public static final int activity_connection=0x7f030000;
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 1.0 MiB |
@@ -28,9 +28,9 @@ int nv_opus_get_channel_count(void) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// This number assumes 2 channels at 48 KHz
|
||||
// This number assumes 16-bit samples at 48 KHz with 2.5 ms frames
|
||||
int nv_opus_get_max_out_shorts(void) {
|
||||
return 512*nv_opus_get_channel_count();
|
||||
return 240*nv_opus_get_channel_count();
|
||||
}
|
||||
|
||||
// The Opus stream is 48 KHz
|
||||
|
||||
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 64 KiB |
@@ -1,4 +1,4 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
@@ -7,6 +7,10 @@
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
tools:context=".Connection" >
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<EditText
|
||||
android:id="@+id/hostTextView"
|
||||
@@ -30,7 +34,7 @@
|
||||
|
||||
<requestFocus />
|
||||
|
||||
</Button>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
android:id="@+id/pairButton"
|
||||
@@ -43,8 +47,9 @@
|
||||
<RadioGroup
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/rrGroup"
|
||||
android:layout_below="@+id/rrGroup"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/streamConfigGroup"
|
||||
android:layout_marginTop="25dp"
|
||||
android:orientation="vertical" >
|
||||
|
||||
@@ -58,7 +63,7 @@
|
||||
android:id="@+id/autoDec"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Auto-select Decoder" />
|
||||
android:text="Auto-select Decoder (Recommended)" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/hardwareDec"
|
||||
@@ -68,56 +73,43 @@
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rrGroup"
|
||||
android:id="@+id/streamConfigGroup"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/resolutionGroup"
|
||||
android:layout_below="@+id/resolutionGroup"
|
||||
android:layout_marginTop="15dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rr30Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="30 FPS" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rr60Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="60 FPS (may increase lag)" />
|
||||
</RadioGroup>
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/resolutionGroup"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/imageQualityCheckbox"
|
||||
android:layout_below="@+id/imageQualityCheckbox"
|
||||
android:layout_marginTop="17dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/res720pSelected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="720p" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/res1080pSelected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="1080p (may increase lag)" />
|
||||
</RadioGroup>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/imageQualityCheckbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/pairButton"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Prefer image quality over performance" />
|
||||
android:layout_marginTop="25dp"
|
||||
android:orientation="vertical" >
|
||||
|
||||
</RelativeLayout>
|
||||
<RadioButton
|
||||
android:id="@+id/config720p30Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="720p 30 FPS (Only recommended for poor devices or networks)" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/config720p60Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="720p 60 FPS (Recommended for most devices and networks)" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/config1080p30Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="1080p 30 FPS (Recommended for most devices if 1080p streaming is desired)" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/config1080p60Selected"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="1080p 60 FPS (Requires extremely fast device and network)" />
|
||||
</RadioGroup>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
@@ -15,8 +15,8 @@ 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.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.RadioButton;
|
||||
@@ -31,8 +31,7 @@ public class Connection extends Activity {
|
||||
private Button statusButton, pairButton;
|
||||
private TextView hostText;
|
||||
private SharedPreferences prefs;
|
||||
private CheckBox qualityCheckbox;
|
||||
private RadioButton rbutton720p, rbutton1080p, rbutton30fps, rbutton60fps;
|
||||
private RadioButton rbutton720p30, rbutton720p60, rbutton1080p30, rbutton1080p60;
|
||||
private RadioButton forceSoftDec, autoDec, forceHardDec;
|
||||
|
||||
private static final String DEFAULT_HOST = "";
|
||||
@@ -54,38 +53,45 @@ 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);
|
||||
this.qualityCheckbox = (CheckBox) findViewById(R.id.imageQualityCheckbox);
|
||||
this.rbutton720p = (RadioButton) findViewById(R.id.res720pSelected);
|
||||
this.rbutton1080p = (RadioButton) findViewById(R.id.res1080pSelected);
|
||||
this.rbutton30fps = (RadioButton) findViewById(R.id.rr30Selected);
|
||||
this.rbutton60fps = (RadioButton) findViewById(R.id.rr60Selected);
|
||||
this.rbutton720p30 = (RadioButton) findViewById(R.id.config720p30Selected);
|
||||
this.rbutton720p60 = (RadioButton) findViewById(R.id.config720p60Selected);
|
||||
this.rbutton1080p30 = (RadioButton) findViewById(R.id.config1080p30Selected);
|
||||
this.rbutton1080p60 = (RadioButton) findViewById(R.id.config1080p60Selected);
|
||||
this.forceSoftDec = (RadioButton) findViewById(R.id.softwareDec);
|
||||
this.autoDec = (RadioButton) findViewById(R.id.autoDec);
|
||||
this.forceHardDec = (RadioButton) findViewById(R.id.hardwareDec);
|
||||
|
||||
prefs = getSharedPreferences(Game.PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||
this.hostText.setText(prefs.getString(Connection.HOST_KEY, Connection.DEFAULT_HOST));
|
||||
this.qualityCheckbox.setChecked(prefs.getBoolean(Game.QUALITY_PREF_STRING, false));
|
||||
|
||||
if (prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720) {
|
||||
rbutton720p.setChecked(true);
|
||||
rbutton1080p.setChecked(false);
|
||||
boolean res720p = prefs.getInt(Game.HEIGHT_PREF_STRING, Game.DEFAULT_HEIGHT) == 720;
|
||||
boolean fps30 = prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30;
|
||||
|
||||
rbutton720p30.setChecked(false);
|
||||
rbutton720p60.setChecked(false);
|
||||
rbutton1080p30.setChecked(false);
|
||||
rbutton1080p60.setChecked(false);
|
||||
if (res720p) {
|
||||
if (fps30) {
|
||||
rbutton720p30.setChecked(true);
|
||||
}
|
||||
else {
|
||||
rbutton720p60.setChecked(true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
rbutton1080p.setChecked(true);
|
||||
rbutton720p.setChecked(false);
|
||||
}
|
||||
|
||||
if (prefs.getInt(Game.REFRESH_RATE_PREF_STRING, Game.DEFAULT_REFRESH_RATE) == 30) {
|
||||
rbutton30fps.setChecked(true);
|
||||
rbutton60fps.setChecked(false);
|
||||
}
|
||||
else {
|
||||
rbutton60fps.setChecked(true);
|
||||
rbutton30fps.setChecked(false);
|
||||
if (fps30) {
|
||||
rbutton1080p30.setChecked(true);
|
||||
}
|
||||
else {
|
||||
rbutton1080p60.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
||||
@@ -115,19 +121,25 @@ public class Connection extends Activity {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buttonView == rbutton30fps) {
|
||||
prefs.edit().putInt(Game.REFRESH_RATE_PREF_STRING, 30).commit();
|
||||
}
|
||||
else if (buttonView == rbutton60fps) {
|
||||
prefs.edit().putInt(Game.REFRESH_RATE_PREF_STRING, 60).commit();
|
||||
}
|
||||
else if (buttonView == rbutton720p) {
|
||||
if (buttonView == rbutton720p30) {
|
||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||
putInt(Game.HEIGHT_PREF_STRING, 720).commit();
|
||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).commit();
|
||||
}
|
||||
else if (buttonView == rbutton1080p) {
|
||||
else if (buttonView == rbutton720p60) {
|
||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1280).
|
||||
putInt(Game.HEIGHT_PREF_STRING, 720).
|
||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).commit();
|
||||
}
|
||||
else if (buttonView == rbutton1080p30) {
|
||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||
putInt(Game.HEIGHT_PREF_STRING, 1080).commit();
|
||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||
putInt(Game.REFRESH_RATE_PREF_STRING, 30).commit();
|
||||
}
|
||||
else if (buttonView == rbutton1080p60) {
|
||||
prefs.edit().putInt(Game.WIDTH_PREF_STRING, 1920).
|
||||
putInt(Game.HEIGHT_PREF_STRING, 1080).
|
||||
putInt(Game.REFRESH_RATE_PREF_STRING, 60).commit();
|
||||
}
|
||||
else if (buttonView == forceSoftDec) {
|
||||
prefs.edit().putInt(Game.DECODER_PREF_STRING, Game.FORCE_SOFTWARE_DECODER).commit();
|
||||
@@ -140,26 +152,22 @@ public class Connection extends Activity {
|
||||
}
|
||||
}
|
||||
};
|
||||
rbutton720p.setOnCheckedChangeListener(occl);
|
||||
rbutton1080p.setOnCheckedChangeListener(occl);
|
||||
rbutton30fps.setOnCheckedChangeListener(occl);
|
||||
rbutton60fps.setOnCheckedChangeListener(occl);
|
||||
rbutton720p30.setOnCheckedChangeListener(occl);
|
||||
rbutton720p60.setOnCheckedChangeListener(occl);
|
||||
rbutton1080p30.setOnCheckedChangeListener(occl);
|
||||
rbutton1080p60.setOnCheckedChangeListener(occl);
|
||||
forceSoftDec.setOnCheckedChangeListener(occl);
|
||||
forceHardDec.setOnCheckedChangeListener(occl);
|
||||
autoDec.setOnCheckedChangeListener(occl);
|
||||
|
||||
this.qualityCheckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton checkbox, boolean isChecked) {
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putBoolean(Game.QUALITY_PREF_STRING, isChecked);
|
||||
editor.commit();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
@@ -182,7 +190,7 @@ public class Connection extends Activity {
|
||||
}
|
||||
|
||||
if (macAddress == null) {
|
||||
System.out.println("Couldn't find a MAC address");
|
||||
LimeLog.severe("Couldn't find a MAC address");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,19 @@ 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;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.PixelFormat;
|
||||
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;
|
||||
@@ -39,25 +43,31 @@ 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;
|
||||
|
||||
public static final String PREFS_FILE_NAME = "gameprefs";
|
||||
|
||||
public static final String QUALITY_PREF_STRING = "Quality";
|
||||
public static final String WIDTH_PREF_STRING = "Width";
|
||||
public static final String HEIGHT_PREF_STRING = "Height";
|
||||
public static final String REFRESH_RATE_PREF_STRING = "RefreshRate";
|
||||
public static final String WIDTH_PREF_STRING = "ResH";
|
||||
public static final String HEIGHT_PREF_STRING = "ResV";
|
||||
public static final String REFRESH_RATE_PREF_STRING = "FPS";
|
||||
public static final String DECODER_PREF_STRING = "Decoder";
|
||||
|
||||
public static final int DEFAULT_WIDTH = 1280;
|
||||
public static final int DEFAULT_HEIGHT = 720;
|
||||
public static final int DEFAULT_REFRESH_RATE = 30;
|
||||
public static final int DEFAULT_REFRESH_RATE = 60;
|
||||
public static final int DEFAULT_DECODER = 0;
|
||||
|
||||
public static final int FORCE_HARDWARE_DECODER = -1;
|
||||
@@ -78,6 +88,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
// We don't want a title bar
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
// Change volume button behavior
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
// Inflate the content
|
||||
setContentView(R.layout.activity_game);
|
||||
|
||||
@@ -87,7 +100,6 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
sv.setOnTouchListener(this);
|
||||
|
||||
SurfaceHolder sh = sv.getHolder();
|
||||
sh.setFormat(PixelFormat.RGBX_8888);
|
||||
|
||||
// Start the spinner
|
||||
spinner = SpinnerDialog.displayDialog(this, "Establishing Connection", "Starting connection", true);
|
||||
@@ -95,9 +107,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;
|
||||
if (prefs.getBoolean(QUALITY_PREF_STRING, false)) {
|
||||
drFlags |= VideoDecoderRenderer.FLAG_PREFER_QUALITY;
|
||||
}
|
||||
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
||||
case Game.FORCE_SOFTWARE_DECODER:
|
||||
drFlags |= VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING;
|
||||
@@ -109,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());
|
||||
}
|
||||
@@ -129,10 +142,11 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
{
|
||||
ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (mgr.isActiveNetworkMetered()) {
|
||||
displayMessage("Warning: Your active network connection is metered!");
|
||||
displayTransientMessage("Warning: Your active network connection is metered!");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private void hideSystemUi() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
@@ -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
|
||||
@@ -567,4 +647,14 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void displayTransientMessage(final String message) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(Game.this, message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,15 +4,19 @@ import android.media.AudioFormat;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioTrack;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.audio.AudioRenderer;
|
||||
|
||||
public class AndroidAudioRenderer implements AudioRenderer {
|
||||
|
||||
public static final int FRAME_SIZE = 960;
|
||||
|
||||
private AudioTrack track;
|
||||
|
||||
@Override
|
||||
public void streamInitialized(int channelCount, int sampleRate) {
|
||||
int channelConfig;
|
||||
int bufferSize;
|
||||
|
||||
switch (channelCount)
|
||||
{
|
||||
@@ -26,11 +30,20 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
throw new IllegalArgumentException("Decoder returned unhandled channel count");
|
||||
}
|
||||
|
||||
bufferSize = Math.max(AudioTrack.getMinBufferSize(sampleRate,
|
||||
channelConfig,
|
||||
AudioFormat.ENCODING_PCM_16BIT),
|
||||
FRAME_SIZE * 2);
|
||||
|
||||
// Round to next frame
|
||||
bufferSize = (((bufferSize + (FRAME_SIZE - 1)) / FRAME_SIZE) * FRAME_SIZE);
|
||||
|
||||
LimeLog.info("Audio track buffer size: "+bufferSize);
|
||||
track = new AudioTrack(AudioManager.STREAM_MUSIC,
|
||||
sampleRate,
|
||||
channelConfig,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
1024, // 1KB buffer
|
||||
bufferSize,
|
||||
AudioTrack.MODE_STREAM);
|
||||
|
||||
track.play();
|
||||
@@ -47,4 +60,9 @@ public class AndroidAudioRenderer implements AudioRenderer {
|
||||
track.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import android.graphics.PixelFormat;
|
||||
import android.view.SurfaceHolder;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.ByteBufferDescriptor;
|
||||
import com.limelight.nvstream.av.DecodeUnit;
|
||||
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
|
||||
@@ -120,19 +122,22 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
|
||||
// Disable the non-compliant speed optimizations
|
||||
avcFlags &= ~AvcDecoder.FAST_DECODE;
|
||||
|
||||
System.out.println("Using high quality decoding");
|
||||
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());
|
||||
|
||||
System.out.println("Using software decoding (performance level: "+perfLevel+")");
|
||||
LimeLog.info("Using software decoding (performance level: "+perfLevel+")");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,4 +212,9 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
return (AvcDecoder.decode(data, 0, decodeUnit.getDataLength()) == 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,4 +40,9 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
|
||||
return decoderRenderer.submitDecodeUnit(du);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return decoderRenderer.getCapabilities();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,12 +4,15 @@ import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.nvstream.av.ByteBufferDescriptor;
|
||||
import com.limelight.nvstream.av.DecodeUnit;
|
||||
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;
|
||||
@@ -22,15 +25,17 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
private Thread rendererThread;
|
||||
private int redrawRate;
|
||||
private boolean needsSpsFixup;
|
||||
private boolean fastInputQueueing;
|
||||
|
||||
public static final List<String> blacklistedDecoderPrefixes;
|
||||
public static final List<String> spsFixupDecoderPrefixes;
|
||||
public static final List<String> fastInputQueueingPrefixes;
|
||||
|
||||
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 {
|
||||
@@ -38,8 +43,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
spsFixupDecoderPrefixes.add("omx.nvidia");
|
||||
}
|
||||
|
||||
private static boolean decoderNeedsSpsFixup(String decoderName) {
|
||||
for (String badPrefix : spsFixupDecoderPrefixes) {
|
||||
static {
|
||||
fastInputQueueingPrefixes = new LinkedList<String>();
|
||||
fastInputQueueingPrefixes.add("omx.nvidia");
|
||||
}
|
||||
|
||||
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||
for (String badPrefix : decoderList) {
|
||||
if (decoderName.length() >= badPrefix.length()) {
|
||||
String prefix = decoderName.substring(0, badPrefix.length());
|
||||
if (prefix.equalsIgnoreCase(badPrefix)) {
|
||||
@@ -50,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) {
|
||||
System.out.println("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")) {
|
||||
System.out.println("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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,17 +123,24 @@ 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());
|
||||
needsSpsFixup = decoderNeedsSpsFixup(safeDecoder.getName());
|
||||
needsSpsFixup = isDecoderInList(spsFixupDecoderPrefixes, safeDecoder.getName());
|
||||
if (needsSpsFixup) {
|
||||
System.out.println("Decoder "+safeDecoder.getName()+" needs SPS fixup");
|
||||
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS fixup");
|
||||
}
|
||||
fastInputQueueing = isDecoderInList(fastInputQueueingPrefixes, safeDecoder.getName());
|
||||
if (fastInputQueueing) {
|
||||
LimeLog.info("Decoder "+safeDecoder.getName()+" supports fast input queueing");
|
||||
}
|
||||
}
|
||||
else {
|
||||
videoDecoder = MediaCodec.createDecoderByType("video/avc");
|
||||
needsSpsFixup = false;
|
||||
fastInputQueueing = false;
|
||||
}
|
||||
|
||||
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height);
|
||||
@@ -113,7 +150,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
videoDecoderInputBuffers = videoDecoder.getInputBuffers();
|
||||
|
||||
System.out.println("Using hardware decoding");
|
||||
LimeLog.info("Using hardware decoding");
|
||||
}
|
||||
|
||||
private void startRendererThread()
|
||||
@@ -122,30 +159,40 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
@Override
|
||||
public void run() {
|
||||
long nextFrameTimeUs = 0;
|
||||
BufferInfo info = new BufferInfo();
|
||||
while (!isInterrupted())
|
||||
{
|
||||
BufferInfo info = new BufferInfo();
|
||||
int outIndex = videoDecoder.dequeueOutputBuffer(info, 100);
|
||||
// Block for a maximum of 100 ms
|
||||
int outIndex = videoDecoder.dequeueOutputBuffer(info, 100000);
|
||||
switch (outIndex) {
|
||||
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
System.out.println("Output buffers changed");
|
||||
LimeLog.info("Output buffers changed");
|
||||
break;
|
||||
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
|
||||
System.out.println("Output format changed");
|
||||
System.out.println("New output Format: " + videoDecoder.getOutputFormat());
|
||||
LimeLog.info("Output format changed");
|
||||
LimeLog.info("New output Format: " + videoDecoder.getOutputFormat());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (outIndex >= 0) {
|
||||
int lastIndex = outIndex;
|
||||
boolean render = false;
|
||||
|
||||
|
||||
if (currentTimeUs() >= nextFrameTimeUs) {
|
||||
render = true;
|
||||
nextFrameTimeUs = computePresentationTime(redrawRate);
|
||||
}
|
||||
|
||||
// Get the last output buffer in the queue
|
||||
while ((outIndex = videoDecoder.dequeueOutputBuffer(info, 0)) >= 0) {
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, false);
|
||||
lastIndex = outIndex;
|
||||
}
|
||||
|
||||
videoDecoder.releaseOutputBuffer(outIndex, render);
|
||||
// Render that buffer if it's time for the next frame
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, render);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +237,17 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
return false;
|
||||
}
|
||||
|
||||
int mcFlags = 0;
|
||||
|
||||
if ((decodeUnit.getFlags() & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
|
||||
LimeLog.info("Codec config");
|
||||
mcFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
if ((decodeUnit.getFlags() & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) {
|
||||
LimeLog.info("Sync frame");
|
||||
mcFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
|
||||
}
|
||||
|
||||
int inputIndex = videoDecoder.dequeueInputBuffer(-1);
|
||||
if (inputIndex >= 0)
|
||||
{
|
||||
@@ -209,7 +267,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
switch (header.length) {
|
||||
case 26:
|
||||
System.out.println("Modifying SPS (26)");
|
||||
LimeLog.info("Modifying SPS (26)");
|
||||
buf.put(header.data, header.offset, 24);
|
||||
buf.put((byte) 0x11);
|
||||
buf.put((byte) 0xe3);
|
||||
@@ -218,7 +276,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
spsLength = header.length + 2;
|
||||
break;
|
||||
case 27:
|
||||
System.out.println("Modifying SPS (27)");
|
||||
LimeLog.info("Modifying SPS (27)");
|
||||
buf.put(header.data, header.offset, 25);
|
||||
buf.put((byte) 0x04);
|
||||
buf.put((byte) 0x78);
|
||||
@@ -227,7 +285,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
spsLength = header.length + 2;
|
||||
break;
|
||||
default:
|
||||
System.out.println("Unknown SPS of length "+header.length);
|
||||
LimeLog.warning("Unknown SPS of length "+header.length);
|
||||
buf.put(header.data, header.offset, header.length);
|
||||
spsLength = header.length;
|
||||
break;
|
||||
@@ -235,7 +293,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, spsLength,
|
||||
0, decodeUnit.getFlags());
|
||||
0, mcFlags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -248,9 +306,14 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, decodeUnit.getDataLength(),
|
||||
0, decodeUnit.getFlags());
|
||||
0, mcFlags);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapabilities() {
|
||||
return fastInputQueueing ? VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT : 0;
|
||||
}
|
||||
}
|
||||
|
||||