diff --git a/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java b/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java index 62fb9c04b1..47f4c7a758 100644 --- a/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java +++ b/libraries/decoder_opus/src/main/java/androidx/media3/decoder/opus/OpusDecoder.java @@ -99,6 +99,7 @@ public final class OpusDecoder } preSkipSamples = getPreSkipSamples(initializationData); seekPreRollSamples = getSeekPreRollSamples(initializationData); + skipSamples = preSkipSamples; byte[] headerBytes = initializationData.get(0); if (headerBytes.length < 19) { diff --git a/libraries/decoder_opus/src/test/java/androidx/media3/decoder/opus/OpusDecoderTest.java b/libraries/decoder_opus/src/test/java/androidx/media3/decoder/opus/OpusDecoderTest.java index f1994d6d0e..63cc0d072c 100644 --- a/libraries/decoder_opus/src/test/java/androidx/media3/decoder/opus/OpusDecoderTest.java +++ b/libraries/decoder_opus/src/test/java/androidx/media3/decoder/opus/OpusDecoderTest.java @@ -17,11 +17,16 @@ package androidx.media3.decoder.opus; import static com.google.common.truth.Truth.assertThat; +import androidx.annotation.Nullable; import androidx.media3.common.C; +import androidx.media3.common.util.LibraryLoader; +import androidx.media3.decoder.DecoderInputBuffer; +import androidx.media3.decoder.SimpleDecoderOutputBuffer; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -29,9 +34,14 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class OpusDecoderTest { + private static final LibraryLoader LOADER = new LibraryLoader("opusV2JNI"); + private static final byte[] HEADER = new byte[] {79, 112, 117, 115, 72, 101, 97, 100, 0, 2, 1, 56, 0, 0, -69, -128, 0, 0, 0}; + private static final byte[] ENCODED_DATA = new byte[] {-4}; + private static final int DECODED_DATA_SIZE = 3840; + private static final int HEADER_PRE_SKIP_SAMPLES = 14337; private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; @@ -39,6 +49,7 @@ public final class OpusDecoderTest { private static final ImmutableList HEADER_ONLY_INITIALIZATION_DATA = ImmutableList.of(HEADER); + private static final long PRE_SKIP_NANOS = 6_500_000; private static final long CUSTOM_PRE_SKIP_SAMPLES = 28674; private static final byte[] CUSTOM_PRE_SKIP_BYTES = buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES)); @@ -50,6 +61,11 @@ public final class OpusDecoderTest { private static final ImmutableList FULL_INITIALIZATION_DATA = ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES); + @Before + public void setUp() { + assertThat(LOADER.isAvailable()).isTrue(); + } + @Test public void getChannelCount() { int channelCount = OpusDecoder.getChannelCount(HEADER); @@ -80,11 +96,56 @@ public final class OpusDecoderTest { assertThat(seekPreRollSamples).isEqualTo(DEFAULT_SEEK_PRE_ROLL_SAMPLES); } + @Test + public void decode_removesPreSkipFromOutput() throws OpusDecoderException { + OpusDecoder decoder = + new OpusDecoder( + /* numInputBuffers= */ 0, + /* numOutputBuffers= */ 0, + /* initialInputBufferSize= */ 0, + createInitializationData(/* preSkipNanos= */ PRE_SKIP_NANOS), + /* cryptoConfig= */ null, + /* outputFloat= */ false); + DecoderInputBuffer input = + createInputBuffer(decoder, ENCODED_DATA, /* supplementalData= */ null); + SimpleDecoderOutputBuffer output = decoder.createOutputBuffer(); + assertThat(decoder.decode(input, output, false)).isNull(); + assertThat(output.data.remaining()) + .isEqualTo(DECODED_DATA_SIZE - nanosecondsToBytes(PRE_SKIP_NANOS)); + } + private static long sampleCountToNanoseconds(long sampleCount) { return (sampleCount * C.NANOS_PER_SECOND) / OpusDecoder.SAMPLE_RATE; } + private static long nanosecondsToSampleCount(long nanoseconds) { + return (nanoseconds * OpusDecoder.SAMPLE_RATE) / C.NANOS_PER_SECOND; + } + + private static long nanosecondsToBytes(long nanoseconds) { + return nanosecondsToSampleCount(nanoseconds) * 4; + } + private static byte[] buildNativeOrderByteArray(long value) { return ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(value).array(); } + + private static ImmutableList createInitializationData(long preSkipNanos) { + byte[] preSkip = buildNativeOrderByteArray(preSkipNanos); + return ImmutableList.of(HEADER, preSkip, CUSTOM_SEEK_PRE_ROLL_BYTES); + } + + private static DecoderInputBuffer createInputBuffer( + OpusDecoder decoder, byte[] data, @Nullable byte[] supplementalData) { + DecoderInputBuffer input = decoder.createInputBuffer(); + input.ensureSpaceForWrite(data.length); + input.data.put(data); + input.data.position(0).limit(data.length); + if (supplementalData != null) { + input.resetSupplementalData(supplementalData.length); + input.supplementalData.put(supplementalData).rewind(); + input.addFlag(C.BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA); + } + return input; + } }