Skip to content

Commit

Permalink
Introduce audio offload scheduling tests
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 330918396
  • Loading branch information
krocard authored and ojw28 committed Sep 11, 2020
1 parent 4d2a238 commit 52a1c79
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.android.exoplayer2.testutil.FakeSampleStream.FakeSampleStreamItem.oneByteSample;
import static com.google.android.exoplayer2.testutil.TestExoPlayer.playUntilStartOfWindow;
import static com.google.android.exoplayer2.testutil.TestExoPlayer.runUntilPlaybackState;
import static com.google.android.exoplayer2.testutil.TestExoPlayer.runUntilReceiveOffloadSchedulingEnabledNewState;
import static com.google.android.exoplayer2.testutil.TestExoPlayer.runUntilTimelineChanged;
import static com.google.android.exoplayer2.testutil.TestUtil.runMainLooperUntil;
import static com.google.common.truth.Truth.assertThat;
Expand Down Expand Up @@ -110,6 +111,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
Expand Down Expand Up @@ -8208,6 +8210,109 @@ protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
assertThat(player.getCurrentWindowIndex()).isEqualTo(0);
}

@Test
public void enableOffloadSchedulingWhileIdle_isToggled_isReported() throws Exception {
SimpleExoPlayer player = new TestExoPlayer.Builder(context).build();

player.experimentalSetOffloadSchedulingEnabled(true);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isTrue();

player.experimentalSetOffloadSchedulingEnabled(false);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse();
}

@Test
public void enableOffloadSchedulingWhilePlaying_isToggled_isReported() throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();
SimpleExoPlayer player = new TestExoPlayer.Builder(context).setRenderers(sleepRenderer).build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.prepare();
player.play();

player.experimentalSetOffloadSchedulingEnabled(true);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isTrue();

player.experimentalSetOffloadSchedulingEnabled(false);
assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse();
}

@Test
public void enableOffloadSchedulingWhileSleepingForOffload_isDisabled_isReported()
throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();
SimpleExoPlayer player = new TestExoPlayer.Builder(context).setRenderers(sleepRenderer).build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.experimentalSetOffloadSchedulingEnabled(true);
runUntilReceiveOffloadSchedulingEnabledNewState(player);
player.prepare();
player.play();
runMainLooperUntil(sleepRenderer::isSleeping);

player.experimentalSetOffloadSchedulingEnabled(false);

assertThat(runUntilReceiveOffloadSchedulingEnabledNewState(player)).isFalse();
}

@Test
public void enableOffloadScheduling_isEnable_playerSleeps() throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO);
SimpleExoPlayer player = new TestExoPlayer.Builder(context).setRenderers(sleepRenderer).build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.experimentalSetOffloadSchedulingEnabled(true);
player.prepare();
player.play();

sleepRenderer.sleepOnNextRender();

runMainLooperUntil(sleepRenderer::isSleeping);
// TODO(b/163303129): There is currently no way to check that the player is sleeping for
// offload, for now use a timeout to check that the renderer is never woken up.
final int renderTimeoutMs = 500;
assertThrows(
TimeoutException.class,
() ->
runMainLooperUntil(() -> !sleepRenderer.isSleeping(), renderTimeoutMs, Clock.DEFAULT));
}

@Test
public void
experimentalEnableOffloadSchedulingWhileSleepingForOffload_isDisabled_renderingResumes()
throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();
SimpleExoPlayer player = new TestExoPlayer.Builder(context).setRenderers(sleepRenderer).build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.experimentalSetOffloadSchedulingEnabled(true);
player.prepare();
player.play();
runMainLooperUntil(sleepRenderer::isSleeping);

player.experimentalSetOffloadSchedulingEnabled(false); // Force the player to exit offload sleep

runMainLooperUntil(() -> !sleepRenderer.isSleeping());
runUntilPlaybackState(player, Player.STATE_ENDED);
}

