Remove dependency from opus module to extractor module
PiperOrigin-RevId: 405429757
This commit is contained in:
parent
2ab7f28ec3
commit
101b94f874
@ -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<OpusDecoder> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<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 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<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) {
|
||||
int value = input[offset] & 0xFF;
|
||||
value |= (input[offset + 1] & 0xFF) << 8;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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<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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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<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 buildInitializationData() {
|
||||
List<byte[]> 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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user