Compare commits

...

31 Commits

Author SHA1 Message Date
Cameron Gutman c1ad6f83b9 Remove touchscreen requirement 2014-03-17 20:55:37 -04:00
Cameron Gutman 209b649eb8 Increment version 2014-03-17 14:21:16 -04:00
Cameron Gutman 9bf7bd366f Update common 2014-03-17 14:21:02 -04:00
Cameron Gutman cfd788bec0 Use the minimum stream buffer size and the sample size when choosing a buffer size 2014-03-17 14:20:12 -04:00
Cameron Gutman 5284db761c Fix leftover warning 2014-03-17 12:39:17 -04:00
Cameron Gutman 07b5e9224c Don't explicitly specify a pixel format for the rendering surface. The system will choose the most optimal format if we leave it at the default. 2014-03-17 12:34:47 -04:00
Cameron Gutman f31736ee2d Set the volume buttons to control the music stream while streaming 2014-03-17 03:55:51 -04:00
Cameron Gutman 47a3a959e5 Update common 2014-03-16 18:28:51 -04:00
Cameron Gutman c2a679e5ef File mode modified on libs 2014-03-16 18:23:03 -04:00
Cameron Gutman 71b4980553 Rebuild JNI Opus decoder 2014-03-16 17:24:43 -04:00
Cameron Gutman 0b360f8f1d Calculate a proper value for the sample buffer (48000 Hz * 2 bytes per sample * 2.5 ms) 2014-03-16 17:16:51 -04:00
Cameron Gutman 52823df62f Don't use large heap anymore for improved performance 2014-03-15 13:17:22 -04:00
Cameron Gutman 39472bdc7b Update common for reduced memory usage 2014-03-14 22:26:25 -04:00
Cameron Gutman 49ae5a6d0c Add icons by XDA member MalaBouM 2014-03-14 22:15:26 -04:00
Cameron Gutman 1c426417f3 Increment version 2014-03-13 01:38:48 -04:00
Cameron Gutman 2cd41b5141 Update common 2014-03-13 01:38:29 -04:00
Cameron Gutman 74d9afd685 Update common 2014-02-27 17:19:26 -05:00
Cameron Gutman e13241eebf Use LimeLog for logging 2014-02-26 17:20:10 -05:00
Cameron Gutman 700ec361b0 Update common 2014-02-26 17:19:49 -05:00
Cameron Gutman c7f26a179d Add the frame skipping code back for certain decoders (Qualcomm) that are slow when rendering all frames and stall the whole decoder pipeline 2014-02-26 01:56:05 -05:00
Cameron Gutman 4b2552ed64 Set MediaCodec flags on the input buffer based on the DU flags 2014-02-26 01:01:54 -05:00
Cameron Gutman ddc70e39c9 Only use direct submit on decoders that have (effectively) infinite input buffers 2014-02-19 22:18:26 -05:00
Cameron Gutman 61c0de6ca1 Update common jar 2014-02-19 21:01:09 -05:00
Cameron Gutman b0c4d47962 Report direct submit support for the MediaCodec decoder renderer 2014-02-19 21:00:39 -05:00
Cameron Gutman 6fc848ef56 Block for 100 ms instead of 100 us waiting for a frame. This reduces the CPU time wasted by useless iterations of the rendering loop. 2014-02-19 19:49:57 -05:00
Cameron Gutman c537af2273 Render the latest frame available at the time 2014-02-19 19:40:35 -05:00
Cameron Gutman 839540dc74 UI updates to make options more clear. Add scroll view to UI. 2014-02-19 19:22:54 -05:00
Cameron Gutman 22ecdd9cf8 Don't skip frames when rendering 2014-02-19 19:22:00 -05:00
Cameron Gutman bf1c7dd675 Update common jar 2014-02-17 19:22:23 -05:00
Cameron Gutman 426b40ae82 Don't allocate a new BufferInfo object for each output buffer 2014-02-17 14:21:42 -05:00
Michelle Bergeron 6683c25a39 Update readme to match Limelight-PC's changes 2014-01-21 16:54:32 -05:00
19 changed files with 222 additions and 155 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.limelight"
android:versionCode="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>
+8 -8
View File
@@ -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)
+6 -8
View File
@@ -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;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

+2 -2
View File
@@ -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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 64 KiB

+45 -53
View File
@@ -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>
+46 -47
View File
@@ -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;
}
+21 -11
View File
@@ -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;
}
}