Compare commits

..

8 Commits

8 changed files with 186 additions and 95 deletions
+3 -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="16"
android:versionName="2.2.1.2" >
android:versionCode="18"
android:versionName="2.4" >
<uses-sdk
android:minSdkVersion="16"
@@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
Binary file not shown.
+6 -2
View File
@@ -1,5 +1,6 @@
package com.limelight;
import java.io.FileNotFoundException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
@@ -251,7 +252,7 @@ public class Connection extends Activity {
return;
}
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_LONG).show();
Toast.makeText(Connection.this, "Pairing...", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
@@ -297,8 +298,11 @@ public class Connection extends Activity {
message = null;
}
}
} catch (UnknownHostException e1) {
} catch (UnknownHostException e) {
message = "Failed to resolve host";
} catch (FileNotFoundException e) {
message = "GFE returned an HTTP 404 error. Make sure your PC is running a supported GPU. Using remote desktop software can also cause this error. "
+ "Try rebooting your machine or reinstalling GFE.";
} catch (Exception e) {
message = e.getMessage();
}
+128 -2
View File
@@ -1,5 +1,12 @@
package com.limelight;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import com.limelight.binding.PlatformBinding;
import com.limelight.binding.input.ControllerHandler;
import com.limelight.binding.input.KeyboardTranslator;
@@ -19,6 +26,8 @@ import android.content.SharedPreferences;
import android.graphics.Point;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.Display;
@@ -56,6 +65,8 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
private boolean connecting = false;
private boolean connected = false;
private WifiManager.WifiLock wifiLock;
private int drFlags = 0;
public static final String PREFS_FILE_NAME = "gameprefs";
@@ -152,10 +163,35 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
// Warn the user if they're on a metered connection
checkDataConnection();
// Make sure Wi-Fi is fully powered up
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiLock = wifiMgr.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "Limelight");
wifiLock.setReferenceCounted(false);
wifiLock.acquire();
String host = Game.this.getIntent().getStringExtra("host");
InetAddress addr;
boolean enableLargePackets;
try {
// If this does a DNS lookup, it could cause a NetworkOnMainThread exception
// Chances are if it has to do this, we're not on the same network anyways
addr = InetAddress.getByName(host);
// Check if we can enable large packets if we get this far
enableLargePackets = shouldEnableLargePackets(addr);
} catch (Exception e) {
// We don't want to deal with any exceptions here. The user will be notified
// when the connection fails
enableLargePackets = false;
}
LimeLog.info("Using large packets? "+enableLargePackets);
// Start the connection
conn = new NvConnection(Game.this.getIntent().getStringExtra("host"), Game.this,
new StreamConfiguration(width, height, refreshRate, bitrate * 1000), PlatformBinding.getCryptoProvider(this));
conn = new NvConnection(host, Game.this,
new StreamConfiguration(width, height, refreshRate, bitrate * 1000,
enableLargePackets ? 1460 : 1024), PlatformBinding.getCryptoProvider(this));
keybTranslator = new KeyboardTranslator(conn);
controllerHandler = new ControllerHandler(conn);
@@ -163,6 +199,89 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
sh.addCallback(this);
}
private boolean shouldEnableLargePackets(InetAddress targetAddress) {
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
String matchingPrefix;
if (activeNetworkInfo == null) {
return false;
}
switch (activeNetworkInfo.getType())
{
case ConnectivityManager.TYPE_ETHERNET:
matchingPrefix = "eth";
break;
case ConnectivityManager.TYPE_WIFI:
matchingPrefix = "wlan";
break;
default:
// Must be on Ethernet or Wifi to consider that we can send large packets
return false;
}
// Try to find the interface that corresponds to the active network
try {
Enumeration<NetworkInterface> ifaceList = NetworkInterface.getNetworkInterfaces();
while (ifaceList.hasMoreElements()) {
NetworkInterface iface = ifaceList.nextElement();
// Look for an interface that matches the prefix we expect
if (iface.isUp() && iface.getName().startsWith(matchingPrefix)) {
// Find the IPv4 address for the interface
for (InterfaceAddress addr : iface.getInterfaceAddresses()) {
if (!(addr.getAddress() instanceof Inet4Address)) {
// Skip non-IPv4 addresses
continue;
}
// Found the right address on the right interface
return isOnSameSubnet(targetAddress, addr.getAddress(), addr.getNetworkPrefixLength());
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
// We didn't find the interface or something else went wrong
return false;
}
private boolean isOnSameSubnet(InetAddress targetAddress, InetAddress localAddress, short networkPrefixLength) {
byte[] targetBytes = targetAddress.getAddress();
byte[] localBytes = localAddress.getAddress();
for (int byteIndex = 0; networkPrefixLength > 0; byteIndex++) {
byte target = targetBytes[byteIndex];
byte local = localBytes[byteIndex];
if (networkPrefixLength >= 8) {
// Do a full byte comparison
if (target != local) {
return false;
}
networkPrefixLength -= 8;
}
else {
target &= (byte)(0xFF << (8 - networkPrefixLength));
local &= (byte)(0xFF << (8 - networkPrefixLength));
// Do a masked comparison
if (target != local) {
return false;
}
networkPrefixLength = 0;
}
}
return true;
}
private void checkDataConnection()
{
ConnectivityManager mgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -214,6 +333,13 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
finish();
}
@Override
protected void onDestroy() {
super.onDestroy();
wifiLock.release();
}
private static byte getModifierState(KeyEvent event) {
byte modifier = 0;
if (event.isShiftPressed()) {
@@ -29,7 +29,7 @@ public class ControllerHandler {
private long lastRbUpTime = 0;
private static final int MAXIMUM_BUMPER_UP_DELAY_MS = 100;
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 10;
private static final int MINIMUM_BUTTON_DOWN_TIME_MS = 25;
private static final int EMULATING_SPECIAL = 0x1;
private static final int EMULATING_SELECT = 0x2;
@@ -13,6 +13,7 @@ import com.limelight.LimeLog;
import com.limelight.nvstream.av.ByteBufferDescriptor;
import com.limelight.nvstream.av.DecodeUnit;
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.av.video.VideoDepacketizer;
import com.limelight.nvstream.av.video.cpu.AvcDecoder;
public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
@@ -141,22 +142,23 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
}
@Override
public void start() {
public void start(final VideoDepacketizer depacketizer) {
rendererThread = new Thread() {
@Override
public void run() {
long nextFrameTime = System.currentTimeMillis();
DecodeUnit du;
while (!isInterrupted())
{
du = depacketizer.pollNextDecodeUnit();
if (du != null) {
submitDecodeUnit(du);
}
long diff = nextFrameTime - System.currentTimeMillis();
if (diff > WAIT_CEILING_MS) {
try {
Thread.sleep(diff);
} catch (InterruptedException e) {
return;
}
continue;
}
nextFrameTime = computePresentationTimeMs(targetFps);
@@ -165,6 +167,7 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
}
};
rendererThread.setName("Video - Renderer (CPU)");
rendererThread.setPriority(Thread.MAX_PRIORITY);
rendererThread.start();
}
@@ -186,8 +189,7 @@ public class AndroidCpuDecoderRenderer implements VideoDecoderRenderer {
AvcDecoder.destroy();
}
@Override
public boolean submitDecodeUnit(DecodeUnit decodeUnit) {
private boolean submitDecodeUnit(DecodeUnit decodeUnit) {
byte[] data;
// Use the reserved decoder buffer if this decode unit will fit
@@ -1,7 +1,7 @@
package com.limelight.binding.video;
import com.limelight.nvstream.av.DecodeUnit;
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.av.video.VideoDepacketizer;
public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
@@ -26,8 +26,8 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
}
@Override
public void start() {
decoderRenderer.start();
public void start(VideoDepacketizer depacketizer) {
decoderRenderer.start(depacketizer);
}
@Override
@@ -35,11 +35,6 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
decoderRenderer.stop();
}
@Override
public boolean submitDecodeUnit(DecodeUnit du) {
return decoderRenderer.submitDecodeUnit(du);
}
@Override
public int getCapabilities() {
return decoderRenderer.getCapabilities();
@@ -8,6 +8,7 @@ import com.limelight.LimeLog;
import com.limelight.nvstream.av.ByteBufferDescriptor;
import com.limelight.nvstream.av.DecodeUnit;
import com.limelight.nvstream.av.video.VideoDecoderRenderer;
import com.limelight.nvstream.av.video.VideoDepacketizer;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
@@ -23,15 +24,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
private ByteBuffer[] videoDecoderInputBuffers;
private MediaCodec videoDecoder;
private Thread rendererThread;
private int redrawRate;
private boolean needsSpsBitstreamFixup;
private boolean needsSpsNumRefFixup;
private boolean fastInputQueueing;
private VideoDepacketizer depacketizer;
public static final List<String> blacklistedDecoderPrefixes;
public static final List<String> spsFixupBitsreamFixupDecoderPrefixes;
public static final List<String> spsFixupNumRefFixupDecoderPrefixes;
public static final List<String> fastInputQueueingPrefixes;
static {
blacklistedDecoderPrefixes = new LinkedList<String>();
@@ -45,11 +44,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
spsFixupNumRefFixupDecoderPrefixes = new LinkedList<String>();
spsFixupNumRefFixupDecoderPrefixes.add("omx.TI");
}
static {
fastInputQueueingPrefixes = new LinkedList<String>();
fastInputQueueingPrefixes.add("omx.nvidia");
spsFixupNumRefFixupDecoderPrefixes.add("omx.qcom");
}
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
@@ -124,9 +119,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
}
@Override
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
this.redrawRate = redrawRate;
public void setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
//dumpDecoders();
MediaCodecInfo safeDecoder = findSafeDecoder();
@@ -140,16 +133,11 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
if (needsSpsNumRefFixup) {
LimeLog.info("Decoder "+safeDecoder.getName()+" needs SPS ref num fixup");
}
fastInputQueueing = isDecoderInList(fastInputQueueingPrefixes, safeDecoder.getName());
if (fastInputQueueing) {
LimeLog.info("Decoder "+safeDecoder.getName()+" supports fast input queueing");
}
}
else {
videoDecoder = MediaCodec.createDecoderByType("video/avc");
needsSpsBitstreamFixup = false;
needsSpsNumRefFixup = false;
fastInputQueueing = false;
}
MediaFormat videoFormat = MediaFormat.createVideoFormat("video/avc", width, height);
@@ -167,59 +155,51 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
rendererThread = new Thread() {
@Override
public void run() {
long nextFrameTimeUs = 0;
BufferInfo info = new BufferInfo();
DecodeUnit du;
while (!isInterrupted())
{
// Block for a maximum of 100 ms
int outIndex = videoDecoder.dequeueOutputBuffer(info, 100000);
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
LimeLog.info("Output buffers changed");
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
LimeLog.info("Output format changed");
LimeLog.info("New output Format: " + videoDecoder.getOutputFormat());
break;
default:
break;
}
du = depacketizer.pollNextDecodeUnit();
if (du != null) {
submitDecodeUnit(du);
}
int outIndex = videoDecoder.dequeueOutputBuffer(info, 0);
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;
}
// Render that buffer if it's time for the next frame
videoDecoder.releaseOutputBuffer(lastIndex, render);
// Render the last buffer
videoDecoder.releaseOutputBuffer(lastIndex, true);
} else {
switch (outIndex) {
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
LimeLog.info("Output buffers changed");
break;
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
LimeLog.info("Output format changed");
LimeLog.info("New output Format: " + videoDecoder.getOutputFormat());
break;
default:
break;
}
}
}
}
};
rendererThread.setName("Video - Renderer (MediaCodec)");
rendererThread.setPriority(Thread.MAX_PRIORITY);
rendererThread.start();
}
private static long currentTimeUs() {
return System.nanoTime() / 1000;
}
private long computePresentationTime(int frameRate) {
return currentTimeUs() + (1000000 / frameRate);
}
@Override
public void start() {
public void start(VideoDepacketizer depacketizer) {
this.depacketizer = depacketizer;
startRendererThread();
}
@@ -239,24 +219,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
}
}
@Override
public boolean submitDecodeUnit(DecodeUnit decodeUnit) {
if (decodeUnit.getType() != DecodeUnit.TYPE_H264) {
System.err.println("Unknown decode unit type");
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;
}
private boolean submitDecodeUnit(DecodeUnit decodeUnit) {
int inputIndex = videoDecoder.dequeueInputBuffer(-1);
if (inputIndex >= 0)
{
@@ -272,7 +235,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
if (needsSpsNumRefFixup) {
LimeLog.info("Fixing up num ref frames");
this.replace(header, 80, 9, new byte[] {0x40}, 3);
}
}
// 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.
@@ -312,7 +275,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoDecoder.queueInputBuffer(inputIndex,
0, spsLength,
0, mcFlags);
0, 0);
return true;
}
}
@@ -325,7 +288,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
videoDecoder.queueInputBuffer(inputIndex,
0, decodeUnit.getDataLength(),
0, mcFlags);
0, 0);
}
return true;
@@ -333,7 +296,7 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
@Override
public int getCapabilities() {
return fastInputQueueing ? VideoDecoderRenderer.CAPABILITY_DIRECT_SUBMIT : 0;
return 0;
}
/**