| /* | |
| * 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 static java.lang.Math.ceil; | |
| import static java.util.concurrent.TimeUnit.MILLISECONDS; | |
| import static org.mockito.Mockito.inOrder; | |
| import static org.mockito.Mockito.times; | |
| import static org.mockito.Mockito.verify; | |
| import androidx.test.runner.AndroidJUnit4; | |
| import java.time.Duration; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.mockito.InOrder; | |
| import org.mockito.Mock; | |
| import org.mockito.MockitoAnnotations; | |
| import org.robolectric.annotation.Config; | |
| import org.robolectric.shadows.ShadowLooper; | |
| import org.robolectric.shadows.ShadowChoreographer; | |
| @RunWith(AndroidJUnit4.class) | |
| @Config(manifest = Config.NONE) | |
| public class RenderSynchronizerTest { | |
| private static final Duration FRAME_DELAY = Duration.ofMillis(16); | |
| private static final float DEFAULT_FRAME_RATE = 30f; | |
| @Mock private RenderSynchronizer.Listener mockListener; | |
| private RenderSynchronizer renderSynchronizer; | |
| private Duration refreshDelay; | |
| private float frameRate; | |
| @Before | |
| public void setUp() { | |
| MockitoAnnotations.initMocks(this); | |
| ShadowChoreographer.setPaused(true); | |
| } | |
| private void init(float displayRefreshRate, float renderFrameRate) { | |
| refreshDelay = Duration.ofMillis((long)ceil(1000 / displayRefreshRate)); | |
| frameRate = renderFrameRate; | |
| ShadowChoreographer.setFrameDelay(refreshDelay); | |
| renderSynchronizer = new RenderSynchronizer(renderFrameRate); | |
| renderSynchronizer.registerListener(mockListener); | |
| } | |
| @Test | |
| public void renderWindowOpensOnFirstFrame() { | |
| init(/* displayRefreshRate= */60f, /* renderFrameRate= */30f); | |
| advanceOneRefreshCycle(); | |
| verify(mockListener).onRenderWindowOpen(); | |
| } | |
| @Test | |
| public void doubleDisplayRefreshRate() { | |
| init(/* displayRefreshRate= */60f, /* renderFrameRate= */30f); | |
| InOrder inOrder = inOrder(mockListener); | |
| for (int i = 0; i < 10; ++i) { | |
| advanceOneRefreshCycle(); | |
| inOrder.verify(mockListener).onRenderWindowOpen(); | |
| advanceOneRefreshCycle(); | |
| inOrder.verify(mockListener).onRenderWindowClose(); | |
| } | |
| } | |
| @Test | |
| public void trippleRefreshRate() { | |
| init(/* displayRefreshRate= */90f, /* renderFrameRate= */30f); | |
| InOrder inOrder = inOrder(mockListener); | |
| for (int i = 0; i < 10; ++i) { | |
| advanceOneRefreshCycle(); | |
| inOrder.verify(mockListener).onRenderWindowOpen(); | |
| advanceOneRefreshCycle(); | |
| inOrder.verify(mockListener).onRenderWindowClose(); | |
| advanceOneRefreshCycle(); | |
| // No action expected, window stays closed/ | |
| } | |
| } | |
| @Test | |
| public void equalRefreshRate() { | |
| init(/* displayRefreshRate= */30f, /* renderFrameRate= */30f); | |
| for (int i = 0; i < 10; ++i) { | |
| advanceOneRefreshCycle(); | |
| } | |
| verify(mockListener, times(10)).onRenderWindowOpen(); | |
| verify(mockListener, times(0)).onRenderWindowClose(); | |
| } | |
| @Test | |
| public void halfRefreshRate() { | |
| init(/* displayRefreshRate= */30f, /* renderFrameRate= */60f); | |
| for (int i = 0; i < 10; ++i) { | |
| advanceOneRefreshCycle(); | |
| } | |
| verify(mockListener, times(10)).onRenderWindowOpen(); | |
| verify(mockListener, times(0)).onRenderWindowClose(); | |
| } | |
| private void advanceOneRefreshCycle() { | |
| advanceBy(refreshDelay); | |
| } | |
| private void advanceBy(Duration duration) { | |
| ShadowLooper.idleMainLooper(duration.toMillis(), MILLISECONDS); | |
| } | |
| } |