From 101b94f8740e5f385bee7b75998de86a001a99c8 Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 25 Oct 2021 18:11:53 +0100 Subject: [PATCH] Remove dependency from opus module to extractor module PiperOrigin-RevId: 405429757 --- .../ext/opus/LibopusAudioRenderer.java | 3 +- .../exoplayer2/ext/opus/OpusDecoder.java | 65 ++++++++++++-- .../exoplayer2/ext/opus/OpusDecoderTest.java | 90 +++++++++++++++++++ .../android/exoplayer2/audio/OpusUtil.java | 37 -------- .../exoplayer2/audio/OpusUtilTest.java | 39 -------- 5 files changed, 150 insertions(+), 84 deletions(-) create mode 100644 extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java index 452a0411c1..1b92708573 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/LibopusAudioRenderer.java @@ -24,7 +24,6 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.AudioSink.SinkFormatSupport; import com.google.android.exoplayer2.audio.DecoderAudioRenderer; -import com.google.android.exoplayer2.audio.OpusUtil; import com.google.android.exoplayer2.decoder.CryptoConfig; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; @@ -124,6 +123,6 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer { protected Format getOutputFormat(OpusDecoder decoder) { @C.PcmEncoding int pcmEncoding = decoder.outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT; - return Util.getPcmFormat(pcmEncoding, decoder.channelCount, OpusUtil.SAMPLE_RATE); + return Util.getPcmFormat(pcmEncoding, decoder.channelCount, OpusDecoder.SAMPLE_RATE); } } diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java index 56617b7d13..b6b7567d81 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer2/ext/opus/OpusDecoder.java @@ -20,7 +20,6 @@ import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.audio.OpusUtil; import com.google.android.exoplayer2.decoder.CryptoConfig; import com.google.android.exoplayer2.decoder.CryptoException; import com.google.android.exoplayer2.decoder.CryptoInfo; @@ -30,6 +29,7 @@ import com.google.android.exoplayer2.decoder.SimpleDecoderOutputBuffer; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.List; /** Opus decoder. */ @@ -37,6 +37,12 @@ import java.util.List; public final class OpusDecoder extends SimpleDecoder { + /** Opus streams are always 48000 Hz. */ + /* package */ static final int SAMPLE_RATE = 48_000; + + private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; + private static final int FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT = 3; + private static final int NO_ERROR = 0; private static final int DECODE_ERROR = -1; private static final int DRM_ERROR = -2; @@ -89,14 +95,14 @@ public final class OpusDecoder && (initializationData.get(1).length != 8 || initializationData.get(2).length != 8)) { throw new OpusDecoderException("Invalid pre-skip or seek pre-roll"); } - preSkipSamples = OpusUtil.getPreSkipSamples(initializationData); - seekPreRollSamples = OpusUtil.getSeekPreRollSamples(initializationData); + preSkipSamples = getPreSkipSamples(initializationData); + seekPreRollSamples = getSeekPreRollSamples(initializationData); byte[] headerBytes = initializationData.get(0); if (headerBytes.length < 19) { throw new OpusDecoderException("Invalid header length"); } - channelCount = OpusUtil.getChannelCount(headerBytes); + channelCount = getChannelCount(headerBytes); if (channelCount > 8) { throw new OpusDecoderException("Invalid channel count: " + channelCount); } @@ -124,7 +130,7 @@ public final class OpusDecoder System.arraycopy(headerBytes, 21, streamMap, 0, channelCount); } nativeDecoderContext = - opusInit(OpusUtil.SAMPLE_RATE, channelCount, numStreams, numCoupled, gain, streamMap); + opusInit(SAMPLE_RATE, channelCount, numStreams, numCoupled, gain, streamMap); if (nativeDecoderContext == 0) { throw new OpusDecoderException("Failed to initialize decoder"); } @@ -176,7 +182,7 @@ public final class OpusDecoder inputData, inputData.limit(), outputBuffer, - OpusUtil.SAMPLE_RATE, + SAMPLE_RATE, cryptoConfig, cryptoInfo.mode, Assertions.checkNotNull(cryptoInfo.key), @@ -225,6 +231,53 @@ public final class OpusDecoder opusClose(nativeDecoderContext); } + /** + * Parses the channel count from an Opus Identification Header. + * + * @param header An Opus Identification Header, as defined by RFC 7845. + * @return The parsed channel count. + */ + @VisibleForTesting + /* package */ static int getChannelCount(byte[] header) { + return header[9] & 0xFF; + } + + /** + * Returns the number of pre-skip samples specified by the given Opus codec initialization data. + * + * @param initializationData The codec initialization data. + * @return The number of pre-skip samples. + */ + @VisibleForTesting + /* package */ static int getPreSkipSamples(List initializationData) { + if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { + long codecDelayNs = + ByteBuffer.wrap(initializationData.get(1)).order(ByteOrder.nativeOrder()).getLong(); + return (int) ((codecDelayNs * SAMPLE_RATE) / C.NANOS_PER_SECOND); + } + // Fall back to parsing directly from the Opus Identification header. + byte[] headerData = initializationData.get(0); + return ((headerData[11] & 0xFF) << 8) | (headerData[10] & 0xFF); + } + + /** + * Returns the number of seek per-roll samples specified by the given Opus codec initialization + * data. + * + * @param initializationData The codec initialization data. + * @return The number of seek pre-roll samples. + */ + @VisibleForTesting + /* package */ static int getSeekPreRollSamples(List initializationData) { + if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { + long seekPreRollNs = + ByteBuffer.wrap(initializationData.get(2)).order(ByteOrder.nativeOrder()).getLong(); + return (int) ((seekPreRollNs * SAMPLE_RATE) / C.NANOS_PER_SECOND); + } + // Fall back to returning the default seek pre-roll. + return DEFAULT_SEEK_PRE_ROLL_SAMPLES; + } + private static int readSignedLittleEndian16(byte[] input, int offset) { int value = input[offset] & 0xFF; value |= (input[offset + 1] & 0xFF) << 8; diff --git a/extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java b/extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java new file mode 100644 index 0000000000..f723237646 --- /dev/null +++ b/extensions/opus/src/test/java/com/google/android/exoplayer2/ext/opus/OpusDecoderTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ext.opus; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.common.collect.ImmutableList; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link OpusDecoder}. */ +@RunWith(AndroidJUnit4.class) +public final class OpusDecoderTest { + + 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 int HEADER_PRE_SKIP_SAMPLES = 14337; + + private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; + + private static final ImmutableList HEADER_ONLY_INITIALIZATION_DATA = + ImmutableList.of(HEADER); + + private static final long CUSTOM_PRE_SKIP_SAMPLES = 28674; + private static final byte[] CUSTOM_PRE_SKIP_BYTES = + buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES)); + + private static final long CUSTOM_SEEK_PRE_ROLL_SAMPLES = 7680; + private static final byte[] CUSTOM_SEEK_PRE_ROLL_BYTES = + buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_SEEK_PRE_ROLL_SAMPLES)); + + private static final ImmutableList FULL_INITIALIZATION_DATA = + ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES); + + @Test + public void getChannelCount() { + int channelCount = OpusDecoder.getChannelCount(HEADER); + assertThat(channelCount).isEqualTo(2); + } + + @Test + public void getPreSkipSamples_fullInitializationData_returnsOverrideValue() { + int preSkipSamples = OpusDecoder.getPreSkipSamples(FULL_INITIALIZATION_DATA); + assertThat(preSkipSamples).isEqualTo(CUSTOM_PRE_SKIP_SAMPLES); + } + + @Test + public void getPreSkipSamples_headerOnlyInitializationData_returnsHeaderValue() { + int preSkipSamples = OpusDecoder.getPreSkipSamples(HEADER_ONLY_INITIALIZATION_DATA); + assertThat(preSkipSamples).isEqualTo(HEADER_PRE_SKIP_SAMPLES); + } + + @Test + public void getSeekPreRollSamples_fullInitializationData_returnsInitializationDataValue() { + int seekPreRollSamples = OpusDecoder.getSeekPreRollSamples(FULL_INITIALIZATION_DATA); + assertThat(seekPreRollSamples).isEqualTo(CUSTOM_SEEK_PRE_ROLL_SAMPLES); + } + + @Test + public void getSeekPreRollSamples_headerOnlyInitializationData_returnsDefaultValue() { + int seekPreRollSamples = OpusDecoder.getSeekPreRollSamples(HEADER_ONLY_INITIALIZATION_DATA); + assertThat(seekPreRollSamples).isEqualTo(DEFAULT_SEEK_PRE_ROLL_SAMPLES); + } + + private static long sampleCountToNanoseconds(long sampleCount) { + return (sampleCount * C.NANOS_PER_SECOND) / OpusDecoder.SAMPLE_RATE; + } + + private static byte[] buildNativeOrderByteArray(long value) { + return ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(value).array(); + } +} diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java index 3e434bb7e8..70b1e96a8c 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/audio/OpusUtil.java @@ -61,39 +61,6 @@ public class OpusUtil { return initializationData; } - /** - * Returns the number of pre-skip samples specified by the given Opus codec initialization data. - * - * @param initializationData The codec initialization data. - * @return The number of pre-skip samples. - */ - public static int getPreSkipSamples(List initializationData) { - if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { - long codecDelayNs = - ByteBuffer.wrap(initializationData.get(1)).order(ByteOrder.nativeOrder()).getLong(); - return (int) nanosecondsToSampleCount(codecDelayNs); - } - // Fall back to parsing directly from the Opus Identification header. - return getPreSkipSamples(initializationData.get(0)); - } - - /** - * Returns the number of seek per-roll samples specified by the given Opus codec initialization - * data. - * - * @param initializationData The codec initialization data. - * @return The number of seek pre-roll samples. - */ - public static int getSeekPreRollSamples(List initializationData) { - if (initializationData.size() == FULL_CODEC_INITIALIZATION_DATA_BUFFER_COUNT) { - long seekPreRollNs = - ByteBuffer.wrap(initializationData.get(2)).order(ByteOrder.nativeOrder()).getLong(); - return (int) nanosecondsToSampleCount(seekPreRollNs); - } - // Fall back to returning the default seek pre-roll. - return DEFAULT_SEEK_PRE_ROLL_SAMPLES; - } - private static int getPreSkipSamples(byte[] header) { return ((header[11] & 0xFF) << 8) | (header[10] & 0xFF); } @@ -105,8 +72,4 @@ public class OpusUtil { private static long sampleCountToNanoseconds(long sampleCount) { return (sampleCount * C.NANOS_PER_SECOND) / SAMPLE_RATE; } - - private static long nanosecondsToSampleCount(long nanoseconds) { - return (nanoseconds * SAMPLE_RATE) / C.NANOS_PER_SECOND; - } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java index 4fe18aa4d0..d3026359c2 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/audio/OpusUtilTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.C; -import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.List; @@ -41,20 +40,6 @@ public final class OpusUtilTest { private static final byte[] DEFAULT_SEEK_PRE_ROLL_BYTES = buildNativeOrderByteArray(sampleCountToNanoseconds(DEFAULT_SEEK_PRE_ROLL_SAMPLES)); - private static final ImmutableList HEADER_ONLY_INITIALIZATION_DATA = - ImmutableList.of(HEADER); - - private static final long CUSTOM_PRE_SKIP_SAMPLES = 28674; - private static final byte[] CUSTOM_PRE_SKIP_BYTES = - buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_PRE_SKIP_SAMPLES)); - - private static final long CUSTOM_SEEK_PRE_ROLL_SAMPLES = 7680; - private static final byte[] CUSTOM_SEEK_PRE_ROLL_BYTES = - buildNativeOrderByteArray(sampleCountToNanoseconds(CUSTOM_SEEK_PRE_ROLL_SAMPLES)); - - private static final ImmutableList FULL_INITIALIZATION_DATA = - ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES); - @Test public void buildInitializationData() { List initializationData = OpusUtil.buildInitializationData(HEADER); @@ -70,30 +55,6 @@ public final class OpusUtilTest { assertThat(channelCount).isEqualTo(2); } - @Test - public void getPreSkipSamples_fullInitializationData_returnsOverrideValue() { - int preSkipSamples = OpusUtil.getPreSkipSamples(FULL_INITIALIZATION_DATA); - assertThat(preSkipSamples).isEqualTo(CUSTOM_PRE_SKIP_SAMPLES); - } - - @Test - public void getPreSkipSamples_headerOnlyInitializationData_returnsHeaderValue() { - int preSkipSamples = OpusUtil.getPreSkipSamples(HEADER_ONLY_INITIALIZATION_DATA); - assertThat(preSkipSamples).isEqualTo(HEADER_PRE_SKIP_SAMPLES); - } - - @Test - public void getSeekPreRollSamples_fullInitializationData_returnsInitializationDataValue() { - int seekPreRollSamples = OpusUtil.getSeekPreRollSamples(FULL_INITIALIZATION_DATA); - assertThat(seekPreRollSamples).isEqualTo(CUSTOM_SEEK_PRE_ROLL_SAMPLES); - } - - @Test - public void getSeekPreRollSamples_headerOnlyInitializationData_returnsDefaultValue() { - int seekPreRollSamples = OpusUtil.getSeekPreRollSamples(HEADER_ONLY_INITIALIZATION_DATA); - assertThat(seekPreRollSamples).isEqualTo(DEFAULT_SEEK_PRE_ROLL_SAMPLES); - } - private static long sampleCountToNanoseconds(long sampleCount) { return (sampleCount * C.NANOS_PER_SECOND) / OpusUtil.SAMPLE_RATE; }