mirror of
https://github.com/samsonjs/media.git
synced 2026-03-27 09:45:47 +00:00
Factor out common audio processor functionality
PiperOrigin-RevId: 234025553
This commit is contained in:
parent
03006f0595
commit
83545fb7b2
8 changed files with 218 additions and 465 deletions
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue