From 111ac63695607a06a38b68c632e1cee46b317065 Mon Sep 17 00:00:00 2001 From: michaelkatz Date: Wed, 18 Oct 2023 09:09:03 -0700 Subject: [PATCH] Send decode-only Opus samples in bypass mode for seekPreRoll skip As Opus decoders skip some bytes prior to playback during a seek, the renderer for bypass playback should send samples to the decoder even if they would be decode-only. #minor-release PiperOrigin-RevId: 574494666 (cherry picked from commit 00193e0304a5ea2c20012fabf77f82f29e218372) --- .../mediacodec/MediaCodecRenderer.java | 5 +- .../e2etest/OggOpusPlaybackTest.java | 73 +++++++++++++------ .../ogg/bear.opus.oggOpusWithSeek.dump | 8 ++ 3 files changed, 63 insertions(+), 23 deletions(-) create mode 100644 libraries/test_data/src/test/assets/playbackdumps/ogg/bear.opus.oggOpusWithSeek.dump diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java index 16a62ded06..f818df250c 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java @@ -2279,7 +2279,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // Process any batched data. checkState(!outputStreamEnded); if (bypassBatchBuffer.hasSamples()) { - boolean isDecodeOnly = bypassBatchBuffer.getLastSampleTimeUs() < getLastResetPositionUs(); + boolean isDecodeOnly = + bypassBatchBuffer.getLastSampleTimeUs() < getLastResetPositionUs() + && (outputFormat == null + || !Objects.equals(outputFormat.sampleMimeType, MimeTypes.AUDIO_OPUS)); if (processOutputBuffer( positionUs, elapsedRealtimeUs, diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/OggOpusPlaybackTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/OggOpusPlaybackTest.java index a9ac6d46ec..610747b0bc 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/OggOpusPlaybackTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/e2etest/OggOpusPlaybackTest.java @@ -15,7 +15,7 @@ */ package androidx.media3.exoplayer.e2etest; -import static androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED; +import static androidx.media3.common.TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_REQUIRED; import android.content.Context; import androidx.annotation.Nullable; @@ -40,12 +40,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) -public class OggOpusPlaybackTest { +public final class OggOpusPlaybackTest { public static final String INPUT_FILE = "bear.opus"; @@ -53,25 +54,31 @@ public class OggOpusPlaybackTest { public ShadowMediaCodecConfig mediaCodecConfig = ShadowMediaCodecConfig.forAllSupportedMimeTypes(); - @Test - public void checkOggOpusEncodings() throws Exception { - Context applicationContext = ApplicationProvider.getApplicationContext(); - OffloadRenderersFactory offloadRenderersFactory = - new OffloadRenderersFactory(applicationContext); - DefaultTrackSelector trackSelector = new DefaultTrackSelector(applicationContext); + public FakeClock fakeClock; + public OffloadRenderersFactory offloadRenderersFactory; + public DefaultTrackSelector trackSelector; + + @Before + public void setUp() { + fakeClock = new FakeClock(/* isAutoAdvancing= */ true); + offloadRenderersFactory = + new OffloadRenderersFactory(ApplicationProvider.getApplicationContext()); + trackSelector = new DefaultTrackSelector(ApplicationProvider.getApplicationContext()); trackSelector.setParameters( trackSelector .buildUponParameters() .setAudioOffloadPreferences( new AudioOffloadPreferences.Builder() - .setAudioOffloadMode(AUDIO_OFFLOAD_MODE_ENABLED) - .setIsGaplessSupportRequired(false) - .setIsSpeedChangeSupportRequired(false) + .setAudioOffloadMode(AUDIO_OFFLOAD_MODE_REQUIRED) .build()) .build()); + } + + @Test + public void oggOpusPlayback_generatesCorrectOggOpusEncodings() throws Exception { ExoPlayer player = - new ExoPlayer.Builder(applicationContext, offloadRenderersFactory) - .setClock(new FakeClock(/* isAutoAdvancing= */ true)) + new ExoPlayer.Builder(ApplicationProvider.getApplicationContext(), offloadRenderersFactory) + .setClock(fakeClock) .setTrackSelector(trackSelector) .build(); player.setMediaItem(MediaItem.fromUri("asset:///media/ogg/" + INPUT_FILE)); @@ -82,12 +89,33 @@ public class OggOpusPlaybackTest { player.release(); DumpFileAsserts.assertOutput( - applicationContext, + ApplicationProvider.getApplicationContext(), offloadRenderersFactory, "playbackdumps/ogg/" + INPUT_FILE + ".oggOpus.dump"); } - private static class OffloadRenderersFactory extends DefaultRenderersFactory + @Test + public void oggOpusPlayback_withSeek_generatesCorrectOggOpusEncodings() throws Exception { + ExoPlayer player = + new ExoPlayer.Builder(ApplicationProvider.getApplicationContext(), offloadRenderersFactory) + .setClock(fakeClock) + .setTrackSelector(trackSelector) + .build(); + player.setMediaItem(MediaItem.fromUri("asset:///media/ogg/" + INPUT_FILE)); + player.prepare(); + player.seekTo(/* positionMs= */ 1415); + player.play(); + + TestPlayerRunHelper.runUntilPlaybackState(player, Player.STATE_ENDED); + player.release(); + + DumpFileAsserts.assertOutput( + ApplicationProvider.getApplicationContext(), + offloadRenderersFactory, + "playbackdumps/ogg/" + INPUT_FILE + ".oggOpusWithSeek.dump"); + } + + private static final class OffloadRenderersFactory extends DefaultRenderersFactory implements Dumper.Dumpable { private DumpingAudioSink dumpingAudioSink; @@ -117,7 +145,8 @@ public class OggOpusPlaybackTest { } } - private static class DumpingAudioSink extends ForwardingAudioSink implements Dumper.Dumpable { + private static final class DumpingAudioSink extends ForwardingAudioSink + implements Dumper.Dumpable { /** All handleBuffer interactions recorded with this audio sink. */ private final List capturedInteractions; @@ -174,14 +203,14 @@ public class OggOpusPlaybackTest { buffer.position(originalPosition); return bytes; } - } - /** Data record */ - private static class CapturedInputBuffer { - private final byte[] contents; + /** Data record */ + private static final class CapturedInputBuffer { + private final byte[] contents; - private CapturedInputBuffer(byte[] contents) { - this.contents = contents; + private CapturedInputBuffer(byte[] contents) { + this.contents = contents; + } } } } diff --git a/libraries/test_data/src/test/assets/playbackdumps/ogg/bear.opus.oggOpusWithSeek.dump b/libraries/test_data/src/test/assets/playbackdumps/ogg/bear.opus.oggOpusWithSeek.dump new file mode 100644 index 0000000000..bae80c8ffb --- /dev/null +++ b/libraries/test_data/src/test/assets/playbackdumps/ogg/bear.opus.oggOpusWithSeek.dump @@ -0,0 +1,8 @@ +SinkDump (OggOpus): + buffers.length = 6 + buffers[0] = length 207, hash D462AF66 + buffers[1] = length 3891, hash FE9EE7C1 + buffers[2] = length 3732, hash C2249BC1 + buffers[3] = length 3731, hash A9384B0F + buffers[4] = length 4091, hash 9631FA86 + buffers[5] = length 776, hash 4BC27E65