From 83545fb7b2d929c7bcd83210496546cb009de2b4 Mon Sep 17 00:00:00 2001 From: andrewlewis Date: Thu, 14 Feb 2019 22:06:03 +0000 Subject: [PATCH] Factor out common audio processor functionality PiperOrigin-RevId: 234025553 --- .../exoplayer2/audio/AudioProcessor.java | 7 +- .../exoplayer2/audio/BaseAudioProcessor.java | 155 ++++++++++++++++++ .../audio/ChannelMappingAudioProcessor.java | 74 +-------- .../audio/FloatResamplingAudioProcessor.java | 86 +--------- .../audio/ResamplingAudioProcessor.java | 80 +-------- .../audio/SilenceSkippingAudioProcessor.java | 93 ++--------- .../exoplayer2/audio/TeeAudioProcessor.java | 113 ++----------- .../audio/TrimmingAudioProcessor.java | 75 ++------- 8 files changed, 218 insertions(+), 465 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java index f82be31f72..4cf8ef2a8b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioProcessor.java @@ -38,7 +38,8 @@ public interface AudioProcessor { /** Exception thrown when a processor can't be configured for a given input audio format. */ final class UnhandledFormatException extends Exception { - public UnhandledFormatException(int sampleRateHz, int channelCount, @C.Encoding int encoding) { + public UnhandledFormatException( + int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding " + encoding); } @@ -60,7 +61,7 @@ public interface AudioProcessor { * @return Whether to {@link #flush()} the processor. * @throws UnhandledFormatException Thrown if the specified format can't be handled as input. */ - boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) + boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) throws UnhandledFormatException; /** Returns whether the processor is configured and will process input buffers. */ @@ -78,7 +79,7 @@ public interface AudioProcessor { * result of calling {@link #configure(int, int, int)} and is undefined if the instance is not * active. */ - @C.Encoding + @C.PcmEncoding int getOutputEncoding(); /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java new file mode 100644 index 0000000000..0357326a31 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/BaseAudioProcessor.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2019 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.audio; + +import android.support.annotation.CallSuper; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Base class for audio processors that keep an output buffer and an internal buffer that is reused + * whenever input is queued. + */ +public abstract class BaseAudioProcessor implements AudioProcessor { + + /** The configured input sample rate, in Hertz, or {@link Format#NO_VALUE} if not configured. */ + protected int sampleRateHz; + /** The configured input channel count, or {@link Format#NO_VALUE} if not configured. */ + protected int channelCount; + /** The configured input encoding, or {@link Format#NO_VALUE} if not configured. */ + @C.PcmEncoding protected int encoding; + + private ByteBuffer buffer; + private ByteBuffer outputBuffer; + private boolean inputEnded; + + public BaseAudioProcessor() { + buffer = EMPTY_BUFFER; + outputBuffer = EMPTY_BUFFER; + channelCount = Format.NO_VALUE; + sampleRateHz = Format.NO_VALUE; + encoding = Format.NO_VALUE; + } + + @Override + public boolean isActive() { + return sampleRateHz != Format.NO_VALUE; + } + + @Override + public int getOutputChannelCount() { + return channelCount; + } + + @Override + public int getOutputEncoding() { + return encoding; + } + + @Override + public int getOutputSampleRateHz() { + return sampleRateHz; + } + + @Override + public final void queueEndOfStream() { + inputEnded = true; + onQueueEndOfStream(); + } + + @CallSuper + @Override + public ByteBuffer getOutput() { + ByteBuffer outputBuffer = this.outputBuffer; + this.outputBuffer = EMPTY_BUFFER; + return outputBuffer; + } + + @CallSuper + @SuppressWarnings("ReferenceEquality") + @Override + public boolean isEnded() { + return inputEnded && outputBuffer == EMPTY_BUFFER; + } + + @Override + public final void flush() { + outputBuffer = EMPTY_BUFFER; + inputEnded = false; + onFlush(); + } + + @Override + public final void reset() { + flush(); + buffer = EMPTY_BUFFER; + sampleRateHz = Format.NO_VALUE; + channelCount = Format.NO_VALUE; + encoding = Format.NO_VALUE; + onReset(); + } + + /** Sets the input format of this processor, returning whether the input format has changed. */ + protected final boolean setInputFormat( + int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { + if (sampleRateHz == this.sampleRateHz + && channelCount == this.channelCount + && encoding == this.encoding) { + return false; + } + this.sampleRateHz = sampleRateHz; + this.channelCount = channelCount; + this.encoding = encoding; + return true; + } + + /** + * Replaces the current output buffer with a buffer of at least {@code count} bytes and returns + * it. Callers should write to the returned buffer then {@link ByteBuffer#flip()} it so it can be + * read via {@link #getOutput()}. + */ + protected final ByteBuffer replaceOutputBuffer(int count) { + if (buffer.capacity() < count) { + buffer = ByteBuffer.allocateDirect(count).order(ByteOrder.nativeOrder()); + } else { + buffer.clear(); + } + outputBuffer = buffer; + return buffer; + } + + /** Returns whether the current output buffer has any data remaining. */ + protected final boolean hasPendingOutput() { + return outputBuffer.hasRemaining(); + } + + /** Called when the end-of-stream is queued to the processor. */ + protected void onQueueEndOfStream() { + // Do nothing. + } + + /** Called when the processor is flushed, directly or as part of resetting. */ + protected void onFlush() { + // Do nothing. + } + + /** Called when the processor is reset. */ + protected void onReset() { + // Do nothing. + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java index a6d4d08077..a345cebd02 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ChannelMappingAudioProcessor.java @@ -17,36 +17,20 @@ package com.google.android.exoplayer2.audio; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.C.Encoding; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Assertions; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.util.Arrays; /** * An {@link AudioProcessor} that applies a mapping from input channels onto specified output * channels. This can be used to reorder, duplicate or discard channels. */ -/* package */ final class ChannelMappingAudioProcessor implements AudioProcessor { +/* package */ final class ChannelMappingAudioProcessor extends BaseAudioProcessor { - private int channelCount; - private int sampleRateHz; @Nullable private int[] pendingOutputChannels; private boolean active; @Nullable private int[] outputChannels; - private ByteBuffer buffer; - private ByteBuffer outputBuffer; - private boolean inputEnded; - - /** Creates a new processor that applies a channel mapping. */ - public ChannelMappingAudioProcessor() { - buffer = EMPTY_BUFFER; - outputBuffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; - } /** * Resets the channel mapping. After calling this method, call {@link #configure(int, int, int)} @@ -61,10 +45,12 @@ import java.util.Arrays; } @Override - public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding) + public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) throws UnhandledFormatException { boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels); outputChannels = pendingOutputChannels; + + int[] outputChannels = this.outputChannels; if (outputChannels == null) { active = false; return outputChannelsChanged; @@ -72,12 +58,9 @@ import java.util.Arrays; if (encoding != C.ENCODING_PCM_16BIT) { throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); } - if (!outputChannelsChanged && this.sampleRateHz == sampleRateHz - && this.channelCount == channelCount) { + if (!outputChannelsChanged && !setInputFormat(sampleRateHz, channelCount, encoding)) { return false; } - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; active = channelCount != outputChannels.length; for (int i = 0; i < outputChannels.length; i++) { @@ -100,16 +83,6 @@ import java.util.Arrays; return outputChannels == null ? channelCount : outputChannels.length; } - @Override - public int getOutputEncoding() { - return C.ENCODING_PCM_16BIT; - } - - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; - } - @Override public void queueInput(ByteBuffer inputBuffer) { int[] outputChannels = Assertions.checkNotNull(this.outputChannels); @@ -117,11 +90,7 @@ import java.util.Arrays; int limit = inputBuffer.limit(); int frameCount = (limit - position) / (2 * channelCount); int outputSize = frameCount * outputChannels.length * 2; - if (buffer.capacity() < outputSize) { - buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder()); - } else { - buffer.clear(); - } + ByteBuffer buffer = replaceOutputBuffer(outputSize); while (position < limit) { for (int channelIndex : outputChannels) { buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex)); @@ -130,39 +99,10 @@ import java.util.Arrays; } inputBuffer.position(limit); buffer.flip(); - outputBuffer = buffer; } @Override - public void queueEndOfStream() { - inputEnded = true; - } - - @Override - public ByteBuffer getOutput() { - ByteBuffer outputBuffer = this.outputBuffer; - this.outputBuffer = EMPTY_BUFFER; - return outputBuffer; - } - - @SuppressWarnings("ReferenceEquality") - @Override - public boolean isEnded() { - return inputEnded && outputBuffer == EMPTY_BUFFER; - } - - @Override - public void flush() { - outputBuffer = EMPTY_BUFFER; - inputEnded = false; - } - - @Override - public void reset() { - flush(); - buffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; + protected void onReset() { outputChannels = null; pendingOutputChannels = null; active = false; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java index c347cce0d8..2274d53b55 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/FloatResamplingAudioProcessor.java @@ -16,61 +16,30 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; -import java.nio.ByteOrder; /** * An {@link AudioProcessor} that converts 24-bit and 32-bit integer PCM audio to 32-bit float PCM * audio. */ -/* package */ final class FloatResamplingAudioProcessor implements AudioProcessor { +/* package */ final class FloatResamplingAudioProcessor extends BaseAudioProcessor { private static final int FLOAT_NAN_AS_INT = Float.floatToIntBits(Float.NaN); private static final double PCM_32_BIT_INT_TO_PCM_32_BIT_FLOAT_FACTOR = 1.0 / 0x7FFFFFFF; - private int sampleRateHz; - private int channelCount; - @C.PcmEncoding private int sourceEncoding; - private ByteBuffer buffer; - private ByteBuffer outputBuffer; - private boolean inputEnded; - - /** Creates a new audio processor that converts audio data to {@link C#ENCODING_PCM_FLOAT}. */ - public FloatResamplingAudioProcessor() { - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; - sourceEncoding = C.ENCODING_INVALID; - buffer = EMPTY_BUFFER; - outputBuffer = EMPTY_BUFFER; - } - @Override - public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) + public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) throws UnhandledFormatException { if (!Util.isEncodingHighResolutionIntegerPcm(encoding)) { throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); } - if (this.sampleRateHz == sampleRateHz - && this.channelCount == channelCount - && sourceEncoding == encoding) { - return false; - } - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; - sourceEncoding = encoding; - return true; + return setInputFormat(sampleRateHz, channelCount, encoding); } @Override public boolean isActive() { - return Util.isEncodingHighResolutionIntegerPcm(sourceEncoding); - } - - @Override - public int getOutputChannelCount() { - return channelCount; + return Util.isEncodingHighResolutionIntegerPcm(encoding); } @Override @@ -78,24 +47,15 @@ import java.nio.ByteOrder; return C.ENCODING_PCM_FLOAT; } - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; - } - @Override public void queueInput(ByteBuffer inputBuffer) { - boolean isInput32Bit = sourceEncoding == C.ENCODING_PCM_32BIT; + boolean isInput32Bit = encoding == C.ENCODING_PCM_32BIT; int position = inputBuffer.position(); int limit = inputBuffer.limit(); int size = limit - position; int resampledSize = isInput32Bit ? size : (size / 3) * 4; - if (buffer.capacity() < resampledSize) { - buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder()); - } else { - buffer.clear(); - } + ByteBuffer buffer = replaceOutputBuffer(resampledSize); if (isInput32Bit) { for (int i = position; i < limit; i += 4) { int pcm32BitInteger = @@ -117,40 +77,6 @@ import java.nio.ByteOrder; inputBuffer.position(inputBuffer.limit()); buffer.flip(); - outputBuffer = buffer; - } - - @Override - public void queueEndOfStream() { - inputEnded = true; - } - - @Override - public ByteBuffer getOutput() { - ByteBuffer outputBuffer = this.outputBuffer; - this.outputBuffer = EMPTY_BUFFER; - return outputBuffer; - } - - @SuppressWarnings("ReferenceEquality") - @Override - public boolean isEnded() { - return inputEnded && outputBuffer == EMPTY_BUFFER; - } - - @Override - public void flush() { - outputBuffer = EMPTY_BUFFER; - inputEnded = false; - } - - @Override - public void reset() { - flush(); - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; - sourceEncoding = C.ENCODING_INVALID; - buffer = EMPTY_BUFFER; } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java index ba84b06e95..d0c057b676 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ResamplingAudioProcessor.java @@ -18,45 +18,21 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import java.nio.ByteBuffer; -import java.nio.ByteOrder; /** * An {@link AudioProcessor} that converts 8-bit, 24-bit and 32-bit integer PCM audio to 16-bit * integer PCM audio. */ -/* package */ final class ResamplingAudioProcessor implements AudioProcessor { - - private int sampleRateHz; - private int channelCount; - @C.PcmEncoding private int encoding; - private ByteBuffer buffer; - private ByteBuffer outputBuffer; - private boolean inputEnded; - - /** Creates a new audio processor that converts audio data to {@link C#ENCODING_PCM_16BIT}. */ - public ResamplingAudioProcessor() { - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; - encoding = C.ENCODING_INVALID; - buffer = EMPTY_BUFFER; - outputBuffer = EMPTY_BUFFER; - } +/* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor { @Override - public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) + public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) throws UnhandledFormatException { if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT && encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) { throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); } - if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount - && this.encoding == encoding) { - return false; - } - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; - this.encoding = encoding; - return true; + return setInputFormat(sampleRateHz, channelCount, encoding); } @Override @@ -64,21 +40,11 @@ import java.nio.ByteOrder; return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT; } - @Override - public int getOutputChannelCount() { - return channelCount; - } - @Override public int getOutputEncoding() { return C.ENCODING_PCM_16BIT; } - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; - } - @Override public void queueInput(ByteBuffer inputBuffer) { // Prepare the output buffer. @@ -105,13 +71,9 @@ import java.nio.ByteOrder; default: throw new IllegalStateException(); } - if (buffer.capacity() < resampledSize) { - buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder()); - } else { - buffer.clear(); - } // Resample the little endian input and update the input/output buffers. + ByteBuffer buffer = replaceOutputBuffer(resampledSize); switch (encoding) { case C.ENCODING_PCM_8BIT: // 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up. @@ -146,40 +108,6 @@ import java.nio.ByteOrder; } inputBuffer.position(inputBuffer.limit()); buffer.flip(); - outputBuffer = buffer; - } - - @Override - public void queueEndOfStream() { - inputEnded = true; - } - - @Override - public ByteBuffer getOutput() { - ByteBuffer outputBuffer = this.outputBuffer; - this.outputBuffer = EMPTY_BUFFER; - return outputBuffer; - } - - @SuppressWarnings("ReferenceEquality") - @Override - public boolean isEnded() { - return inputEnded && outputBuffer == EMPTY_BUFFER; - } - - @Override - public void flush() { - outputBuffer = EMPTY_BUFFER; - inputEnded = false; - } - - @Override - public void reset() { - flush(); - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; - encoding = C.ENCODING_INVALID; - buffer = EMPTY_BUFFER; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java index 1149a10fb8..9052ea12ec 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/SilenceSkippingAudioProcessor.java @@ -17,19 +17,17 @@ package com.google.android.exoplayer2.audio; import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.ByteBuffer; -import java.nio.ByteOrder; /** * An {@link AudioProcessor} that skips silence in the input stream. Input and output are 16-bit * PCM. */ -public final class SilenceSkippingAudioProcessor implements AudioProcessor { +public final class SilenceSkippingAudioProcessor extends BaseAudioProcessor { /** * The minimum duration of audio that must be below {@link #SILENCE_THRESHOLD_LEVEL} to classify @@ -70,16 +68,10 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { /** State when the input is silent. */ private static final int STATE_SILENT = 2; - private int channelCount; - private int sampleRateHz; private int bytesPerFrame; private boolean enabled; - private ByteBuffer buffer; - private ByteBuffer outputBuffer; - private boolean inputEnded; - /** * Buffers audio data that may be classified as silence while in {@link #STATE_MAYBE_SILENT}. If * the input becomes noisy before the buffer has filled, it will be output. Otherwise, the buffer @@ -101,10 +93,6 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { /** Creates a new silence trimming audio processor. */ public SilenceSkippingAudioProcessor() { - buffer = EMPTY_BUFFER; - outputBuffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY; paddingBuffer = Util.EMPTY_BYTE_ARRAY; } @@ -131,43 +119,23 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { // AudioProcessor implementation. @Override - public boolean configure(int sampleRateHz, int channelCount, int encoding) + public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) throws UnhandledFormatException { if (encoding != C.ENCODING_PCM_16BIT) { throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); } - if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) { - return false; - } - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; bytesPerFrame = channelCount * 2; - return true; + return setInputFormat(sampleRateHz, channelCount, encoding); } @Override public boolean isActive() { - return sampleRateHz != Format.NO_VALUE && enabled; - } - - @Override - public int getOutputChannelCount() { - return channelCount; - } - - @Override - public @C.Encoding int getOutputEncoding() { - return C.ENCODING_PCM_16BIT; - } - - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; + return super.isActive() && enabled; } @Override public void queueInput(ByteBuffer inputBuffer) { - while (inputBuffer.hasRemaining() && !outputBuffer.hasRemaining()) { + while (inputBuffer.hasRemaining() && !hasPendingOutput()) { switch (state) { case STATE_NOISY: processNoisy(inputBuffer); @@ -185,8 +153,7 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { } @Override - public void queueEndOfStream() { - inputEnded = true; + protected void onQueueEndOfStream() { if (maybeSilenceBufferSize > 0) { // We haven't received enough silence to transition to the silent state, so output the buffer. output(maybeSilenceBuffer, maybeSilenceBufferSize); @@ -197,20 +164,7 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { } @Override - public ByteBuffer getOutput() { - ByteBuffer outputBuffer = this.outputBuffer; - this.outputBuffer = EMPTY_BUFFER; - return outputBuffer; - } - - @SuppressWarnings("ReferenceEquality") - @Override - public boolean isEnded() { - return inputEnded && outputBuffer == EMPTY_BUFFER; - } - - @Override - public void flush() { + protected void onFlush() { if (isActive()) { int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame; if (maybeSilenceBuffer.length != maybeSilenceBufferSize) { @@ -222,20 +176,14 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { } } state = STATE_NOISY; - outputBuffer = EMPTY_BUFFER; - inputEnded = false; skippedFrames = 0; maybeSilenceBufferSize = 0; hasOutputNoise = false; } @Override - public void reset() { + protected void onReset() { enabled = false; - flush(); - buffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; paddingSize = 0; maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY; paddingBuffer = Util.EMPTY_BYTE_ARRAY; @@ -330,30 +278,19 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor { * processor. */ private void output(byte[] data, int length) { - prepareForOutput(length); - buffer.put(data, 0, length); - buffer.flip(); - outputBuffer = buffer; + replaceOutputBuffer(length).put(data, 0, length).flip(); + if (length > 0) { + hasOutputNoise = true; + } } /** * Copies remaining bytes from {@code data} to populate a new output buffer from the processor. */ private void output(ByteBuffer data) { - prepareForOutput(data.remaining()); - buffer.put(data); - buffer.flip(); - outputBuffer = buffer; - } - - /** Prepares to output {@code size} bytes in {@code buffer}. */ - private void prepareForOutput(int size) { - if (buffer.capacity() < size) { - buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); - } else { - buffer.clear(); - } - if (size > 0) { + int length = data.remaining(); + replaceOutputBuffer(length).put(data).flip(); + if (length > 0) { hasOutputNoise = true; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java index 442ade6afb..ba651912ff 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/TeeAudioProcessor.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer2.audio; import android.support.annotation.Nullable; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Util; @@ -36,13 +35,13 @@ import java.nio.ByteOrder; * custom {@link com.google.android.exoplayer2.audio.DefaultAudioSink.AudioProcessorChain} when * creating the audio sink, and include this audio processor after all other audio processors. */ -public final class TeeAudioProcessor implements AudioProcessor { +public final class TeeAudioProcessor extends BaseAudioProcessor { /** A sink for audio buffers handled by the audio processor. */ public interface AudioBufferSink { /** Called when the audio processor is flushed with a format of subsequent input. */ - void flush(int sampleRateHz, int channelCount, @C.Encoding int encoding); + void flush(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding); /** * Called when data is written to the audio processor. @@ -54,14 +53,6 @@ public final class TeeAudioProcessor implements AudioProcessor { private final AudioBufferSink audioBufferSink; - private int sampleRateHz; - private int channelCount; - @C.Encoding private int encoding; - - private ByteBuffer buffer; - private ByteBuffer outputBuffer; - private boolean inputEnded; - /** * Creates a new tee audio processor, sending incoming data to the given {@link AudioBufferSink}. * @@ -70,100 +61,28 @@ public final class TeeAudioProcessor implements AudioProcessor { */ public TeeAudioProcessor(AudioBufferSink audioBufferSink) { this.audioBufferSink = Assertions.checkNotNull(audioBufferSink); - - buffer = EMPTY_BUFFER; - outputBuffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; } @Override - public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) { - boolean formatChanged = - sampleRateHz != this.sampleRateHz - || channelCount != this.channelCount - || encoding != this.encoding; - this.sampleRateHz = sampleRateHz; - this.channelCount = channelCount; - this.encoding = encoding; - // The sink always needs to be flushed if the format is changing. - return formatChanged; + public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { + return setInputFormat(sampleRateHz, channelCount, encoding); } @Override - public boolean isActive() { - return sampleRateHz != Format.NO_VALUE; - } - - @Override - public int getOutputChannelCount() { - return channelCount; - } - - @Override - public int getOutputEncoding() { - return encoding; - } - - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; - } - - @Override - public void queueInput(ByteBuffer buffer) { - int remaining = buffer.remaining(); + public void queueInput(ByteBuffer inputBuffer) { + int remaining = inputBuffer.remaining(); if (remaining == 0) { return; } + audioBufferSink.handleBuffer(inputBuffer.asReadOnlyBuffer()); + replaceOutputBuffer(remaining).put(inputBuffer).flip(); + } - audioBufferSink.handleBuffer(buffer.asReadOnlyBuffer()); - - if (this.buffer.capacity() < remaining) { - this.buffer = ByteBuffer.allocateDirect(remaining).order(ByteOrder.nativeOrder()); - } else { - this.buffer.clear(); + @Override + protected void onFlush() { + if (isActive()) { + audioBufferSink.flush(sampleRateHz, channelCount, encoding); } - - this.buffer.put(buffer); - - this.buffer.flip(); - outputBuffer = this.buffer; - } - - @Override - public void queueEndOfStream() { - inputEnded = true; - } - - @Override - public ByteBuffer getOutput() { - ByteBuffer outputBuffer = this.outputBuffer; - this.outputBuffer = EMPTY_BUFFER; - return outputBuffer; - } - - @SuppressWarnings("ReferenceEquality") - @Override - public boolean isEnded() { - return inputEnded && outputBuffer == EMPTY_BUFFER; - } - - @Override - public void flush() { - outputBuffer = EMPTY_BUFFER; - inputEnded = false; - - audioBufferSink.flush(sampleRateHz, channelCount, encoding); - } - - @Override - public void reset() { - flush(); - buffer = EMPTY_BUFFER; - sampleRateHz = Format.NO_VALUE; - channelCount = Format.NO_VALUE; - encoding = Format.NO_VALUE; } /** @@ -188,8 +107,8 @@ public final class TeeAudioProcessor implements AudioProcessor { private int sampleRateHz; private int channelCount; - private @C.Encoding int encoding; - private @Nullable RandomAccessFile randomAccessFile; + @C.PcmEncoding private int encoding; + @Nullable private RandomAccessFile randomAccessFile; private int counter; private int bytesWritten; @@ -205,7 +124,7 @@ public final class TeeAudioProcessor implements AudioProcessor { } @Override - public void flush(int sampleRateHz, int channelCount, int encoding) { + public void flush(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) { try { reset(); } catch (IOException e) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java index 3225defaf9..09325ca14b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java @@ -16,39 +16,27 @@ package com.google.android.exoplayer2.audio; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.C.Encoding; -import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.util.Util; import java.nio.ByteBuffer; -import java.nio.ByteOrder; /** Audio processor for trimming samples from the start/end of data. */ -/* package */ final class TrimmingAudioProcessor implements AudioProcessor { +/* package */ final class TrimmingAudioProcessor extends BaseAudioProcessor { - @C.Encoding private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT; + @C.PcmEncoding private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT; private boolean isActive; private int trimStartFrames; private int trimEndFrames; - private int channelCount; - private int sampleRateHz; private int bytesPerFrame; private boolean receivedInputSinceConfigure; private int pendingTrimStartBytes; - private ByteBuffer buffer; - private ByteBuffer outputBuffer; private byte[] endBuffer; private int endBufferSize; - private boolean inputEnded; private long trimmedFrameCount; /** Creates a new audio processor for trimming samples from the start/end of data. */ public TrimmingAudioProcessor() { - buffer = EMPTY_BUFFER; - outputBuffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; endBuffer = Util.EMPTY_BYTE_ARRAY; } @@ -80,7 +68,7 @@ import java.nio.ByteOrder; } @Override - public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding) + public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) throws UnhandledFormatException { if (encoding != OUTPUT_ENCODING) { throw new UnhandledFormatException(sampleRateHz, channelCount, encoding); @@ -88,8 +76,6 @@ import java.nio.ByteOrder; if (endBufferSize > 0) { trimmedFrameCount += endBufferSize / bytesPerFrame; } - this.channelCount = channelCount; - this.sampleRateHz = sampleRateHz; bytesPerFrame = Util.getPcmFrameSize(OUTPUT_ENCODING, channelCount); endBuffer = new byte[trimEndFrames * bytesPerFrame]; endBufferSize = 0; @@ -97,6 +83,7 @@ import java.nio.ByteOrder; boolean wasActive = isActive; isActive = trimStartFrames != 0 || trimEndFrames != 0; receivedInputSinceConfigure = false; + setInputFormat(sampleRateHz, channelCount, encoding); return wasActive != isActive; } @@ -105,21 +92,6 @@ import java.nio.ByteOrder; return isActive; } - @Override - public int getOutputChannelCount() { - return channelCount; - } - - @Override - public int getOutputEncoding() { - return OUTPUT_ENCODING; - } - - @Override - public int getOutputSampleRateHz() { - return sampleRateHz; - } - @Override public void queueInput(ByteBuffer inputBuffer) { int position = inputBuffer.position(); @@ -147,11 +119,7 @@ import java.nio.ByteOrder; // endBuffer as full as possible, the output should be any surplus bytes currently in endBuffer // followed by any surplus bytes in the new inputBuffer. int remainingBytesToOutput = endBufferSize + remaining - endBuffer.length; - if (buffer.capacity() < remainingBytesToOutput) { - buffer = ByteBuffer.allocateDirect(remainingBytesToOutput).order(ByteOrder.nativeOrder()); - } else { - buffer.clear(); - } + ByteBuffer buffer = replaceOutputBuffer(remainingBytesToOutput); // Output from endBuffer. int endBufferBytesToOutput = Util.constrainValue(remainingBytesToOutput, 0, endBufferSize); @@ -172,48 +140,31 @@ import java.nio.ByteOrder; endBufferSize += remaining; buffer.flip(); - outputBuffer = buffer; - } - - @Override - public void queueEndOfStream() { - inputEnded = true; } @SuppressWarnings("ReferenceEquality") @Override public ByteBuffer getOutput() { - ByteBuffer outputBuffer = this.outputBuffer; - if (inputEnded && endBufferSize > 0 && outputBuffer == EMPTY_BUFFER) { + if (super.isEnded() && endBufferSize > 0) { // Because audio processors may be drained in the middle of the stream we assume that the // contents of the end buffer need to be output. Gapless transitions don't involve a call to // queueEndOfStream so won't be affected. When audio is actually ending we play the padding // data which is incorrect. This behavior can be fixed once we have the timestamps associated // with input buffers. - if (buffer.capacity() < endBufferSize) { - buffer = ByteBuffer.allocateDirect(endBufferSize).order(ByteOrder.nativeOrder()); - } else { - buffer.clear(); - } - buffer.put(endBuffer, 0, endBufferSize); + replaceOutputBuffer(endBufferSize).put(endBuffer, 0, endBufferSize).flip(); endBufferSize = 0; - buffer.flip(); - outputBuffer = buffer; } - this.outputBuffer = EMPTY_BUFFER; - return outputBuffer; + return super.getOutput(); } @SuppressWarnings("ReferenceEquality") @Override public boolean isEnded() { - return inputEnded && endBufferSize == 0 && outputBuffer == EMPTY_BUFFER; + return super.isEnded() && endBufferSize == 0; } @Override - public void flush() { - outputBuffer = EMPTY_BUFFER; - inputEnded = false; + protected void onFlush() { if (receivedInputSinceConfigure) { // Audio processors are flushed after initial configuration, so we leave the pending trim // start byte count unmodified if the processor was just configured. Otherwise we (possibly @@ -226,11 +177,7 @@ import java.nio.ByteOrder; } @Override - public void reset() { - flush(); - buffer = EMPTY_BUFFER; - channelCount = Format.NO_VALUE; - sampleRateHz = Format.NO_VALUE; + protected void onReset() { endBuffer = Util.EMPTY_BYTE_ARRAY; }