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)
This commit is contained in:
michaelkatz 2023-10-18 09:09:03 -07:00 committed by Rohit Singh
parent fe60d0d7b4
commit 111ac63695
3 changed files with 63 additions and 23 deletions

View File

@ -2279,7 +2279,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// Process any batched data. // Process any batched data.
checkState(!outputStreamEnded); checkState(!outputStreamEnded);
if (bypassBatchBuffer.hasSamples()) { if (bypassBatchBuffer.hasSamples()) {
boolean isDecodeOnly = bypassBatchBuffer.getLastSampleTimeUs() < getLastResetPositionUs(); boolean isDecodeOnly =
bypassBatchBuffer.getLastSampleTimeUs() < getLastResetPositionUs()
&& (outputFormat == null
|| !Objects.equals(outputFormat.sampleMimeType, MimeTypes.AUDIO_OPUS));
if (processOutputBuffer( if (processOutputBuffer(
positionUs, positionUs,
elapsedRealtimeUs, elapsedRealtimeUs,

View File

@ -15,7 +15,7 @@
*/ */
package androidx.media3.exoplayer.e2etest; 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 android.content.Context;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -40,12 +40,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class OggOpusPlaybackTest { public final class OggOpusPlaybackTest {
public static final String INPUT_FILE = "bear.opus"; public static final String INPUT_FILE = "bear.opus";
@ -53,25 +54,31 @@ public class OggOpusPlaybackTest {
public ShadowMediaCodecConfig mediaCodecConfig = public ShadowMediaCodecConfig mediaCodecConfig =
ShadowMediaCodecConfig.forAllSupportedMimeTypes(); ShadowMediaCodecConfig.forAllSupportedMimeTypes();
@Test public FakeClock fakeClock;
public void checkOggOpusEncodings() throws Exception { public OffloadRenderersFactory offloadRenderersFactory;
Context applicationContext = ApplicationProvider.getApplicationContext(); public DefaultTrackSelector trackSelector;
OffloadRenderersFactory offloadRenderersFactory =
new OffloadRenderersFactory(applicationContext); @Before
DefaultTrackSelector trackSelector = new DefaultTrackSelector(applicationContext); public void setUp() {
fakeClock = new FakeClock(/* isAutoAdvancing= */ true);
offloadRenderersFactory =
new OffloadRenderersFactory(ApplicationProvider.getApplicationContext());
trackSelector = new DefaultTrackSelector(ApplicationProvider.getApplicationContext());
trackSelector.setParameters( trackSelector.setParameters(
trackSelector trackSelector
.buildUponParameters() .buildUponParameters()
.setAudioOffloadPreferences( .setAudioOffloadPreferences(
new AudioOffloadPreferences.Builder() new AudioOffloadPreferences.Builder()
.setAudioOffloadMode(AUDIO_OFFLOAD_MODE_ENABLED) .setAudioOffloadMode(AUDIO_OFFLOAD_MODE_REQUIRED)
.setIsGaplessSupportRequired(false)
.setIsSpeedChangeSupportRequired(false)
.build()) .build())
.build()); .build());
}
@Test
public void oggOpusPlayback_generatesCorrectOggOpusEncodings() throws Exception {
ExoPlayer player = ExoPlayer player =
new ExoPlayer.Builder(applicationContext, offloadRenderersFactory) new ExoPlayer.Builder(ApplicationProvider.getApplicationContext(), offloadRenderersFactory)
.setClock(new FakeClock(/* isAutoAdvancing= */ true)) .setClock(fakeClock)
.setTrackSelector(trackSelector) .setTrackSelector(trackSelector)
.build(); .build();
player.setMediaItem(MediaItem.fromUri("asset:///media/ogg/" + INPUT_FILE)); player.setMediaItem(MediaItem.fromUri("asset:///media/ogg/" + INPUT_FILE));
@ -82,12 +89,33 @@ public class OggOpusPlaybackTest {
player.release(); player.release();
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
applicationContext, ApplicationProvider.getApplicationContext(),
offloadRenderersFactory, offloadRenderersFactory,
"playbackdumps/ogg/" + INPUT_FILE + ".oggOpus.dump"); "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 { implements Dumper.Dumpable {
private DumpingAudioSink dumpingAudioSink; 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. */ /** All handleBuffer interactions recorded with this audio sink. */
private final List<CapturedInputBuffer> capturedInteractions; private final List<CapturedInputBuffer> capturedInteractions;
@ -174,14 +203,14 @@ public class OggOpusPlaybackTest {
buffer.position(originalPosition); buffer.position(originalPosition);
return bytes; return bytes;
} }
}
/** Data record */ /** Data record */
private static class CapturedInputBuffer { private static final class CapturedInputBuffer {
private final byte[] contents; private final byte[] contents;
private CapturedInputBuffer(byte[] contents) { private CapturedInputBuffer(byte[] contents) {
this.contents = contents; this.contents = contents;
}
} }
} }
} }

View File

@ -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