diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/libwebrtc/sdk/android/api/org/webrtc/RenderSynchronizer.java | 116 |
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(); + } + } +} |