From 5b456aba27ddd9a676ba52e85cb856fdbafd2fcc Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sat, 4 Jun 2022 17:00:58 -0500 Subject: [PATCH] Use a separate HandlerThread for Choreographer callbacks --- .../video/MediaCodecDecoderRenderer.java | 63 ++++++++++++++----- 1 file changed, 46 insertions(+), 17 deletions(-) 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 e608353c..c9c29866 100644 --- a/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java +++ b/app/src/main/java/com/limelight/binding/video/MediaCodecDecoderRenderer.java @@ -23,7 +23,8 @@ import android.media.MediaCodec.BufferInfo; import android.media.MediaCodec.CodecException; import android.os.Build; import android.os.Handler; -import android.os.Looper; +import android.os.HandlerThread; +import android.os.Process; import android.os.SystemClock; import android.util.Range; import android.view.Choreographer; @@ -88,6 +89,8 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C private LinkedBlockingQueue outputBufferQueue = new LinkedBlockingQueue<>(); private static final int OUTPUT_BUFFER_QUEUE_LIMIT = 2; private long lastRenderedFrameTimeNanos; + private HandlerThread choreographerHandlerThread; + private Handler choreographerHandler; private int numSpsIn; private int numPpsIn; @@ -478,6 +481,26 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C Choreographer.getInstance().postFrameCallback(this); } + private void startChoreographerThread() { + if (prefs.framePacing != PreferenceConfiguration.FRAME_PACING_BALANCED) { + // Not using Choreographer in this pacing mode + return; + } + + // We use a separate thread to avoid any main thread delays from delaying rendering + choreographerHandlerThread = new HandlerThread("Video - Choreographer", Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_MORE_FAVORABLE); + choreographerHandlerThread.start(); + + // Start the frame callbacks + choreographerHandler = new Handler(choreographerHandlerThread.getLooper()); + choreographerHandler.post(new Runnable() { + @Override + public void run() { + Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this); + } + }); + } + private void startRendererThread() { rendererThread = new Thread() { @@ -620,18 +643,7 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C @Override public void start() { startRendererThread(); - - // Start Choreographer callbacks for rendering with frame pacing in balanced mode - // NB: This must be done on a thread with a looper! - if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_BALANCED) { - Handler h = new Handler(Looper.getMainLooper()); - h.post(new Runnable() { - @Override - public void run() { - Choreographer.getInstance().postFrameCallback(MediaCodecDecoderRenderer.this); - } - }); - } + startChoreographerThread(); } // !!! May be called even if setup()/start() fails !!! @@ -644,12 +656,15 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C rendererThread.interrupt(); } - // Halt further Choreographer callbacks - if (prefs.framePacing == PreferenceConfiguration.FRAME_PACING_BALANCED) { - Handler h = new Handler(Looper.getMainLooper()); - h.post(new Runnable() { + // Post a quit message to the Choreographer looper (if we have one) + if (choreographerHandler != null) { + choreographerHandler.post(new Runnable() { @Override public void run() { + // Don't allow any further messages to be queued + choreographerHandlerThread.quit(); + + // Deregister the frame callback (if registered) Choreographer.getInstance().removeFrameCallback(MediaCodecDecoderRenderer.this); } }); @@ -661,6 +676,20 @@ public class MediaCodecDecoderRenderer extends VideoDecoderRenderer implements C // May be called already, but we'll call it now to be safe prepareForStop(); + // Wait for the Choreographer looper to shut down (if we have one) + if (choreographerHandlerThread != null) { + try { + choreographerHandlerThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + + // InterruptedException clears the thread's interrupt status. Since we can't + // handle that here, we will re-interrupt the thread to set the interrupt + // status back to true. + Thread.currentThread().interrupt(); + } + } + // Wait for the renderer thread to shut down try { rendererThread.join();