NekoX/TMessagesProj/src/main/java/com/google/android/exoplayer2/audio/TrimmingAudioProcessor.java

181 lines
6.7 KiB
Java

/*
* Copyright (C) 2017 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 com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer;
/** Audio processor for trimming samples from the start/end of data. */
/* package */ final class TrimmingAudioProcessor extends BaseAudioProcessor {
@C.PcmEncoding private static final int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT;
private int trimStartFrames;
private int trimEndFrames;
private boolean reconfigurationPending;
private int pendingTrimStartBytes;
private byte[] endBuffer;
private int endBufferSize;
private long trimmedFrameCount;
/** Creates a new audio processor for trimming samples from the start/end of data. */
public TrimmingAudioProcessor() {
endBuffer = Util.EMPTY_BYTE_ARRAY;
}
/**
* Sets the number of audio frames to trim from the start and end of audio passed to this
* processor. After calling this method, call {@link #configure(AudioFormat)} to apply the new
* trimming frame counts.
*
* @param trimStartFrames The number of audio frames to trim from the start of audio.
* @param trimEndFrames The number of audio frames to trim from the end of audio.
* @see AudioSink#configure(int, int, int, int, int[], int, int)
*/
public void setTrimFrameCount(int trimStartFrames, int trimEndFrames) {
this.trimStartFrames = trimStartFrames;
this.trimEndFrames = trimEndFrames;
}
/** Sets the trimmed frame count returned by {@link #getTrimmedFrameCount()} to zero. */
public void resetTrimmedFrameCount() {
trimmedFrameCount = 0;
}
/**
* Returns the number of audio frames trimmed since the last call to {@link
* #resetTrimmedFrameCount()}.
*/
public long getTrimmedFrameCount() {
return trimmedFrameCount;
}
@Override
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException {
if (inputAudioFormat.encoding != OUTPUT_ENCODING) {
throw new UnhandledAudioFormatException(inputAudioFormat);
}
reconfigurationPending = true;
return trimStartFrames != 0 || trimEndFrames != 0 ? inputAudioFormat : AudioFormat.NOT_SET;
}
@Override
public void queueInput(ByteBuffer inputBuffer) {
int position = inputBuffer.position();
int limit = inputBuffer.limit();
int remaining = limit - position;
if (remaining == 0) {
return;
}
// Trim any pending start bytes from the input buffer.
int trimBytes = Math.min(remaining, pendingTrimStartBytes);
trimmedFrameCount += trimBytes / inputAudioFormat.bytesPerFrame;
pendingTrimStartBytes -= trimBytes;
inputBuffer.position(position + trimBytes);
if (pendingTrimStartBytes > 0) {
// Nothing to output yet.
return;
}
remaining -= trimBytes;
// endBuffer must be kept as full as possible, so that we trim the right amount of media if we
// don't receive any more input. After taking into account the number of bytes needed to keep
// 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;
ByteBuffer buffer = replaceOutputBuffer(remainingBytesToOutput);
// Output from endBuffer.
int endBufferBytesToOutput = Util.constrainValue(remainingBytesToOutput, 0, endBufferSize);
buffer.put(endBuffer, 0, endBufferBytesToOutput);
remainingBytesToOutput -= endBufferBytesToOutput;
// Output from inputBuffer, restoring its limit afterwards.
int inputBufferBytesToOutput = Util.constrainValue(remainingBytesToOutput, 0, remaining);
inputBuffer.limit(inputBuffer.position() + inputBufferBytesToOutput);
buffer.put(inputBuffer);
inputBuffer.limit(limit);
remaining -= inputBufferBytesToOutput;
// Compact endBuffer, then repopulate it using the new input.
endBufferSize -= endBufferBytesToOutput;
System.arraycopy(endBuffer, endBufferBytesToOutput, endBuffer, 0, endBufferSize);
inputBuffer.get(endBuffer, endBufferSize, remaining);
endBufferSize += remaining;
buffer.flip();
}
@Override
public ByteBuffer getOutput() {
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. For gapless transitions, configure will
// always be called, so the end buffer is cleared in onQueueEndOfStream.
replaceOutputBuffer(endBufferSize).put(endBuffer, 0, endBufferSize).flip();
endBufferSize = 0;
}
return super.getOutput();
}
@Override
public boolean isEnded() {
return super.isEnded() && endBufferSize == 0;
}
@Override
protected void onQueueEndOfStream() {
if (reconfigurationPending) {
// Trim audio in the end buffer.
if (endBufferSize > 0) {
trimmedFrameCount += endBufferSize / inputAudioFormat.bytesPerFrame;
}
endBufferSize = 0;
}
}
@Override
protected void onFlush() {
if (reconfigurationPending) {
// Flushing activates the new configuration, so prepare to trim bytes from the start/end.
reconfigurationPending = false;
endBuffer = new byte[trimEndFrames * inputAudioFormat.bytesPerFrame];
pendingTrimStartBytes = trimStartFrames * inputAudioFormat.bytesPerFrame;
}
// TODO(internal b/77292509): Flushing occurs to activate a configuration (handled above) but
// also when seeking within a stream. This implementation currently doesn't handle seek to start
// (where we need to trim at the start again), nor seeks to non-zero positions before start
// trimming has occurred (where we should set pendingTrimStartBytes to zero). These cases can be
// fixed by trimming in queueInput based on timestamp, once that information is available.
// Any data in the end buffer should no longer be output if we are playing from a different
// position, so discard it and refill the buffer using new input.
endBufferSize = 0;
}
@Override
protected void onReset() {
endBuffer = Util.EMPTY_BYTE_ARRAY;
}
}