Compare commits

..
1 Commits
14 changed files with 137 additions and 338 deletions
+6 -12
View File
@@ -4,23 +4,19 @@ import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion '26.0.0'
compileSdkVersion 25
buildToolsVersion '25.0.3'
defaultConfig {
minSdkVersion 16
targetSdkVersion 26
targetSdkVersion 25
versionName "5.1"
versionCode = 128
versionName "5.0.1"
versionCode = 123
}
productFlavors {
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"
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64"
@@ -56,8 +52,6 @@ android {
dependencies {
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
compile 'org.bouncycastle:bcpkix-jdk15on:1.52'
compile project(':moonlight-common')
compile files('libs/jcodec-0.1.9-patched.jar')
debugCompile project(path:':moonlight-common', configuration:'debug')
releaseCompile project(path:':moonlight-common', configuration:'release')
}
+28 -57
View File
@@ -171,20 +171,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
streamView.setOnGenericMotionListener(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
checkDataConnection();
@@ -214,7 +200,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
// Initialize the MediaCodec helper before creating the decoder
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
if (prefConfig.videoFormat == PreferenceConfiguration.FORCE_H265_ON && !decoderRenderer.isHevcSupported()) {
@@ -271,6 +257,8 @@ public class Game extends Activity implements SurfaceHolder.Callback,
getWindow().setSustainedPerformanceMode(true);
}
inputCaptureProvider = InputCaptureManager.getInputCaptureProvider(this, this);
if (prefConfig.onscreenController) {
// create virtual onscreen controller
virtualController = new VirtualController(conn,
@@ -289,20 +277,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
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() {
Display display = getWindowManager().getDefaultDisplay();
WindowManager.LayoutParams windowLayoutParams = getWindow().getAttributes();
@@ -446,17 +420,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
unbindService(usbDriverServiceConnection);
}
// Destroy the capture provider
inputCaptureProvider.destroy();
}
@Override
protected void onStop() {
super.onStop();
SpinnerDialog.closeDialogs(this);
Dialog.closeDialogs();
if (conn != null) {
int videoFormat = decoderRenderer.getActiveVideoFormat();
@@ -490,6 +453,14 @@ public class Game extends Activity implements SurfaceHolder.Callback,
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
}
@Override
protected void onStop() {
super.onStop();
SpinnerDialog.closeDialogs(this);
Dialog.closeDialogs();
finish();
}
@@ -700,8 +671,7 @@ public class Game extends Activity implements SurfaceHolder.Callback,
return true;
}
}
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0 ||
event.getSource() == InputDevice.SOURCE_MOUSE_RELATIVE)
else if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
{
// This case is for mice
if (event.getSource() == InputDevice.SOURCE_MOUSE ||
@@ -710,11 +680,6 @@ public class Game extends Activity implements SurfaceHolder.Callback,
{
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) {
// Send the vertical scroll packet
byte vScrollClicks = (byte) event.getAxisValue(MotionEvent.AXIS_VSCROLL);
@@ -928,6 +893,12 @@ public class Game extends Activity implements SurfaceHolder.Callback,
connecting = connected = false;
conn.stop();
}
// Enable cursor visibility again
inputCaptureProvider.disableCapture();
// Destroy the capture provider
inputCaptureProvider.destroy();
}
@Override
@@ -937,13 +908,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
spinner = null;
}
// Enable cursor visibility again
inputCaptureProvider.disableCapture();
if (!displayedFailureDialog) {
displayedFailureDialog = true;
LimeLog.severe(stage+" failed: "+errorCode);
stopConnection();
Dialog.displayDialog(this, getResources().getString(R.string.conn_error_title),
getResources().getString(R.string.conn_error_msg)+" "+stage, true);
}
@@ -951,14 +920,11 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void connectionTerminated(long errorCode) {
// Enable cursor visibility again
inputCaptureProvider.disableCapture();
if (!displayedFailureDialog) {
displayedFailureDialog = true;
LimeLog.severe("Connection terminated: "+errorCode);
stopConnection();
stopConnection();
Dialog.displayDialog(this, getResources().getString(R.string.conn_terminated_title),
getResources().getString(R.string.conn_terminated_msg), true);
}
@@ -1025,10 +991,15 @@ public class Game extends Activity implements SurfaceHolder.Callback,
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Let the decoder know immediately that the surface is gone
decoderRenderer.prepareForStop();
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();
}
}
@@ -63,10 +63,6 @@ public class ControllerHandler implements InputManager.InputDeviceListener, UsbD
int[] ids = InputDevice.getDeviceIds();
for (int id : ids) {
InputDevice dev = InputDevice.getDevice(id);
if (dev == null) {
// This device was removed during enumeration
continue;
}
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
@@ -10,11 +10,11 @@ import android.view.View;
import android.view.ViewGroup;
@TargetApi(Build.VERSION_CODES.N)
public class AndroidPointerIconCaptureProvider extends InputCaptureProvider {
public class AndroidCaptureProvider extends InputCaptureProvider {
private ViewGroup rootViewGroup;
private Context context;
public AndroidPointerIconCaptureProvider(Activity activity) {
public AndroidCaptureProvider(Activity activity) {
this.context = activity;
this.rootViewGroup = (ViewGroup) activity.getWindow().getDecorView();
}
@@ -33,13 +33,11 @@ public class AndroidPointerIconCaptureProvider extends InputCaptureProvider {
@Override
public void enableCapture() {
super.enableCapture();
setPointerIconOnAllViews(PointerIcon.getSystemIcon(context, PointerIcon.TYPE_NULL));
}
@Override
public void disableCapture() {
super.disableCapture();
setPointerIconOnAllViews(null);
}
@@ -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 com.limelight.LimeLog;
import com.limelight.R;
import com.limelight.binding.input.evdev.EvdevCaptureProvider;
import com.limelight.binding.input.evdev.EvdevListener;
public class InputCaptureManager {
public static InputCaptureProvider getInputCaptureProvider(Activity activity, EvdevListener rootListener) {
if (AndroidNativePointerCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Android O+ native mouse capture");
return new AndroidNativePointerCaptureProvider(activity.findViewById(R.id.surfaceView));
}
else if (ShieldCaptureProvider.isCaptureProviderSupported()) {
// Shield capture is preferred because it can capture when the cursor is over
// the system UI. Android N native capture can only capture over views owned
// by the application.
if (ShieldCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using NVIDIA mouse capture extension");
return new ShieldCaptureProvider(activity);
}
@@ -21,11 +19,9 @@ public class InputCaptureManager {
LimeLog.info("Using Evdev mouse capture");
return new EvdevCaptureProvider(activity, rootListener);
}
else if (AndroidPointerIconCaptureProvider.isCaptureProviderSupported()) {
// Android N's native capture can't capture over system UI elements
// so we want to only use it if there's no other option.
LimeLog.info("Using Android N+ pointer hiding");
return new AndroidPointerIconCaptureProvider(activity);
else if (AndroidCaptureProvider.isCaptureProviderSupported()) {
LimeLog.info("Using Android N+ native mouse capture");
return new AndroidCaptureProvider(activity);
}
else {
LimeLog.info("Mouse capture not available");
@@ -3,25 +3,10 @@ package com.limelight.binding.input.capture;
import android.view.MotionEvent;
public abstract class InputCaptureProvider {
protected boolean isCapturing;
public void enableCapture() {
isCapturing = true;
}
public void disableCapture() {
isCapturing = false;
}
public abstract void enableCapture();
public abstract void disableCapture();
public void destroy() {}
public boolean isCapturingEnabled() {
return isCapturing;
}
public boolean isCapturingActive() {
return isCapturing;
}
public boolean eventHasRelativeMouseAxes(MotionEvent event) {
return false;
}
@@ -1,4 +1,10 @@
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
public void enableCapture() {
super.enableCapture();
setCursorVisibility(false);
}
@Override
public void disableCapture() {
super.disableCapture();
setCursorVisibility(true);
}
@@ -221,7 +221,6 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
@Override
public void enableCapture() {
super.enableCapture();
if (!started) {
// Start the handler thread if it's our first time
// capturing
@@ -248,7 +247,6 @@ public class EvdevCaptureProvider extends InputCaptureProvider {
@Override
public void disableCapture() {
super.disableCapture();
// This may be called on the main thread
runInNetworkSafeContextSynchronously(new Runnable() {
@Override
@@ -23,7 +23,17 @@ import android.view.SurfaceHolder;
public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
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
private ByteBuffer[] legacyInputBuffers;
@@ -31,10 +41,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
private MediaCodecInfo avcDecoder;
private MediaCodecInfo hevcDecoder;
// Used for HEVC only
private byte[] vpsBuffer;
private byte[] spsBuffer;
private MediaCodec videoDecoder;
private Thread rendererThread;
private Thread[] spinnerThreads;
@@ -45,8 +51,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
private boolean refFrameInvalidationActive;
private int initialWidth, initialHeight;
private int videoFormat;
private SurfaceHolder renderTarget;
private volatile boolean stopping;
private Object renderTarget;
private boolean stopping;
private boolean needsBaselineSpsHack;
private SeqParameterSet savedSps;
@@ -55,11 +61,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
private long decoderTimeMs;
private long totalTimeMs;
private int totalFrames;
private int frameLossEvents;
private int framesLost;
private int lastFrameNumber;
private int refreshRate;
private int bitrate;
private int numSpsIn;
private int numPpsIn;
@@ -101,15 +102,13 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
return decoderInfo;
}
public void setRenderTarget(SurfaceHolder renderTarget) {
public void setRenderTarget(Object renderTarget) {
this.renderTarget = renderTarget;
}
public MediaCodecDecoderRenderer(int videoFormat, int bitrate) {
public MediaCodecDecoderRenderer(int videoFormat) {
//dumpDecoders();
this.bitrate = bitrate;
spinnerThreads = new Thread[Runtime.getRuntime().availableProcessors()];
avcDecoder = findAvcDecoder();
@@ -168,7 +167,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
this.initialWidth = width;
this.initialHeight = height;
this.videoFormat = format;
this.refreshRate = redrawRate;
String mimeType;
String selectedDecoderName;
@@ -246,7 +244,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
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);
if (USE_FRAME_RENDER_TIME && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -279,12 +277,12 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
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 (e instanceof CodecException) {
CodecException codecExc = (CodecException) e;
if (codecExc.isTransient() && !throwOnTransient) {
if (codecExc.isTransient()) {
// We'll let transient exceptions go
LimeLog.warning(codecExc.getDiagnosticInfo());
return;
@@ -294,8 +292,9 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
}
// Only throw if the surface is still around and we're not stopping
if (!stopping && renderTarget.getSurface().isValid()) {
// Only throw if this happens at the beginning of a stream
// but not if we're stopping
if (totalFrames > 0 && totalFrames < 20 && !stopping) {
if (buf != null || codecFlags != 0) {
throw new RendererException(this, e, buf, codecFlags);
}
@@ -328,7 +327,17 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
// 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)
long delta = MediaCodecHelper.getMonotonicMillis() - (presentationTimeUs / 1000);
@@ -351,7 +360,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
}
} catch (Exception e) {
handleDecoderException(e, null, 0, false);
handleDecoderException(e, null, 0);
}
}
}
@@ -391,11 +400,11 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
startTime = MediaCodecHelper.getMonotonicMillis();
try {
while (index < 0 && !stopping) {
while (rendererThread.isAlive() && index < 0 && !stopping) {
index = videoDecoder.dequeueInputBuffer(10000);
}
} catch (Exception e) {
handleDecoderException(e, null, 0, true);
handleDecoderException(e, null, 0);
return MediaCodec.INFO_TRY_AGAIN_LATER;
}
@@ -418,37 +427,16 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
startSpinnerThreads();
}
public void prepareForStop() {
// Let the decoding code know to ignore codec exceptions now
stopping = true;
}
@Override
public void stop() {
stopping = true;
// Halt the rendering thread
rendererThread.interrupt();
try {
// Invalidate pending decode buffers
videoDecoder.flush();
} catch (Exception e) {
e.printStackTrace();
}
// Wait for the renderer thread to shut down
try {
rendererThread.join();
} catch (InterruptedException ignored) { }
try {
// Stop the video decoder
videoDecoder.stop();
} catch (Exception e) {
e.printStackTrace();
}
// Halt the spinner threads
for (Thread t : spinnerThreads) {
t.interrupt();
@@ -466,13 +454,31 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
private boolean queueInputBuffer(int inputBufferIndex, int offset, int length, long timestampUs, int codecFlags) {
try {
videoDecoder.queueInputBuffer(inputBufferIndex,
offset, length,
timestampUs, codecFlags);
// Try 25 times to submit the input buffer before throwing a real exception
int i;
Exception lastException = null;
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;
} catch (Exception e) {
handleDecoderException(e, null, codecFlags, true);
}
else {
// Failed to queue
return false;
}
}
@@ -483,12 +489,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
ByteBuffer buf;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
buf = videoDecoder.getInputBuffer(inputBufferIndex);
} catch (Exception e) {
handleDecoderException(e, null, 0, true);
return null;
}
buf = videoDecoder.getInputBuffer(inputBufferIndex);
}
else {
buf = legacyInputBuffers[inputBufferIndex];
@@ -519,36 +520,25 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
@SuppressWarnings("deprecation")
@Override
public int submitDecodeUnit(byte[] frameData, int frameLength, int frameNumber, long receiveTimeMs) {
public int submitDecodeUnit(byte[] frameData, int frameLength) {
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;
if (!FRAME_RENDER_TIME_ONLY) {
// Count time from first packet received to decode start
totalTimeMs += (timestampUs / 1000) - receiveTimeMs;
}
if (timestampUs <= lastTimestampUs) {
// We can't submit multiple buffers with the same timestamp
// so bump it up by one before queuing
timestampUs = lastTimestampUs + 1;
}
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;
boolean needsSpsReplay = false;
@@ -650,18 +640,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
// Patch the SPS constraint flags
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
buf.put(frameData, 0, 5);
@@ -684,17 +662,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
numPpsIn++;
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) {
LimeLog.info("Saw PPS; disabling SPS hack");
@@ -706,66 +673,22 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
else if (frameData[4] == 0x40) {
numVpsIn++;
// 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;
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
}
else if (frameData[4] == 0x42) {
numSpsIn++;
// 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;
codecFlags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
}
else if (frameData[4] == 0x44) {
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;
}
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
buf.put(frameData, 0, frameLength);
if (!queueInputBuffer(inputBufferIndex,
0, buf.position(),
0, frameLength,
timestampUs, codecFlags)) {
return MoonBridge.DR_NEED_IDR;
}
@@ -788,9 +711,6 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
}
ByteBuffer inputBuffer = getEmptyInputBuffer(inputIndex);
if (inputBuffer == null) {
return false;
}
// Write the Annex B header
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 += "HEVC Decoder: "+((renderer.hevcDecoder != null) ? renderer.hevcDecoder.getName():"(none)")+"\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 += "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 hardware decoder latency: "+getAverageDecoderLatency()+"ms\n";
@@ -900,6 +817,13 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer {
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";
try {
str += MediaCodecHelper.dumpDecoders();
@@ -63,24 +63,15 @@ public class MediaCodecHelper {
static {
blacklistedDecoderPrefixes = new LinkedList<>();
// Blacklist software decoders that don't support H264 high profile,
// but exclude the official AOSP emulator from this restriction.
if (!Build.HARDWARE.equals("ranchu") || !Build.BRAND.equals("google")) {
blacklistedDecoderPrefixes.add("omx.google");
blacklistedDecoderPrefixes.add("AVCDecoder");
}
// Software decoders that don't support H264 high profile
blacklistedDecoderPrefixes.add("omx.google");
blacklistedDecoderPrefixes.add("AVCDecoder");
// Without bitstream fixups, we perform horribly on NVIDIA's HEVC
// decoder. While not strictly necessary, I'm going to fully blacklist this
// one to avoid users getting inaccurate impressions of Tegra X1/Moonlight performance.
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 {
@@ -107,11 +98,6 @@ public class MediaCodecHelper {
static {
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
whitelistedHevcDecoders.add("omx.exynos");
+1 -1
View File
@@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
classpath 'com.android.tools.build:gradle:2.3.2'
}
}