Compare commits

...

18 Commits

Author SHA1 Message Date
Cameron Gutman 7fafb8e0ff Revert extractNativeLibraries=false change due to install failure on Fire TV 3 2018-01-11 00:03:03 -08:00
Cameron Gutman fbcbe09255 Version 5.6.3 2018-01-10 00:40:08 -08:00
Cameron Gutman e336a4446a Update common to work with GFE 3.12 2018-01-10 00:38:15 -08:00
Cameron Gutman ffb35b2cdd Use smaller packets for streaming at 1080p and below to attempt to mitigate some reported regressions with v5.6.2 2018-01-09 23:38:25 -08:00
Cameron Gutman 2d0af6281c Ensure polling threads terminate even when polling resumes immediately 2017-12-29 14:05:29 -08:00
Cameron Gutman 472a7f6c8a Version 5.6.2 2017-12-27 22:45:07 -08:00
Cameron Gutman cd06559c66 Also count link-local addresses as local 2017-12-27 22:41:21 -08:00
Cameron Gutman d833933aaa Allow up to 1 second for fast poll to address connection flakiness 2017-12-27 22:27:35 -08:00
Cameron Gutman dc3495d59b Improve local vs. remote heuristics 2017-12-27 21:43:12 -08:00
Cameron Gutman e3a2e40043 Shrink large box art down to the normal size by changing sample size 2017-12-27 21:28:38 -08:00
Cameron Gutman 31e1fb743e Update common to address some null PC name crashes 2017-12-27 20:36:07 -08:00
Cameron Gutman bc59f11096 Disable RFI on b3_att_us 2017-12-27 20:34:02 -08:00
Cameron Gutman 6d97775aa9 Try disabling RFI if the previous run crashes 2017-12-27 20:32:34 -08:00
Cameron Gutman 3fff34e08a Don't extract native libraries for non-root build 2017-12-27 19:40:49 -08:00
Cameron Gutman 15e856dccb Move AudioTrack flush to cleanup() callback since all sample submission has ceased by then 2017-12-06 20:43:58 -08:00
Cameron Gutman 07d04171c3 Force HEVC enabled if HDR is requested 2017-12-05 17:38:25 -08:00
Cameron Gutman 42bd93cb3a Update common to fix mDNS race condition 2017-12-05 17:33:55 -08:00
Cameron Gutman 7d289f1134 Fix race conditions when frames are submitted after stop() has been called 2017-12-05 17:28:04 -08:00
11 changed files with 132 additions and 59 deletions
+3 -3
View File
@@ -5,14 +5,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 27
buildToolsVersion '27.0.1'
buildToolsVersion '27.0.3'
defaultConfig {
minSdkVersion 16
targetSdkVersion 27
versionName "5.6.1"
versionCode = 139
versionName "5.6.3"
versionCode = 142
}
flavorDimensions "root"
+1 -1
View File
@@ -302,7 +302,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
.setBitrate(prefConfig.bitrate * 1000)
.setEnableSops(prefConfig.enableSops)
.enableLocalAudioPlayback(prefConfig.playHostAudio)
.setMaxPacketSize(remote ? 1024 : 1292)
.setMaxPacketSize((remote || prefConfig.width <= 1920) ? 1024 : 1292)
.setRemote(remote)
.setHevcBitratePercentageMultiplier(75)
.setHevcSupported(decoderRenderer.isHevcSupported())
@@ -176,14 +176,14 @@ public class AndroidAudioRenderer implements AudioRenderer {
public void start() {}
@Override
public void stop() {
// Immediately drop all pending data
track.pause();
track.flush();
}
public void stop() {}
@Override
public void cleanup() {
// Immediately drop all pending data
track.pause();
track.flush();
track.release();
}
}
@@ -100,10 +100,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// for even required levels of HEVC.
MediaCodecInfo decoderInfo = MediaCodecHelper.findProbableSafeDecoder("video/hevc", -1);
if (decoderInfo != null) {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork, requestedHdr)) {
if (!MediaCodecHelper.decoderIsWhitelistedForHevc(decoderInfo.getName(), meteredNetwork)) {
LimeLog.info("Found HEVC decoder, but it's not whitelisted - "+decoderInfo.getName());
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON) {
// HDR implies HEVC forced on, since HEVCMain10HDR10 is required for HDR
if (prefs.videoFormat == PreferenceConfiguration.FORCE_H265_ON || requestedHdr) {
LimeLog.info("Forcing H265 enabled despite non-whitelisted decoder");
}
else {
@@ -165,6 +166,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
refFrameInvalidationAvc = MediaCodecHelper.decoderSupportsRefFrameInvalidationAvc(avcDecoder.getName(), prefs.height);
refFrameInvalidationHevc = MediaCodecHelper.decoderSupportsRefFrameInvalidationHevc(avcDecoder.getName());
if (consecutiveCrashCount % 2 == 1) {
refFrameInvalidationAvc = refFrameInvalidationHevc = false;
LimeLog.warning("Disabling RFI due to previous crash");
}
if (directSubmit) {
LimeLog.info("Decoder "+avcDecoder.getName()+" will use direct submit");
}
@@ -573,25 +579,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// May be called already, but we'll call it now to be safe
prepareForStop();
try {
// Invalidate pending decode buffers
videoDecoder.flush();
} catch (Exception e) {
e.printStackTrace();
}
// Wait for the renderer thread to shut down
try {
rendererThread.join();
} catch (InterruptedException ignored) { }
try {
// Stop the video decoder
videoDecoder.stop();
} catch (Exception e) {
e.printStackTrace();
}
// Halt the spinner threads
stopSpinnerThreads();
}
@@ -657,6 +649,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
@Override
public int submitDecodeUnit(byte[] decodeUnitData, int decodeUnitLength, int decodeUnitType,
int frameNumber, long receiveTimeMs) {
if (stopping) {
// Don't bother if we're stopping
return MoonBridge.DR_OK;
}
totalFramesReceived++;
// We can receive the same "frame" multiple times if it's an IDR frame.
@@ -1043,6 +1040,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
str += "Build fingerprint: "+Build.FINGERPRINT+"\n";
str += "Foreground: "+renderer.foreground+"\n";
str += "Consecutive crashes: "+renderer.consecutiveCrashCount+"\n";
str += "RFI active: "+renderer.refFrameInvalidationActive+"\n";
str += "Video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
str += "FPS target: "+renderer.refreshRate+"\n";
str += "Bitrate: "+renderer.prefs.bitrate+" Mbps \n";
@@ -292,6 +292,13 @@ public class MediaCodecHelper {
if (videoHeight > 720 && isLowEndSnapdragon) {
return false;
}
// This device seems to crash constantly at 720p, so try disabling
// RFI to see if we can get that under control.
if (Build.PRODUCT.equalsIgnoreCase("b3_att_us")) {
return false;
}
return isDecoderInList(refFrameInvalidationAvcPrefixes, decoderName);
}
@@ -299,7 +306,7 @@ public class MediaCodecHelper {
return isDecoderInList(refFrameInvalidationHevcPrefixes, decoderName);
}
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData, boolean willStreamHdr) {
public static boolean decoderIsWhitelistedForHevc(String decoderName, boolean meteredData) {
// TODO: Shield Tablet K1/LTE?
//
// NVIDIA does partial HEVC acceleration on the Shield Tablet. I don't know
@@ -335,7 +342,7 @@ public class MediaCodecHelper {
// typically because it can't support reference frame invalidation.
// However, we will use it for HDR and for streaming over mobile networks
// since it works fine otherwise.
if ((meteredData || willStreamHdr) && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
if (meteredData && isDecoderInList(deprioritizedHevcDecoders, decoderName)) {
LimeLog.info("Selected deprioritized decoder");
return true;
}
@@ -3,8 +3,10 @@ package com.limelight.computers;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -35,7 +37,7 @@ public class ComputerManagerService extends Service {
private static final int APPLIST_POLLING_PERIOD_MS = 30000;
private static final int APPLIST_FAILED_POLLING_RETRY_MS = 2000;
private static final int MDNS_QUERY_PERIOD_MS = 1000;
private static final int FAST_POLL_TIMEOUT = 500;
private static final int FAST_POLL_TIMEOUT = 1000;
private static final int OFFLINE_POLL_TRIES = 5;
private static final int INITIAL_POLL_TRIES = 2;
private static final int EMPTY_LIST_THRESHOLD = 3;
@@ -132,7 +134,7 @@ public class ComputerManagerService extends Service {
public void run() {
int offlineCount = 0;
while (!isInterrupted() && pollingActive) {
while (!isInterrupted() && pollingActive && tuple.thread == this) {
try {
// Only allow one request to the machine at a time
synchronized (tuple.networkLock) {
@@ -367,6 +369,10 @@ public class ComputerManagerService extends Service {
return true;
}
else {
if (!manuallyAdded) {
LimeLog.warning("Auto-discovered PC failed to respond: "+addr);
}
return false;
}
}
@@ -386,6 +392,7 @@ public class ComputerManagerService extends Service {
if (tuple.thread != null) {
// Interrupt the thread on this entry
tuple.thread.interrupt();
tuple.thread = null;
}
pollingTuples.remove(tuple);
break;
@@ -500,14 +507,26 @@ public class ComputerManagerService extends Service {
return ComputerDetails.Reachability.OFFLINE;
}
private static boolean isAddressLikelyLocal(String str) {
try {
// This will tend to be wrong for IPv6 but falling back to
// remote will be fine in that case. For IPv4, it should be
// pretty accurate due to NAT prevalence.
InetAddress addr = InetAddress.getByName(str);
return addr.isSiteLocalAddress() || addr.isLinkLocalAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
return false;
}
}
private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
ComputerDetails polledDetails;
ComputerDetails.Reachability reachability;
// If the local address is routable across the Internet,
// always consider this PC remote to be conservative
if (details.localAddress.equals(details.remoteAddress)) {
reachability = ComputerDetails.Reachability.REMOTE;
reachability = isAddressLikelyLocal(details.localAddress) ?
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
}
else {
// Do a TCP-level connection to the HTTP server to see if it's listening
@@ -553,7 +572,14 @@ public class ComputerManagerService extends Service {
return null;
}
if (polledDetails.remoteAddress.equals(reachableAddr)) {
// If both addresses are the same, guess whether we're local based on
// IP address heuristics.
if (reachableAddr.equals(polledDetails.localAddress) &&
reachableAddr.equals(polledDetails.remoteAddress)) {
polledDetails.reachability = isAddressLikelyLocal(reachableAddr) ?
ComputerDetails.Reachability.LOCAL : ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.remoteAddress.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.localAddress.equals(reachableAddr)) {
@@ -815,6 +841,7 @@ public class ComputerManagerService extends Service {
} while (waitPollingDelay());
}
};
thread.setName("App list polling thread for " + computer.localAddress);
thread.start();
}
@@ -13,7 +13,11 @@ import java.io.OutputStream;
public class DiskAssetLoader {
// 5 MB
private final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
private static final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
// Standard box art is 300x400
private static final int STANDARD_ASSET_WIDTH = 300;
private static final int STANDARD_ASSET_HEIGHT = 400;
private final File cacheDir;
@@ -25,33 +29,65 @@ public class DiskAssetLoader {
return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
}
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
InputStream in = null;
Bitmap bmp = null;
try {
// Make sure the cached asset doesn't exceed the maximum size
if (CacheHelper.getFileSize(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png") > MAX_ASSET_SIZE) {
LimeLog.warning("Removing cached tuple exceeding size threshold: "+tuple);
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
return null;
}
// https://developer.android.com/topic/performance/graphics/load-bitmap.html
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
in = CacheHelper.openCacheFileForInput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
bmp = BitmapFactory.decodeStream(in, null, options);
} catch (IOException ignored) {
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignored) {}
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculates the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
File file = CacheHelper.openPath(false, cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
// Don't bother with anything if it doesn't exist
if (!file.exists()) {
return null;
}
// Make sure the cached asset doesn't exceed the maximum size
if (file.length() > MAX_ASSET_SIZE) {
LimeLog.warning("Removing cached tuple exceeding size threshold: "+tuple);
file.delete();
return null;
}
// Lookup bounds of the downloaded image
BitmapFactory.Options decodeOnlyOptions = new BitmapFactory.Options();
decodeOnlyOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), decodeOnlyOptions);
if (decodeOnlyOptions.outWidth <= 0 || decodeOnlyOptions.outHeight <= 0) {
// Dimensions set to -1 on error. Return value always null.
return null;
}
LimeLog.info("Tuple "+tuple+" has cached art of size: "+decodeOnlyOptions.outWidth+"x"+decodeOnlyOptions.outHeight);
// Load the image scaled to the appropriate size
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calculateInSampleSize(decodeOnlyOptions,
STANDARD_ASSET_WIDTH / sampleSize,
STANDARD_ASSET_HEIGHT / sampleSize);
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inDither = true;
Bitmap bmp = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
if (bmp != null) {
LimeLog.info("Disk cache hit for tuple: "+tuple);
LimeLog.info("Tuple "+tuple+" decoded from disk cache with sample size: "+options.inSampleSize);
}
return bmp;
@@ -13,7 +13,7 @@ import java.io.OutputStream;
import java.io.Reader;
public class CacheHelper {
private static File openPath(boolean createPath, File root, String... path) {
public static File openPath(boolean createPath, File root, String... path) {
File f = root;
for (int i = 0; i < path.length; i++) {
String component = path[i];
+1
View File
@@ -2,5 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Non-root application name -->
<!-- FIXME: We should set extractNativeLibs=false but this breaks installation on the Fire TV 3 -->
<application android:label="Moonlight" />
</manifest>
+5 -1
View File
@@ -2,5 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Root application name -->
<application android:label="Moonlight (Root)" />
<!-- Ensure native libraries are always extracted for root builds,
since we must invoke the evdev_reader binary ourselves -->
<application
android:label="Moonlight (Root)"
android:extractNativeLibs="true" />
</manifest>