summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/sdk/android/api/org/webrtc/RenderSynchronizer.java
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/sdk/android/api/org/webrtc/RenderSynchronizer.java')
-rw-r--r--third_party/libwebrtc/sdk/android/api/org/webrtc/RenderSynchronizer.java116
1 files changed, 116 insertions, 0 deletions
diff --git a/third_party/libwebrtc/sdk/android/api/org/webrtc/RenderSynchronizer.java b/third_party/libwebrtc/sdk/android/api/org/webrtc/RenderSynchronizer.java
new file mode 100644
index 0000000000..b1ade84298
--- /dev/null
+++ b/third_party/libwebrtc/sdk/android/api/org/webrtc/RenderSynchronizer.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+package org.webrtc;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+import androidx.annotation.GuardedBy;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to synchronize rendering updates with display refresh cycles and save power by blocking
+ * updates that exceeds the target frame rate.
+ */
+public final class RenderSynchronizer {
+
+ /** Interface for listening to render window updates. */
+ public interface Listener {
+ void onRenderWindowOpen();
+
+ void onRenderWindowClose();
+ }
+
+ private static final String TAG = "RenderSynchronizer";
+ private static final float DEFAULT_TARGET_FPS = 30f;
+ private final Object lock = new Object();
+ private final List<Listener> listeners = new CopyOnWriteArrayList<>();
+ private final long targetFrameIntervalNanos;
+ private final Handler mainThreadHandler;
+ private Choreographer choreographer;
+
+ @GuardedBy("lock")
+ private boolean isListening;
+
+ private boolean renderWindowOpen;
+ private long lastRefreshTimeNanos;
+ private long lastOpenedTimeNanos;
+
+ public RenderSynchronizer(float targetFrameRateFps) {
+ this.targetFrameIntervalNanos = Math.round(TimeUnit.SECONDS.toNanos(1) / targetFrameRateFps);
+ this.mainThreadHandler = new Handler(Looper.getMainLooper());
+ mainThreadHandler.post(() -> this.choreographer = Choreographer.getInstance());
+ Logging.d(TAG, "Created");
+ }
+
+ public RenderSynchronizer() {
+ this(DEFAULT_TARGET_FPS);
+ }
+
+ public void registerListener(Listener listener) {
+ listeners.add(listener);
+
+ synchronized (lock) {
+ if (!isListening) {
+ Logging.d(TAG, "First listener, subscribing to frame callbacks");
+ isListening = true;
+ mainThreadHandler.post(
+ () -> choreographer.postFrameCallback(this::onDisplayRefreshCycleBegin));
+ }
+ }
+ }
+
+ public void removeListener(Listener listener) {
+ listeners.remove(listener);
+ }
+
+ private void onDisplayRefreshCycleBegin(long refreshTimeNanos) {
+ synchronized (lock) {
+ if (listeners.isEmpty()) {
+ Logging.d(TAG, "No listeners, unsubscribing to frame callbacks");
+ isListening = false;
+ return;
+ }
+ }
+ choreographer.postFrameCallback(this::onDisplayRefreshCycleBegin);
+
+ long lastOpenDeltaNanos = refreshTimeNanos - lastOpenedTimeNanos;
+ long refreshDeltaNanos = refreshTimeNanos - lastRefreshTimeNanos;
+ lastRefreshTimeNanos = refreshTimeNanos;
+
+ // Make a greedy choice whether to open (or keep open) the render window. If the current time
+ // since the render window was last opened is closer to the target than what we predict it would
+ // be in the next refresh cycle then we open the window.
+ if (Math.abs(lastOpenDeltaNanos - targetFrameIntervalNanos)
+ < Math.abs(lastOpenDeltaNanos - targetFrameIntervalNanos + refreshDeltaNanos)) {
+ lastOpenedTimeNanos = refreshTimeNanos;
+ openRenderWindow();
+ } else if (renderWindowOpen) {
+ closeRenderWindow();
+ }
+ }
+
+ private void openRenderWindow() {
+ renderWindowOpen = true;
+ for (Listener listener : listeners) {
+ listener.onRenderWindowOpen();
+ }
+ }
+
+ private void closeRenderWindow() {
+ renderWindowOpen = false;
+ for (Listener listener : listeners) {
+ listener.onRenderWindowClose();
+ }
+ }
+}