diff --git a/sdk/android/api/org/webrtc/EglRenderer.java b/sdk/android/api/org/webrtc/EglRenderer.java index 4ec02cd0b2..304caf35a2 100644 --- a/sdk/android/api/org/webrtc/EglRenderer.java +++ b/sdk/android/api/org/webrtc/EglRenderer.java @@ -21,6 +21,8 @@ import java.nio.ByteBuffer; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Iterator; +import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -93,6 +95,10 @@ public class EglRenderer implements VideoSink { protected final String name; + // An id to uniquely identify the renderer, used for when we're scheduling + // frames for render. + private Optional id = Optional.empty(); + // `eglThread` is used for rendering, and is synchronized on `threadLock`. private final Object threadLock = new Object(); @GuardedBy("threadLock") @Nullable private EglThread eglThread; @@ -214,6 +220,23 @@ public class EglRenderer implements VideoSink { } } + /** + * Intializes this class with the given parameters. + * + * The new parameter here, `overwritePendingFrames` overwrites instead of + * queueing frames when passing them to the synchronized renderer. + */ + public void init( + EglThread eglThread, + RendererCommon.GlDrawer drawer, + boolean usePresentationTimeStamp, + boolean overwritePendingFrames) { + if (overwritePendingFrames) { + id = Optional.of(UUID.randomUUID()); + } + init(eglThread, drawer, usePresentationTimeStamp); + } + /** * Initialize this class, sharing resources with `sharedContext`. The custom `drawer` will be used * for drawing frames on the EGLSurface. This class is responsible for calling release() on @@ -444,7 +467,7 @@ public class EglRenderer implements VideoSink { } /** - * Register a callback to be invoked when a new video frame has been rendered. + * Register a callback to be invoked when a new video frame has been rendered. * * @param listener The callback to be invoked. The callback will be invoked on the render thread. * It should be lightweight and must not call removeRenderListener. @@ -611,30 +634,36 @@ public class EglRenderer implements VideoSink { private void swapBuffersOnRenderThread(final VideoFrame frame, long swapBuffersStartTimeNs) { synchronized (threadLock) { - if (eglThread != null) { - eglThread.scheduleRenderUpdate( - runsInline -> { - if (!runsInline) { - if (eglBase == null || !eglBase.hasSurface()) { - return; - } - eglBase.makeCurrent(); + if (eglThread == null) { + return; + } + EglThread.RenderUpdate renderUpdate = + runsInline -> { + if (!runsInline) { + if (eglBase == null || !eglBase.hasSurface()) { + return; } + eglBase.makeCurrent(); + } - if (usePresentationTimeStamp) { - eglBase.swapBuffers(frame.getTimestampNs()); - } else { - eglBase.swapBuffers(); - } + if (usePresentationTimeStamp) { + eglBase.swapBuffers(frame.getTimestampNs()); + } else { + eglBase.swapBuffers(); + } - for (var listener : renderListeners) { - listener.onRender(System.nanoTime()); - } + for (var listener : renderListeners) { + listener.onRender(System.nanoTime()); + } - synchronized (statisticsLock) { - renderSwapBufferTimeNs += (System.nanoTime() - swapBuffersStartTimeNs); - } - }); + synchronized (statisticsLock) { + renderSwapBufferTimeNs += (System.nanoTime() - swapBuffersStartTimeNs); + } + }; + if (id.isPresent()) { + eglThread.scheduleRenderUpdate(id.get(), renderUpdate); + } else { + eglThread.scheduleRenderUpdate(renderUpdate); } } } diff --git a/sdk/android/api/org/webrtc/EglThread.java b/sdk/android/api/org/webrtc/EglThread.java index 73323d59c8..a2d111139d 100644 --- a/sdk/android/api/org/webrtc/EglThread.java +++ b/sdk/android/api/org/webrtc/EglThread.java @@ -16,8 +16,13 @@ import android.os.Looper; import android.os.Message; import androidx.annotation.GuardedBy; import androidx.annotation.Nullable; + import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.UUID; + import org.webrtc.EglBase.EglConnection; /** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */ @@ -122,7 +127,10 @@ public class EglThread implements RenderSynchronizer.Listener { private final HandlerWithExceptionCallbacks handler; private final EglConnection eglConnection; private final RenderSynchronizer renderSynchronizer; - private final List pendingRenderUpdates = new ArrayList<>(); + // Pending render updates if they're overwritten per renderer. + private final Map pendingRenderUpdates = new HashMap<>(); + // Pending render updates if they're in a global queue. + private final List pendingRenderUpdatesQueued = new ArrayList<>(); private boolean renderWindowOpen = true; private EglThread( @@ -188,12 +196,25 @@ public class EglThread implements RenderSynchronizer.Listener { * Schedules a render update (like swapBuffers) to be run in sync with other updates on the next * open render window. If the render window is currently open the update will run immediately. * This method must be called on the EglThread during a render pass. + * + * @param id a unique id of the renderer that scheduled this render update. */ + public void scheduleRenderUpdate(UUID id, RenderUpdate update) { + if (renderWindowOpen) { + update.update(/* runsInline = */ true); + } else { + pendingRenderUpdates.put(id, update); + } + } + + // The same as above, except that the ids are randomly generated for each frame. + // So this essentially becomes a queue of frame updates. + @Deprecated public void scheduleRenderUpdate(RenderUpdate update) { if (renderWindowOpen) { - update.update(/* runsInline = */true); + update.update(/* runsInline = */ true); } else { - pendingRenderUpdates.add(update); + pendingRenderUpdatesQueued.add(update); } } @@ -202,10 +223,14 @@ public class EglThread implements RenderSynchronizer.Listener { handler.post( () -> { renderWindowOpen = true; - for (RenderUpdate update : pendingRenderUpdates) { - update.update(/* runsInline = */false); + for (RenderUpdate update : pendingRenderUpdates.values()) { + update.update(/* runsInline = */ false); } pendingRenderUpdates.clear(); + for (RenderUpdate update: pendingRenderUpdatesQueued) { + update.update(/* runsInline = */ false); + } + pendingRenderUpdatesQueued.clear(); }); }