mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2026-10-20 07:24:42 +00:00
Compare commits
1
Commits
v5.1
..
frame-pacing
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91261f9520 |
+6
-12
@@ -4,23 +4,19 @@ import org.apache.tools.ant.taskdefs.condition.Os
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 26
|
compileSdkVersion 25
|
||||||
buildToolsVersion '26.0.0'
|
buildToolsVersion '25.0.3'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 26
|
targetSdkVersion 25
|
||||||
|
|
||||||
versionName "5.1"
|
versionName "5.0.1"
|
||||||
versionCode = 128
|
versionCode = 123
|
||||||
}
|
}
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
root {
|
root {
|
||||||
// Android O has native mouse capture, so don't show the rooted
|
|
||||||
// version to devices running O on the Play Store.
|
|
||||||
maxSdkVersion 25
|
|
||||||
|
|
||||||
applicationId "com.limelight.root"
|
applicationId "com.limelight.root"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
|
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
|
||||||
@@ -56,8 +52,6 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
||||||
compile 'org.bouncycastle:bcpkix-jdk15on:1.52'
|
compile 'org.bouncycastle:bcpkix-jdk15on:1.52'
|
||||||
|
compile project(':moonlight-common')
|
||||||
compile files('libs/jcodec-0.1.9-patched.jar')
|
compile files('libs/jcodec-0.1.9-patched.jar')
|
||||||
|
|
||||||
debugCompile project(path:':moonlight-common', configuration:'debug')
|
|
||||||
releaseCompile project(path:':moonlight-common', configuration:'release')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,20 +171,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
streamView.setOnGenericMotionListener(this);
|
streamView.setOnGenericMotionListener(this);
|
||||||
streamView.setOnTouchListener(this);
|
streamView.setOnTouchListener(this);
|
||||||
|
|
||||||
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
// The view must be focusable for pointer capture to work.
|
|
||||||
streamView.setFocusable(true);
|
|
||||||
streamView.setDefaultFocusHighlightEnabled(false);
|
|
||||||
streamView.setOnCapturedPointerListener(new View.OnCapturedPointerListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onCapturedPointer(View view, MotionEvent motionEvent) {
|
|
||||||
return handleMotionEvent(motionEvent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn the user if they're on a metered connection
|
// Warn the user if they're on a metered connection
|
||||||
checkDataConnection();
|
checkDataConnection();
|
||||||
|
|
||||||
@@ -214,7 +200,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
// Initialize the MediaCodec helper before creating the decoder
|
// Initialize the MediaCodec helper before creating the decoder
|
||||||
MediaCodecHelper.initializeWithContext(this);
|
MediaCodecHelper.initializeWithContext(this);
|
||||||
|
|
||||||
decoderRenderer = new MediaCodecDecoderRenderer(prefConfig.videoFormat, prefConfig.bitrate);
|
decoderRenderer = new MediaCodecDecoderRenderer(prefConfig.videoFormat);
|
||||||
|
|
||||||
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
// Display a message to the user if H.265 was forced on but we still didn't find a decoder
|
||||||
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
|
||||||
@@ -271,6 +257,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
getWindow().setSustainedPerformanceMode(true);
|
getWindow().setSustainedPerformanceMode(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
|
||||||
|
|
||||||
if (prefConfig.onscreenController) {
|
if (prefConfig.onscreenController) {
|
||||||
// create virtual onscreen controller
|
// create virtual onscreen controller
|
||||||
virtualController = new VirtualController(conn,
|
virtualController = new VirtualController(conn,
|
||||||
@@ -289,20 +277,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
streamView.getHolder().addCallback(this);
|
streamView.getHolder().addCallback(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
|
||||||
super.onWindowFocusChanged(hasFocus);
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
// Capture is lost when focus is lost, so it must be requested again
|
|
||||||
// when focus is regained.
|
|
||||||
if (inputCaptureProvider.isCapturingEnabled() && hasFocus) {
|
|
||||||
// Recapture the pointer if focus was regained
|
|
||||||
streamView.requestPointerCapture();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void prepareDisplayForRendering() {
|
private void prepareDisplayForRendering() {
|
||||||
Display display = getWindowManager().getDefaultDisplay();
|
Display display = getWindowManager().getDefaultDisplay();
|
||||||
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
|
||||||
@@ -446,17 +420,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
unbindService(usbDriverServiceConnection);
|
unbindService(usbDriverServiceConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy the capture provider
|
|
||||||
inputCaptureProvider.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStop() {
|
|
||||||
super.onStop();
|
|
||||||
|
|
||||||
SpinnerDialog.closeDialogs(this);
|
|
||||||
Dialog.closeDialogs();
|
|
||||||
|
|
||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
int videoFormat = decoderRenderer.getActiveVideoFormat();
|
int videoFormat = decoderRenderer.getActiveVideoFormat();
|
||||||
|
|
||||||
@@ -490,6 +453,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
|
||||||
|
SpinnerDialog.closeDialogs(this);
|
||||||
|
Dialog.closeDialogs();
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@@ -700,8 +671,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 ||
|
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE)
|
|
||||||
{
|
{
|
||||||
// This case is for mice
|
// This case is for mice
|
||||||
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
|
||||||
@@ -710,11 +680,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
{
|
{
|
||||||
int changedButtons = event.getButtonState() ^ lastButtonState;
|
int changedButtons = event.getButtonState() ^ lastButtonState;
|
||||||
|
|
||||||
// Ignore mouse input if we're not capturing from our input source
|
|
||||||
if (!inputCaptureProvider.isCapturingActive()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
|
||||||
// Send the vertical scroll packet
|
// Send the vertical scroll packet
|
||||||
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
|
||||||
@@ -928,6 +893,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
connecting = connected = false;
|
connecting = connected = false;
|
||||||
conn.stop();
|
conn.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable cursor visibility again
|
||||||
|
inputCaptureProvider.disableCapture();
|
||||||
|
|
||||||
|
// Destroy the capture provider
|
||||||
|
inputCaptureProvider.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -937,13 +908,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
spinner = null;
|
spinner = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable cursor visibility again
|
|
||||||
inputCaptureProvider.disableCapture();
|
|
||||||
|
|
||||||
if (!displayedFailureDialog) {
|
if (!displayedFailureDialog) {
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
LimeLog.severe(stage+" failed: "+errorCode);
|
LimeLog.severe(stage+" failed: "+errorCode);
|
||||||
|
|
||||||
|
stopConnection();
|
||||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
|
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
|
||||||
getResources().getString(R.string.conn_error_msg)+" "+stage, true);
|
getResources().getString(R.string.conn_error_msg)+" "+stage, true);
|
||||||
}
|
}
|
||||||
@@ -951,14 +920,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void connectionTerminated(long errorCode) {
|
public void connectionTerminated(long errorCode) {
|
||||||
// Enable cursor visibility again
|
|
||||||
inputCaptureProvider.disableCapture();
|
|
||||||
|
|
||||||
if (!displayedFailureDialog) {
|
if (!displayedFailureDialog) {
|
||||||
displayedFailureDialog = true;
|
displayedFailureDialog = true;
|
||||||
LimeLog.severe("Connection terminated: "+errorCode);
|
LimeLog.severe("Connection terminated: "+errorCode);
|
||||||
stopConnection();
|
|
||||||
|
|
||||||
|
stopConnection();
|
||||||
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
|
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
|
||||||
getResources().getString(R.string.conn_terminated_msg), true);
|
getResources().getString(R.string.conn_terminated_msg), true);
|
||||||
}
|
}
|
||||||
@@ -1025,10 +991,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
// Let the decoder know immediately that the surface is gone
|
|
||||||
decoderRenderer.prepareForStop();
|
|
||||||
|
|
||||||
if (connected) {
|
if (connected) {
|
||||||
|
// HACK: Android is supposed to let you return from this function
|
||||||
|
// before throwing a fit if you access the surface again. Unfortunately,
|
||||||
|
// MediaCodec often tries to access the destroyed surface and triggers
|
||||||
|
// an IllegalStateException. To workaround this, we will invoke
|
||||||
|
// the DecoderRenderer's stop function ourselves, so it will hopefully
|
||||||
|
// happen early enough to not trigger the bug
|
||||||
|
decoderRenderer.stop();
|
||||||
|
|
||||||
stopConnection();
|
stopConnection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
|
|||||||
int[] ids = InputDevice.getDeviceIds();
|
int[] ids = InputDevice.getDeviceIds();
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
InputDevice dev = InputDevice.getDevice(id);
|
InputDevice dev = InputDevice.getDevice(id);
|
||||||
if (dev == null) {
|
|
||||||
// This device was removed during enumeration
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 ||
|
if ((dev.getSources() & InputDevice.SOURCE_JOYSTICK) != 0 ||
|
||||||
(dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) {
|
(dev.getSources() & InputDevice.SOURCE_GAMEPAD) != 0) {
|
||||||
// This looks like a gamepad, but we'll check X and Y to be sure
|
// This looks like a gamepad, but we'll check X and Y to be sure
|
||||||
|
|||||||
+2
-4
@@ -10,11 +10,11 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
public class AndroidPointerIconCaptureProvider extends InputCaptureProvider {
|
public class AndroidCaptureProvider extends InputCaptureProvider {
|
||||||
private ViewGroup rootViewGroup;
|
private ViewGroup rootViewGroup;
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
public AndroidPointerIconCaptureProvider(Activity activity) {
|
public AndroidCaptureProvider(Activity activity) {
|
||||||
this.context = activity;
|
this.context = activity;
|
||||||
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
|
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
|
||||||
}
|
}
|
||||||
@@ -33,13 +33,11 @@ public class AndroidPointerIconCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableCapture() {
|
public void enableCapture() {
|
||||||
super.enableCapture();
|
|
||||||
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
|
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableCapture() {
|
public void disableCapture() {
|
||||||
super.disableCapture();
|
|
||||||
setPointerIconOnAllViews(null);
|
setPointerIconOnAllViews(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
-53
@@ -1,53 +0,0 @@
|
|||||||
package com.limelight.binding.input.capture;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.view.InputDevice;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.O)
|
|
||||||
public class AndroidNativePointerCaptureProvider extends InputCaptureProvider {
|
|
||||||
|
|
||||||
private View targetView;
|
|
||||||
|
|
||||||
public AndroidNativePointerCaptureProvider(View targetView) {
|
|
||||||
this.targetView = targetView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isCaptureProviderSupported() {
|
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enableCapture() {
|
|
||||||
super.enableCapture();
|
|
||||||
targetView.requestPointerCapture();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disableCapture() {
|
|
||||||
super.disableCapture();
|
|
||||||
targetView.releasePointerCapture();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCapturingActive() {
|
|
||||||
return targetView.hasPointerCapture();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
|
||||||
return event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getRelativeAxisX(MotionEvent event) {
|
|
||||||
return event.getX();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getRelativeAxisY(MotionEvent event) {
|
|
||||||
return event.getY();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,17 +3,15 @@ package com.limelight.binding.input.capture;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
|
||||||
import com.limelight.LimeLog;
|
import com.limelight.LimeLog;
|
||||||
import com.limelight.R;
|
|
||||||
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
|
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
|
||||||
import com.limelight.binding.input.evdev.EvdevListener;
|
import com.limelight.binding.input.evdev.EvdevListener;
|
||||||
|
|
||||||
public class InputCaptureManager {
|
public class InputCaptureManager {
|
||||||
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
|
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
|
||||||
if (AndroidNativePointerCaptureProvider.isCaptureProviderSupported()) {
|
// Shield capture is preferred because it can capture when the cursor is over
|
||||||
LimeLog.info("Using Android O+ native mouse capture");
|
// the system UI. Android N native capture can only capture over views owned
|
||||||
return new AndroidNativePointerCaptureProvider(activity.findViewById(R.id.surfaceView));
|
// by the application.
|
||||||
}
|
if (ShieldCaptureProvider.isCaptureProviderSupported()) {
|
||||||
else if (ShieldCaptureProvider.isCaptureProviderSupported()) {
|
|
||||||
LimeLog.info("Using NVIDIA mouse capture extension");
|
LimeLog.info("Using NVIDIA mouse capture extension");
|
||||||
return new ShieldCaptureProvider(activity);
|
return new ShieldCaptureProvider(activity);
|
||||||
}
|
}
|
||||||
@@ -21,11 +19,9 @@ public class InputCaptureManager {
|
|||||||
LimeLog.info("Using Evdev mouse capture");
|
LimeLog.info("Using Evdev mouse capture");
|
||||||
return new EvdevCaptureProvider(activity, rootListener);
|
return new EvdevCaptureProvider(activity, rootListener);
|
||||||
}
|
}
|
||||||
else if (AndroidPointerIconCaptureProvider.isCaptureProviderSupported()) {
|
else if (AndroidCaptureProvider.isCaptureProviderSupported()) {
|
||||||
// Android N's native capture can't capture over system UI elements
|
LimeLog.info("Using Android N+ native mouse capture");
|
||||||
// so we want to only use it if there's no other option.
|
return new AndroidCaptureProvider(activity);
|
||||||
LimeLog.info("Using Android N+ pointer hiding");
|
|
||||||
return new AndroidPointerIconCaptureProvider(activity);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LimeLog.info("Mouse capture not available");
|
LimeLog.info("Mouse capture not available");
|
||||||
|
|||||||
@@ -3,25 +3,10 @@ package com.limelight.binding.input.capture;
|
|||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
public abstract class InputCaptureProvider {
|
public abstract class InputCaptureProvider {
|
||||||
protected boolean isCapturing;
|
public abstract void enableCapture();
|
||||||
|
public abstract void disableCapture();
|
||||||
public void enableCapture() {
|
|
||||||
isCapturing = true;
|
|
||||||
}
|
|
||||||
public void disableCapture() {
|
|
||||||
isCapturing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void destroy() {}
|
public void destroy() {}
|
||||||
|
|
||||||
public boolean isCapturingEnabled() {
|
|
||||||
return isCapturing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCapturingActive() {
|
|
||||||
return isCapturing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
package com.limelight.binding.input.capture;
|
package com.limelight.binding.input.capture;
|
||||||
|
|
||||||
|
|
||||||
public class NullCaptureProvider extends InputCaptureProvider {}
|
public class NullCaptureProvider extends InputCaptureProvider {
|
||||||
|
@Override
|
||||||
|
public void enableCapture() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableCapture() {}
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,13 +63,11 @@ public class ShieldCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableCapture() {
|
public void enableCapture() {
|
||||||
super.enableCapture();
|
|
||||||
setCursorVisibility(false);
|
setCursorVisibility(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableCapture() {
|
public void disableCapture() {
|
||||||
super.disableCapture();
|
|
||||||
setCursorVisibility(true);
|
setCursorVisibility(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,7 +221,6 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableCapture() {
|
public void enableCapture() {
|
||||||
super.enableCapture();
|
|
||||||
if (!started) {
|
if (!started) {
|
||||||
// Start the handler thread if it's our first time
|
// Start the handler thread if it's our first time
|
||||||
// capturing
|
// capturing
|
||||||
@@ -248,7 +247,6 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disableCapture() {
|
public void disableCapture() {
|
||||||
super.disableCapture();
|
|
||||||
// This may be called on the main thread
|
// This may be called on the main thread
|
||||||
runInNetworkSafeContextSynchronously(new Runnable() {
|
runInNetworkSafeContextSynchronously(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,7 +23,17 @@ import android.view.SurfaceHolder;
|
|||||||
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
||||||
|
|
||||||
private static final boolean USE_FRAME_RENDER_TIME = false;
|
private static final boolean USE_FRAME_RENDER_TIME = false;
|
||||||
private static final boolean FRAME_RENDER_TIME_ONLY = USE_FRAME_RENDER_TIME && false;
|
|
||||||
|
// This value is added to the PTS for each frame to force frames delivered right at
|
||||||
|
// V-sync time to be delayed until the next V-sync rather than dropping the existing frame.
|
||||||
|
// To make matters worse, these frames often end up being displayed for 2 V-syncs because
|
||||||
|
// the next frame isn't ready at the next V-sync. So without this, we end up with a pattern of:
|
||||||
|
// F0 - Displayed | F1 - Dropped | F2 - Displayed | F2 - Displayed again | F3 - Displayed ...
|
||||||
|
// which can be very noticeable for some video content and users.
|
||||||
|
//
|
||||||
|
// This value is currently 6 ms, which means that a frame ready for display less than 6 ms from
|
||||||
|
// the next V-sync will be delayed until the following V-sync to ensure smooth frame pacing.
|
||||||
|
private static final int PTS_OFFSET_NS = 6 * 1000000;
|
||||||
|
|
||||||
// Used on versions < 5.0
|
// Used on versions < 5.0
|
||||||
private ByteBuffer[] legacyInputBuffers;
|
private ByteBuffer[] legacyInputBuffers;
|
||||||
@@ -31,10 +41,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
private MediaCodecInfo avcDecoder;
|
private MediaCodecInfo avcDecoder;
|
||||||
private MediaCodecInfo hevcDecoder;
|
private MediaCodecInfo hevcDecoder;
|
||||||
|
|
||||||
// Used for HEVC only
|
|
||||||
private byte[] vpsBuffer;
|
|
||||||
private byte[] spsBuffer;
|
|
||||||
|
|
||||||
private MediaCodec videoDecoder;
|
private MediaCodec videoDecoder;
|
||||||
private Thread rendererThread;
|
private Thread rendererThread;
|
||||||
private Thread[] spinnerThreads;
|
private Thread[] spinnerThreads;
|
||||||
@@ -45,8 +51,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
private boolean refFrameInvalidationActive;
|
private boolean refFrameInvalidationActive;
|
||||||
private int initialWidth, initialHeight;
|
private int initialWidth, initialHeight;
|
||||||
private int videoFormat;
|
private int videoFormat;
|
||||||
private SurfaceHolder renderTarget;
|
private Object renderTarget;
|
||||||
private volatile boolean stopping;
|
private boolean stopping;
|
||||||
|
|
||||||
private boolean needsBaselineSpsHack;
|
private boolean needsBaselineSpsHack;
|
||||||
private SeqParameterSet savedSps;
|
private SeqParameterSet savedSps;
|
||||||
@@ -55,11 +61,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
private long decoderTimeMs;
|
private long decoderTimeMs;
|
||||||
private long totalTimeMs;
|
private long totalTimeMs;
|
||||||
private int totalFrames;
|
private int totalFrames;
|
||||||
private int frameLossEvents;
|
|
||||||
private int framesLost;
|
|
||||||
private int lastFrameNumber;
|
|
||||||
private int refreshRate;
|
|
||||||
private int bitrate;
|
|
||||||
|
|
||||||
private int numSpsIn;
|
private int numSpsIn;
|
||||||
private int numPpsIn;
|
private int numPpsIn;
|
||||||
@@ -101,15 +102,13 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
return decoderInfo;
|
return decoderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRenderTarget(SurfaceHolder renderTarget) {
|
public void setRenderTarget(Object renderTarget) {
|
||||||
this.renderTarget = renderTarget;
|
this.renderTarget = renderTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaCodecDecoderRenderer(int videoFormat, int bitrate) {
|
public MediaCodecDecoderRenderer(int videoFormat) {
|
||||||
//dumpDecoders();
|
//dumpDecoders();
|
||||||
|
|
||||||
this.bitrate = bitrate;
|
|
||||||
|
|
||||||
spinnerThreads = new Thread[Runtime.getRuntime().availableProcessors()];
|
spinnerThreads = new Thread[Runtime.getRuntime().availableProcessors()];
|
||||||
|
|
||||||
avcDecoder = findAvcDecoder();
|
avcDecoder = findAvcDecoder();
|
||||||
@@ -168,7 +167,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
this.initialWidth = width;
|
this.initialWidth = width;
|
||||||
this.initialHeight = height;
|
this.initialHeight = height;
|
||||||
this.videoFormat = format;
|
this.videoFormat = format;
|
||||||
this.refreshRate = redrawRate;
|
|
||||||
|
|
||||||
String mimeType;
|
String mimeType;
|
||||||
String selectedDecoderName;
|
String selectedDecoderName;
|
||||||
@@ -246,7 +244,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
videoDecoder.configure(videoFormat, renderTarget.getSurface(), null, 0);
|
videoDecoder.configure(videoFormat, ((SurfaceHolder)renderTarget).getSurface(), null, 0);
|
||||||
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
videoDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
||||||
|
|
||||||
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
@@ -279,12 +277,12 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDecoderException(Exception e, ByteBuffer buf, int codecFlags, boolean throwOnTransient) {
|
private void handleDecoderException(Exception e, ByteBuffer buf, int codecFlags) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
if (e instanceof CodecException) {
|
if (e instanceof CodecException) {
|
||||||
CodecException codecExc = (CodecException) e;
|
CodecException codecExc = (CodecException) e;
|
||||||
|
|
||||||
if (codecExc.isTransient() && !throwOnTransient) {
|
if (codecExc.isTransient()) {
|
||||||
// We'll let transient exceptions go
|
// We'll let transient exceptions go
|
||||||
LimeLog.warning(codecExc.getDiagnosticInfo());
|
LimeLog.warning(codecExc.getDiagnosticInfo());
|
||||||
return;
|
return;
|
||||||
@@ -294,8 +292,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only throw if the surface is still around and we're not stopping
|
// Only throw if this happens at the beginning of a stream
|
||||||
if (!stopping && renderTarget.getSurface().isValid()) {
|
// but not if we're stopping
|
||||||
|
if (totalFrames > 0 && totalFrames < 20 && !stopping) {
|
||||||
if (buf != null || codecFlags != 0) {
|
if (buf != null || codecFlags != 0) {
|
||||||
throw new RendererException(this, e, buf, codecFlags);
|
throw new RendererException(this, e, buf, codecFlags);
|
||||||
}
|
}
|
||||||
@@ -328,7 +327,17 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the last buffer
|
// Render the last buffer
|
||||||
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
|
// On L and later, we can fine tune our PTS right before rendering, so
|
||||||
|
// we take advantage of that for frame pacing using the PTS offset. Using
|
||||||
|
// this version of releaseOutputBuffer() also lets SurfaceFlinger drop extra
|
||||||
|
// frames in the SurfaceView<->SurfaceFlinger BufferQueue that reduces
|
||||||
|
// latency if an stray extra frame ends up in the output queue.
|
||||||
|
videoDecoder.releaseOutputBuffer(lastIndex, System.nanoTime() + PTS_OFFSET_NS);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
videoDecoder.releaseOutputBuffer(lastIndex, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Add delta time to the totals (excluding probable outliers)
|
// Add delta time to the totals (excluding probable outliers)
|
||||||
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
|
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
|
||||||
@@ -351,7 +360,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handleDecoderException(e, null, 0, false);
|
handleDecoderException(e, null, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,11 +400,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
startTime = MediaCodecHelper.getMonotonicMillis();
|
startTime = MediaCodecHelper.getMonotonicMillis();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (index < 0 && !stopping) {
|
while (rendererThread.isAlive() && index < 0 && !stopping) {
|
||||||
index = videoDecoder.dequeueInputBuffer(10000);
|
index = videoDecoder.dequeueInputBuffer(10000);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handleDecoderException(e, null, 0, true);
|
handleDecoderException(e, null, 0);
|
||||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,37 +427,16 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
startSpinnerThreads();
|
startSpinnerThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepareForStop() {
|
|
||||||
// Let the decoding code know to ignore codec exceptions now
|
|
||||||
stopping = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
stopping = true;
|
stopping = true;
|
||||||
|
|
||||||
// Halt the rendering thread
|
// Halt the rendering thread
|
||||||
rendererThread.interrupt();
|
rendererThread.interrupt();
|
||||||
|
|
||||||
try {
|
|
||||||
// Invalidate pending decode buffers
|
|
||||||
videoDecoder.flush();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the renderer thread to shut down
|
|
||||||
try {
|
try {
|
||||||
rendererThread.join();
|
rendererThread.join();
|
||||||
} catch (InterruptedException ignored) { }
|
} catch (InterruptedException ignored) { }
|
||||||
|
|
||||||
try {
|
|
||||||
// Stop the video decoder
|
|
||||||
videoDecoder.stop();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Halt the spinner threads
|
// Halt the spinner threads
|
||||||
for (Thread t : spinnerThreads) {
|
for (Thread t : spinnerThreads) {
|
||||||
t.interrupt();
|
t.interrupt();
|
||||||
@@ -466,13 +454,31 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
private boolean queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
|
||||||
try {
|
// Try 25 times to submit the input buffer before throwing a real exception
|
||||||
videoDecoder.queueInputBuffer(inputBufferIndex,
|
int i;
|
||||||
offset, length,
|
Exception lastException = null;
|
||||||
timestampUs, codecFlags);
|
|
||||||
|
for (i = 0; i < 25; i++) {
|
||||||
|
try {
|
||||||
|
videoDecoder.queueInputBuffer(inputBufferIndex,
|
||||||
|
offset, length,
|
||||||
|
timestampUs, codecFlags);
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
handleDecoderException(e, null, codecFlags);
|
||||||
|
lastException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 25 && totalFrames > 0 && totalFrames < 20 && !stopping) {
|
||||||
|
throw new RendererException(this, lastException, null, codecFlags);
|
||||||
|
}
|
||||||
|
else if (i != 25) {
|
||||||
|
// Queued input buffer
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
}
|
||||||
handleDecoderException(e, null, codecFlags, true);
|
else {
|
||||||
|
// Failed to queue
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,12 +489,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
ByteBuffer buf;
|
ByteBuffer buf;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
try {
|
buf = videoDecoder.getInputBuffer(inputBufferIndex);
|
||||||
buf = videoDecoder.getInputBuffer(inputBufferIndex);
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleDecoderException(e, null, 0, true);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
buf = legacyInputBuffers[inputBufferIndex];
|
buf = legacyInputBuffers[inputBufferIndex];
|
||||||
@@ -519,36 +520,25 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public int submitDecodeUnit(byte[] frameData, int frameLength, int frameNumber, long receiveTimeMs) {
|
public int submitDecodeUnit(byte[] frameData, int frameLength) {
|
||||||
totalFrames++;
|
totalFrames++;
|
||||||
|
|
||||||
// We can receive the same "frame" multiple times if it's an IDR frame.
|
|
||||||
// In that case, each frame start NALU is submitted independently.
|
|
||||||
if (frameNumber != lastFrameNumber && frameNumber != lastFrameNumber + 1) {
|
|
||||||
framesLost += frameNumber - lastFrameNumber - 1;
|
|
||||||
frameLossEvents++;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFrameNumber = frameNumber;
|
|
||||||
|
|
||||||
int inputBufferIndex;
|
|
||||||
ByteBuffer buf;
|
|
||||||
|
|
||||||
long timestampUs = System.nanoTime() / 1000;
|
long timestampUs = System.nanoTime() / 1000;
|
||||||
|
|
||||||
if (!FRAME_RENDER_TIME_ONLY) {
|
|
||||||
// Count time from first packet received to decode start
|
|
||||||
totalTimeMs += (timestampUs / 1000) - receiveTimeMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestampUs <= lastTimestampUs) {
|
if (timestampUs <= lastTimestampUs) {
|
||||||
// We can't submit multiple buffers with the same timestamp
|
// We can't submit multiple buffers with the same timestamp
|
||||||
// so bump it up by one before queuing
|
// so bump it up by one before queuing
|
||||||
timestampUs = lastTimestampUs + 1;
|
timestampUs = lastTimestampUs + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTimestampUs = timestampUs;
|
lastTimestampUs = timestampUs;
|
||||||
|
|
||||||
|
int inputBufferIndex = dequeueInputBuffer();
|
||||||
|
if (inputBufferIndex < 0) {
|
||||||
|
// We're being torn down now
|
||||||
|
return MoonBridge.DR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buf = getEmptyInputBuffer(inputBufferIndex);
|
||||||
|
|
||||||
int codecFlags = 0;
|
int codecFlags = 0;
|
||||||
boolean needsSpsReplay = false;
|
boolean needsSpsReplay = false;
|
||||||
|
|
||||||
@@ -650,18 +640,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
// Patch the SPS constraint flags
|
// Patch the SPS constraint flags
|
||||||
doProfileSpecificSpsPatching(sps);
|
doProfileSpecificSpsPatching(sps);
|
||||||
|
|
||||||
inputBufferIndex = dequeueInputBuffer();
|
|
||||||
if (inputBufferIndex < 0) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = getEmptyInputBuffer(inputBufferIndex);
|
|
||||||
if (buf == null) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the annex B header
|
// Write the annex B header
|
||||||
buf.put(frameData, 0, 5);
|
buf.put(frameData, 0, 5);
|
||||||
|
|
||||||
@@ -684,17 +662,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
numPpsIn++;
|
numPpsIn++;
|
||||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||||
|
|
||||||
inputBufferIndex = dequeueInputBuffer();
|
|
||||||
if (inputBufferIndex < 0) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = getEmptyInputBuffer(inputBufferIndex);
|
|
||||||
if (buf == null) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsBaselineSpsHack) {
|
if (needsBaselineSpsHack) {
|
||||||
LimeLog.info("Saw PPS; disabling SPS hack");
|
LimeLog.info("Saw PPS; disabling SPS hack");
|
||||||
@@ -706,66 +673,22 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
else if (frameData[4] == 0x40) {
|
else if (frameData[4] == 0x40) {
|
||||||
numVpsIn++;
|
numVpsIn++;
|
||||||
|
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||||
// Batch this to submit together with SPS and PPS per AOSP docs
|
|
||||||
vpsBuffer = new byte[frameLength];
|
|
||||||
System.arraycopy(frameData, 0, vpsBuffer, 0, frameLength);
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
}
|
||||||
else if (frameData[4] == 0x42) {
|
else if (frameData[4] == 0x42) {
|
||||||
numSpsIn++;
|
numSpsIn++;
|
||||||
|
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||||
// Batch this to submit together with VPS and PPS per AOSP docs
|
|
||||||
spsBuffer = new byte[frameLength];
|
|
||||||
System.arraycopy(frameData, 0, spsBuffer, 0, frameLength);
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
}
|
||||||
else if (frameData[4] == 0x44) {
|
else if (frameData[4] == 0x44) {
|
||||||
numPpsIn++;
|
numPpsIn++;
|
||||||
|
|
||||||
inputBufferIndex = dequeueInputBuffer();
|
|
||||||
if (inputBufferIndex < 0) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = getEmptyInputBuffer(inputBufferIndex);
|
|
||||||
if (buf == null) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// When we get the PPS, submit the VPS and SPS together with
|
|
||||||
// the PPS, as required by AOSP docs on use of HEVC and MediaCodec.
|
|
||||||
if (vpsBuffer != null) {
|
|
||||||
buf.put(vpsBuffer);
|
|
||||||
}
|
|
||||||
if (spsBuffer != null) {
|
|
||||||
buf.put(spsBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the HEVC CSD blob
|
|
||||||
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
inputBufferIndex = dequeueInputBuffer();
|
|
||||||
if (inputBufferIndex < 0) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = getEmptyInputBuffer(inputBufferIndex);
|
|
||||||
if (buf == null) {
|
|
||||||
// We're being torn down now
|
|
||||||
return MoonBridge.DR_OK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy data from our buffer list into the input buffer
|
// Copy data from our buffer list into the input buffer
|
||||||
buf.put(frameData, 0, frameLength);
|
buf.put(frameData, 0, frameLength);
|
||||||
|
|
||||||
if (!queueInputBuffer(inputBufferIndex,
|
if (!queueInputBuffer(inputBufferIndex,
|
||||||
0, buf.position(),
|
0, frameLength,
|
||||||
timestampUs, codecFlags)) {
|
timestampUs, codecFlags)) {
|
||||||
return MoonBridge.DR_NEED_IDR;
|
return MoonBridge.DR_NEED_IDR;
|
||||||
}
|
}
|
||||||
@@ -788,9 +711,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer inputBuffer = getEmptyInputBuffer(inputIndex);
|
ByteBuffer inputBuffer = getEmptyInputBuffer(inputIndex);
|
||||||
if (inputBuffer == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the Annex B header
|
// Write the Annex B header
|
||||||
inputBuffer.put(new byte[]{0x00, 0x00, 0x00, 0x01, 0x67});
|
inputBuffer.put(new byte[]{0x00, 0x00, 0x00, 0x01, 0x67});
|
||||||
@@ -880,11 +800,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
str += "AVC Decoder: "+((renderer.avcDecoder != null) ? renderer.avcDecoder.getName():"(none)")+"\n";
|
str += "AVC Decoder: "+((renderer.avcDecoder != null) ? renderer.avcDecoder.getName():"(none)")+"\n";
|
||||||
str += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\n";
|
str += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\n";
|
||||||
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
str += "Initial video dimensions: "+renderer.initialWidth+"x"+renderer.initialHeight+"\n";
|
||||||
str += "FPS target: "+renderer.refreshRate+"\n";
|
|
||||||
str += "Bitrate: "+renderer.bitrate+" Mbps \n";
|
|
||||||
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
|
str += "In stats: "+renderer.numVpsIn+", "+renderer.numSpsIn+", "+renderer.numPpsIn+"\n";
|
||||||
str += "Total frames: "+renderer.totalFrames+"\n";
|
str += "Total frames: "+renderer.totalFrames+"\n";
|
||||||
str += "Frame losses: "+renderer.framesLost+" in "+frameLossEvents+" loss events\n";
|
|
||||||
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
|
str += "Average end-to-end client latency: "+getAverageEndToEndLatency()+"ms\n";
|
||||||
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
|
str += "Average hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
|
||||||
|
|
||||||
@@ -900,6 +817,13 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
|
|||||||
|
|
||||||
str += "Is Exynos 4: "+renderer.isExynos4+"\n";
|
str += "Is Exynos 4: "+renderer.isExynos4+"\n";
|
||||||
|
|
||||||
|
str += "/proc/cpuinfo:\n";
|
||||||
|
try {
|
||||||
|
str += MediaCodecHelper.readCpuinfo();
|
||||||
|
} catch (Exception e) {
|
||||||
|
str += e.getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
str += "Full decoder dump:\n";
|
str += "Full decoder dump:\n";
|
||||||
try {
|
try {
|
||||||
str += MediaCodecHelper.dumpDecoders();
|
str += MediaCodecHelper.dumpDecoders();
|
||||||
|
|||||||
@@ -63,24 +63,15 @@ public class MediaCodecHelper {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
blacklistedDecoderPrefixes = new LinkedList<>();
|
blacklistedDecoderPrefixes = new LinkedList<>();
|
||||||
|
|
||||||
// Blacklist software decoders that don't support H264 high profile,
|
// Software decoders that don't support H264 high profile
|
||||||
// but exclude the official AOSP emulator from this restriction.
|
blacklistedDecoderPrefixes.add("omx.google");
|
||||||
if (!Build.HARDWARE.equals("ranchu") || !Build.BRAND.equals("google")) {
|
blacklistedDecoderPrefixes.add("AVCDecoder");
|
||||||
blacklistedDecoderPrefixes.add("omx.google");
|
|
||||||
blacklistedDecoderPrefixes.add("AVCDecoder");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Without bitstream fixups, we perform horribly on NVIDIA's HEVC
|
// Without bitstream fixups, we perform horribly on NVIDIA's HEVC
|
||||||
// decoder. While not strictly necessary, I'm going to fully blacklist this
|
// decoder. While not strictly necessary, I'm going to fully blacklist this
|
||||||
// one to avoid users getting inaccurate impressions of Tegra X1/Moonlight performance.
|
// one to avoid users getting inaccurate impressions of Tegra X1/Moonlight performance.
|
||||||
blacklistedDecoderPrefixes.add("OMX.Nvidia.h265.decode");
|
blacklistedDecoderPrefixes.add("OMX.Nvidia.h265.decode");
|
||||||
|
|
||||||
// Force these decoders disabled because:
|
|
||||||
// 1) They are software decoders, so the performance is terrible
|
|
||||||
// 2) They crash with our HEVC stream anyway (at least prior to CSD batching)
|
|
||||||
blacklistedDecoderPrefixes.add("OMX.qcom.video.decoder.hevcswvdec");
|
|
||||||
blacklistedDecoderPrefixes.add("OMX.SEC.hevc.sw.dec");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -107,11 +98,6 @@ public class MediaCodecHelper {
|
|||||||
static {
|
static {
|
||||||
whitelistedHevcDecoders = new LinkedList<>();
|
whitelistedHevcDecoders = new LinkedList<>();
|
||||||
|
|
||||||
// Allow software HEVC decoding in the official AOSP emulator
|
|
||||||
if (Build.HARDWARE.equals("ranchu") && Build.BRAND.equals("google")) {
|
|
||||||
whitelistedHevcDecoders.add("omx.google");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exynos seems to be the only HEVC decoder that works reliably
|
// Exynos seems to be the only HEVC decoder that works reliably
|
||||||
whitelistedHevcDecoders.add("omx.exynos");
|
whitelistedHevcDecoders.add("omx.exynos");
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.3.3'
|
classpath 'com.android.tools.build:gradle:2.3.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule moonlight-common updated: 13bf4f8855...d7f9be6c27
Reference in New Issue
Block a user