From c797318ece299676920dc51d4fa1cd76799a7479 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 4 Feb 2018 12:59:04 -0800 Subject: [PATCH] Use frame drop hack to reduce latency and micro-stuttering for now --- app/src/main/java/com/limelight/Game.java | 27 ++++++++++++++++++- .../video/MediaCodecDecoderRenderer.java | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/limelight/Game.java b/app/src/main/java/com/limelight/Game.java index 664956bf..c8d4649d 100644 --- a/app/src/main/java/com/limelight/Game.java +++ b/app/src/main/java/com/limelight/Game.java @@ -308,7 +308,32 @@ public class Game extends Activity implements SurfaceHolder.Callback, // 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 (!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) diff --git a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java index 24c303c8..b6d8dfb4 100644 --- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java +++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java @@ -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);