mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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. */
|
/** Exception thrown when a processor can't be configured for a given input audio format. */
|
||||||
final class UnhandledFormatException extends Exception {
|
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 "
|
super("Unhandled format: " + sampleRateHz + " Hz, " + channelCount + " channels in encoding "
|
||||||
+ encoding);
|
+ encoding);
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +61,7 @@ public interface AudioProcessor {
|
||||||
* @return Whether to {@link #flush()} the processor.
|
* @return Whether to {@link #flush()} the processor.
|
||||||
* @throws UnhandledFormatException Thrown if the specified format can't be handled as input.
|
* @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;
|
throws UnhandledFormatException;
|
||||||
|
|
||||||
/** Returns whether the processor is configured and will process input buffers. */
|
/** 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
|
* result of calling {@link #configure(int, int, int)} and is undefined if the instance is not
|
||||||
* active.
|
* active.
|
||||||
*/
|
*/
|
||||||
@C.Encoding
|
@C.PcmEncoding
|
||||||
int getOutputEncoding();
|
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 android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
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 com.google.android.exoplayer2.util.Assertions;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AudioProcessor} that applies a mapping from input channels onto specified output
|
* An {@link AudioProcessor} that applies a mapping from input channels onto specified output
|
||||||
* channels. This can be used to reorder, duplicate or discard channels.
|
* 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;
|
@Nullable private int[] pendingOutputChannels;
|
||||||
|
|
||||||
private boolean active;
|
private boolean active;
|
||||||
@Nullable private int[] outputChannels;
|
@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)}
|
* Resets the channel mapping. After calling this method, call {@link #configure(int, int, int)}
|
||||||
|
|
@ -61,10 +45,12 @@ import java.util.Arrays;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding)
|
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledFormatException {
|
||||||
boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels);
|
boolean outputChannelsChanged = !Arrays.equals(pendingOutputChannels, outputChannels);
|
||||||
outputChannels = pendingOutputChannels;
|
outputChannels = pendingOutputChannels;
|
||||||
|
|
||||||
|
int[] outputChannels = this.outputChannels;
|
||||||
if (outputChannels == null) {
|
if (outputChannels == null) {
|
||||||
active = false;
|
active = false;
|
||||||
return outputChannelsChanged;
|
return outputChannelsChanged;
|
||||||
|
|
@ -72,12 +58,9 @@ import java.util.Arrays;
|
||||||
if (encoding != C.ENCODING_PCM_16BIT) {
|
if (encoding != C.ENCODING_PCM_16BIT) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||||
}
|
}
|
||||||
if (!outputChannelsChanged && this.sampleRateHz == sampleRateHz
|
if (!outputChannelsChanged && !setInputFormat(sampleRateHz, channelCount, encoding)) {
|
||||||
&& this.channelCount == channelCount) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.sampleRateHz = sampleRateHz;
|
|
||||||
this.channelCount = channelCount;
|
|
||||||
|
|
||||||
active = channelCount != outputChannels.length;
|
active = channelCount != outputChannels.length;
|
||||||
for (int i = 0; i < outputChannels.length; i++) {
|
for (int i = 0; i < outputChannels.length; i++) {
|
||||||
|
|
@ -100,16 +83,6 @@ import java.util.Arrays;
|
||||||
return outputChannels == null ? channelCount : outputChannels.length;
|
return outputChannels == null ? channelCount : outputChannels.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputEncoding() {
|
|
||||||
return C.ENCODING_PCM_16BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputSampleRateHz() {
|
|
||||||
return sampleRateHz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInput(ByteBuffer inputBuffer) {
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
int[] outputChannels = Assertions.checkNotNull(this.outputChannels);
|
int[] outputChannels = Assertions.checkNotNull(this.outputChannels);
|
||||||
|
|
@ -117,11 +90,7 @@ import java.util.Arrays;
|
||||||
int limit = inputBuffer.limit();
|
int limit = inputBuffer.limit();
|
||||||
int frameCount = (limit - position) / (2 * channelCount);
|
int frameCount = (limit - position) / (2 * channelCount);
|
||||||
int outputSize = frameCount * outputChannels.length * 2;
|
int outputSize = frameCount * outputChannels.length * 2;
|
||||||
if (buffer.capacity() < outputSize) {
|
ByteBuffer buffer = replaceOutputBuffer(outputSize);
|
||||||
buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
|
|
||||||
} else {
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
while (position < limit) {
|
while (position < limit) {
|
||||||
for (int channelIndex : outputChannels) {
|
for (int channelIndex : outputChannels) {
|
||||||
buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex));
|
buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex));
|
||||||
|
|
@ -130,39 +99,10 @@ import java.util.Arrays;
|
||||||
}
|
}
|
||||||
inputBuffer.position(limit);
|
inputBuffer.position(limit);
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
outputBuffer = buffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueEndOfStream() {
|
protected void onReset() {
|
||||||
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;
|
|
||||||
outputChannels = null;
|
outputChannels = null;
|
||||||
pendingOutputChannels = null;
|
pendingOutputChannels = null;
|
||||||
active = false;
|
active = false;
|
||||||
|
|
|
||||||
|
|
@ -16,61 +16,30 @@
|
||||||
package com.google.android.exoplayer2.audio;
|
package com.google.android.exoplayer2.audio;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AudioProcessor} that converts 24-bit and 32-bit integer PCM audio to 32-bit float PCM
|
* An {@link AudioProcessor} that converts 24-bit and 32-bit integer PCM audio to 32-bit float PCM
|
||||||
* audio.
|
* 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 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 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
|
@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 {
|
throws UnhandledFormatException {
|
||||||
if (!Util.isEncodingHighResolutionIntegerPcm(encoding)) {
|
if (!Util.isEncodingHighResolutionIntegerPcm(encoding)) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||||
}
|
}
|
||||||
if (this.sampleRateHz == sampleRateHz
|
return setInputFormat(sampleRateHz, channelCount, encoding);
|
||||||
&& this.channelCount == channelCount
|
|
||||||
&& sourceEncoding == encoding) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.sampleRateHz = sampleRateHz;
|
|
||||||
this.channelCount = channelCount;
|
|
||||||
sourceEncoding = encoding;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return Util.isEncodingHighResolutionIntegerPcm(sourceEncoding);
|
return Util.isEncodingHighResolutionIntegerPcm(encoding);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputChannelCount() {
|
|
||||||
return channelCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -78,24 +47,15 @@ import java.nio.ByteOrder;
|
||||||
return C.ENCODING_PCM_FLOAT;
|
return C.ENCODING_PCM_FLOAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputSampleRateHz() {
|
|
||||||
return sampleRateHz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInput(ByteBuffer inputBuffer) {
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
boolean isInput32Bit = sourceEncoding == C.ENCODING_PCM_32BIT;
|
boolean isInput32Bit = encoding == C.ENCODING_PCM_32BIT;
|
||||||
int position = inputBuffer.position();
|
int position = inputBuffer.position();
|
||||||
int limit = inputBuffer.limit();
|
int limit = inputBuffer.limit();
|
||||||
int size = limit - position;
|
int size = limit - position;
|
||||||
|
|
||||||
int resampledSize = isInput32Bit ? size : (size / 3) * 4;
|
int resampledSize = isInput32Bit ? size : (size / 3) * 4;
|
||||||
if (buffer.capacity() < resampledSize) {
|
ByteBuffer buffer = replaceOutputBuffer(resampledSize);
|
||||||
buffer = ByteBuffer.allocateDirect(resampledSize).order(ByteOrder.nativeOrder());
|
|
||||||
} else {
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
if (isInput32Bit) {
|
if (isInput32Bit) {
|
||||||
for (int i = position; i < limit; i += 4) {
|
for (int i = position; i < limit; i += 4) {
|
||||||
int pcm32BitInteger =
|
int pcm32BitInteger =
|
||||||
|
|
@ -117,40 +77,6 @@ import java.nio.ByteOrder;
|
||||||
|
|
||||||
inputBuffer.position(inputBuffer.limit());
|
inputBuffer.position(inputBuffer.limit());
|
||||||
buffer.flip();
|
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.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import java.nio.ByteBuffer;
|
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
|
* An {@link AudioProcessor} that converts 8-bit, 24-bit and 32-bit integer PCM audio to 16-bit
|
||||||
* integer PCM audio.
|
* integer PCM audio.
|
||||||
*/
|
*/
|
||||||
/* package */ final class ResamplingAudioProcessor implements AudioProcessor {
|
/* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor {
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@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 {
|
throws UnhandledFormatException {
|
||||||
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
|
if (encoding != C.ENCODING_PCM_8BIT && encoding != C.ENCODING_PCM_16BIT
|
||||||
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
|
&& encoding != C.ENCODING_PCM_24BIT && encoding != C.ENCODING_PCM_32BIT) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||||
}
|
}
|
||||||
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount
|
return setInputFormat(sampleRateHz, channelCount, encoding);
|
||||||
&& this.encoding == encoding) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.sampleRateHz = sampleRateHz;
|
|
||||||
this.channelCount = channelCount;
|
|
||||||
this.encoding = encoding;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -64,21 +40,11 @@ import java.nio.ByteOrder;
|
||||||
return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT;
|
return encoding != C.ENCODING_INVALID && encoding != C.ENCODING_PCM_16BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputChannelCount() {
|
|
||||||
return channelCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getOutputEncoding() {
|
public int getOutputEncoding() {
|
||||||
return C.ENCODING_PCM_16BIT;
|
return C.ENCODING_PCM_16BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputSampleRateHz() {
|
|
||||||
return sampleRateHz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInput(ByteBuffer inputBuffer) {
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
// Prepare the output buffer.
|
// Prepare the output buffer.
|
||||||
|
|
@ -105,13 +71,9 @@ import java.nio.ByteOrder;
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException();
|
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.
|
// Resample the little endian input and update the input/output buffers.
|
||||||
|
ByteBuffer buffer = replaceOutputBuffer(resampledSize);
|
||||||
switch (encoding) {
|
switch (encoding) {
|
||||||
case C.ENCODING_PCM_8BIT:
|
case C.ENCODING_PCM_8BIT:
|
||||||
// 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up.
|
// 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());
|
inputBuffer.position(inputBuffer.limit());
|
||||||
buffer.flip();
|
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 android.support.annotation.IntDef;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link AudioProcessor} that skips silence in the input stream. Input and output are 16-bit
|
* An {@link AudioProcessor} that skips silence in the input stream. Input and output are 16-bit
|
||||||
* PCM.
|
* 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
|
* 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. */
|
/** State when the input is silent. */
|
||||||
private static final int STATE_SILENT = 2;
|
private static final int STATE_SILENT = 2;
|
||||||
|
|
||||||
private int channelCount;
|
|
||||||
private int sampleRateHz;
|
|
||||||
private int bytesPerFrame;
|
private int bytesPerFrame;
|
||||||
|
|
||||||
private boolean enabled;
|
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
|
* 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
|
* 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. */
|
/** Creates a new silence trimming audio processor. */
|
||||||
public SilenceSkippingAudioProcessor() {
|
public SilenceSkippingAudioProcessor() {
|
||||||
buffer = EMPTY_BUFFER;
|
|
||||||
outputBuffer = EMPTY_BUFFER;
|
|
||||||
channelCount = Format.NO_VALUE;
|
|
||||||
sampleRateHz = Format.NO_VALUE;
|
|
||||||
maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY;
|
maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY;
|
||||||
paddingBuffer = Util.EMPTY_BYTE_ARRAY;
|
paddingBuffer = Util.EMPTY_BYTE_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
@ -131,43 +119,23 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor {
|
||||||
// AudioProcessor implementation.
|
// AudioProcessor implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configure(int sampleRateHz, int channelCount, int encoding)
|
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledFormatException {
|
||||||
if (encoding != C.ENCODING_PCM_16BIT) {
|
if (encoding != C.ENCODING_PCM_16BIT) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||||
}
|
}
|
||||||
if (this.sampleRateHz == sampleRateHz && this.channelCount == channelCount) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.sampleRateHz = sampleRateHz;
|
|
||||||
this.channelCount = channelCount;
|
|
||||||
bytesPerFrame = channelCount * 2;
|
bytesPerFrame = channelCount * 2;
|
||||||
return true;
|
return setInputFormat(sampleRateHz, channelCount, encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return sampleRateHz != Format.NO_VALUE && enabled;
|
return super.isActive() && enabled;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputChannelCount() {
|
|
||||||
return channelCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @C.Encoding int getOutputEncoding() {
|
|
||||||
return C.ENCODING_PCM_16BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputSampleRateHz() {
|
|
||||||
return sampleRateHz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInput(ByteBuffer inputBuffer) {
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
while (inputBuffer.hasRemaining() && !outputBuffer.hasRemaining()) {
|
while (inputBuffer.hasRemaining() && !hasPendingOutput()) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_NOISY:
|
case STATE_NOISY:
|
||||||
processNoisy(inputBuffer);
|
processNoisy(inputBuffer);
|
||||||
|
|
@ -185,8 +153,7 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueEndOfStream() {
|
protected void onQueueEndOfStream() {
|
||||||
inputEnded = true;
|
|
||||||
if (maybeSilenceBufferSize > 0) {
|
if (maybeSilenceBufferSize > 0) {
|
||||||
// We haven't received enough silence to transition to the silent state, so output the buffer.
|
// We haven't received enough silence to transition to the silent state, so output the buffer.
|
||||||
output(maybeSilenceBuffer, maybeSilenceBufferSize);
|
output(maybeSilenceBuffer, maybeSilenceBufferSize);
|
||||||
|
|
@ -197,20 +164,7 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer getOutput() {
|
protected void onFlush() {
|
||||||
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() {
|
|
||||||
if (isActive()) {
|
if (isActive()) {
|
||||||
int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame;
|
int maybeSilenceBufferSize = durationUsToFrames(MINIMUM_SILENCE_DURATION_US) * bytesPerFrame;
|
||||||
if (maybeSilenceBuffer.length != maybeSilenceBufferSize) {
|
if (maybeSilenceBuffer.length != maybeSilenceBufferSize) {
|
||||||
|
|
@ -222,20 +176,14 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state = STATE_NOISY;
|
state = STATE_NOISY;
|
||||||
outputBuffer = EMPTY_BUFFER;
|
|
||||||
inputEnded = false;
|
|
||||||
skippedFrames = 0;
|
skippedFrames = 0;
|
||||||
maybeSilenceBufferSize = 0;
|
maybeSilenceBufferSize = 0;
|
||||||
hasOutputNoise = false;
|
hasOutputNoise = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
protected void onReset() {
|
||||||
enabled = false;
|
enabled = false;
|
||||||
flush();
|
|
||||||
buffer = EMPTY_BUFFER;
|
|
||||||
channelCount = Format.NO_VALUE;
|
|
||||||
sampleRateHz = Format.NO_VALUE;
|
|
||||||
paddingSize = 0;
|
paddingSize = 0;
|
||||||
maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY;
|
maybeSilenceBuffer = Util.EMPTY_BYTE_ARRAY;
|
||||||
paddingBuffer = Util.EMPTY_BYTE_ARRAY;
|
paddingBuffer = Util.EMPTY_BYTE_ARRAY;
|
||||||
|
|
@ -330,30 +278,19 @@ public final class SilenceSkippingAudioProcessor implements AudioProcessor {
|
||||||
* processor.
|
* processor.
|
||||||
*/
|
*/
|
||||||
private void output(byte[] data, int length) {
|
private void output(byte[] data, int length) {
|
||||||
prepareForOutput(length);
|
replaceOutputBuffer(length).put(data, 0, length).flip();
|
||||||
buffer.put(data, 0, length);
|
if (length > 0) {
|
||||||
buffer.flip();
|
hasOutputNoise = true;
|
||||||
outputBuffer = buffer;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies remaining bytes from {@code data} to populate a new output buffer from the processor.
|
* Copies remaining bytes from {@code data} to populate a new output buffer from the processor.
|
||||||
*/
|
*/
|
||||||
private void output(ByteBuffer data) {
|
private void output(ByteBuffer data) {
|
||||||
prepareForOutput(data.remaining());
|
int length = data.remaining();
|
||||||
buffer.put(data);
|
replaceOutputBuffer(length).put(data).flip();
|
||||||
buffer.flip();
|
if (length > 0) {
|
||||||
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) {
|
|
||||||
hasOutputNoise = true;
|
hasOutputNoise = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.audio;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
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.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
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
|
* custom {@link com.google.android.exoplayer2.audio.DefaultAudioSink.AudioProcessorChain} when
|
||||||
* creating the audio sink, and include this audio processor after all other audio processors.
|
* 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. */
|
/** A sink for audio buffers handled by the audio processor. */
|
||||||
public interface AudioBufferSink {
|
public interface AudioBufferSink {
|
||||||
|
|
||||||
/** Called when the audio processor is flushed with a format of subsequent input. */
|
/** 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.
|
* Called when data is written to the audio processor.
|
||||||
|
|
@ -54,14 +53,6 @@ public final class TeeAudioProcessor implements AudioProcessor {
|
||||||
|
|
||||||
private final AudioBufferSink audioBufferSink;
|
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}.
|
* 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) {
|
public TeeAudioProcessor(AudioBufferSink audioBufferSink) {
|
||||||
this.audioBufferSink = Assertions.checkNotNull(audioBufferSink);
|
this.audioBufferSink = Assertions.checkNotNull(audioBufferSink);
|
||||||
|
|
||||||
buffer = EMPTY_BUFFER;
|
|
||||||
outputBuffer = EMPTY_BUFFER;
|
|
||||||
channelCount = Format.NO_VALUE;
|
|
||||||
sampleRateHz = Format.NO_VALUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configure(int sampleRateHz, int channelCount, @C.Encoding int encoding) {
|
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
|
||||||
boolean formatChanged =
|
return setInputFormat(sampleRateHz, channelCount, encoding);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActive() {
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
return sampleRateHz != Format.NO_VALUE;
|
int remaining = inputBuffer.remaining();
|
||||||
}
|
|
||||||
|
|
||||||
@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();
|
|
||||||
if (remaining == 0) {
|
if (remaining == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
audioBufferSink.handleBuffer(inputBuffer.asReadOnlyBuffer());
|
||||||
|
replaceOutputBuffer(remaining).put(inputBuffer).flip();
|
||||||
|
}
|
||||||
|
|
||||||
audioBufferSink.handleBuffer(buffer.asReadOnlyBuffer());
|
@Override
|
||||||
|
protected void onFlush() {
|
||||||
if (this.buffer.capacity() < remaining) {
|
if (isActive()) {
|
||||||
this.buffer = ByteBuffer.allocateDirect(remaining).order(ByteOrder.nativeOrder());
|
audioBufferSink.flush(sampleRateHz, channelCount, encoding);
|
||||||
} else {
|
|
||||||
this.buffer.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 sampleRateHz;
|
||||||
private int channelCount;
|
private int channelCount;
|
||||||
private @C.Encoding int encoding;
|
@C.PcmEncoding private int encoding;
|
||||||
private @Nullable RandomAccessFile randomAccessFile;
|
@Nullable private RandomAccessFile randomAccessFile;
|
||||||
private int counter;
|
private int counter;
|
||||||
private int bytesWritten;
|
private int bytesWritten;
|
||||||
|
|
||||||
|
|
@ -205,7 +124,7 @@ public final class TeeAudioProcessor implements AudioProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush(int sampleRateHz, int channelCount, int encoding) {
|
public void flush(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding) {
|
||||||
try {
|
try {
|
||||||
reset();
|
reset();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
||||||
|
|
@ -16,39 +16,27 @@
|
||||||
package com.google.android.exoplayer2.audio;
|
package com.google.android.exoplayer2.audio;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
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 com.google.android.exoplayer2.util.Util;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
|
|
||||||
/** Audio processor for trimming samples from the start/end of data. */
|
/** 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 boolean isActive;
|
||||||
private int trimStartFrames;
|
private int trimStartFrames;
|
||||||
private int trimEndFrames;
|
private int trimEndFrames;
|
||||||
private int channelCount;
|
|
||||||
private int sampleRateHz;
|
|
||||||
private int bytesPerFrame;
|
private int bytesPerFrame;
|
||||||
private boolean receivedInputSinceConfigure;
|
private boolean receivedInputSinceConfigure;
|
||||||
|
|
||||||
private int pendingTrimStartBytes;
|
private int pendingTrimStartBytes;
|
||||||
private ByteBuffer buffer;
|
|
||||||
private ByteBuffer outputBuffer;
|
|
||||||
private byte[] endBuffer;
|
private byte[] endBuffer;
|
||||||
private int endBufferSize;
|
private int endBufferSize;
|
||||||
private boolean inputEnded;
|
|
||||||
private long trimmedFrameCount;
|
private long trimmedFrameCount;
|
||||||
|
|
||||||
/** Creates a new audio processor for trimming samples from the start/end of data. */
|
/** Creates a new audio processor for trimming samples from the start/end of data. */
|
||||||
public TrimmingAudioProcessor() {
|
public TrimmingAudioProcessor() {
|
||||||
buffer = EMPTY_BUFFER;
|
|
||||||
outputBuffer = EMPTY_BUFFER;
|
|
||||||
channelCount = Format.NO_VALUE;
|
|
||||||
sampleRateHz = Format.NO_VALUE;
|
|
||||||
endBuffer = Util.EMPTY_BYTE_ARRAY;
|
endBuffer = Util.EMPTY_BYTE_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +68,7 @@ import java.nio.ByteOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean configure(int sampleRateHz, int channelCount, @Encoding int encoding)
|
public boolean configure(int sampleRateHz, int channelCount, @C.PcmEncoding int encoding)
|
||||||
throws UnhandledFormatException {
|
throws UnhandledFormatException {
|
||||||
if (encoding != OUTPUT_ENCODING) {
|
if (encoding != OUTPUT_ENCODING) {
|
||||||
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
throw new UnhandledFormatException(sampleRateHz, channelCount, encoding);
|
||||||
|
|
@ -88,8 +76,6 @@ import java.nio.ByteOrder;
|
||||||
if (endBufferSize > 0) {
|
if (endBufferSize > 0) {
|
||||||
trimmedFrameCount += endBufferSize / bytesPerFrame;
|
trimmedFrameCount += endBufferSize / bytesPerFrame;
|
||||||
}
|
}
|
||||||
this.channelCount = channelCount;
|
|
||||||
this.sampleRateHz = sampleRateHz;
|
|
||||||
bytesPerFrame = Util.getPcmFrameSize(OUTPUT_ENCODING, channelCount);
|
bytesPerFrame = Util.getPcmFrameSize(OUTPUT_ENCODING, channelCount);
|
||||||
endBuffer = new byte[trimEndFrames * bytesPerFrame];
|
endBuffer = new byte[trimEndFrames * bytesPerFrame];
|
||||||
endBufferSize = 0;
|
endBufferSize = 0;
|
||||||
|
|
@ -97,6 +83,7 @@ import java.nio.ByteOrder;
|
||||||
boolean wasActive = isActive;
|
boolean wasActive = isActive;
|
||||||
isActive = trimStartFrames != 0 || trimEndFrames != 0;
|
isActive = trimStartFrames != 0 || trimEndFrames != 0;
|
||||||
receivedInputSinceConfigure = false;
|
receivedInputSinceConfigure = false;
|
||||||
|
setInputFormat(sampleRateHz, channelCount, encoding);
|
||||||
return wasActive != isActive;
|
return wasActive != isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,21 +92,6 @@ import java.nio.ByteOrder;
|
||||||
return isActive;
|
return isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputChannelCount() {
|
|
||||||
return channelCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputEncoding() {
|
|
||||||
return OUTPUT_ENCODING;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getOutputSampleRateHz() {
|
|
||||||
return sampleRateHz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInput(ByteBuffer inputBuffer) {
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
int position = inputBuffer.position();
|
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
|
// endBuffer as full as possible, the output should be any surplus bytes currently in endBuffer
|
||||||
// followed by any surplus bytes in the new inputBuffer.
|
// followed by any surplus bytes in the new inputBuffer.
|
||||||
int remainingBytesToOutput = endBufferSize + remaining - endBuffer.length;
|
int remainingBytesToOutput = endBufferSize + remaining - endBuffer.length;
|
||||||
if (buffer.capacity() < remainingBytesToOutput) {
|
ByteBuffer buffer = replaceOutputBuffer(remainingBytesToOutput);
|
||||||
buffer = ByteBuffer.allocateDirect(remainingBytesToOutput).order(ByteOrder.nativeOrder());
|
|
||||||
} else {
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output from endBuffer.
|
// Output from endBuffer.
|
||||||
int endBufferBytesToOutput = Util.constrainValue(remainingBytesToOutput, 0, endBufferSize);
|
int endBufferBytesToOutput = Util.constrainValue(remainingBytesToOutput, 0, endBufferSize);
|
||||||
|
|
@ -172,48 +140,31 @@ import java.nio.ByteOrder;
|
||||||
endBufferSize += remaining;
|
endBufferSize += remaining;
|
||||||
|
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
outputBuffer = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void queueEndOfStream() {
|
|
||||||
inputEnded = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
@Override
|
@Override
|
||||||
public ByteBuffer getOutput() {
|
public ByteBuffer getOutput() {
|
||||||
ByteBuffer outputBuffer = this.outputBuffer;
|
if (super.isEnded() && endBufferSize > 0) {
|
||||||
if (inputEnded && endBufferSize > 0 && outputBuffer == EMPTY_BUFFER) {
|
|
||||||
// Because audio processors may be drained in the middle of the stream we assume that the
|
// 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
|
// 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
|
// 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
|
// data which is incorrect. This behavior can be fixed once we have the timestamps associated
|
||||||
// with input buffers.
|
// with input buffers.
|
||||||
if (buffer.capacity() < endBufferSize) {
|
replaceOutputBuffer(endBufferSize).put(endBuffer, 0, endBufferSize).flip();
|
||||||
buffer = ByteBuffer.allocateDirect(endBufferSize).order(ByteOrder.nativeOrder());
|
|
||||||
} else {
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
buffer.put(endBuffer, 0, endBufferSize);
|
|
||||||
endBufferSize = 0;
|
endBufferSize = 0;
|
||||||
buffer.flip();
|
|
||||||
outputBuffer = buffer;
|
|
||||||
}
|
}
|
||||||
this.outputBuffer = EMPTY_BUFFER;
|
return super.getOutput();
|
||||||
return outputBuffer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnded() {
|
public boolean isEnded() {
|
||||||
return inputEnded && endBufferSize == 0 && outputBuffer == EMPTY_BUFFER;
|
return super.isEnded() && endBufferSize == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() {
|
protected void onFlush() {
|
||||||
outputBuffer = EMPTY_BUFFER;
|
|
||||||
inputEnded = false;
|
|
||||||
if (receivedInputSinceConfigure) {
|
if (receivedInputSinceConfigure) {
|
||||||
// Audio processors are flushed after initial configuration, so we leave the pending trim
|
// 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
|
// start byte count unmodified if the processor was just configured. Otherwise we (possibly
|
||||||
|
|
@ -226,11 +177,7 @@ import java.nio.ByteOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
protected void onReset() {
|
||||||
flush();
|
|
||||||
buffer = EMPTY_BUFFER;
|
|
||||||
channelCount = Format.NO_VALUE;
|
|
||||||
sampleRateHz = Format.NO_VALUE;
|
|
||||||
endBuffer = Util.EMPTY_BYTE_ARRAY;
|
endBuffer = Util.EMPTY_BYTE_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue