Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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="3"
|
||||
android:versionName="2.0.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"
|
||||
@@ -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>
|
||||
|
||||
@@ -16,7 +16,6 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.RadioButton;
|
||||
@@ -31,8 +30,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 = "";
|
||||
@@ -57,35 +55,39 @@ public class Connection extends Activity {
|
||||
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 +117,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,23 +148,14 @@ 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) {
|
||||
@@ -182,7 +181,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@ import com.limelight.nvstream.input.ControllerPacket;
|
||||
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.media.AudioManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.InputDevice;
|
||||
@@ -49,15 +50,14 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
|
||||
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 +78,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 +90,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 +97,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;
|
||||
@@ -129,10 +128,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
|
||||
@@ -567,4 +567,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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.nio.ByteBuffer;
|
||||
|
||||
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,7 +121,7 @@ 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");
|
||||
}
|
||||
|
||||
int err = AvcDecoder.init(width, height, avcFlags, threadCount);
|
||||
@@ -132,7 +133,7 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
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 +208,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,6 +4,7 @@ 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;
|
||||
@@ -22,9 +23,11 @@ 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>();
|
||||
@@ -38,8 +41,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)) {
|
||||
@@ -74,13 +82,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
}
|
||||
|
||||
if (badCodec) {
|
||||
System.out.println("Blacklisted decoder: "+codecInfo.getName());
|
||||
LimeLog.info("Blacklisted decoder: "+codecInfo.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String mime : codecInfo.getSupportedTypes()) {
|
||||
if (mime.equalsIgnoreCase("video/avc")) {
|
||||
System.out.println("Selected decoder: "+codecInfo.getName());
|
||||
LimeLog.info("Selected decoder: "+codecInfo.getName());
|
||||
return codecInfo;
|
||||
}
|
||||
}
|
||||
@@ -96,14 +104,19 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
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 +126,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 +135,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 +213,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 +243,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 +252,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 +261,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 +269,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, spsLength,
|
||||
0, decodeUnit.getFlags());
|
||||
0, mcFlags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -248,9 +282,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;
|
||||
}
|
||||
}
|
||||
|
||||