Compare commits

..

14 Commits

Author SHA1 Message Date
Cameron Gutman b52a6ce93c Merge branch 'master' of github.com:cgutman/limelight 2014-05-06 10:25:10 -04:00
Cameron Gutman 7ab4e5d0a5 Bump version to 2.1.6 2014-05-06 10:24:34 -04:00
Cameron Gutman 095dfd8035 Update common 2014-05-06 10:24:21 -04:00
Cameron Gutman f7c33ef975 Display the same warning when trying to pair without an IP address supplied as we do when trying to stream 2014-05-06 10:23:39 -04:00
Aaron Neyer 57b0bce5a4 update some wording for internet streaming 2014-05-02 12:14:52 -04:00
Cameron Gutman 7a017d7b97 Make the d-pad center button emulate the A button so remotes with only d-pad buttons are usable in the Steam UI 2014-04-21 17:40:50 -04:00
Cameron Gutman d2773be32e Separate the different SPS fixups by decoder. Go back to the old way of doing bitstream restrictions because the new way seems to be broken. TI OMAP4's Ducati decoder works now :) 2014-04-14 13:24:52 -04:00
Cameron Gutman 93a7d9f181 Fix crash on devices that re-create the surface when we set the format 2014-04-14 12:46:59 -04:00
Cameron Gutman 9703cf4ffe Merge branch 'master' of github.com:cgutman/limelight 2014-04-14 11:38:16 -04:00
Cameron Gutman 41ec64e87c Add controllers.json for the Fire TV 2014-04-14 11:37:55 -04:00
Cameron Gutman aca92a5056 Increment version to 2.1.5 2014-04-13 21:01:58 -04:00
Cameron Gutman fc9c4d9aaa Use a much better method for adding bitstream restrictions to the SPS. Fix a violation in H.264 spec after adding bitstream restrictions. Credit to irtimmer for the changes. 2014-04-13 20:47:37 -04:00
Cameron Gutman d8c6a544f0 Fix race condition with the destruction of the rendering surface and stopping the renderer to fix a random crash on exit 2014-04-13 20:24:38 -04:00
Cameron Gutman 7e100f2c9c Update common 2014-04-13 20:23:15 -04:00
8 changed files with 196 additions and 50 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.limelight"
android:versionCode="10"
android:versionName="2.1.4" >
android:versionCode="12"
android:versionName="2.1.6" >
<uses-sdk
android:minSdkVersion="16"
+5 -6
View File
@@ -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!
+12
View File
@@ -0,0 +1,12 @@
{
"SupportedControllers" : {
"Gamepad" : {},
"Remote" : "false",
"SecondScreen" : {
"DPad" : "false",
"AnalogSticks" : "0",
"DigitalButtons" : "0",
"Mouse" : "false"
}
}
}
Binary file not shown.
+5
View File
@@ -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 -4
View File
@@ -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;
}
}