@Test
public void wakeupListenerWhileSleepingForOffload_isWokenUp_renderingResumes() throws Exception {
FakeSleepRenderer sleepRenderer = new FakeSleepRenderer(C.TRACK_TYPE_AUDIO).sleepOnNextRender();
SimpleExoPlayer player = new TestExoPlayer.Builder(context).setRenderers(sleepRenderer).build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 1);
player.setMediaSource(new FakeMediaSource(timeline, ExoPlayerTestRunner.AUDIO_FORMAT));
player.experimentalSetOffloadSchedulingEnabled(true);
player.prepare();
player.play();
runMainLooperUntil(sleepRenderer::isSleeping);

sleepRenderer.wakeup();

runMainLooperUntil(() -> !sleepRenderer.isSleeping());
runUntilPlaybackState(player, Player.STATE_ENDED);
}

// Internal methods.

private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
Expand Down Expand Up @@ -8237,6 +8342,63 @@ private static void deliverBroadcast(Intent intent) {

// Internal classes.

/* {@link FakeRenderer} that can sleep and be woken-up. */
private static class FakeSleepRenderer extends FakeRenderer {
private static final long WAKEUP_DEADLINE_MS = 60 * C.MICROS_PER_SECOND;
private final AtomicBoolean sleepOnNextRender;
private final AtomicBoolean isSleeping;
private final AtomicReference<Renderer.WakeupListener> wakeupListenerReceiver;

public FakeSleepRenderer(int trackType) {
super(trackType);
sleepOnNextRender = new AtomicBoolean(false);
isSleeping = new AtomicBoolean(false);
wakeupListenerReceiver = new AtomicReference<>();
}

public void wakeup() {
wakeupListenerReceiver.get().onWakeup();
}

/**
* Call {@link Renderer.WakeupListener#onSleep(long)} on the next {@link #render(long, long)}
*/
public FakeSleepRenderer sleepOnNextRender() {
sleepOnNextRender.set(true);
return this;
}

/**
* Returns whether {@link Renderer.WakeupListener#onSleep(long)} was called on the last {@link
* #render(long, long)}
*/
public boolean isSleeping() {
return isSleeping.get();
}

@Override
public void handleMessage(int what, @Nullable Object object) throws ExoPlaybackException {
if (what == MSG_SET_WAKEUP_LISTENER) {
assertThat(object).isNotNull();
wakeupListenerReceiver.set((WakeupListener) object);
}
super.handleMessage(what, object);
}

@Override
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
super.render(positionUs, elapsedRealtimeUs);
if (sleepOnNextRender.compareAndSet(/* expect= */ true, /* update= */ false)) {
wakeupListenerReceiver.get().onSleep(WAKEUP_DEADLINE_MS);
// TODO(b/163303129): Use an actual message from the player instead of guessing that the
// player will always sleep for offload after calling `onSleep`.
isSleeping.set(true);
} else {
isSleeping.set(false);
}
}
}

private static final class CountingMessageTarget implements PlayerMessage.Target {

public int messageCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/**
Expand Down Expand Up @@ -438,6 +439,33 @@ public void onPlayerError(ExoPlaybackException error) {
return receivedError.get();
}

/**
* Runs tasks of the main {@link Looper} until a {@link
* Player.EventListener#onExperimentalOffloadSchedulingEnabledChanged} callback occurred.
*
* @param player The {@link Player}.
* @return The new offloadSchedulingEnabled state.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static boolean runUntilReceiveOffloadSchedulingEnabledNewState(Player player)
throws TimeoutException {
verifyMainTestThread(player);
AtomicReference<@NullableType Boolean> offloadSchedulingEnabledReceiver =
new AtomicReference<>();
Player.EventListener listener =
new Player.EventListener() {
@Override
public void onExperimentalOffloadSchedulingEnabledChanged(
boolean offloadSchedulingEnabled) {
offloadSchedulingEnabledReceiver.set(offloadSchedulingEnabled);
}
};
player.addListener(listener);
runMainLooperUntil(() -> offloadSchedulingEnabledReceiver.get() != null);
return Assertions.checkNotNull(offloadSchedulingEnabledReceiver.get());
}

/**
* Runs tasks of the main {@link Looper} until the {@link VideoListener#onRenderedFirstFrame}
* callback has been called.
Expand Down

0 comments on commit 52a1c79

Please sign in to comment.