Compare commits

...

26 Commits

Author SHA1 Message Date
Cameron Gutman c025f9f02b Reduce code duplication 2015-10-17 15:44:24 -07:00
Cameron Gutman b737acedb0 Bump version to 3.1.11 2015-10-15 02:00:29 -07:00
Cameron Gutman f15bfe3038 Add support for mouse drag using long press 2015-10-15 01:50:05 -07:00
Cameron Gutman 8938f51292 Fix weird stair-stepping upward mouse movement on devices with a low scaling factor caused by rounding error (Nexus 9) 2015-10-15 01:48:31 -07:00
Cameron Gutman 4b92b8f714 Fix bug allowing computer polling to continue when the stream is resumed from the PcView activity 2015-10-15 00:55:05 -07:00
Cameron Gutman 5f13b9bca4 Don't set constraints 4 & 5 when using baseline profile hack 2015-10-13 19:29:36 -07:00
Cameron Gutman 2f219aac6f Only apply the constrained high profile SPS modification to Intel devices to avoid crashing other devices 2015-10-12 20:54:50 -07:00
Cameron Gutman 1d9efb30e2 Update to version 3.1.10 2015-10-11 17:11:48 -07:00
Cameron Gutman ed7be00881 Update IML file 2015-10-11 17:11:08 -07:00
Cameron Gutman a6003f6bff Remove MediaTek decoders from the decoders that need bitstream restrictions. The correct fix was to lower level_idc to reduce the required buffering. On newer MediaTek chipsets, sending bitstream restrictions actually slows down decoding by a factor of 3. 2015-10-11 16:57:37 -07:00
Cameron Gutman 4619045375 Revert "Update common to increase SSL handshake timeout"
This reverts commit 57b0da1a3a.
2015-10-11 14:54:45 -07:00
Cameron Gutman e61b8f1b34 Try a TCP connection before trying HTTPS to quickly eliminate transport layer connectivity issues 2015-10-11 14:39:02 -07:00
Cameron Gutman 79b6ec839a Fix machines becoming unreachable after they report IP addresses that they can't be contacted with 2015-10-11 14:16:38 -07:00
Cameron Gutman fd12e30c53 Set constraint flags corresponding to Constrained High Profile on KitKat and higher. Fixes Nexus Player high latency on Android 6.0. 2015-10-10 23:28:48 -07:00
Cameron Gutman 87a9ca4318 Make touchscreen and stylus support more robust (supporting Bluetooth stylus in 6.0 and hopefully fixing broken touchscreen input on some devices) 2015-10-10 19:17:19 -07:00
Cameron Gutman 3f64411174 Only reload the PcView activity if UI settings were changed 2015-10-10 18:53:10 -07:00
Cameron Gutman 57b0da1a3a Update common to increase SSL handshake timeout 2015-10-10 18:15:01 -07:00
Cameron Gutman 7d3e74a67f Allow the offline context menu to be opened when the PC state is unknown 2015-10-10 18:10:57 -07:00
Cameron Gutman d704e322df Use RGB_565 for box art to reduce image size in memory 2015-10-10 18:09:51 -07:00
Cameron Gutman f598153818 Small improvements to Media Codec DR 2015-10-10 15:17:24 -07:00
Cameron Gutman f395a0c170 Fix warning 2015-10-10 15:04:49 -07:00
Cameron Gutman 654b33d27f Update build tools version to 23.0.1 2015-10-10 15:04:16 -07:00
Cameron Gutman 6c12da96c9 Add patched Jcodec library built from master a5d138efec2e940897e7e3d91a63a1f58abedd95 with changes from https://github.com/jcodec/jcodec/pull/90 2015-10-10 15:03:47 -07:00
Cameron Gutman 1a6f639b81 Fix discovery issues when adding a PC 2015-10-10 14:43:29 -07:00
Cameron Gutman 59a00a38c9 Limit box art assets to 5 MB each to prevent OOM crashes 2015-10-10 14:43:17 -07:00
Cameron Gutman 2beee168e3 Update README.md with additional download links 2015-08-30 12:37:23 -07:00
15 changed files with 358 additions and 145 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ Check our [wiki](https://github.com/moonlight-stream/moonlight-android/wiki) for
##Installation
* Download and install Moonlight for Android from
[Google Play](https://play.google.com/store/apps/details?id=com.limelight)
[Google Play](https://play.google.com/store/apps/details?id=com.limelight), [Amazon App Store](http://www.amazon.com/gp/product/B00JK4MFN2), or directly from the [releases page](https://github.com/moonlight-stream/moonlight-android/releases)
* Download [GeForce Experience](http://www.geforce.com/geforce-experience) and install on your Windows PC
##Requirements
+1 -1
View File
@@ -114,7 +114,7 @@
<orderEntry type="library" exported="" name="limelight-common" level="project" />
<orderEntry type="library" exported="" name="jmdns-3.4.2" level="project" />
<orderEntry type="library" exported="" name="okhttp-2.4.0" level="project" />
<orderEntry type="library" exported="" name="jcodec-0.1.9" level="project" />
<orderEntry type="library" exported="" name="jcodec-0.1.9-patched" level="project" />
<orderEntry type="library" exported="" name="okio-1.5.0" level="project" />
</component>
</module>
+4 -5
View File
@@ -5,14 +5,14 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "22.0.1"
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 16
targetSdkVersion 23
versionName "3.1.9"
versionCode = 64
versionName "3.1.11"
versionCode = 66
}
productFlavors {
@@ -62,8 +62,6 @@ android {
}
dependencies {
compile group: 'org.jcodec', name: 'jcodec', version: '0.1.9'
compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.52'
compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.52'
@@ -73,4 +71,5 @@ dependencies {
compile files('libs/jmdns-3.4.2.jar')
compile files('libs/limelight-common.jar')
compile files('libs/tinyrtsp.jar')
compile files('libs/jcodec-0.1.9-patched.jar')
}
Binary file not shown.
+49 -55
View File
@@ -520,9 +520,56 @@ public class Game extends Activity implements SurfaceHolder.Callback,
}
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
{
// This case is for mice
if (event.getSource() == InputDevice.SOURCE_MOUSE)
{
int changedButtons = event.getButtonState() ^ lastButtonState;
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
// Send the vertical scroll packet
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
conn.sendMouseScroll(vScrollClicks);
}
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
}
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
}
}
// First process the history
for (int i = 0; i < event.getHistorySize(); i++) {
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
}
// Now process the current values
updateMousePosition((int)event.getX(), (int)event.getY());
lastButtonState = event.getButtonState();
}
// This case is for touch-based input devices
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN ||
event.getSource() == InputDevice.SOURCE_STYLUS)
else
{
int actionIndex = event.getActionIndex();
@@ -601,59 +648,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return false;
}
}
// This case is for mice
else if (event.getSource() == InputDevice.SOURCE_MOUSE)
{
int changedButtons = event.getButtonState() ^ lastButtonState;
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
// Send the vertical scroll packet
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
conn.sendMouseScroll(vScrollClicks);
}
if ((changedButtons & MotionEvent.BUTTON_PRIMARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_LEFT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_LEFT);
}
}
if ((changedButtons & MotionEvent.BUTTON_SECONDARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_RIGHT);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_RIGHT);
}
}
if ((changedButtons & MotionEvent.BUTTON_TERTIARY) != 0) {
if ((event.getButtonState() & MotionEvent.BUTTON_TERTIARY) != 0) {
conn.sendMouseButtonDown(MouseButtonPacket.BUTTON_MIDDLE);
}
else {
conn.sendMouseButtonUp(MouseButtonPacket.BUTTON_MIDDLE);
}
}
// First process the history
for (int i = 0; i < event.getHistorySize(); i++) {
updateMousePosition((int)event.getHistoricalX(i), (int)event.getHistoricalY(i));
}
// Now process the current values
updateMousePosition((int)event.getX(), (int)event.getY());
lastButtonState = event.getButtonState();
}
else
{
// Unknown source
return false;
}
// Handled a known source
return true;
+15 -13
View File
@@ -53,7 +53,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
private RelativeLayout noPcFoundLayout;
private PcGridAdapter pcGridAdapter;
private ComputerManagerService.ComputerManagerBinder managerBinder;
private boolean freezeUpdates, runningPolling;
private boolean freezeUpdates, runningPolling, hasResumed;
private final ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
final ComputerManagerService.ComputerManagerBinder localBinder =
@@ -215,6 +215,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
protected void onResume() {
super.onResume();
hasResumed = true;
startComputerUpdates();
}
@@ -222,6 +223,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
protected void onPause() {
super.onPause();
hasResumed = false;
stopComputerUpdates(false);
}
@@ -241,13 +243,10 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(info.position);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
startComputerUpdates();
return;
}
// Inflate the context menu
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE ||
computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
menu.add(Menu.NONE, WOL_ID, 1, getResources().getString(R.string.pcview_menu_send_wol));
menu.add(Menu.NONE, DELETE_ID, 2, getResources().getString(R.string.pcview_menu_delete_pc));
}
@@ -271,7 +270,11 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
@Override
public void onContextMenuClosed(Menu menu) {
startComputerUpdates();
// For some reason, this gets called again _after_ onPause() is called on this activity.
// We don't want to start computer updates again, so we need to keep track of whether we're paused.
if (hasResumed) {
startComputerUpdates();
}
}
private void doPair(final ComputerDetails computer) {
@@ -378,7 +381,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
}
private void doWakeOnLan(final ComputerDetails computer) {
if (computer.reachability != ComputerDetails.Reachability.OFFLINE) {
if (computer.state == ComputerDetails.State.ONLINE) {
Toast.makeText(PcView.this, getResources().getString(R.string.wol_pc_online), Toast.LENGTH_SHORT).show();
return;
}
@@ -614,10 +617,9 @@ public class PcView extends Activity implements AdapterFragmentCallbacks {
public void onItemClick(AdapterView<?> arg0, View arg1, int pos,
long id) {
ComputerObject computer = (ComputerObject) pcGridAdapter.getItem(pos);
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN) {
// Do nothing
} else if (computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
// Open the context menu if a PC is offline
if (computer.details.reachability == ComputerDetails.Reachability.UNKNOWN ||
computer.details.reachability == ComputerDetails.Reachability.OFFLINE) {
// Open the context menu if a PC is offline or refreshing
openContextMenu(arg1);
} else if (computer.details.pairState != PairState.PAIRED) {
// Pair an unpaired machine by default
@@ -3,6 +3,9 @@ package com.limelight.binding.input;
import com.limelight.nvstream.NvConnection;
import com.limelight.nvstream.input.MouseButtonPacket;
import java.util.Timer;
import java.util.TimerTask;
public class TouchContext {
private int lastTouchX = 0;
private int lastTouchY = 0;
@@ -10,14 +13,20 @@ public class TouchContext {
private int originalTouchY = 0;
private long originalTouchTime = 0;
private boolean cancelled;
private boolean confirmedMove;
private boolean confirmedDrag;
private Timer dragTimer;
private double distanceMoved;
private final NvConnection conn;
private final int actionIndex;
private final double xFactor;
private final double yFactor;
private static final int TAP_MOVEMENT_THRESHOLD = 10;
private static final int TAP_MOVEMENT_THRESHOLD = 20;
private static final int TAP_DISTANCE_THRESHOLD = 25;
private static final int TAP_TIME_THRESHOLD = 250;
private static final int DRAG_TIME_THRESHOLD = 650;
public TouchContext(NvConnection conn, int actionIndex, double xFactor, double yFactor)
{
@@ -32,15 +41,19 @@ public class TouchContext {
return actionIndex;
}
private boolean isWithinTapBounds(int touchX, int touchY)
{
int xDelta = Math.abs(touchX - originalTouchX);
int yDelta = Math.abs(touchY - originalTouchY);
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
yDelta <= TAP_MOVEMENT_THRESHOLD;
}
private boolean isTap()
{
int xDelta = Math.abs(lastTouchX - originalTouchX);
int yDelta = Math.abs(lastTouchY - originalTouchY);
long timeDelta = System.currentTimeMillis() - originalTouchTime;
return xDelta <= TAP_MOVEMENT_THRESHOLD &&
yDelta <= TAP_MOVEMENT_THRESHOLD &&
timeDelta <= TAP_TIME_THRESHOLD;
return isWithinTapBounds(lastTouchX, lastTouchY) && timeDelta <= TAP_TIME_THRESHOLD;
}
private byte getMouseButtonIndex()
@@ -58,7 +71,13 @@ public class TouchContext {
originalTouchX = lastTouchX = eventX;
originalTouchY = lastTouchY = eventY;
originalTouchTime = System.currentTimeMillis();
cancelled = false;
cancelled = confirmedDrag = confirmedMove = false;
distanceMoved = 0;
if (actionIndex == 0) {
// Start the timer for engaging a drag
startDragTimer();
}
return true;
}
@@ -69,10 +88,17 @@ public class TouchContext {
return;
}
if (isTap())
{
byte buttonIndex = getMouseButtonIndex();
// Cancel the drag timer
cancelDragTimer();
byte buttonIndex = getMouseButtonIndex();
if (confirmedDrag) {
// Raise the button after a drag
conn.sendMouseButtonUp(buttonIndex);
}
else if (isTap())
{
// Lower the mouse button
conn.sendMouseButtonDown(buttonIndex);
@@ -87,24 +113,101 @@ public class TouchContext {
}
}
private synchronized void startDragTimer() {
dragTimer = new Timer(true);
dragTimer.schedule(new TimerTask() {
@Override
public void run() {
synchronized (TouchContext.this) {
// Check if someone already set move
if (confirmedMove) {
return;
}
// Check if someone cancelled us
if (dragTimer == null) {
return;
}
// Uncancellable now
dragTimer = null;
// We haven't been cancelled before the timer expired so begin dragging
confirmedDrag = true;
conn.sendMouseButtonDown(getMouseButtonIndex());
}
}
}, DRAG_TIME_THRESHOLD);
}
private synchronized void cancelDragTimer() {
if (dragTimer != null) {
dragTimer.cancel();
dragTimer = null;
}
}
private synchronized void checkForConfirmedMove(int eventX, int eventY) {
// If we've already confirmed something, get out now
if (confirmedMove || confirmedDrag) {
return;
}
// If it leaves the tap bounds before the drag time expires, it's a move.
if (!isWithinTapBounds(eventX, eventY)) {
confirmedMove = true;
cancelDragTimer();
return;
}
// Check if we've exceeded the maximum distance moved
distanceMoved += Math.sqrt(Math.pow(eventX - lastTouchX, 2) + Math.pow(eventY - lastTouchY, 2));
if (distanceMoved >= TAP_DISTANCE_THRESHOLD) {
confirmedMove = true;
cancelDragTimer();
return;
}
}
public boolean touchMoveEvent(int eventX, int eventY)
{
if (eventX != lastTouchX || eventY != lastTouchY)
{
// We only send moves for the primary touch point
// We only send moves and drags for the primary touch point
if (actionIndex == 0) {
checkForConfirmedMove(eventX, eventY);
int deltaX = eventX - lastTouchX;
int deltaY = eventY - lastTouchY;
// Scale the deltas based on the factors passed to our constructor
deltaX = (int)Math.round((double)deltaX * xFactor);
deltaY = (int)Math.round((double)deltaY * yFactor);
deltaX = (int)Math.round((double)Math.abs(deltaX) * xFactor);
deltaY = (int)Math.round((double)Math.abs(deltaY) * yFactor);
// Fix up the signs
if (eventX < lastTouchX) {
deltaX = -deltaX;
}
if (eventY < lastTouchY) {
deltaY = -deltaY;
}
// If the scaling factor ended up rounding deltas to zero, wait until they are
// non-zero to update lastTouch that way devices that report small touch events often
// will work correctly
if (deltaX != 0) {
lastTouchX = eventX;
}
if (deltaY != 0) {
lastTouchY = eventY;
}
conn.sendMouseMove((short)deltaX, (short)deltaY);
}
lastTouchX = eventX;
lastTouchY = eventY;
else {
lastTouchX = eventX;
lastTouchY = eventY;
}
}
return true;
@@ -112,6 +215,14 @@ public class TouchContext {
public void cancelTouch() {
cancelled = true;
// Cancel the drag timer
cancelDragTimer();
// If it was a confirmed drag, we'll need to raise the button now
if (confirmedDrag) {
conn.sendMouseButtonUp(getMouseButtonIndex());
}
}
public boolean isCancelled() {
@@ -31,6 +31,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private final boolean needsSpsBitstreamFixup, isExynos4;
private VideoDepacketizer depacketizer;
private final boolean adaptivePlayback, directSubmit;
private final boolean constrainedHighProfile;
private int initialWidth, initialHeight;
private boolean needsBaselineSpsHack;
@@ -56,7 +57,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
if (decoder == null) {
// This case is handled later in setup()
needsSpsBitstreamFixup = isExynos4 =
adaptivePlayback = directSubmit = false;
adaptivePlayback = directSubmit =
constrainedHighProfile = false;
return;
}
@@ -67,6 +69,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
adaptivePlayback = MediaCodecHelper.decoderSupportsAdaptivePlayback(decoderName, decoder);
needsSpsBitstreamFixup = MediaCodecHelper.decoderNeedsSpsBitstreamRestrictions(decoderName, decoder);
needsBaselineSpsHack = MediaCodecHelper.decoderNeedsBaselineSpsHack(decoderName, decoder);
constrainedHighProfile = MediaCodecHelper.decoderNeedsConstrainedHighProfile(decoderName, decoder);
isExynos4 = MediaCodecHelper.isExynos4Device();
if (needsSpsBitstreamFixup) {
LimeLog.info("Decoder "+decoderName+" needs SPS bitstream restrictions fixup");
@@ -74,6 +77,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
if (needsBaselineSpsHack) {
LimeLog.info("Decoder "+decoderName+" needs baseline SPS hack");
}
if (constrainedHighProfile) {
LimeLog.info("Decoder "+decoderName+" needs constrained high profile");
}
if (isExynos4) {
LimeLog.info("Decoder "+decoderName+" is on Exynos 4");
}
@@ -433,6 +439,23 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
return buf;
}
private void doProfileSpecificSpsPatching(SeqParameterSet sps) {
// Some devices benefit from setting constraint flags 4 & 5 to make this Constrained
// High Profile which allows the decoder to assume there will be no B-frames and
// reduce delay and buffering accordingly. Some devices (Marvell, Exynos 4) don't
// like it so we only set them on devices that are confirmed to benefit from it.
if (sps.profile_idc == 100 && constrainedHighProfile) {
LimeLog.info("Setting constraint set flags for constrained high profile");
sps.constraint_set_4_flag = true;
sps.constraint_set_5_flag = true;
}
else {
// Force the constraints unset otherwise (some may be set by default)
sps.constraint_set_4_flag = false;
sps.constraint_set_5_flag = false;
}
}
@SuppressWarnings("deprecation")
private void submitDecodeUnit(DecodeUnit decodeUnit, int inputBufferIndex) {
long timestampUs = System.nanoTime() / 1000;
@@ -499,12 +522,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Some devices don't like these so we remove them here.
sps.vuiParams.video_signal_type_present_flag = false;
sps.vuiParams.colour_description_present_flag = false;
sps.vuiParams.colour_primaries = 2;
sps.vuiParams.transfer_characteristics = 2;
sps.vuiParams.matrix_coefficients = 2;
sps.vuiParams.chroma_loc_info_present_flag = false;
sps.vuiParams.chroma_sample_loc_type_bottom_field = 0;
sps.vuiParams.chroma_sample_loc_type_top_field = 0;
if (needsSpsBitstreamFixup || isExynos4) {
// The SPS that comes in the current H264 bytestream doesn't set bitstream_restriction_flag
@@ -547,6 +565,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
savedSps = sps;
}
// Patch the SPS constraint flags
doProfileSpecificSpsPatching(sps);
// Write the annex B header
buf.put(header.data, header.offset, 5);
@@ -599,6 +620,9 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Switch the H264 profile back to high
savedSps.profile_idc = 100;
// Patch the SPS constraint flags
doProfileSpecificSpsPatching(savedSps);
// Write the SPS data
savedSps.write(inputBuffer);
@@ -704,6 +728,8 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
str += "In stats: "+renderer.numSpsIn+", "+renderer.numPpsIn+", "+renderer.numIframeIn+"\n";
str += "Total frames: "+renderer.totalFrames+"\n";
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
if (currentBuffer != null) {
str += "Current buffer: ";
@@ -27,6 +27,7 @@ public class MediaCodecHelper {
private static final List<String> whitelistedAdaptiveResolutionPrefixes;
private static final List<String> baselineProfileHackPrefixes;
private static final List<String> directSubmitPrefixes;
private static final List<String> constrainedHighProfilePrefixes;
static {
directSubmitPrefixes = new LinkedList<String>();
@@ -58,7 +59,6 @@ public class MediaCodecHelper {
spsFixupBitstreamFixupDecoderPrefixes = new LinkedList<String>();
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
baselineProfileHackPrefixes = new LinkedList<String>();
@@ -69,6 +69,9 @@ public class MediaCodecHelper {
whitelistedAdaptiveResolutionPrefixes.add("omx.qcom");
whitelistedAdaptiveResolutionPrefixes.add("omx.sec");
whitelistedAdaptiveResolutionPrefixes.add("omx.TI");
constrainedHighProfilePrefixes = new LinkedList<String>();
constrainedHighProfilePrefixes.add("omx.intel");
}
private static boolean isDecoderInList(List<String> decoderList, String decoderName) {
@@ -117,6 +120,10 @@ public class MediaCodecHelper {
return false;
}
public static boolean decoderNeedsConstrainedHighProfile(String decoderName, MediaCodecInfo decoderInfo) {
return isDecoderInList(constrainedHighProfilePrefixes, decoderName);
}
public static boolean decoderCanDirectSubmit(String decoderName, MediaCodecInfo decoderInfo) {
return isDecoderInList(directSubmitPrefixes, decoderName) && !isExynos4Device();
}
@@ -368,6 +368,11 @@ public class ComputerManagerService extends Service {
}
private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
// Fast poll this address first to determine if we can connect at the TCP layer
if (!fastPollIp(ipAddr)) {
return null;
}
try {
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
@@ -451,7 +456,7 @@ public class ComputerManagerService extends Service {
return ComputerDetails.Reachability.OFFLINE;
}
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
private ReachabilityTuple pollForReachability(ComputerDetails details) throws InterruptedException {
ComputerDetails polledDetails;
ComputerDetails.Reachability reachability;
@@ -465,11 +470,11 @@ public class ComputerManagerService extends Service {
LimeLog.info("Starting fast poll for "+details.name+" ("+details.localIp+", "+details.remoteIp+")");
reachability = fastPollPc(details.localIp, details.remoteIp);
LimeLog.info("Fast poll for "+details.name+" returned "+reachability.toString());
}
// If no connection could be established to either IP address, there's nothing we can do
if (reachability == ComputerDetails.Reachability.OFFLINE) {
return false;
// If no connection could be established to either IP address, there's nothing we can do
if (reachability == ComputerDetails.Reachability.OFFLINE) {
return null;
}
}
boolean localFirst = (reachability == ComputerDetails.Reachability.LOCAL);
@@ -481,6 +486,7 @@ public class ComputerManagerService extends Service {
polledDetails = tryPollIp(details, details.remoteIp);
}
InetAddress reachableAddr = null;
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
// Failed, so let's try the fallback
if (!localFirst) {
@@ -490,27 +496,59 @@ public class ComputerManagerService extends Service {
polledDetails = tryPollIp(details, details.remoteIp);
}
// The fallback poll worked
if (polledDetails != null) {
polledDetails.reachability = !localFirst ? ComputerDetails.Reachability.LOCAL :
ComputerDetails.Reachability.REMOTE;
// The fallback poll worked
reachableAddr = !localFirst ? details.localIp : details.remoteIp;
}
}
else if (polledDetails != null) {
polledDetails.reachability = localFirst ? ComputerDetails.Reachability.LOCAL :
ComputerDetails.Reachability.REMOTE;
reachableAddr = localFirst ? details.localIp : details.remoteIp;
}
// Machine was unreachable both tries
if (polledDetails == null) {
if (reachableAddr == null) {
return null;
}
if (polledDetails.remoteIp.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.REMOTE;
}
else if (polledDetails.localIp.equals(reachableAddr)) {
polledDetails.reachability = ComputerDetails.Reachability.LOCAL;
}
else {
polledDetails.reachability = ComputerDetails.Reachability.UNKNOWN;
}
return new ReachabilityTuple(polledDetails, reachableAddr);
}
private boolean pollComputer(ComputerDetails details) throws InterruptedException {
ReachabilityTuple initialReachTuple = pollForReachability(details);
if (initialReachTuple == null) {
return false;
}
if (initialReachTuple.computer.reachability == ComputerDetails.Reachability.UNKNOWN) {
// Neither IP address reported in the serverinfo response was the one we used.
// Poll again to see if we can contact this machine on either of its reported addresses.
ReachabilityTuple confirmationReachTuple = pollForReachability(initialReachTuple.computer);
if (confirmationReachTuple == null) {
// Neither of those seem to work, so we'll hold onto the address that did work
initialReachTuple.computer.localIp = initialReachTuple.reachableAddress;
initialReachTuple.computer.reachability = ComputerDetails.Reachability.LOCAL;
}
else {
// We got it on one of the returned addresses; replace the original reach tuple
// with the new one
initialReachTuple = confirmationReachTuple;
}
}
// Save the old MAC address
String savedMacAddress = details.macAddress;
// If we got here, it's reachable
details.update(polledDetails);
details.update(initialReachTuple.computer);
// If the new MAC address is empty, restore the old one (workaround for GFE bug)
if (details.macAddress.equals("00:00:00:00:00:00") && savedMacAddress != null) {
@@ -685,4 +723,14 @@ class PollingTuple {
this.computer = computer;
this.thread = thread;
}
}
class ReachabilityTuple {
public final InetAddress reachableAddress;
public final ComputerDetails computer;
public ReachabilityTuple(ComputerDetails computer, InetAddress reachableAddress) {
this.computer = computer;
this.reachableAddress = reachableAddress;
}
}
@@ -13,6 +13,9 @@ import java.io.InputStream;
import java.io.OutputStream;
public class DiskAssetLoader {
// 5 MB
private final long MAX_ASSET_SIZE = 5 * 1024 * 1024;
private final File cacheDir;
public DiskAssetLoader(File cacheDir) {
@@ -27,13 +30,19 @@ public class DiskAssetLoader {
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;
}
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 (FileNotFoundException ignored) {
} catch (IOException e) {
e.printStackTrace();
} catch (IOException ignored) {
} finally {
if (in != null) {
try {
@@ -51,9 +60,11 @@ public class DiskAssetLoader {
public void populateCacheWithStream(CachedAppAssetLoader.LoaderTuple tuple, InputStream input) {
OutputStream out = null;
boolean success = false;
try {
out = CacheHelper.openCacheFileForOutput(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
CacheHelper.writeInputStreamToOutputStream(input, out);
CacheHelper.writeInputStreamToOutputStream(input, out, MAX_ASSET_SIZE);
success = true;
} catch (IOException e) {
e.printStackTrace();
} finally {
@@ -62,6 +73,11 @@ public class DiskAssetLoader {
out.close();
} catch (IOException ignored) {}
}
if (!success) {
LimeLog.warning("Unable to populate cache with tuple: "+tuple);
CacheHelper.deleteCacheFile(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
}
}
}
}
@@ -90,24 +90,7 @@ public class PreferenceConfiguration {
public static int getDefaultBitrate(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String str = prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS);
if (str.equals("720p30")) {
return BITRATE_DEFAULT_720_30;
}
else if (str.equals("720p60")) {
return BITRATE_DEFAULT_720_60;
}
else if (str.equals("1080p30")) {
return BITRATE_DEFAULT_1080_30;
}
else if (str.equals("1080p60")) {
return BITRATE_DEFAULT_1080_60;
}
else {
// Should never get here
return DEFAULT_BITRATE;
}
return getDefaultBitrate(prefs.getString(RES_FPS_PREF_STRING, DEFAULT_RES_FPS));
}
private static int getDecoderValue(Context context) {
@@ -16,14 +16,17 @@ import com.limelight.utils.UiHelper;
import java.util.Locale;
public class StreamSettings extends Activity {
private PreferenceConfiguration previousPrefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String locale = PreferenceConfiguration.readPreferences(this).language;
if (!locale.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
previousPrefs = PreferenceConfiguration.readPreferences(this);
if (!previousPrefs.language.equals(PreferenceConfiguration.DEFAULT_LANGUAGE)) {
Configuration config = new Configuration(getResources().getConfiguration());
config.locale = new Locale(locale);
config.locale = new Locale(previousPrefs.language);
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
}
@@ -39,10 +42,16 @@ public class StreamSettings extends Activity {
public void onBackPressed() {
finish();
// Restart the PC view to apply UI changes
Intent intent = new Intent(this, PcView.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent, null);
// Check for changes that require a UI reload to take effect
PreferenceConfiguration newPrefs = PreferenceConfiguration.readPreferences(this);
if (newPrefs.listMode != previousPrefs.listMode ||
newPrefs.smallIconMode != previousPrefs.smallIconMode ||
!newPrefs.language.equals(previousPrefs.language)) {
// Restart the PC view to apply UI changes
Intent intent = new Intent(this, PcView.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent, null);
}
}
public static class SettingsFragment extends PreferenceFragment {
@@ -30,6 +30,14 @@ public class CacheHelper {
return f;
}
public static long getFileSize(File root, String... path) {
return openPath(false, root, path).length();
}
public static boolean deleteCacheFile(File root, String... path) {
return openPath(false, root, path).delete();
}
public static boolean cacheFileExists(File root, String... path) {
return openPath(false, root, path).exists();
}
@@ -42,11 +50,15 @@ public class CacheHelper {
return new BufferedOutputStream(new FileOutputStream(openPath(true, root, path)));
}
public static void writeInputStreamToOutputStream(InputStream in, OutputStream out) throws IOException {
public static void writeInputStreamToOutputStream(InputStream in, OutputStream out, long maxLength) throws IOException {
byte[] buf = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buf)) != -1) {
maxLength -= bytesRead;
if (maxLength <= 0) {
throw new IOException("Stream exceeded max size");
}
out.write(buf, 0, bytesRead);
}
}
+10 -4
View File
@@ -12,8 +12,8 @@ This file serves to document some of the decoder errata when using MediaCodec ha
4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS on first I-frame
- Affected decoders: Qualcomm in GS3 on 4.3+, Exynos 4 at 1080p only
5. Some decoders will hang if max_dec_frame_buffering is not present
- Affected decoders: MediaTek decoder in Fire HD 7 (2014)
5. Some decoders will hang or crash if max_dec_frame_buffering is not present and level_idc is >= 50
- Affected decoders: MediaTek decoder in Fire HD 6/7 (2014)
6. Some decoders will hang if max_dec_frame_buffering IS present
- Affected decoders: Exynos 5 in Galaxy Note 10.1 (2014)
@@ -21,5 +21,11 @@ This file serves to document some of the decoder errata when using MediaCodec ha
7. Some decoders will not enter low latency mode if adaptive playback is enabled
- Affected decoders: Intel decoder in Nexus Player
8. Some decoders will not enter low latency mode if the profile isn't baseline in the first SPS.
- Affected decoders: Intel decoder in Nexus Player
8. Some decoders will not enter low latency mode if the profile isn't baseline in the first SPS because B-frames may be present.
- Affected decoders: Intel decoder in Nexus Player (prior to Android 6.0)
9. Some decoders will not enter low latency mode if the profile isn't constrained high profile because B-frames may be present.
- Affected decoders: Intel decoder in Nexus Player (after Android 6.0)
10. Some decoders actually suffer increased latency when max_dec_frame_buffering=1
- Affected decoders: MediaTek decoder in Fire TV 2015