Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 79f888fe47 | |||
| 9393bf7f79 | |||
| 1b1d4399a9 | |||
| b2ad259a7c | |||
| ac5c264090 | |||
| fcfdd4e323 | |||
| ea65bb2c0a | |||
| b340055588 | |||
| 2918039b6f | |||
| e8fd1f262a | |||
| 8887401644 | |||
| f892db6ee8 | |||
| 829532c572 |
+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="29"
|
||||
android:versionName="2.5.2.1" >
|
||||
android:versionCode="30"
|
||||
android:versionName="2.5.3" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
|
||||
Binary file not shown.
@@ -91,13 +91,13 @@ public class AppView extends Activity {
|
||||
}
|
||||
});
|
||||
registerForContextMenu(appList);
|
||||
updateAppList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
Dialog.closeDialogs();
|
||||
}
|
||||
|
||||
|
||||
@@ -164,13 +164,6 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
||||
SurfaceView sv = (SurfaceView) findViewById(R.id.surfaceView);
|
||||
sv.setOnGenericMotionListener(this);
|
||||
sv.setOnTouchListener(this);
|
||||
|
||||
SurfaceHolder sh = sv.getHolder();
|
||||
|
||||
if (stretchToFit) {
|
||||
// Set the surface to the size of the video
|
||||
sh.setFixedSize(width, height);
|
||||
}
|
||||
|
||||
// Warn the user if they're on a metered connection
|
||||
checkDataConnection();
|
||||
@@ -191,7 +184,15 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
||||
PlatformBinding.getCryptoProvider(this));
|
||||
keybTranslator = new KeyboardTranslator(conn);
|
||||
controllerHandler = new ControllerHandler(conn);
|
||||
|
||||
decoderRenderer = new ConfigurableDecoderRenderer();
|
||||
decoderRenderer.initializeWithFlags(drFlags);
|
||||
|
||||
SurfaceHolder sh = sv.getHolder();
|
||||
if (stretchToFit || !decoderRenderer.isHardwareAccelerated()) {
|
||||
// Set the surface to the size of the video
|
||||
sh.setFixedSize(width, height);
|
||||
}
|
||||
|
||||
// The connection will be started when the surface gets created
|
||||
sh.addCallback(this);
|
||||
@@ -254,7 +255,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
SpinnerDialog.closeDialogs();
|
||||
SpinnerDialog.closeDialogs(this);
|
||||
Dialog.closeDialogs();
|
||||
|
||||
displayedFailureDialog = true;
|
||||
@@ -610,7 +611,7 @@ public class Game extends Activity implements SurfaceHolder.Callback, OnGenericM
|
||||
|
||||
// Resize the surface to match the aspect ratio of the video
|
||||
// This must be done after the surface is created.
|
||||
if (!stretchToFit) {
|
||||
if (!stretchToFit && decoderRenderer.isHardwareAccelerated()) {
|
||||
resizeSurfaceWithAspectRatio((SurfaceView) findViewById(R.id.surfaceView), width, height);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,28 @@ public class ConfigurableDecoderRenderer implements VideoDecoderRenderer {
|
||||
|
||||
@Override
|
||||
public boolean setup(int width, int height, int redrawRate, Object renderTarget, int drFlags) {
|
||||
if (decoderRenderer == null) {
|
||||
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
||||
}
|
||||
return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
|
||||
}
|
||||
|
||||
public void initializeWithFlags(int drFlags) {
|
||||
if ((drFlags & VideoDecoderRenderer.FLAG_FORCE_HARDWARE_DECODING) != 0 ||
|
||||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
|
||||
MediaCodecDecoderRenderer.findSafeDecoder() != null)) {
|
||||
((drFlags & VideoDecoderRenderer.FLAG_FORCE_SOFTWARE_DECODING) == 0 &&
|
||||
MediaCodecDecoderRenderer.findSafeDecoder() != null)) {
|
||||
decoderRenderer = new MediaCodecDecoderRenderer();
|
||||
}
|
||||
else {
|
||||
decoderRenderer = new AndroidCpuDecoderRenderer();
|
||||
}
|
||||
return decoderRenderer.setup(width, height, redrawRate, renderTarget, drFlags);
|
||||
}
|
||||
|
||||
public boolean isHardwareAccelerated() {
|
||||
if (decoderRenderer == null) {
|
||||
throw new IllegalStateException("ConfigurableDecoderRenderer not initialized");
|
||||
}
|
||||
return (decoderRenderer instanceof MediaCodecDecoderRenderer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -48,12 +48,13 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
static {
|
||||
spsFixupBitsreamFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupBitsreamFixupDecoderPrefixes.add("omx.nvidia");
|
||||
spsFixupBitsreamFixupDecoderPrefixes.add("omx.qcom");
|
||||
|
||||
spsFixupNumRefFixupDecoderPrefixes = new LinkedList<String>();
|
||||
spsFixupNumRefFixupDecoderPrefixes.add("omx.TI");
|
||||
spsFixupNumRefFixupDecoderPrefixes.add("omx.qcom");
|
||||
}
|
||||
|
||||
|
||||
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
|
||||
for (String badPrefix : decoderList) {
|
||||
if (decoderName.length() >= badPrefix.length()) {
|
||||
@@ -177,8 +178,14 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
{
|
||||
du = depacketizer.pollNextDecodeUnit();
|
||||
if (du != null) {
|
||||
submitDecodeUnit(du);
|
||||
depacketizer.freeDecodeUnit(du);
|
||||
if (!submitDecodeUnit(du)) {
|
||||
// Thread was interrupted
|
||||
depacketizer.freeDecodeUnit(du);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
depacketizer.freeDecodeUnit(du);
|
||||
}
|
||||
}
|
||||
|
||||
int outIndex = videoDecoder.dequeueOutputBuffer(info, 0);
|
||||
@@ -250,105 +257,111 @@ public class MediaCodecDecoderRenderer implements VideoDecoderRenderer {
|
||||
}
|
||||
|
||||
private boolean submitDecodeUnit(DecodeUnit decodeUnit) {
|
||||
int inputIndex = videoDecoder.dequeueInputBuffer(-1);
|
||||
if (inputIndex >= 0)
|
||||
{
|
||||
ByteBuffer buf = videoDecoderInputBuffers[inputIndex];
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long delta = currentTime-decodeUnit.getReceiveTimestamp();
|
||||
if (delta >= 0 && delta < 300) {
|
||||
totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp();
|
||||
totalFrames++;
|
||||
int inputIndex;
|
||||
|
||||
do {
|
||||
if (Thread.interrupted()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear old input data
|
||||
buf.clear();
|
||||
|
||||
int codecFlags = 0;
|
||||
int decodeUnitFlags = decodeUnit.getFlags();
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
|
||||
}
|
||||
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0 &&
|
||||
(needsSpsBitstreamFixup || needsSpsNumRefFixup)) {
|
||||
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
|
||||
if (header.data[header.offset+4] == 0x67) {
|
||||
byte last = header.data[header.length+header.offset-1];
|
||||
inputIndex = videoDecoder.dequeueInputBuffer(100000);
|
||||
} while (inputIndex < 0);
|
||||
|
||||
ByteBuffer buf = videoDecoderInputBuffers[inputIndex];
|
||||
|
||||
// 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);
|
||||
}
|
||||
long currentTime = System.currentTimeMillis();
|
||||
long delta = currentTime-decodeUnit.getReceiveTimestamp();
|
||||
if (delta >= 0 && delta < 300) {
|
||||
totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp();
|
||||
totalFrames++;
|
||||
}
|
||||
|
||||
// Clear old input data
|
||||
buf.clear();
|
||||
|
||||
int codecFlags = 0;
|
||||
int decodeUnitFlags = decodeUnit.getFlags();
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||
}
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_SYNC_FRAME) != 0) {
|
||||
codecFlags |= MediaCodec.BUFFER_FLAG_SYNC_FRAME;
|
||||
}
|
||||
|
||||
if ((decodeUnitFlags & DecodeUnit.DU_FLAG_CODEC_CONFIG) != 0 &&
|
||||
(needsSpsBitstreamFixup || needsSpsNumRefFixup)) {
|
||||
ByteBufferDescriptor header = decodeUnit.getBufferList().get(0);
|
||||
if (header.data[header.offset+4] == 0x67) {
|
||||
byte last = header.data[header.length+header.offset-1];
|
||||
|
||||
// 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) {
|
||||
if (!needsSpsNumRefFixup) {
|
||||
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 {
|
||||
// Set bitstream restrictions to only buffer single frame
|
||||
// (starts 9 bits before stop bit and 6 bits earlier because of the shortening above)
|
||||
this.replace(header, header.length*8+Integer.numberOfLeadingZeros(last & - last)%8-9-6, 2, BITSTREAM_RESTRICTIONS, 3*8);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (!needsSpsNumRefFixup) {
|
||||
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 {
|
||||
// Set bitstream restrictions to only buffer single frame
|
||||
// (starts 9 bits before stop bit and 6 bits earlier because of the shortening above)
|
||||
this.replace(header, header.length*8+Integer.numberOfLeadingZeros(last & - last)%8-9-6, 2, BITSTREAM_RESTRICTIONS, 3*8);
|
||||
buf.put(header.data, header.offset, header.length);
|
||||
spsLength = header.length;
|
||||
}
|
||||
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, spsLength,
|
||||
currentTime * 1000, codecFlags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
buf.put(header.data, header.offset, header.length);
|
||||
spsLength = header.length;
|
||||
}
|
||||
|
||||
// Copy data from our buffer list into the input buffer
|
||||
for (ByteBufferDescriptor desc : decodeUnit.getBufferList())
|
||||
{
|
||||
buf.put(desc.data, desc.offset, desc.length);
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, spsLength,
|
||||
currentTime * 1000, codecFlags);
|
||||
return true;
|
||||
}
|
||||
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, decodeUnit.getDataLength(),
|
||||
currentTime * 1000, codecFlags);
|
||||
}
|
||||
|
||||
// Copy data from our buffer list into the input buffer
|
||||
for (ByteBufferDescriptor desc : decodeUnit.getBufferList())
|
||||
{
|
||||
buf.put(desc.data, desc.offset, desc.length);
|
||||
}
|
||||
|
||||
videoDecoder.queueInputBuffer(inputIndex,
|
||||
0, decodeUnit.getDataLength(),
|
||||
currentTime * 1000, codecFlags);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ public class ComputerDatabaseManager {
|
||||
computerList.add(details);
|
||||
}
|
||||
|
||||
c.close();
|
||||
|
||||
return computerList;
|
||||
}
|
||||
|
||||
@@ -117,6 +119,7 @@ public class ComputerDatabaseManager {
|
||||
ComputerDetails details = new ComputerDetails();
|
||||
if (!c.moveToFirst()) {
|
||||
// No matching computer
|
||||
c.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -146,6 +149,8 @@ public class ComputerDatabaseManager {
|
||||
|
||||
details.macAddress = c.getString(4);
|
||||
|
||||
c.close();
|
||||
|
||||
// If a field is corrupt or missing, delete the database entry
|
||||
if (details.uuid == null || details.localIp == null || details.remoteIp == null ||
|
||||
details.macAddress == null) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.util.TimerTask;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.PlatformBinding;
|
||||
@@ -39,6 +40,8 @@ public class ComputerManagerService extends Service {
|
||||
private ComputerManagerBinder binder = new ComputerManagerBinder();
|
||||
|
||||
private ComputerDatabaseManager dbManager;
|
||||
private AtomicInteger dbRefCount = new AtomicInteger(0);
|
||||
|
||||
private IdentityManager idManager;
|
||||
private ThreadPoolExecutor pollingPool;
|
||||
private Timer pollingTimer;
|
||||
@@ -173,15 +176,41 @@ public class ComputerManagerService extends Service {
|
||||
}
|
||||
|
||||
public void removeComputer(String name) {
|
||||
if (!getLocalDatabaseReference()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove it from the database
|
||||
dbManager.deleteComputer(name);
|
||||
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
|
||||
private boolean getLocalDatabaseReference() {
|
||||
if (dbRefCount.get() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dbRefCount.incrementAndGet();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void releaseLocalDatabaseReference() {
|
||||
if (dbRefCount.decrementAndGet() == 0) {
|
||||
dbManager.close();
|
||||
}
|
||||
}
|
||||
|
||||
private TimerTask getTimerTask() {
|
||||
return new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!getLocalDatabaseReference()) {
|
||||
return;
|
||||
}
|
||||
List<ComputerDetails> computerList = dbManager.getAllComputers();
|
||||
releaseLocalDatabaseReference();
|
||||
|
||||
for (ComputerDetails computer : computerList) {
|
||||
pollingPool.execute(getPollingRunnable(computer));
|
||||
}
|
||||
@@ -356,6 +385,10 @@ public class ComputerManagerService extends Service {
|
||||
public void run() {
|
||||
boolean newPc = (details.name == null);
|
||||
|
||||
if (!getLocalDatabaseReference()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Poll the machine
|
||||
if (!doPollMachine(details)) {
|
||||
details.state = ComputerDetails.State.OFFLINE;
|
||||
@@ -369,6 +402,7 @@ public class ComputerManagerService extends Service {
|
||||
// removed after this was issued
|
||||
if (dbManager.getComputerByName(details.name) == null) {
|
||||
// It's gone
|
||||
releaseLocalDatabaseReference();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -376,10 +410,12 @@ public class ComputerManagerService extends Service {
|
||||
dbManager.updateComputer(details);
|
||||
}
|
||||
|
||||
// Update anyone listening
|
||||
if (listener != null) {
|
||||
// Don't call the listener if this is a failed lookup of a new PC
|
||||
if ((!newPc || details.state == ComputerDetails.State.ONLINE) && listener != null) {
|
||||
listener.notifyComputerUpdated(details);
|
||||
}
|
||||
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -400,6 +436,7 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
// Initialize the DB
|
||||
dbManager = new ComputerDatabaseManager(this);
|
||||
dbRefCount.set(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -411,12 +448,11 @@ public class ComputerManagerService extends Service {
|
||||
|
||||
// Stop the thread pool
|
||||
pollingPool.shutdownNow();
|
||||
try {
|
||||
pollingPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
|
||||
} catch (InterruptedException e) {}
|
||||
|
||||
// Close the DB
|
||||
dbManager.close();
|
||||
// FIXME: Should await termination here but we have timeout issues in HttpURLConnection
|
||||
|
||||
// Remove the initial DB reference
|
||||
releaseLocalDatabaseReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.limelight.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
@@ -31,16 +32,19 @@ public class SpinnerDialog implements Runnable,OnCancelListener {
|
||||
return spinner;
|
||||
}
|
||||
|
||||
public static void closeDialogs()
|
||||
public static void closeDialogs(Activity activity)
|
||||
{
|
||||
synchronized (rundownDialogs) {
|
||||
for (SpinnerDialog d : rundownDialogs) {
|
||||
if (d.progress.isShowing()) {
|
||||
d.progress.dismiss();
|
||||
Iterator<SpinnerDialog> i = rundownDialogs.iterator();
|
||||
while (i.hasNext()) {
|
||||
SpinnerDialog dialog = i.next();
|
||||
if (dialog.activity == activity) {
|
||||
i.remove();
|
||||
if (dialog.progress.isShowing()) {
|
||||
dialog.progress.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rundownDialogs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,13 +66,14 @@ public class SpinnerDialog implements Runnable,OnCancelListener {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// If we're dying, don't bother doing anything
|
||||
if (activity.isFinishing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress == null)
|
||||
{
|
||||
// If we're dying, don't bother creating a dialog
|
||||
if (activity.isFinishing())
|
||||
return;
|
||||
|
||||
progress = new ProgressDialog(activity);
|
||||
|
||||
progress.setTitle(title);
|
||||
|
||||
Reference in New Issue
Block a user