diff --git a/app/app.iml b/app/app.iml
index 1c289387..539222d2 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -102,17 +102,17 @@
-
+
-
+
-
+
diff --git a/app/build.gradle b/app/build.gradle
index d17049e4..f3f1e5b7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -11,8 +11,8 @@ android {
minSdkVersion 16
targetSdkVersion 21
- versionName "3.0"
- versionCode = 46
+ versionName "3.0.1"
+ versionCode = 47
}
productFlavors {
@@ -62,13 +62,19 @@ android {
}
dependencies {
- compile group: 'org.jcodec', name: 'jcodec', version: '0.1.9'
- compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.51'
- compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.51'
- compile group: 'com.google.android', name: 'support-v4', version:'r7'
- compile group: 'com.koushikdutta.ion', name: 'ion', version:'2.0.1'
- compile group: 'com.squareup.okhttp', name: 'okhttp', version:'2.1.0'
- compile group: 'com.squareup.okio', name:'okio', version:'1.0.1'
+ compile group: 'org.jcodec', name: 'jcodec', version: '+'
+ compile group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '+'
+ compile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '+'
+ compile group: 'com.google.android', name: 'support-v4', version:'+'
+
+ // FIXME: Pending resolution of issue #346 using custom build
+ //compile group: 'com.koushikdutta.ion', name: 'ion', version:'+'
+ compile group: 'com.google.code.gson', name: 'gson', version:'+'
+ compile files('libs/androidasync-e1dfb4.jar')
+ compile files('libs/ion-2f46fa.jar')
+
+ compile group: 'com.squareup.okhttp', name: 'okhttp', version:'+'
+ compile group: 'com.squareup.okio', name:'okio', version:'+'
compile files('libs/jmdns-fixed.jar')
compile files('libs/limelight-common.jar')
compile files('libs/tinyrtsp.jar')
diff --git a/app/libs/androidasync-e1dfb4.jar b/app/libs/androidasync-e1dfb4.jar
new file mode 100644
index 00000000..a583e4d5
Binary files /dev/null and b/app/libs/androidasync-e1dfb4.jar differ
diff --git a/app/libs/ion-2f46fa.jar b/app/libs/ion-2f46fa.jar
new file mode 100644
index 00000000..08fbea63
Binary files /dev/null and b/app/libs/ion-2f46fa.jar differ
diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java
index a3300a52..c5906df8 100644
--- a/app/src/main/java/com/limelight/Game.java
+++ b/app/src/main/java/com/limelight/Game.java
@@ -178,7 +178,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
.enableAdaptiveResolution((decoderRenderer.getCapabilities() &
VideoDecoderRenderer.CAPABILITY_ADAPTIVE_RESOLUTION) != 0)
.enableLocalAudioPlayback(prefConfig.playHostAudio)
- .setMaxPacketSize(remote ? 1024 : 1392)
+ .setMaxPacketSize(remote ? 1024 : 1292)
.build();
// Initialize the connection
diff --git a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java
index 10c88a25..e68b475d 100644
--- a/app/src/main/java/com/limelight/binding/input/ControllerHandler.java
+++ b/app/src/main/java/com/limelight/binding/input/ControllerHandler.java
@@ -47,6 +47,7 @@ public class ControllerHandler {
private NvConnection conn;
private double stickDeadzone;
private final ControllerMapping defaultMapping = new ControllerMapping();
+ private boolean hasGameController;
public ControllerHandler(NvConnection conn, int deadzonePercentage) {
this.conn = conn;
@@ -55,6 +56,20 @@ public class ControllerHandler {
// is required for controller batching support to work.
deadzonePercentage = 10;
+ int[] ids = InputDevice.getDeviceIds();
+ for (int i = 0; i < ids.length; i++) {
+ InputDevice dev = InputDevice.getDevice(ids[i]);
+ if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 ||
+ (dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) {
+ // This looks like a gamepad, but we'll check X and Y to be sure
+ if (getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_X) != null &&
+ getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_Y) != null) {
+ // This is a gamepad
+ hasGameController = true;
+ }
+ }
+ }
+
// 1% is the lowest possible deadzone we support
if (deadzonePercentage <= 0) {
deadzonePercentage = 1;
@@ -94,6 +109,12 @@ public class ControllerHandler {
mapping.leftStickXAxis = MotionEvent.AXIS_X;
mapping.leftStickYAxis = MotionEvent.AXIS_Y;
+ if (getMotionRangeForJoystickAxis(dev, mapping.leftStickXAxis) != null &&
+ getMotionRangeForJoystickAxis(dev, mapping.leftStickYAxis) != null) {
+ // This is a gamepad
+ hasGameController = true;
+ mapping.hasJoystickAxes = true;
+ }
InputDevice.MotionRange leftTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_LTRIGGER);
InputDevice.MotionRange rightTriggerRange = getMotionRangeForJoystickAxis(dev, MotionEvent.AXIS_RTRIGGER);
@@ -182,27 +203,43 @@ public class ControllerHandler {
// It's important to have a valid deadzone so controller packet batching works properly
mapping.triggerDeadzone = Math.max(Math.abs(ltRange.getFlat()), Math.abs(rtRange.getFlat()));
- // For triggers without (valid) deadzones, we'll use 10%
- if (mapping.triggerDeadzone <= 0.02 ||
- mapping.triggerDeadzone > 0.30)
+ // For triggers without (valid) deadzones, we'll use 13% (around XInput's default)
+ if (mapping.triggerDeadzone < 0.13f ||
+ mapping.triggerDeadzone > 0.30f)
{
- mapping.triggerDeadzone = 0.1f;
+ mapping.triggerDeadzone = 0.13f;
}
}
- // For the Nexus Player (and probably other ATV devices), we should
- // use the back button as start since it doesn't have a start/menu button
- // on the controller
- if (devName != null && devName.contains("ASUS Gamepad")) {
- // We can only do this check on KitKat or higher, but it doesn't matter since ATV
- // is Android 5.0 anyway
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
- boolean[] hasStartKey = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_MENU, 0);
- if (!hasStartKey[0] && !hasStartKey[1]) {
- mapping.backIsStart = true;
+ if (devName != null) {
+ // For the Nexus Player (and probably other ATV devices), we should
+ // use the back button as start since it doesn't have a start/menu button
+ // on the controller
+ if (devName.contains("ASUS Gamepad")) {
+ // We can only do this check on KitKat or higher, but it doesn't matter since ATV
+ // is Android 5.0 anyway
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
+ boolean[] hasStartKey = dev.hasKeys(KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_MENU, 0);
+ if (!hasStartKey[0] && !hasStartKey[1]) {
+ mapping.backIsStart = true;
+ }
+ }
+
+ // The ASUS Gamepad has triggers that sit far forward and are prone to false presses
+ // so we increase the deadzone on them to minimize this
+ mapping.triggerDeadzone = 0.30f;
+ }
+ // Classify this device as a remote by name
+ else if (devName.contains("Fire TV Remote") || devName.contains("Nexus Remote")) {
+ // It's only a remote if it doesn't any sticks
+ if (!mapping.hasJoystickAxes) {
+ mapping.isRemote = true;
}
}
}
+
+ LimeLog.info("Analog stick deadzone: "+mapping.leftStickDeadzoneRadius+" "+mapping.rightStickDeadzoneRadius);
+ LimeLog.info("Trigger deadzone: "+mapping.triggerDeadzone);
return mapping;
}
@@ -232,9 +269,18 @@ public class ControllerHandler {
conn.sendControllerInput(inputMap, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
-
- private static int handleRemapping(ControllerMapping mapping, KeyEvent event) {
- if (mapping.isDualShock4) {
+
+ // Return a valid keycode, 0 to consume, or -1 to not consume the event
+ // Device MAY BE NULL
+ private int handleRemapping(ControllerMapping mapping, KeyEvent event) {
+ // For remotes, don't capture the back button
+ if (mapping.isRemote) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ return -1;
+ }
+ }
+
+ if (mapping.isDualShock4) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_BUTTON_Y:
return KeyEvent.KEYCODE_BUTTON_L1;
@@ -718,5 +764,7 @@ public class ControllerHandler {
public boolean isDualShock4;
public boolean isXboxController;
public boolean backIsStart;
+ public boolean isRemote;
+ public boolean hasJoystickAxes;
}
}
diff --git a/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java
index 7b05b341..c43fd930 100644
--- a/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java
+++ b/app/src/main/java/com/limelight/binding/video/AndroidCpuDecoderRenderer.java
@@ -141,7 +141,9 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
throw new IllegalStateException("AVC decoder initialization failure: "+err);
}
- AvcDecoder.setRenderTarget(sh.getSurface());
+ if (!AvcDecoder.setRenderTarget(sh.getSurface())) {
+ return false;
+ }
decoderBuffer = ByteBuffer.allocate(DECODER_BUFFER_SIZE + AvcDecoder.getInputPaddingSize());
@@ -252,7 +254,7 @@ public class AndroidCpuDecoderRenderer extends EnhancedDecoderRenderer {
// Add delta time to the totals (excluding probable outliers)
long delta = timeAfterDecode - decodeUnit.getReceiveTimestamp();
- if (delta >= 0 && delta < 300) {
+ if (delta >= 0 && delta < 1000) {
totalTimeMs += delta;
totalFrames++;
}
diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
index 02e5ce36..5af027e0 100644
--- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
+++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java
@@ -273,7 +273,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
// Add delta time to the totals (excluding probable outliers)
long delta = System.currentTimeMillis()-(presentationTimeUs/1000);
- if (delta >= 0 && delta < 300) {
+ if (delta >= 0 && delta < 1000) {
decoderTimeMs += delta;
totalTimeMs += delta;
}
@@ -371,7 +371,7 @@ public class MediaCodecDecoderRenderer extends EnhancedDecoderRenderer {
private void submitDecodeUnit(DecodeUnit decodeUnit, ByteBuffer buf, int inputBufferIndex) {
long currentTime = System.currentTimeMillis();
long delta = currentTime-decodeUnit.getReceiveTimestamp();
- if (delta >= 0 && delta < 300) {
+ if (delta >= 0 && delta < 1000) {
totalTimeMs += currentTime-decodeUnit.getReceiveTimestamp();
totalFrames++;
}
diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java b/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java
index a101a2b2..e106271e 100644
--- a/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java
+++ b/app/src/main/java/com/limelight/binding/video/MediaCodecHelper.java
@@ -44,6 +44,7 @@ public class MediaCodecHelper {
spsFixupBitstreamFixupDecoderPrefixes.add("omx.nvidia");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.qcom");
spsFixupBitstreamFixupDecoderPrefixes.add("omx.mtk");
+ spsFixupBitstreamFixupDecoderPrefixes.add("omx.brcm");
baselineProfileHackPrefixes = new LinkedList();
baselineProfileHackPrefixes.add("omx.intel");
diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerService.java b/app/src/main/java/com/limelight/computers/ComputerManagerService.java
index da125682..182ff690 100644
--- a/app/src/main/java/com/limelight/computers/ComputerManagerService.java
+++ b/app/src/main/java/com/limelight/computers/ComputerManagerService.java
@@ -244,9 +244,12 @@ public class ComputerManagerService extends Service {
for (PollingTuple tuple : pollingTuples) {
// Check if this is the same computer
if (tuple.computer == details ||
- tuple.computer.localIp.equals(details.localIp) ||
- tuple.computer.remoteIp.equals(details.remoteIp) ||
- tuple.computer.name.equals(details.name)) {
+ // If there's no name on one of these computers, compare with the local IP
+ ((details.name.isEmpty() || tuple.computer.name.isEmpty()) &&
+ tuple.computer.localIp.equals(details.localIp)) ||
+ // If there is a name on both computers, compare with name
+ ((!details.name.isEmpty() && !tuple.computer.name.isEmpty()) &&
+ tuple.computer.name.equals(details.name))) {
// Start a polling thread if polling is active
if (pollingActive && tuple.thread == null) {
@@ -329,12 +332,22 @@ public class ComputerManagerService extends Service {
}
}
- private ComputerDetails tryPollIp(InetAddress ipAddr) {
+ private ComputerDetails tryPollIp(ComputerDetails details, InetAddress ipAddr) {
try {
NvHTTP http = new NvHTTP(ipAddr, idManager.getUniqueId(),
null, PlatformBinding.getCryptoProvider(ComputerManagerService.this));
- return http.getComputerDetails();
+ ComputerDetails newDetails = http.getComputerDetails();
+
+ // Check if this is the PC we expected
+ if (details.uuid != null && newDetails.uuid != null &&
+ !details.uuid.equals(newDetails.uuid)) {
+ // We got the wrong PC!
+ LimeLog.info("Polling returned the wrong PC!");
+ return null;
+ }
+
+ return newDetails;
} catch (Exception e) {
return null;
}
@@ -342,21 +355,27 @@ public class ComputerManagerService extends Service {
private boolean pollComputer(ComputerDetails details, boolean localFirst) {
ComputerDetails polledDetails;
+
+ // If the local address is routable across the Internet,
+ // always consider this PC remote to be conservative
+ if (details.localIp.equals(details.remoteIp)) {
+ localFirst = false;
+ }
if (localFirst) {
- polledDetails = tryPollIp(details.localIp);
+ polledDetails = tryPollIp(details, details.localIp);
}
else {
- polledDetails = tryPollIp(details.remoteIp);
+ polledDetails = tryPollIp(details, details.remoteIp);
}
if (polledDetails == null && !details.localIp.equals(details.remoteIp)) {
// Failed, so let's try the fallback
if (!localFirst) {
- polledDetails = tryPollIp(details.localIp);
+ polledDetails = tryPollIp(details, details.localIp);
}
else {
- polledDetails = tryPollIp(details.remoteIp);
+ polledDetails = tryPollIp(details, details.remoteIp);
}
// The fallback poll worked
@@ -414,8 +433,8 @@ public class ComputerManagerService extends Service {
}
for (ComputerDetails computer : dbManager.getAllComputers()) {
- // Add this computer without a thread
- pollingTuples.add(new PollingTuple(computer, null));
+ // Add tuples for each computer
+ addTuple(computer);
}
releaseLocalDatabaseReference();
diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
index fbcfa423..787324ef 100644
--- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
+++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java
@@ -13,6 +13,7 @@ public class PreferenceConfiguration {
private static final String DISABLE_TOASTS_PREF_STRING = "checkbox_disable_warnings";
private static final String HOST_AUDIO_PREF_STRING = "checkbox_host_audio";
private static final String DEADZONE_PREF_STRING = "seekbar_deadzone";
+ private static final String LANGUAGE_PREF_STRING = "list_languages";
private static final int BITRATE_DEFAULT_720_30 = 5;
private static final int BITRATE_DEFAULT_720_60 = 10;
@@ -27,6 +28,7 @@ public class PreferenceConfiguration {
private static final boolean DEFAULT_DISABLE_TOASTS = false;
private static final boolean DEFAULT_HOST_AUDIO = false;
private static final int DEFAULT_DEADZONE = 15;
+ private static final String DEFAULT_LANGUAGE = "default";
public static final int FORCE_HARDWARE_DECODER = -1;
public static final int AUTOSELECT_DECODER = 0;
@@ -37,6 +39,7 @@ public class PreferenceConfiguration {
public int decoder;
public int deadzonePercentage;
public boolean stretchVideo, enableSops, playHostAudio, disableWarnings;
+ public String language;
public static int getDefaultBitrate(String resFpsString) {
if (resFpsString.equals("720p30")) {
@@ -135,6 +138,8 @@ public class PreferenceConfiguration {
config.deadzonePercentage = prefs.getInt(DEADZONE_PREF_STRING, DEFAULT_DEADZONE);
+ config.language = prefs.getString(LANGUAGE_PREF_STRING, DEFAULT_LANGUAGE);
+
// Checkbox preferences
config.disableWarnings = prefs.getBoolean(DISABLE_TOASTS_PREF_STRING, DEFAULT_DISABLE_TOASTS);
config.enableSops = prefs.getBoolean(SOPS_PREF_STRING, DEFAULT_SOPS);
diff --git a/app/src/main/jni/nv_avc_dec/nv_avc_dec.c b/app/src/main/jni/nv_avc_dec/nv_avc_dec.c
index 1209cbc4..500319bd 100644
--- a/app/src/main/jni/nv_avc_dec/nv_avc_dec.c
+++ b/app/src/main/jni/nv_avc_dec/nv_avc_dec.c
@@ -69,9 +69,6 @@ int nv_avc_init(int width, int height, int perf_lvl, int thread_count) {
return -1;
}
- // Show frames even before a reference frame
- decoder_ctx->flags2 |= CODEC_FLAG2_SHOW_ALL;
-
if (perf_lvl & DISABLE_LOOP_FILTER) {
// Skip the loop filter for performance reasons
decoder_ctx->skip_loop_filter = AVDISCARD_ALL;
@@ -370,17 +367,20 @@ int nv_avc_decode(unsigned char* indata, int inlen) {
// Only copy the picture at the end of decoding the packet
if (got_pic) {
+ // Clone the current decode frame outside of the mutex
+ AVFrame* new_frame = av_frame_clone(dec_frame);
+ AVFrame* old_frame;
+
+ // Swap it in under lock
pthread_mutex_lock(&mutex);
-
- // Only clone this frame if the last frame was taken.
- // This saves on extra copies for frames that don't get
- // rendered.
- if (yuv_frame == NULL) {
- // Clone a new frame
- yuv_frame = av_frame_clone(dec_frame);
- }
-
+ old_frame = yuv_frame;
+ yuv_frame = new_frame;
pthread_mutex_unlock(&mutex);
+
+ // Free the old frame outside of the mutex
+ if (old_frame != NULL) {
+ av_frame_free(&old_frame);
+ }
}
return err < 0 ? err : 0;
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 70cea328..7c39b917 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -87,7 +87,7 @@
Abbassa il bitrate per ridurre lo stuttering; alza il bitrate per aumenteare la qualità dell\'immagine
Mbps
Forza video in full-screen
- Disabilita i messaggi di warning
+ Disabilita messaggi di warning
Disabilita i messaggi di warning sullo schermo durante lo streaming
Lingua
Lingua da usare in Limelight
@@ -104,5 +104,5 @@
Impostazioni Avanzate
Cambia decoder
- Il decoder software può aumentare la latenza video quando si usano impostazioni streaming basse
+ Il decoder software può ridurre la latenza video quando si usano impostazioni streaming basse
diff --git a/decoder-errata.txt b/decoder-errata.txt
index 0658dd32..577562b4 100644
--- a/decoder-errata.txt
+++ b/decoder-errata.txt
@@ -4,12 +4,12 @@ This file serves to document some of the decoder errata when using MediaCodec ha
- Affected decoders: TI OMAP4, Allwinner A20
2. Some decoders have a huge per-frame latency with the unmodified SPS sent from NVENC. Setting max_dec_frame_buffering fixes this latency issue.
- - Affected decoders: NVIDIA Tegra 3 and 4
+ - Affected decoders: NVIDIA Tegra 3 and 4, Broadcom VideoCore IV
3. Some decoders strictly require that you pass BUFFER_FLAG_CODEC_CONFIG and crash upon the IDR frame if you don't
- Affected decoders: TI OMAP4
-4. Some decoders require num_ref_frames=1 and max_dec_frame_buffering=1 to avoid crashing on SPS or first I-frame
+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