From bd24ec08d13d74f2aae417b85f4cb40092c13f7e Mon Sep 17 00:00:00 2001 From: ivanbuper Date: Thu, 1 May 2025 05:30:34 -0700 Subject: [PATCH] Add test to catch single asset audio sequence underruns This test previews a composition containing only a single audio asset sequence. The audio asset is a 1s, stereo, locally available WAV file. Catching an underrun in this test is a likely indication of something being seriously wrong with the device's state or a performance regression on the audio pipeline. This test is a verification of the fix in https://github.com/androidx/media/commit/2e20d35c3d5769e61516401cfddc8ea5d0de9de3. Without this fix the newly added test fails because MediaCodecAudioRenderer attempts to use dynamic scheduling with AudioGraphInputAudioSync (which is unsupported) after EoS is signalled. PiperOrigin-RevId: 753552825 --- .../transformer/CompositionPlaybackTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java index 1a35d3b235..ee87420d8a 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/CompositionPlaybackTest.java @@ -35,11 +35,14 @@ import androidx.media3.common.PlaybackException; import androidx.media3.common.Player; import androidx.media3.common.util.NullableType; import androidx.media3.effect.GlEffect; +import androidx.media3.exoplayer.audio.AudioSink; +import androidx.media3.exoplayer.audio.DefaultAudioSink; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.junit.After; @@ -504,6 +507,45 @@ public class CompositionPlaybackTest { .isEqualTo(expectedTimestampsUs); } + @Test + public void playback_singleAssetAudioSequence_doesNotUnderrun() + throws PlaybackException, TimeoutException { + EditedMediaItem clip = + new EditedMediaItem.Builder(MediaItem.fromUri(AndroidTestUtil.WAV_ASSET.uri)) + .setDurationUs(1_000_000L) + .build(); + Composition composition = + new Composition.Builder(new EditedMediaItemSequence.Builder(clip).build()).build(); + AtomicInteger underrunCount = new AtomicInteger(); + AudioSink.Listener sinkListener = + new AudioSink.Listener() { + @Override + public void onPositionDiscontinuity() {} + + @Override + public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { + underrunCount.addAndGet(1); + } + + @Override + public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {} + }; + AudioSink sink = new DefaultAudioSink.Builder(context).build(); + sink.setListener(sinkListener); + + getInstrumentation() + .runOnMainSync( + () -> { + player = new CompositionPlayer.Builder(context).setAudioSink(sink).build(); + player.addListener(playerTestListener); + player.setComposition(composition); + player.prepare(); + player.play(); + }); + playerTestListener.waitUntilPlayerEnded(); + assertThat(underrunCount.get()).isEqualTo(0); + } + private void runCompositionPlayer(Composition composition) throws PlaybackException, TimeoutException { runCompositionPlayer(composition, /* videoPrewarmingEnabled= */ true);