Remove dependency from opus module to extractor module

PiperOrigin-RevId: 405429757
This commit is contained in:
olly 2021-10-25 18:11:53 +01:00 committed by Oliver Woodman
parent 2ab7f28ec3
commit 101b94f874
5 changed files with 150 additions and 84 deletions

View File

@ -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;
import com.google.android.exoplayer2.audio.AudioSink.SinkFormatSupport; import com.google.android.exoplayer2.audio.AudioSink.SinkFormatSupport;
import com.google.android.exoplayer2.audio.DecoderAudioRenderer; 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.decoder.CryptoConfig;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
@ -124,6 +123,6 @@ public class LibopusAudioRenderer extends DecoderAudioRenderer<OpusDecoder> {
protected Format getOutputFormat(OpusDecoder decoder) { protected Format getOutputFormat(OpusDecoder decoder) {
@C.PcmEncoding @C.PcmEncoding
int pcmEncoding = decoder.outputFloat ? C.ENCODING_PCM_FLOAT : C.ENCODING_PCM_16BIT; 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);
} }
} }

View File

@ -20,7 +20,6 @@ import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C; 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.CryptoConfig;
import com.google.android.exoplayer2.decoder.CryptoException; import com.google.android.exoplayer2.decoder.CryptoException;
import com.google.android.exoplayer2.decoder.CryptoInfo; 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.Assertions;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List; import java.util.List;
/** Opus decoder. */ /** Opus decoder. */
@ -37,6 +37,12 @@ import java.util.List;
public final class OpusDecoder public final class OpusDecoder
extends SimpleDecoder<DecoderInputBuffer, SimpleDecoderOutputBuffer, OpusDecoderException> { extends SimpleDecoder<DecoderInputBuffer, SimpleDecoderOutputBuffer, OpusDecoderException> {
/** 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 NO_ERROR = 0;
private static final int DECODE_ERROR = -1; private static final int DECODE_ERROR = -1;
private static final int DRM_ERROR = -2; 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)) { && (initializationData.get(1).length != 8 || initializationData.get(2).length != 8)) {
throw new OpusDecoderException("Invalid pre-skip or seek pre-roll"); throw new OpusDecoderException("Invalid pre-skip or seek pre-roll");
} }
preSkipSamples = OpusUtil.getPreSkipSamples(initializationData); preSkipSamples = getPreSkipSamples(initializationData);
seekPreRollSamples = OpusUtil.getSeekPreRollSamples(initializationData); seekPreRollSamples = getSeekPreRollSamples(initializationData);
byte[] headerBytes = initializationData.get(0); byte[] headerBytes = initializationData.get(0);
if (headerBytes.length < 19) { if (headerBytes.length < 19) {
throw new OpusDecoderException("Invalid header length"); throw new OpusDecoderException("Invalid header length");
} }
channelCount = OpusUtil.getChannelCount(headerBytes); channelCount = getChannelCount(headerBytes);
if (channelCount > 8) { if (channelCount > 8) {
throw new OpusDecoderException("Invalid channel count: " + channelCount); throw new OpusDecoderException("Invalid channel count: " + channelCount);
} }
@ -124,7 +130,7 @@ public final class OpusDecoder
System.arraycopy(headerBytes, 21, streamMap, 0, channelCount); System.arraycopy(headerBytes, 21, streamMap, 0, channelCount);
} }
nativeDecoderContext = nativeDecoderContext =
opusInit(OpusUtil.SAMPLE_RATE, channelCount, numStreams, numCoupled, gain, streamMap); opusInit(SAMPLE_RATE, channelCount, numStreams, numCoupled, gain, streamMap);
if (nativeDecoderContext == 0) { if (nativeDecoderContext == 0) {
throw new OpusDecoderException("Failed to initialize decoder"); throw new OpusDecoderException("Failed to initialize decoder");
} }
@ -176,7 +182,7 @@ public final class OpusDecoder
inputData, inputData,
inputData.limit(), inputData.limit(),
outputBuffer, outputBuffer,
OpusUtil.SAMPLE_RATE, SAMPLE_RATE,
cryptoConfig, cryptoConfig,
cryptoInfo.mode, cryptoInfo.mode,
Assertions.checkNotNull(cryptoInfo.key), Assertions.checkNotNull(cryptoInfo.key),
@ -225,6 +231,53 @@ public final class OpusDecoder
opusClose(nativeDecoderContext); 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<byte[]> 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<byte[]> 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) { private static int readSignedLittleEndian16(byte[] input, int offset) {
int value = input[offset] & 0xFF; int value = input[offset] & 0xFF;
value |= (input[offset + 1] & 0xFF) << 8; value |= (input[offset + 1] & 0xFF) << 8;

View File

@ -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<byte[]> 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<byte[]> 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();
}
}

View File

@ -61,39 +61,6 @@ public class OpusUtil {
return initializationData; 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<byte[]> 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<byte[]> 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) { private static int getPreSkipSamples(byte[] header) {
return ((header[11] & 0xFF) << 8) | (header[10] & 0xFF); return ((header[11] & 0xFF) << 8) | (header[10] & 0xFF);
} }
@ -105,8 +72,4 @@ public class OpusUtil {
private static long sampleCountToNanoseconds(long sampleCount) { private static long sampleCountToNanoseconds(long sampleCount) {
return (sampleCount * C.NANOS_PER_SECOND) / SAMPLE_RATE; return (sampleCount * C.NANOS_PER_SECOND) / SAMPLE_RATE;
} }
private static long nanosecondsToSampleCount(long nanoseconds) {
return (nanoseconds * SAMPLE_RATE) / C.NANOS_PER_SECOND;
}
} }

View File

@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.List; import java.util.List;
@ -41,20 +40,6 @@ public final class OpusUtilTest {
private static final byte[] DEFAULT_SEEK_PRE_ROLL_BYTES = private static final byte[] DEFAULT_SEEK_PRE_ROLL_BYTES =
buildNativeOrderByteArray(sampleCountToNanoseconds(DEFAULT_SEEK_PRE_ROLL_SAMPLES)); buildNativeOrderByteArray(sampleCountToNanoseconds(DEFAULT_SEEK_PRE_ROLL_SAMPLES));
private static final ImmutableList<byte[]> 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<byte[]> FULL_INITIALIZATION_DATA =
ImmutableList.of(HEADER, CUSTOM_PRE_SKIP_BYTES, CUSTOM_SEEK_PRE_ROLL_BYTES);
@Test @Test
public void buildInitializationData() { public void buildInitializationData() {
List<byte[]> initializationData = OpusUtil.buildInitializationData(HEADER); List<byte[]> initializationData = OpusUtil.buildInitializationData(HEADER);
@ -70,30 +55,6 @@ public final class OpusUtilTest {
assertThat(channelCount).isEqualTo(2); 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) { private static long sampleCountToNanoseconds(long sampleCount) {
return (sampleCount * C.NANOS_PER_SECOND) / OpusUtil.SAMPLE_RATE; return (sampleCount * C.NANOS_PER_SECOND) / OpusUtil.SAMPLE_RATE;
} }