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 2e20d35c3d. 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
This commit is contained in:
ivanbuper 2025-05-01 05:30:34 -07:00 committed by Copybara-Service
parent e06e9b4cc9
commit bd24ec08d1

View File

@ -35,11 +35,14 @@ import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player; import androidx.media3.common.Player;
import androidx.media3.common.util.NullableType; import androidx.media3.common.util.NullableType;
import androidx.media3.effect.GlEffect; 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 androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.junit.After; import org.junit.After;
@ -504,6 +507,45 @@ public class CompositionPlaybackTest {
.isEqualTo(expectedTimestampsUs); .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) private void runCompositionPlayer(Composition composition)
throws PlaybackException, TimeoutException { throws PlaybackException, TimeoutException {
runCompositionPlayer(composition, /* videoPrewarmingEnabled= */ true); runCompositionPlayer(composition, /* videoPrewarmingEnabled= */ true);