Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b52a6ce93c | |||
| 7ab4e5d0a5 | |||
| 095dfd8035 | |||
| f7c33ef975 | |||
| 57b0bce5a4 | |||
| 7a017d7b97 | |||
| d2773be32e | |||
| 93a7d9f181 | |||
| 9703cf4ffe | |||
| 41ec64e87c | |||
| aca92a5056 | |||
| fc9c4d9aaa | |||
| d8c6a544f0 | |||
| 7e100f2c9c |
+2
-2
@@ -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="10"
|
||||
android:versionName="2.1.4" >
|
||||
android:versionCode="12"
|
||||
android:versionName="2.1.6" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
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.
|
||||
|
||||
Streaming can be done remotely using the [Shield Proxy](http://forum.xda-developers.com/showthread.php?t=2435481)
|
||||
application.
|
||||
Limelight will allow you to stream your full collection of Steam games from your Windows PC to your Android device,
|
||||
in your own home, or over the internet.
|
||||
|
||||
[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.
|
||||
|
||||
@@ -36,9 +34,10 @@ 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).
|
||||
* Turn on Shield Streaming in the GFE settings
|
||||
* In Limelight, enter your PC's IP or Hostname and click "Pair".
|
||||
* If you are connecting from outside the same network, turn on internet
|
||||
streaming
|
||||
* In Limelight, enter your PC's IP or Hostname and click "Pair"
|
||||
* Accept the pairing confirmation on your PC
|
||||
* In Limelight, click "Start Streaming"
|
||||
* Play games!
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"SupportedControllers" : {
|
||||
"Gamepad" : {},
|
||||
"Remote" : "false",
|
||||
"SecondScreen" : {
|
||||
"DPad" : "false",
|
||||
"AnalogSticks" : "0",
|
||||
"DigitalButtons" : "0",
|
||||
"Mouse" : "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -177,6 +177,11 @@ public class Connection extends Activity {
|
||||
this.pairButton.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;
|
||||
}
|
||||
|
||||
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_LONG).show();
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -34,7 +34,7 @@ import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
||||
public class Game extends Activity implements OnGenericMotionListener, OnTouchListener, NvConnectionListener {
|
||||
public class Game extends Activity implements SurfaceHolder.Callback, OnGenericMotionListener, OnTouchListener, NvConnectionListener {
|
||||
private int lastMouseX = Integer.MIN_VALUE;
|
||||
private int lastMouseY = Integer.MIN_VALUE;
|
||||
private int lastButtonState = 0;
|
||||
@@ -52,6 +52,10 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
private NvConnection conn;
|
||||
private SpinnerDialog spinner;
|
||||
private boolean displayedFailureDialog = false;
|
||||
private boolean connecting = false;
|
||||
private boolean connected = false;
|
||||
|
||||
private int drFlags = 0;
|
||||
|
||||
public static final String PREFS_FILE_NAME = "gameprefs";
|
||||
|
||||
@@ -101,7 +105,6 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
|
||||
// Read the stream preferences
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS_FILE_NAME, Context.MODE_MULTI_PROCESS);
|
||||
int drFlags = 0;
|
||||
switch (prefs.getInt(Game.DECODER_PREF_STRING, Game.DEFAULT_DECODER)) {
|
||||
case Game.FORCE_SOFTWARE_DECODER:
|
||||
drFlags |= VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING;
|
||||
@@ -130,8 +133,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
new StreamConfiguration(width, height, refreshRate));
|
||||
keybTranslator = new KeyboardTranslator(conn);
|
||||
controllerHandler = new ControllerHandler(conn);
|
||||
conn.start(PlatformBinding.getDeviceName(), sv.getHolder(), drFlags,
|
||||
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer());
|
||||
|
||||
// The connection will be started when the surface gets created
|
||||
sh.addCallback(this);
|
||||
}
|
||||
|
||||
private void checkDataConnection()
|
||||
@@ -420,6 +424,7 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
displayedFailureDialog = true;
|
||||
Dialog.displayDialog(this, "Connection Error", "Starting "+stage.getName()+" failed", true);
|
||||
conn.stop();
|
||||
connecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,6 +435,7 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
e.printStackTrace();
|
||||
Dialog.displayDialog(this, "Connection Terminated", "The connection failed unexpectedly", true);
|
||||
conn.stop();
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +444,9 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
spinner.dismiss();
|
||||
spinner = null;
|
||||
|
||||
connecting = false;
|
||||
connected = true;
|
||||
|
||||
hideSystemUi();
|
||||
}
|
||||
|
||||
@@ -460,4 +469,25 @@ public class Game extends Activity implements OnGenericMotionListener, OnTouchLi
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
if (!connected && !connecting) {
|
||||
connecting = true;
|
||||
conn.start(PlatformBinding.getDeviceName(), holder, drFlags,
|
||||
PlatformBinding.getAudioRenderer(), new ConfigurableDecoderRenderer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (connected) {
|
||||
conn.stop();
|
||||
connected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +325,7 @@ public class ControllerHandler {
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
inputMap &= ~ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
inputMap &= ~ControllerPacket.A_FLAG;
|
||||
break;
|
||||
@@ -405,6 +406,7 @@ public class ControllerHandler {
|
||||
case KeyEvent.KEYCODE_BUTTON_B:
|
||||
inputMap |= ControllerPacket.B_FLAG;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
case KeyEvent.KEYCODE_BUTTON_A:
|
||||
inputMap |= ControllerPacket.A_FLAG;
|
||||
break;
|
||||
|
||||
@@ -24,23 +24,27 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
private MediaCodec videoDecoder;
|
||||
private Thread rendererThread;
|
||||
private int redrawRate;
|
||||
private boolean needsSpsFixup;
|
||||
private boolean needsSpsBitstreamFixup;
|
||||
private boolean needsSpsNumRefFixup;
|
||||
private boolean fastInputQueueing;
|
||||
|
||||
public static final List<String> blacklistedDecoderPrefixes;
|
||||
public static final List<String> spsFixupDecoderPrefixes;
|
||||
public static final List<String> spsFixupBitsreamFixupDecoderPrefixes;
|
||||
public static final List<String> spsFixupNumRefFixupDecoderPrefixes;
|
||||
public static final List<String> fastInputQueueingPrefixes;
|
||||
|
||||
static {
|
||||
blacklistedDecoderPrefixes = new LinkedList<String>();
|
||||
|
||||
// TI's decoder technically supports high profile but doesn't work for some reason
|
||||
blacklistedDecoderPrefixes.add("omx.TI");
|
||||
// Nothing here right now :)
|
||||
}
|
||||
|
||||
static {
|
||||
spsFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupDecoderPrefixes.add("omx.nvidia");
|
||||
spsFixupBitsreamFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupBitsreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||
|
||||
spsFixupNumRefFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupNumRefFixupDecoderPrefixes.add("omx.TI");
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -128,9 +132,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
MediaCodecInfo safeDecoder = findSafeDecoder();
|
||||
if (safeDecoder != null) {
|
||||
videoDecoder = MediaCodec.createByCodecName(safeDecoder.getName());
|
||||
needsSpsFixup = isDecoderInList(spsFixupDecoderPrefixes, safeDecoder.getName());
|
||||
if (needsSpsFixup) {
|
||||
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS fixup");
|
||||
needsSpsBitstreamFixup = isDecoderInList(spsFixupBitsreamFixupDecoderPrefixes, safeDecoder.getName());
|
||||
needsSpsNumRefFixup = isDecoderInList(spsFixupNumRefFixupDecoderPrefixes, safeDecoder.getName());
|
||||
if (needsSpsBitstreamFixup) {
|
||||
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS bitstream restrictions fixup");
|
||||
}
|
||||
if (needsSpsNumRefFixup) {
|
||||
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS ref num fixup");
|
||||
}
|
||||
fastInputQueueing = isDecoderInList(fastInputQueueingPrefixes, safeDecoder.getName());
|
||||
if (fastInputQueueing) {
|
||||
@@ -139,7 +147,8 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
}
|
||||
else {
|
||||
videoDecoder = MediaCodec.createDecoderByType("video/avc");
|
||||
needsSpsFixup = false;
|
||||
needsSpsBitstreamFixup = false;
|
||||
needsSpsNumRefFixup = false;
|
||||
fastInputQueueing = false;
|
||||
}
|
||||
|
||||
@@ -256,39 +265,49 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
// Clear old input data
|
||||
buf.clear();
|
||||
|
||||
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
||||
// or max_dec_frame_buffering which increases decoding latency on Tegra.
|
||||
// We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it.
|
||||
if (needsSpsFixup) {
|
||||
if (needsSpsBitstreamFixup || needsSpsNumRefFixup) {
|
||||
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
|
||||
// Check for SPS NALU type
|
||||
if (header.data[header.offset+4] == 0x67) {
|
||||
int spsLength;
|
||||
// TI OMAP4 requires a reference frame count of 1 to decode successfully
|
||||
if (needsSpsNumRefFixup) {
|
||||
LimeLog.info("Fixing up num ref frames");
|
||||
this.replace(header, 80, 9, new byte[] {0x40}, 3);
|
||||
}
|
||||
|
||||
switch (header.length) {
|
||||
case 26:
|
||||
LimeLog.info("Modifying SPS (26)");
|
||||
buf.put(header.data, header.offset, 24);
|
||||
buf.put((byte) 0x11);
|
||||
buf.put((byte) 0xe3);
|
||||
buf.put((byte) 0x06);
|
||||
buf.put((byte) 0x50);
|
||||
spsLength = header.length + 2;
|
||||
break;
|
||||
case 27:
|
||||
LimeLog.info("Modifying SPS (27)");
|
||||
buf.put(header.data, header.offset, 25);
|
||||
buf.put((byte) 0x04);
|
||||
buf.put((byte) 0x78);
|
||||
buf.put((byte) 0xc1);
|
||||
buf.put((byte) 0x94);
|
||||
spsLength = header.length + 2;
|
||||
break;
|
||||
default:
|
||||
LimeLog.warning("Unknown SPS of length "+header.length);
|
||||
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
|
||||
// or max_dec_frame_buffering which increases decoding latency on Tegra.
|
||||
// We manually modify the SPS here to speed-up decoding if the decoder was flagged as needing it.
|
||||
int spsLength;
|
||||
if (needsSpsBitstreamFixup) {
|
||||
switch (header.length) {
|
||||
case 26:
|
||||
LimeLog.info("Adding bitstream restrictions to SPS (26)");
|
||||
buf.put(header.data, header.offset, 24);
|
||||
buf.put((byte) 0x11);
|
||||
buf.put((byte) 0xe3);
|
||||
buf.put((byte) 0x06);
|
||||
buf.put((byte) 0x50);
|
||||
spsLength = header.length + 2;
|
||||
break;
|
||||
case 27:
|
||||
LimeLog.info("Adding bitstream restrictions to SPS (27)");
|
||||
buf.put(header.data, header.offset, 25);
|
||||
buf.put((byte) 0x04);
|
||||
buf.put((byte) 0x78);
|
||||
buf.put((byte) 0xc1);
|
||||
buf.put((byte) 0x94);
|
||||
spsLength = header.length + 2;
|
||||
break;
|
||||
default:
|
||||
LimeLog.warning("Unknown SPS of length "+header.length);
|
||||
buf.put(header.data, header.offset, header.length);
|
||||
spsLength = header.length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
buf.put(header.data, header.offset, header.length);
|
||||
spsLength = header.length;
|
||||
break;
|
||||
}
|
||||
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
@@ -316,4 +335,83 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
public int getCapabilities() {
|
||||
return fastInputQueueing ? VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace bits in array
|
||||
* @param source array in which bits should be replaced
|
||||
* @param srcOffset offset in bits where replacement should take place
|
||||
* @param srcLength length in bits of data that should be replaced
|
||||
* @param data data array with the the replacement data
|
||||
* @param dataLength length of replacement data in bits
|
||||
*/
|
||||
public void replace(ByteBufferDescriptor source, int srcOffset, int srcLength, byte[] data, int dataLength) {
|
||||
//Add 7 to always round up
|
||||
int length = (source.length*8-srcLength+dataLength+7)/8;
|
||||
|
||||
int bitOffset = srcOffset%8;
|
||||
int byteOffset = srcOffset/8;
|
||||
|
||||
byte dest[] = null;
|
||||
int offset = 0;
|
||||
if (length>source.length) {
|
||||
dest = new byte[length];
|
||||
|
||||
//Copy the first bytes
|
||||
System.arraycopy(source.data, source.offset, dest, offset, byteOffset);
|
||||
} else {
|
||||
dest = source.data;
|
||||
offset = source.offset;
|
||||
}
|
||||
|
||||
int byteLength = (bitOffset+dataLength+7)/8;
|
||||
int bitTrailing = 8 - (srcOffset+dataLength) % 8;
|
||||
for (int i=0;i<byteLength;i++) {
|
||||
byte result = 0;
|
||||
if (i != 0)
|
||||
result = (byte) (data[i-1] << 8-bitOffset);
|
||||
else if (bitOffset > 0)
|
||||
result = (byte) (source.data[byteOffset+source.offset] & (0xFF << 8-bitOffset));
|
||||
|
||||
if (i == 0 || i != byteLength-1) {
|
||||
byte moved = (byte) ((data[i]&0xFF) >>> bitOffset);
|
||||
result |= moved;
|
||||
}
|
||||
|
||||
if (i == byteLength-1 && bitTrailing > 0) {
|
||||
int sourceOffset = srcOffset+srcLength/8;
|
||||
int bitMove = (dataLength-srcLength)%8;
|
||||
if (bitMove<0) {
|
||||
result |= (byte) (source.data[sourceOffset+source.offset] << -bitMove & (0xFF >>> bitTrailing));
|
||||
result |= (byte) (source.data[sourceOffset+1+source.offset] << -bitMove & (0xFF >>> 8+bitMove));
|
||||
} else {
|
||||
byte moved = (byte) ((source.data[sourceOffset+source.offset]&0xFF) >>> bitOffset);
|
||||
result |= moved;
|
||||
}
|
||||
}
|
||||
|
||||
dest[i+byteOffset+offset] = result;
|
||||
}
|
||||
|
||||
//Source offset
|
||||
byteOffset += srcLength/8;
|
||||
bitOffset = (srcOffset+dataLength-srcLength)%8;
|
||||
|
||||
//Offset in destination
|
||||
int destOffset = (srcOffset+dataLength)/8;
|
||||
|
||||
for (int i=1;i<source.length-byteOffset;i++) {
|
||||
int diff = destOffset >= byteOffset-1?i:source.length-byteOffset-i;
|
||||
|
||||
byte result = 0;
|
||||
result = (byte) (source.data[byteOffset+diff-1+source.offset] << 8-bitOffset);
|
||||
byte moved = (byte) ((source.data[byteOffset+diff+source.offset]&0xFF) >>> bitOffset);
|
||||
result ^= moved;
|
||||
|
||||
dest[diff+destOffset+offset] = result;
|
||||
}
|
||||
|
||||
source.data = dest;
|
||||
source.offset = offset;
|
||||
source.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user