Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bcd2ee068 | |||
| d4ff58b3ad | |||
| c797318ece | |||
| 82387d23f8 | |||
| 766e629be5 | |||
| b93aa42c0c | |||
| 36f132942f | |||
| e4c251e7ee | |||
| fb54bd5c78 | |||
| 8d4c86e113 |
+34
-2
@@ -11,8 +11,8 @@ android {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 27
|
||||
|
||||
versionName "5.6.3"
|
||||
versionCode = 142
|
||||
versionName "5.6.5"
|
||||
versionCode = 145
|
||||
}
|
||||
|
||||
flavorDimensions "root"
|
||||
@@ -54,6 +54,38 @@ android {
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
release {
|
||||
// To whomever is releasing/using an APK in release mode with
|
||||
// Moonlight's official application ID, please stop. I see every
|
||||
// single one of your crashes in my Play Console and it makes
|
||||
// Moonlight's reliability look worse and makes it more difficult
|
||||
// to distinguish real crashes from your crashy VR app. Seriously,
|
||||
// 44 of the *same* native crash in 72 hours and a few each of
|
||||
// several other crashes.
|
||||
//
|
||||
// This is technically not your fault. I would have hoped Google
|
||||
// would validate the signature of the APK before attributing
|
||||
// the crash to it. I asked their Play Store support about this
|
||||
// and they said they don't and don't have plans to, so that sucks.
|
||||
//
|
||||
// In any case, it's bad form to release an APK using someone
|
||||
// else's application ID. There is no legitimate reason, that
|
||||
// anyone would need to comment out the following line, except me
|
||||
// when I release an official signed Moonlight build. If you feel
|
||||
// like doing so would solve something, I can tell you it will not.
|
||||
// You can't upgrade an app while retaining data without having the
|
||||
// same signature as the official version. Nor can you post it on
|
||||
// the Play Store, since that application ID is already taken.
|
||||
// Reputable APK hosting websites similarly validate the signature
|
||||
// is consistent with the Play Store and won't allow an APK that
|
||||
// isn't signed the same as the original.
|
||||
//
|
||||
// I wish any and all people using Moonlight as the basis of other
|
||||
// cool projects the best of luck with their efforts. All I ask
|
||||
// is to please change the applicationId before you publish.
|
||||
//
|
||||
// TL;DR: Leave the following line alone!
|
||||
applicationIdSuffix ".unofficial"
|
||||
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||
}
|
||||
|
||||
@@ -294,7 +294,50 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
||||
Toast.makeText(this, "No H.265 decoder found.\nFalling back to H.264.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
|
||||
int gamepadMask = ControllerHandler.getAttachedControllerMask(this);
|
||||
if (!prefConfig.multiController && gamepadMask != 0) {
|
||||
// If any gamepads are present in non-MC mode, set only gamepad 1.
|
||||
gamepadMask = 1;
|
||||
}
|
||||
if (prefConfig.onscreenController) {
|
||||
// If we're using OSC, always set at least gamepad 1.
|
||||
gamepadMask |= 1;
|
||||
}
|
||||
|
||||
// Set to the optimal mode for streaming
|
||||
float displayRefreshRate = prepareDisplayForRendering();
|
||||
LimeLog.info("Display refresh rate: "+displayRefreshRate);
|
||||
|
||||
// HACK: Despite many efforts to ensure low latency consistent frame
|
||||
// delivery, the best non-lossy mechanism is to buffer 1 extra frame
|
||||
// in the output pipeline. Android does some buffering on its end
|
||||
// in SurfaceFlinger and it's difficult (impossible?) to inspect
|
||||
// the precise state of the buffer queue to the screen after we
|
||||
// release a frame for rendering.
|
||||
//
|
||||
// Since buffering a frame adds latency and we are primarily a
|
||||
// latency-optimized client, rather than one designed for picture-perfect
|
||||
// accuracy, we will synthetically induce a negative pressure on the display
|
||||
// output pipeline by driving the decoder input pipeline under the speed
|
||||
// that the display can refresh. This ensures a constant negative pressure
|
||||
// to keep latency down but does induce a periodic frame loss. However, this
|
||||
// periodic frame loss is *way* less than what we'd already get in Marshmallow's
|
||||
// display pipeline where frames are dropped outside of our control if they land
|
||||
// on the same V-sync.
|
||||
//
|
||||
// Hopefully, we can get rid of this once someone comes up with a better way
|
||||
// to track the state of the pipeline and time frames.
|
||||
int roundedRefreshRate = Math.round(displayRefreshRate);
|
||||
if (roundedRefreshRate <= 49) {
|
||||
// Let's avoid clearly bogus refresh rates
|
||||
roundedRefreshRate = 60;
|
||||
}
|
||||
if (!prefConfig.disableFrameDrop && prefConfig.fps >= roundedRefreshRate) {
|
||||
prefConfig.fps = roundedRefreshRate - 1;
|
||||
LimeLog.info("Adjusting FPS target for screen to "+prefConfig.fps);
|
||||
}
|
||||
|
||||
StreamConfiguration config = new StreamConfiguration.Builder()
|
||||
.setResolution(prefConfig.width, prefConfig.height)
|
||||
.setRefreshRate(prefConfig.fps)
|
||||
@@ -307,6 +350,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
.setHevcBitratePercentageMultiplier(75)
|
||||
.setHevcSupported(decoderRenderer.isHevcSupported())
|
||||
.setEnableHdr(willStreamHdr)
|
||||
.setAttachedGamepadMask(gamepadMask)
|
||||
.setClientRefreshRateX100((int)(displayRefreshRate * 100))
|
||||
.setAudioConfiguration(prefConfig.enable51Surround ?
|
||||
MoonBridge.AUDIO_CONFIGURATION_51_SURROUND :
|
||||
MoonBridge.AUDIO_CONFIGURATION_STEREO)
|
||||
@@ -319,9 +364,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
|
||||
inputManager.registerInputDeviceListener(controllerHandler, null);
|
||||
|
||||
// Set to the optimal mode for streaming
|
||||
prepareDisplayForRendering();
|
||||
|
||||
// Initialize touch contexts
|
||||
for (int i = 0; i < touchContextMap.length; i++) {
|
||||
touchContextMap[i] = new TouchContext(conn, i,
|
||||
@@ -396,9 +438,10 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareDisplayForRendering() {
|
||||
private float prepareDisplayForRendering() {
|
||||
Display display = getWindowManager().getDefaultDisplay();
|
||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||
float displayRefreshRate;
|
||||
|
||||
// On M, we can explicitly set the optimal display mode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
@@ -436,6 +479,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
LimeLog.info("Selected display mode: "+bestMode.getPhysicalWidth()+"x"+
|
||||
bestMode.getPhysicalHeight()+"x"+bestMode.getRefreshRate());
|
||||
windowLayoutParams.preferredDisplayModeId = bestMode.getModeId();
|
||||
displayRefreshRate = bestMode.getRefreshRate();
|
||||
}
|
||||
// On L, we can at least tell the OS that we want 60 Hz
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
@@ -448,6 +492,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
}
|
||||
LimeLog.info("Selected refresh rate: "+bestRefreshRate);
|
||||
windowLayoutParams.preferredRefreshRate = bestRefreshRate;
|
||||
displayRefreshRate = bestRefreshRate;
|
||||
}
|
||||
else {
|
||||
// Otherwise, the active display refresh rate is just
|
||||
// whatever is currently in use.
|
||||
displayRefreshRate = display.getRefreshRate();
|
||||
}
|
||||
|
||||
// Apply the display mode change
|
||||
@@ -483,6 +533,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
||||
// Set the surface to scale based on the aspect ratio of the stream
|
||||
streamView.setDesiredAspectRatio((double)prefConfig.width / (double)prefConfig.height);
|
||||
}
|
||||
|
||||
return displayRefreshRate;
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.limelight.binding.input;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.SystemClock;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InputDevice;
|
||||
@@ -12,6 +14,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.binding.input.driver.UsbDriverListener;
|
||||
import com.limelight.binding.input.driver.UsbDriverService;
|
||||
import com.limelight.nvstream.NvConnection;
|
||||
import com.limelight.nvstream.input.ControllerPacket;
|
||||
import com.limelight.nvstream.input.MouseButtonPacket;
|
||||
@@ -48,7 +51,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
private boolean hasGameController;
|
||||
|
||||
private final boolean multiControllerEnabled;
|
||||
private short currentControllers;
|
||||
private short currentControllers, initialControllers;
|
||||
|
||||
public ControllerHandler(Context activityContext, NvConnection conn, GameGestures gestures, boolean multiControllerEnabled, int deadzonePercentage) {
|
||||
this.activityContext = activityContext;
|
||||
@@ -102,6 +105,12 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
// consume these. Instead, let's ignore them since that's probably the
|
||||
// most likely case.
|
||||
defaultContext.ignoreBack = true;
|
||||
|
||||
// Get the initially attached set of gamepads. As each gamepad receives
|
||||
// its initial InputEvent, we will move these from this set onto the
|
||||
// currentControllers set which will allow them to properly unplug
|
||||
// if they are removed.
|
||||
initialControllers = getAttachedControllerMask(activityContext);
|
||||
}
|
||||
|
||||
private static InputDevice.MotionRange getMotionRangeForJoystickAxis(InputDevice dev, int axis) {
|
||||
@@ -139,8 +148,47 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
onInputDeviceAdded(deviceId);
|
||||
}
|
||||
|
||||
public static short getAttachedControllerMask(Context context) {
|
||||
int count = 0;
|
||||
short mask = 0;
|
||||
|
||||
// Count all input devices that are gamepads
|
||||
InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
|
||||
for (int id : im.getInputDeviceIds()) {
|
||||
InputDevice dev = im.getInputDevice(id);
|
||||
if (dev == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0) {
|
||||
LimeLog.info("Counting InputDevice: "+dev.getName());
|
||||
mask |= 1 << count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Count all USB devices that match our drivers
|
||||
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
|
||||
for (UsbDevice dev : usbManager.getDeviceList().values()) {
|
||||
if (UsbDriverService.shouldClaimDevice(dev)) {
|
||||
LimeLog.info("Counting UsbDevice: "+dev.getDeviceName());
|
||||
mask |= 1 << count++;
|
||||
}
|
||||
}
|
||||
|
||||
LimeLog.info("Enumerated "+count+" gamepads");
|
||||
return mask;
|
||||
}
|
||||
|
||||
private void releaseControllerNumber(GenericControllerContext context) {
|
||||
// If this device sent data as a gamepad, zero the values before removing
|
||||
// If we reserved a controller number, remove that reservation
|
||||
if (context.reservedControllerNumber) {
|
||||
LimeLog.info("Controller number "+context.controllerNumber+" is now available");
|
||||
currentControllers &= ~(1 << context.controllerNumber);
|
||||
}
|
||||
|
||||
// If this device sent data as a gamepad, zero the values before removing.
|
||||
// We must do this after clearing the currentControllers entry so this
|
||||
// causes the device to be removed on the server PC.
|
||||
if (context.assignedControllerNumber) {
|
||||
conn.sendControllerInput(context.controllerNumber, getActiveControllerMask(),
|
||||
(short) 0,
|
||||
@@ -148,12 +196,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
(short) 0, (short) 0,
|
||||
(short) 0, (short) 0);
|
||||
}
|
||||
|
||||
// If we reserved a controller number, remove that reservation
|
||||
if (context.reservedControllerNumber) {
|
||||
LimeLog.info("Controller number "+context.controllerNumber+" is now available");
|
||||
currentControllers &= ~(1 << context.controllerNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// Called before sending input but after we've determined that this
|
||||
@@ -181,6 +223,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if ((currentControllers & (1 << i)) == 0) {
|
||||
// Found an unused controller value
|
||||
currentControllers |= (1 << i);
|
||||
|
||||
// Take this value out of the initial gamepad set
|
||||
initialControllers &= ~(1 << i);
|
||||
|
||||
context.controllerNumber = i;
|
||||
context.reservedControllerNumber = true;
|
||||
break;
|
||||
@@ -201,6 +247,10 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
if ((currentControllers & (1 << i)) == 0) {
|
||||
// Found an unused controller value
|
||||
currentControllers |= (1 << i);
|
||||
|
||||
// Take this value out of the initial gamepad set
|
||||
initialControllers &= ~(1 << i);
|
||||
|
||||
context.controllerNumber = i;
|
||||
context.reservedControllerNumber = true;
|
||||
break;
|
||||
@@ -473,7 +523,7 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
||||
|
||||
private short getActiveControllerMask() {
|
||||
if (multiControllerEnabled) {
|
||||
return currentControllers;
|
||||
return (short)(currentControllers | initialControllers);
|
||||
}
|
||||
else {
|
||||
// Only Player 1 is active with multi-controller disabled
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.limelight.binding.input.capture;
|
||||
import android.app.Activity;
|
||||
|
||||
import com.limelight.LimeLog;
|
||||
import com.limelight.LimelightBuildProps;
|
||||
import com.limelight.R;
|
||||
import com.limelight.binding.input.evdev.EvdevCaptureProviderShim;
|
||||
import com.limelight.binding.input.evdev.EvdevListener;
|
||||
@@ -13,7 +14,9 @@ public class InputCaptureManager {
|
||||
LimeLog.info("Using Android O+ native mouse capture");
|
||||
return new AndroidNativePointerCaptureProvider(activity.findViewById(R.id.surfaceView));
|
||||
}
|
||||
else if (ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||
// LineageOS implemented broken NVIDIA capture extensions, so avoid using them on root builds.
|
||||
// See https://github.com/LineageOS/android_frameworks_base/commit/d304f478a023430f4712dbdc3ee69d9ad02cebd3
|
||||
else if (!LimelightBuildProps.ROOT_BUILD && ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||
return new ShieldCaptureProvider(activity);
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRecognizedInputDevice(UsbDevice device) {
|
||||
private static boolean isRecognizedInputDevice(UsbDevice device) {
|
||||
// On KitKat and later, we can determine if this VID and PID combo
|
||||
// matches an existing input device and defer to the built-in controller
|
||||
// support in that case. Prior to KitKat, we'll always return true to be safe.
|
||||
@@ -182,7 +182,7 @@ public class UsbDriverService extends Service implements UsbDriverListener {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldClaimDevice(UsbDevice device) {
|
||||
public static boolean shouldClaimDevice(UsbDevice device) {
|
||||
// We always bind to XB1 controllers but only bind to XB360 controllers
|
||||
// if we know the kernel isn't already driving this device.
|
||||
return XboxOneController.canClaimDevice(device) ||
|
||||
|
||||
@@ -410,7 +410,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||
}
|
||||
|
||||
// Render the last buffer
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && prefs.disableFrameDrop) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
// Use a PTS that will cause this frame to never be dropped if frame dropping
|
||||
// is disabled
|
||||
videoDecoder.releaseOutputBuffer(lastIndex, 0);
|
||||
|
||||
@@ -153,27 +153,51 @@ public class MediaCodecHelper {
|
||||
// Qualcomm is currently the only decoders in this group.
|
||||
}
|
||||
|
||||
private static boolean isLowEndSnapdragonRenderer(String glRenderer) {
|
||||
private static String getAdrenoVersionString(String glRenderer) {
|
||||
glRenderer = glRenderer.toLowerCase().trim();
|
||||
|
||||
if (!glRenderer.contains("adreno")) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
Pattern modelNumberPattern = Pattern.compile("(.*)([0-9]{3})(.*)");
|
||||
|
||||
Matcher matcher = modelNumberPattern.matcher(glRenderer);
|
||||
if (!matcher.matches()) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
String modelNumber = matcher.group(2);
|
||||
LimeLog.info("Found Adreno GPU: "+modelNumber);
|
||||
return modelNumber;
|
||||
}
|
||||
|
||||
private static boolean isLowEndSnapdragonRenderer(String glRenderer) {
|
||||
String modelNumber = getAdrenoVersionString(glRenderer);
|
||||
if (modelNumber == null) {
|
||||
// Not an Adreno GPU
|
||||
return false;
|
||||
}
|
||||
|
||||
// The current logic is to identify low-end SoCs based on a zero in the x0x place.
|
||||
return modelNumber.charAt(1) == '0';
|
||||
}
|
||||
|
||||
// This is a workaround for some broken devices that report
|
||||
// only GLES 3.0 even though the GPU is an Adreno 4xx series part.
|
||||
// An example of such a device is the Huawei Honor 5x with the
|
||||
// Snapdragon 616 SoC (Adreno 405).
|
||||
private static boolean isGLES31SnapdragonRenderer(String glRenderer) {
|
||||
String modelNumber = getAdrenoVersionString(glRenderer);
|
||||
if (modelNumber == null) {
|
||||
// Not an Adreno GPU
|
||||
return false;
|
||||
}
|
||||
|
||||
// Snapdragon 4xx and higher support GLES 3.1
|
||||
return modelNumber.charAt(0) >= '4';
|
||||
}
|
||||
|
||||
public static void initialize(Context context, String glRenderer) {
|
||||
if (initialized) {
|
||||
return;
|
||||
@@ -208,9 +232,10 @@ public class MediaCodecHelper {
|
||||
// 3xx - bad
|
||||
// 4xx - good
|
||||
//
|
||||
// Unfortunately, it's not that easy to get that information here, so I'll use an
|
||||
// approximation by checking the GLES level (<= 3.0 is bad).
|
||||
if (configInfo.reqGlEsVersion > 0x30000) {
|
||||
// The "good" GPUs support GLES 3.1, but we can't just check that directly
|
||||
// (see comment on isGLES31SnapdragonRenderer).
|
||||
//
|
||||
if (isGLES31SnapdragonRenderer(glRenderer)) {
|
||||
// We prefer reference frame invalidation support (which is only doable on AVC on
|
||||
// older Qualcomm chips) vs. enabling HEVC by default. The user can override using the settings
|
||||
// to force HEVC on. If HDR or mobile data will be used, we'll override this and use
|
||||
|
||||
+1
-1
Submodule moonlight-common updated: a23b48229b...8a89f2ff82
Reference in New Issue
Block a user