BatchBuffer.java
/*
* Copyright (C) 2020 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 androidx.media3.exoplayer.mediacodec;
import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.decoder.DecoderInputBuffer;
import java.nio.ByteBuffer;
/** Buffer to which multiple sample buffers can be appended for batch processing */
/* package */ final class BatchBuffer extends DecoderInputBuffer {
/** The default maximum number of samples that can be appended before the buffer is full. */
public static final int DEFAULT_MAX_SAMPLE_COUNT = 32;
/**
* The maximum size of the buffer in bytes. This prevents excessive memory usage for high bitrate
* streams. The limit is equivalent of 75s of mp3 at highest bitrate (320kb/s) and 30s of AAC LC
* at highest bitrate (800kb/s). That limit is ignored for the first sample.
*/
@VisibleForTesting /* package */ static final int MAX_SIZE_BYTES = 3 * 1000 * 1024;
private long lastSampleTimeUs;
private int sampleCount;
private int maxSampleCount;
public BatchBuffer() {
super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
maxSampleCount = DEFAULT_MAX_SAMPLE_COUNT;
}
@Override
public void clear() {
super.clear();
sampleCount = 0;
}
/** Sets the maximum number of samples that can be appended before the buffer is full. */
public void setMaxSampleCount(@IntRange(from = 1) int maxSampleCount) {
checkArgument(maxSampleCount > 0);
this.maxSampleCount = maxSampleCount;
}
/**
* Returns the timestamp of the first sample in the buffer. The return value is undefined if
* {@link #hasSamples()} is {@code false}.
*/
public long getFirstSampleTimeUs() {
return timeUs;
}
/**
* Returns the timestamp of the last sample in the buffer. The return value is undefined if {@link
* #hasSamples()} is {@code false}.
*/
public long getLastSampleTimeUs() {
return lastSampleTimeUs;
}
/** Returns the number of samples in the buffer. */
public int getSampleCount() {
return sampleCount;
}
/** Returns whether the buffer contains one or more samples. */
public boolean hasSamples() {
return sampleCount > 0;
}
/**
* Attempts to append the provided buffer.
*
* @param buffer The buffer to try and append.
* @return Whether the buffer was successfully appended.
* @throws IllegalArgumentException If the {@code buffer} is encrypted, has supplemental data, or
* is an end of stream buffer, none of which are supported.
*/
public boolean append(DecoderInputBuffer buffer) {
checkArgument(!buffer.isEncrypted());
checkArgument(!buffer.hasSupplementalData());
checkArgument(!buffer.isEndOfStream());
if (!canAppendSampleBuffer(buffer)) {
return false;
}
if (sampleCount++ == 0) {
timeUs = buffer.timeUs;
if (buffer.isKeyFrame()) {
setFlags(C.BUFFER_FLAG_KEY_FRAME);
}
}
if (buffer.isDecodeOnly()) {
setFlags(C.BUFFER_FLAG_DECODE_ONLY);
}
@Nullable ByteBuffer bufferData = buffer.data;
if (bufferData != null) {
ensureSpaceForWrite(bufferData.remaining());
data.put(bufferData);
}
lastSampleTimeUs = buffer.timeUs;
return true;
}
private boolean canAppendSampleBuffer(DecoderInputBuffer buffer) {
if (!hasSamples()) {
// Always allow appending when the buffer is empty, else no progress can be made.
return true;
}
if (sampleCount >= maxSampleCount) {
return false;
}
if (buffer.isDecodeOnly() != isDecodeOnly()) {
return false;
}
@Nullable ByteBuffer bufferData = buffer.data;
if (bufferData != null
&& data != null
&& data.position() + bufferData.remaining() > MAX_SIZE_BYTES) {
return false;
}
return true;
}
}