mirror of
https://github.com/samsonjs/media.git
synced 2026-04-01 10:35:48 +00:00
Add performance playback test for video effects
PiperOrigin-RevId: 567000714
This commit is contained in:
parent
5ae21b453a
commit
7b580d3cf8
2 changed files with 202 additions and 0 deletions
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.transformer.mh.performance;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_URI_STRING;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.os.ConditionVariable;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.NullableType;
|
||||
import androidx.media3.exoplayer.DecoderCounters;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||
import androidx.media3.exoplayer.util.EventLogger;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Range;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Performance tests for the effects previewing pipeline in ExoPlayer. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class VideoEffectsPreviewPerformanceTest {
|
||||
|
||||
private static final long TEST_TIMEOUT_MS = 10_000;
|
||||
private static final long MEDIA_ITEM_CLIP_DURATION_MS = 500;
|
||||
|
||||
private final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
||||
private @MonotonicNonNull ExoPlayer player;
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
instrumentation.runOnMainSync(
|
||||
() -> {
|
||||
if (player != null) {
|
||||
player.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This test guards against performance regressions in the effects preview pipeline that format
|
||||
* switches do not cause the player to either stall or drop frames.
|
||||
*/
|
||||
@Test
|
||||
public void exoplayerEffectsPreviewTest() throws PlaybackException, TimeoutException {
|
||||
TestListener listener = new TestListener();
|
||||
instrumentation.runOnMainSync(
|
||||
() -> {
|
||||
player = new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build();
|
||||
// Set a surface on the player even though there is no UI on this test. We need a surface
|
||||
// otherwise the player will skip/drop video frames.
|
||||
player.setVideoSurface(new Surface(new SurfaceTexture(0)));
|
||||
player.setPlayWhenReady(false);
|
||||
player.setVideoEffects(ImmutableList.of());
|
||||
player.addListener(listener);
|
||||
player.addAnalyticsListener(listener);
|
||||
// Adding an EventLogger to use its log output in case the test fails.
|
||||
player.addAnalyticsListener(new EventLogger());
|
||||
MediaItem mediaItem = getClippedMediaItem(MP4_ASSET_URI_STRING);
|
||||
// Use the same media item so that format changes do not force exoplayer to re-init codecs
|
||||
// between item transitions.
|
||||
player.addMediaItems(ImmutableList.of(mediaItem, mediaItem, mediaItem, mediaItem));
|
||||
player.prepare();
|
||||
});
|
||||
|
||||
listener.waitUntilPlayerReady();
|
||||
|
||||
AtomicLong playbackStartTimeMs = new AtomicLong();
|
||||
instrumentation.runOnMainSync(
|
||||
() -> {
|
||||
playbackStartTimeMs.set(SystemClock.elapsedRealtime());
|
||||
checkNotNull(player).play();
|
||||
});
|
||||
|
||||
listener.waitUntilPlayerEnded();
|
||||
long playbackDurationMs = SystemClock.elapsedRealtime() - playbackStartTimeMs.get();
|
||||
|
||||
// Playback realtime should take 2 seconds, plus/minus error margin.
|
||||
assertThat(playbackDurationMs).isIn(Range.closed(1950L, 2050L));
|
||||
DecoderCounters decoderCounters = checkNotNull(listener.decoderCounters);
|
||||
assertThat(decoderCounters.droppedBufferCount).isEqualTo(0);
|
||||
assertThat(decoderCounters.skippedInputBufferCount).isEqualTo(0);
|
||||
assertThat(decoderCounters.skippedOutputBufferCount).isEqualTo(0);
|
||||
}
|
||||
|
||||
private static MediaItem getClippedMediaItem(String uri) {
|
||||
return new MediaItem.Builder()
|
||||
.setUri(uri)
|
||||
.setClippingConfiguration(
|
||||
new MediaItem.ClippingConfiguration.Builder()
|
||||
.setEndPositionMs(MEDIA_ITEM_CLIP_DURATION_MS)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static class TestListener implements Player.Listener, AnalyticsListener {
|
||||
private final ConditionVariable playerReady;
|
||||
private final ConditionVariable playerEnded;
|
||||
private final AtomicReference<@NullableType PlaybackException> playbackException;
|
||||
private @MonotonicNonNull DecoderCounters decoderCounters;
|
||||
|
||||
public TestListener() {
|
||||
playerReady = new ConditionVariable();
|
||||
playerEnded = new ConditionVariable();
|
||||
playbackException = new AtomicReference<>();
|
||||
}
|
||||
|
||||
public void waitUntilPlayerReady() throws TimeoutException, PlaybackException {
|
||||
waitOrThrow(playerReady);
|
||||
}
|
||||
|
||||
public void waitUntilPlayerEnded() throws PlaybackException, TimeoutException {
|
||||
waitOrThrow(playerEnded);
|
||||
}
|
||||
|
||||
// Player.Listener methods
|
||||
|
||||
@Override
|
||||
public void onPlaybackStateChanged(int playbackState) {
|
||||
if (playbackState == Player.STATE_READY) {
|
||||
playerReady.open();
|
||||
} else if (playbackState == Player.STATE_ENDED) {
|
||||
playerEnded.open();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
playbackException.set(error);
|
||||
playerReady.open();
|
||||
playerEnded.open();
|
||||
}
|
||||
|
||||
// AnalyticsListener methods
|
||||
|
||||
@Override
|
||||
public void onVideoEnabled(EventTime eventTime, DecoderCounters decoderCounters) {
|
||||
this.decoderCounters = decoderCounters;
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
|
||||
private void waitOrThrow(ConditionVariable conditionVariable)
|
||||
throws TimeoutException, PlaybackException {
|
||||
if (!conditionVariable.block(TEST_TIMEOUT_MS)) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
@Nullable PlaybackException playbackException = this.playbackException.get();
|
||||
if (playbackException != null) {
|
||||
throw playbackException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@NonNullApi
|
||||
package androidx.media3.transformer.mh.performance;
|
||||
|
||||
import androidx.media3.common.util.NonNullApi;
|
||||
Loading…
Reference in a new issue