mirror of
https://github.com/samsonjs/media.git
synced 2026-04-10 12:05:47 +00:00
Do not use MediaCodec in passthrough mode.
Now that MediaCodec is not use in passthrough, no MediaCodec should be created in this mode. Additionally, do not instantiate a MediaCodec in passthrough #exo-offload PiperOrigin-RevId: 309916131
This commit is contained in:
parent
fa7d26dd9f
commit
918963c2b4
6 changed files with 692 additions and 51 deletions
|
|
@ -55,9 +55,6 @@
|
|||
* Add `DataSpec.Builder` and deprecate most `DataSpec` constructors.
|
||||
* Add `DataSpec.customData` to allow applications to pass custom data
|
||||
through `DataSource` chains.
|
||||
* Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
|
||||
and `AudioSink.handleBuffer` to allow batching multiple encoded frames
|
||||
in one buffer.
|
||||
* Add a `Format.Builder` and deprecate all `Format.create*` methods and
|
||||
most `Format.copyWith*` methods.
|
||||
* Split `Format.bitrate` into `Format.averageBitrate` and
|
||||
|
|
@ -133,6 +130,11 @@
|
|||
directly instead.
|
||||
* Update `CachedContentIndex` to use `SecureRandom` for generating the
|
||||
initialization vector used to encrypt the cache contents.
|
||||
* Audio:
|
||||
* Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
|
||||
and `AudioSink.handleBuffer` to allow batching multiple encoded frames
|
||||
in one buffer.
|
||||
* No longer use a `MediaCodec` in audio passthrough mode.
|
||||
* DASH:
|
||||
* Merge trick play adaptation sets (i.e., adaptation sets marked with
|
||||
`http://dashif.org/guidelines/trickmode`) into the same `TrackGroup` as
|
||||
|
|
|
|||
|
|
@ -217,7 +217,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
@TunnelingSupport
|
||||
int tunnelingSupport = Util.SDK_INT >= 21 ? TUNNELING_SUPPORTED : TUNNELING_NOT_SUPPORTED;
|
||||
boolean supportsFormatDrm = supportsFormatDrm(format);
|
||||
if (supportsFormatDrm && usePassthrough(format.channelCount, mimeType)) {
|
||||
if (supportsFormatDrm
|
||||
&& usePassthrough(format.channelCount, mimeType)
|
||||
&& MediaCodecUtil.getPassthroughDecoderInfo() != null) {
|
||||
return RendererCapabilities.create(FORMAT_HANDLED, ADAPTIVE_NOT_SEAMLESS, tunnelingSupport);
|
||||
}
|
||||
if ((MimeTypes.AUDIO_RAW.equals(mimeType)
|
||||
|
|
@ -256,7 +258,10 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
return Collections.emptyList();
|
||||
}
|
||||
if (usePassthrough(format.channelCount, mimeType)) {
|
||||
return Collections.singletonList(MediaCodecUtil.getPassthroughDecoderInfo());
|
||||
@Nullable MediaCodecInfo codecInfo = MediaCodecUtil.getPassthroughDecoderInfo();
|
||||
if (codecInfo != null) {
|
||||
return Collections.singletonList(codecInfo);
|
||||
}
|
||||
}
|
||||
List<MediaCodecInfo> decoderInfos =
|
||||
mediaCodecSelector.getDecoderInfos(
|
||||
|
|
@ -273,19 +278,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
return Collections.unmodifiableList(decoderInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether encoded audio passthrough should be used for playing back the input format.
|
||||
*
|
||||
* @param channelCount The number of channels in the input media, or {@link Format#NO_VALUE} if
|
||||
* not known.
|
||||
* @param mimeType The type of input media.
|
||||
* @return Whether passthrough playback is supported.
|
||||
* @throws DecoderQueryException If there was an error querying the available passthrough
|
||||
* decoders.
|
||||
*/
|
||||
protected boolean usePassthrough(int channelCount, String mimeType) throws DecoderQueryException {
|
||||
return getPassthroughEncoding(channelCount, mimeType) != C.ENCODING_INVALID
|
||||
&& MediaCodecUtil.getPassthroughDecoderInfo() != null;
|
||||
@Override
|
||||
protected boolean usePassthrough(int channelCount, String mimeType) {
|
||||
return getPassthroughEncoding(channelCount, mimeType) != C.ENCODING_INVALID;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -423,20 +418,15 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
} else {
|
||||
channelMap = null;
|
||||
}
|
||||
configureAudioSink(encoding, channelCount, sampleRate, channelMap);
|
||||
}
|
||||
|
||||
try {
|
||||
audioSink.configure(
|
||||
encoding,
|
||||
channelCount,
|
||||
sampleRate,
|
||||
0,
|
||||
channelMap,
|
||||
inputFormat.encoderDelay,
|
||||
inputFormat.encoderPadding);
|
||||
} catch (AudioSink.ConfigurationException e) {
|
||||
// TODO(internal: b/145658993) Use outputFormat instead.
|
||||
throw createRendererException(e, inputFormat);
|
||||
}
|
||||
@Override
|
||||
protected void onOutputPassthroughFormatChanged(Format outputFormat) throws ExoPlaybackException {
|
||||
@C.Encoding
|
||||
int encoding = getPassthroughEncoding(outputFormat.channelCount, outputFormat.sampleMimeType);
|
||||
configureAudioSink(
|
||||
encoding, outputFormat.channelCount, outputFormat.sampleRate, /* channelMap= */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -602,7 +592,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
protected boolean processOutputBuffer(
|
||||
long positionUs,
|
||||
long elapsedRealtimeUs,
|
||||
MediaCodec codec,
|
||||
@Nullable MediaCodec codec,
|
||||
ByteBuffer buffer,
|
||||
int bufferIndex,
|
||||
int bufferFlags,
|
||||
|
|
@ -612,7 +602,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
boolean isLastBuffer,
|
||||
Format format)
|
||||
throws ExoPlaybackException {
|
||||
if (codecNeedsEosBufferTimestampWorkaround
|
||||
if (codec != null
|
||||
&& codecNeedsEosBufferTimestampWorkaround
|
||||
&& bufferPresentationTimeUs == 0
|
||||
&& (bufferFlags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
|
||||
&& getLargestQueuedPresentationTimeUs() != C.TIME_UNSET) {
|
||||
|
|
@ -626,7 +617,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
}
|
||||
|
||||
if (isDecodeOnlyBuffer) {
|
||||
codec.releaseOutputBuffer(bufferIndex, false);
|
||||
if (codec != null) {
|
||||
codec.releaseOutputBuffer(bufferIndex, false);
|
||||
}
|
||||
decoderCounters.skippedOutputBufferCount++;
|
||||
audioSink.handleDiscontinuity();
|
||||
return true;
|
||||
|
|
@ -641,7 +634,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
}
|
||||
|
||||
if (fullyConsumed) {
|
||||
codec.releaseOutputBuffer(bufferIndex, false);
|
||||
if (codec != null) {
|
||||
codec.releaseOutputBuffer(bufferIndex, false);
|
||||
}
|
||||
decoderCounters.renderedOutputBufferCount++;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -769,6 +764,24 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||
return mediaFormat;
|
||||
}
|
||||
|
||||
private void configureAudioSink(
|
||||
int encoding, int channelCount, int sampleRate, @Nullable int[] channelMap)
|
||||
throws ExoPlaybackException {
|
||||
try {
|
||||
audioSink.configure(
|
||||
encoding,
|
||||
channelCount,
|
||||
sampleRate,
|
||||
/* specifiedBufferSize= */ 0,
|
||||
channelMap,
|
||||
inputFormat.encoderDelay,
|
||||
inputFormat.encoderPadding);
|
||||
} catch (AudioSink.ConfigurationException e) {
|
||||
// TODO(internal: b/145658993) Use outputFormat instead.
|
||||
throw createRendererException(e, inputFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCurrentPosition() {
|
||||
long newCurrentPositionUs = audioSink.getCurrentPositionUs(isEnded());
|
||||
if (newCurrentPositionUs != AudioSink.CURRENT_POSITION_NOT_SET) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* 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 com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/** Buffer that stores multiple encoded access units to allow batch processing. */
|
||||
/* package */ final class BatchBuffer extends DecoderInputBuffer {
|
||||
/** Arbitrary limit to the number of access unit in a full batch buffer. */
|
||||
public static final int DEFAULT_BATCH_SIZE_ACCESS_UNITS = 32;
|
||||
/**
|
||||
* Arbitrary limit to the memory used by a full batch buffer to avoid using too much memory for
|
||||
* very high bitrate. 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 access unit to avoid stalling
|
||||
* stream with huge access units.
|
||||
*/
|
||||
private static final int BATCH_SIZE_BYTES = 3 * 1000 * 1024;
|
||||
|
||||
private final DecoderInputBuffer nextAccessUnitBuffer;
|
||||
private boolean hasPendingAccessUnit;
|
||||
|
||||
private long firstAccessUnitTimeUs;
|
||||
private int accessUnitCount;
|
||||
private int maxAccessUnitCount;
|
||||
|
||||
public BatchBuffer() {
|
||||
super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||
nextAccessUnitBuffer =
|
||||
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||
clear();
|
||||
}
|
||||
|
||||
/** Sets the maximum number of access units the buffer can contain before being full. */
|
||||
public void setMaxAccessUnitCount(@IntRange(from = 1) int maxAccessUnitCount) {
|
||||
Assertions.checkArgument(maxAccessUnitCount > 0);
|
||||
this.maxAccessUnitCount = maxAccessUnitCount;
|
||||
}
|
||||
|
||||
/** Gets the maximum number of access units the buffer can contain before being full. */
|
||||
public int getMaxAccessUnitCount() {
|
||||
return maxAccessUnitCount;
|
||||
}
|
||||
|
||||
/** Resets the state of this object to what it was after construction. */
|
||||
@Override
|
||||
public void clear() {
|
||||
flush();
|
||||
maxAccessUnitCount = DEFAULT_BATCH_SIZE_ACCESS_UNITS;
|
||||
}
|
||||
|
||||
/** Clear all access units from the BatchBuffer to empty it. */
|
||||
public void flush() {
|
||||
clearMainBuffer();
|
||||
nextAccessUnitBuffer.clear();
|
||||
hasPendingAccessUnit = false;
|
||||
}
|
||||
|
||||
/** Clears the state of the batch buffer to be ready to receive a new sequence of access units. */
|
||||
public void batchWasConsumed() {
|
||||
clearMainBuffer();
|
||||
if (hasPendingAccessUnit) {
|
||||
putAccessUnit(nextAccessUnitBuffer);
|
||||
hasPendingAccessUnit = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the buffer to fill-out that will then be append to the batch buffer with {@link
|
||||
* #commitNextAccessUnit()}.
|
||||
*/
|
||||
public DecoderInputBuffer getNextAccessUnitBuffer() {
|
||||
return nextAccessUnitBuffer;
|
||||
}
|
||||
|
||||
/** Gets the timestamp of the first access unit in the buffer. */
|
||||
public long getFirstAccessUnitTimeUs() {
|
||||
return firstAccessUnitTimeUs;
|
||||
}
|
||||
|
||||
/** Gets the timestamp of the last access unit in the buffer. */
|
||||
public long getLastAccessUnitTimeUs() {
|
||||
return timeUs;
|
||||
}
|
||||
|
||||
/** Gets the number of access units contained in this batch buffer. */
|
||||
public int getAccessUnitCount() {
|
||||
return accessUnitCount;
|
||||
}
|
||||
|
||||
/** If the buffer contains no access units. */
|
||||
public boolean isEmpty() {
|
||||
return accessUnitCount == 0;
|
||||
}
|
||||
|
||||
/** If more access units should be added to the batch buffer. */
|
||||
public boolean isFull() {
|
||||
return accessUnitCount >= maxAccessUnitCount
|
||||
|| (data != null && data.position() >= BATCH_SIZE_BYTES)
|
||||
|| hasPendingAccessUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the staged access unit in this batch buffer.
|
||||
*
|
||||
* @throws IllegalStateException If calling this method on a full or end of stream batch buffer.
|
||||
* @throws IllegalArgumentException If the {@code accessUnit} is encrypted or has
|
||||
* supplementalData, as batching of those state has not been implemented.
|
||||
*/
|
||||
public void commitNextAccessUnit() {
|
||||
DecoderInputBuffer accessUnit = nextAccessUnitBuffer;
|
||||
Assertions.checkState(!isFull() && !isEndOfStream());
|
||||
Assertions.checkArgument(!accessUnit.isEncrypted() && !accessUnit.hasSupplementalData());
|
||||
if (!canBatch(accessUnit)) {
|
||||
hasPendingAccessUnit = true; // Delay the putAccessUnit until the batch buffer is empty.
|
||||
return;
|
||||
}
|
||||
putAccessUnit(accessUnit);
|
||||
}
|
||||
|
||||
private boolean canBatch(DecoderInputBuffer accessUnit) {
|
||||
if (isEmpty()) {
|
||||
return true; // Batching with an empty batch must always succeed or the stream will stall.
|
||||
}
|
||||
if (accessUnit.isDecodeOnly() != isDecodeOnly()) {
|
||||
return false; // Decode only and non decode only access units can not be batched together.
|
||||
}
|
||||
|
||||
@Nullable ByteBuffer accessUnitData = accessUnit.data;
|
||||
if (accessUnitData != null
|
||||
&& this.data != null
|
||||
&& this.data.position() + accessUnitData.limit() >= BATCH_SIZE_BYTES) {
|
||||
return false; // The batch buffer does not have the capacity to add this access unit.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void putAccessUnit(DecoderInputBuffer accessUnit) {
|
||||
@Nullable ByteBuffer accessUnitData = accessUnit.data;
|
||||
if (accessUnitData != null) {
|
||||
accessUnit.flip();
|
||||
ensureSpaceForWrite(accessUnitData.remaining());
|
||||
this.data.put(accessUnitData);
|
||||
}
|
||||
|
||||
if (accessUnit.isEndOfStream()) {
|
||||
setFlags(C.BUFFER_FLAG_END_OF_STREAM);
|
||||
}
|
||||
if (accessUnit.isDecodeOnly()) {
|
||||
setFlags(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
}
|
||||
if (accessUnit.isKeyFrame()) {
|
||||
setFlags(C.BUFFER_FLAG_KEY_FRAME);
|
||||
}
|
||||
accessUnitCount++;
|
||||
timeUs = accessUnit.timeUs;
|
||||
if (accessUnitCount == 1) { // First read of the buffer
|
||||
firstAccessUnitTimeUs = timeUs;
|
||||
}
|
||||
accessUnit.clear();
|
||||
}
|
||||
|
||||
private void clearMainBuffer() {
|
||||
super.clear();
|
||||
accessUnitCount = 0;
|
||||
firstAccessUnitTimeUs = C.TIME_UNSET;
|
||||
timeUs = C.TIME_UNSET;
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
|
|||
import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||
import com.google.android.exoplayer2.util.TimedValueQueue;
|
||||
import com.google.android.exoplayer2.util.TraceUtil;
|
||||
|
|
@ -361,6 +362,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
private final float assumedMinimumCodecOperatingRate;
|
||||
private final DecoderInputBuffer buffer;
|
||||
private final DecoderInputBuffer flagsOnlyBuffer;
|
||||
private final BatchBuffer passthroughBatchBuffer;
|
||||
private final TimedValueQueue<Format> formatQueue;
|
||||
private final ArrayList<Long> decodeOnlyPresentationTimestamps;
|
||||
private final MediaCodec.BufferInfo outputBufferInfo;
|
||||
|
|
@ -401,6 +403,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
private ByteBuffer outputBuffer;
|
||||
private boolean isDecodeOnlyOutputBuffer;
|
||||
private boolean isLastOutputBuffer;
|
||||
private boolean passthroughEnabled;
|
||||
private boolean passthroughDrainAndReinitialize;
|
||||
private boolean codecReconfigured;
|
||||
@ReconfigurationState private int codecReconfigurationState;
|
||||
@DrainState private int codecDrainState;
|
||||
|
|
@ -453,6 +457,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
||||
pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
|
||||
outputStreamOffsetUs = C.TIME_UNSET;
|
||||
passthroughBatchBuffer = new BatchBuffer();
|
||||
resetCodecStateForRelease();
|
||||
}
|
||||
|
||||
|
|
@ -567,9 +572,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
@Nullable MediaCrypto crypto,
|
||||
float codecOperatingRate);
|
||||
|
||||
protected final void maybeInitCodec() throws ExoPlaybackException {
|
||||
if (codec != null || inputFormat == null) {
|
||||
// We have a codec already, or we don't have a format with which to instantiate one.
|
||||
protected final void maybeInitCodecOrPassthrough() throws ExoPlaybackException {
|
||||
if (codec != null || passthroughEnabled || inputFormat == null) {
|
||||
// We have a codec or using passthrough, or don't have a format to decide how to render.
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputFormat.drmInitData == null
|
||||
&& usePassthrough(inputFormat.channelCount, inputFormat.sampleMimeType)) {
|
||||
initPassthrough(inputFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -618,6 +629,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether encoded passthrough should be used for playing back the input format.
|
||||
*
|
||||
* @param channelCount The number of channels in the input media, or {@link Format#NO_VALUE} if
|
||||
* not known.
|
||||
* @param mimeType The type of input media.
|
||||
* @return Whether passthrough playback is supported.
|
||||
*/
|
||||
protected boolean usePassthrough(int channelCount, String mimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean shouldInitCodec(MediaCodecInfo codecInfo) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -695,7 +718,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
inputStreamEnded = false;
|
||||
outputStreamEnded = false;
|
||||
pendingOutputEndOfStream = false;
|
||||
flushOrReinitializeCodec();
|
||||
if (passthroughEnabled) {
|
||||
passthroughBatchBuffer.flush();
|
||||
} else {
|
||||
flushOrReinitializeCodec();
|
||||
}
|
||||
// If there is a format change on the input side still pending propagation to the output, we
|
||||
// need to queue a format next time a buffer is read. This is because we may not read a new
|
||||
// input format after the position reset.
|
||||
|
|
@ -735,12 +762,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
@Override
|
||||
protected void onReset() {
|
||||
try {
|
||||
disablePassthrough();
|
||||
releaseCodec();
|
||||
} finally {
|
||||
setSourceDrmSession(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void disablePassthrough() {
|
||||
passthroughDrainAndReinitialize = false;
|
||||
passthroughBatchBuffer.clear();
|
||||
passthroughEnabled = false;
|
||||
}
|
||||
|
||||
protected void releaseCodec() {
|
||||
try {
|
||||
if (codecAdapter != null) {
|
||||
|
|
@ -791,8 +825,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
return;
|
||||
}
|
||||
// We have a format.
|
||||
maybeInitCodec();
|
||||
if (codec != null) {
|
||||
maybeInitCodecOrPassthrough();
|
||||
if (passthroughEnabled) {
|
||||
TraceUtil.beginSection("renderPassthrough");
|
||||
while (renderPassthrough(positionUs, elapsedRealtimeUs)) {}
|
||||
TraceUtil.endSection();
|
||||
} else if (codec != null) {
|
||||
long renderStartTimeMs = SystemClock.elapsedRealtime();
|
||||
TraceUtil.beginSection("drainAndFeed");
|
||||
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)
|
||||
|
|
@ -821,7 +859,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
* This method is a no-op if the codec is {@code null}.
|
||||
*
|
||||
* <p>The implementation of this method calls {@link #flushOrReleaseCodec()}, and {@link
|
||||
* #maybeInitCodec()} if the codec needs to be re-instantiated.
|
||||
* #maybeInitCodecOrPassthrough()} if the codec needs to be re-instantiated.
|
||||
*
|
||||
* @return Whether the codec was released and reinitialized, rather than being flushed.
|
||||
* @throws ExoPlaybackException If an error occurs re-instantiating the codec.
|
||||
|
|
@ -829,7 +867,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
protected final boolean flushOrReinitializeCodec() throws ExoPlaybackException {
|
||||
boolean released = flushOrReleaseCodec();
|
||||
if (released) {
|
||||
maybeInitCodec();
|
||||
maybeInitCodecOrPassthrough();
|
||||
}
|
||||
return released;
|
||||
}
|
||||
|
|
@ -1022,6 +1060,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
return codecInfos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures passthrough where no codec is used. Called instead of {@link
|
||||
* #configureCodec(MediaCodecInfo, MediaCodec, Format, MediaCrypto, float)} when no codec is used
|
||||
* in passthrough.
|
||||
*/
|
||||
private void initPassthrough(Format format) {
|
||||
disablePassthrough(); // In case of transition between 2 passthrough formats.
|
||||
|
||||
String mimeType = format.sampleMimeType;
|
||||
if (!MimeTypes.AUDIO_AAC.equals(mimeType)
|
||||
&& !MimeTypes.AUDIO_MPEG.equals(mimeType)
|
||||
&& !MimeTypes.AUDIO_OPUS.equals(mimeType)) {
|
||||
// TODO(b/154746451): Batching provokes frame drops in non offload passthrough.
|
||||
passthroughBatchBuffer.setMaxAccessUnitCount(1);
|
||||
} else {
|
||||
passthroughBatchBuffer.setMaxAccessUnitCount(BatchBuffer.DEFAULT_BATCH_SIZE_ACCESS_UNITS);
|
||||
}
|
||||
passthroughEnabled = true;
|
||||
}
|
||||
|
||||
private void initCodec(MediaCodecInfo codecInfo, MediaCrypto crypto) throws Exception {
|
||||
long codecInitializingTimestamp;
|
||||
long codecInitializedTimestamp;
|
||||
|
|
@ -1373,13 +1431,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
setSourceDrmSession(formatHolder.drmSession);
|
||||
inputFormat = newFormat;
|
||||
|
||||
if (passthroughEnabled) {
|
||||
passthroughDrainAndReinitialize = true;
|
||||
return; // Need to drain passthrough first.
|
||||
}
|
||||
|
||||
if (codec == null) {
|
||||
maybeInitCodec();
|
||||
maybeInitCodecOrPassthrough();
|
||||
return;
|
||||
}
|
||||
|
||||
// We have an existing codec that we may need to reconfigure or re-initialize. If the existing
|
||||
// codec instance is being kept then its operating rate may need to be updated.
|
||||
// We have an existing codec that we may need to reconfigure or re-initialize or release it to
|
||||
// switch to passthrough. If the existing codec instance is being kept then its operating rate
|
||||
// may need to be updated.
|
||||
|
||||
if ((sourceDrmSession == null && codecDrmSession != null)
|
||||
|| (sourceDrmSession != null && codecDrmSession == null)
|
||||
|
|
@ -1473,6 +1537,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the output {@link Format} changes in passthrough.
|
||||
*
|
||||
* <p>The default implementation is a no-op.
|
||||
*
|
||||
* @param outputFormat The new output {@link MediaFormat}.
|
||||
* @throws ExoPlaybackException Thrown if an error occurs handling the new output media format.
|
||||
*/
|
||||
// TODO(b/154849417): merge with {@link #onOutputFormatChanged(Format)}.
|
||||
protected void onOutputPassthroughFormatChanged(Format outputFormat) throws ExoPlaybackException {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles supplemental data associated with an input buffer.
|
||||
*
|
||||
|
|
@ -1821,7 +1898,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
* iteration of the rendering loop.
|
||||
* @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the
|
||||
* start of the current iteration of the rendering loop.
|
||||
* @param codec The {@link MediaCodec} instance.
|
||||
* @param codec The {@link MediaCodec} instance, or null in passthrough mode.
|
||||
* @param buffer The output buffer to process.
|
||||
* @param bufferIndex The index of the output buffer.
|
||||
* @param bufferFlags The flags attached to the output buffer.
|
||||
|
|
@ -1838,7 +1915,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
protected abstract boolean processOutputBuffer(
|
||||
long positionUs,
|
||||
long elapsedRealtimeUs,
|
||||
MediaCodec codec,
|
||||
@Nullable MediaCodec codec,
|
||||
ByteBuffer buffer,
|
||||
int bufferIndex,
|
||||
int bufferFlags,
|
||||
|
|
@ -1950,7 +2027,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
|
||||
private void reinitializeCodec() throws ExoPlaybackException {
|
||||
releaseCodec();
|
||||
maybeInitCodec();
|
||||
maybeInitCodecOrPassthrough();
|
||||
}
|
||||
|
||||
private boolean isDecodeOnlyBuffer(long presentationTimeUs) {
|
||||
|
|
@ -2016,6 +2093,116 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||
return (FrameworkMediaCrypto) mediaCrypto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes any pending batch of buffers without using a decoder, and drains a new batch of
|
||||
* buffers from the source.
|
||||
*
|
||||
* @param positionUs The current media time in microseconds, measured at the start of the current
|
||||
* iteration of the rendering loop.
|
||||
* @param elapsedRealtimeUs {@link SystemClock#elapsedRealtime()} in microseconds, measured at the
|
||||
* start of the current iteration of the rendering loop.
|
||||
* @return If more buffers are ready to be rendered.
|
||||
* @throws ExoPlaybackException If an error occurred while processing a buffer or handling a
|
||||
* format change.
|
||||
*/
|
||||
private boolean renderPassthrough(long positionUs, long elapsedRealtimeUs)
|
||||
throws ExoPlaybackException {
|
||||
BatchBuffer batchBuffer = passthroughBatchBuffer;
|
||||
|
||||
// Let's process the pending buffer if any.
|
||||
Assertions.checkState(!outputStreamEnded);
|
||||
if (!batchBuffer.isEmpty()) { // Optimisation: Do not process buffer if empty.
|
||||
if (processOutputBuffer(
|
||||
positionUs,
|
||||
elapsedRealtimeUs,
|
||||
/* codec= */ null,
|
||||
batchBuffer.data,
|
||||
outputIndex,
|
||||
/* bufferFlags= */ 0,
|
||||
batchBuffer.getAccessUnitCount(),
|
||||
batchBuffer.getFirstAccessUnitTimeUs(),
|
||||
batchBuffer.isDecodeOnly(),
|
||||
batchBuffer.isEndOfStream(),
|
||||
outputFormat)) {
|
||||
// Buffer completely processed
|
||||
onProcessedOutputBuffer(batchBuffer.getLastAccessUnitTimeUs());
|
||||
} else {
|
||||
return false; // Could not process buffer, let's try later.
|
||||
}
|
||||
}
|
||||
if (batchBuffer.isEndOfStream()) {
|
||||
outputStreamEnded = true;
|
||||
return false;
|
||||
}
|
||||
batchBuffer.batchWasConsumed();
|
||||
|
||||
if (passthroughDrainAndReinitialize) {
|
||||
if (!batchBuffer.isEmpty()) {
|
||||
return true; // Drain the batch buffer before propagating the format change.
|
||||
}
|
||||
disablePassthrough(); // The new format might not be supported in passthrough.
|
||||
passthroughDrainAndReinitialize = false;
|
||||
maybeInitCodecOrPassthrough();
|
||||
if (!passthroughEnabled) {
|
||||
return false; // The new format is not supported in passthrough.
|
||||
}
|
||||
}
|
||||
|
||||
// Now refill the empty buffer for the next iteration.
|
||||
Assertions.checkState(!inputStreamEnded);
|
||||
FormatHolder formatHolder = getFormatHolder();
|
||||
boolean formatChange = readBatchFromSource(formatHolder, batchBuffer);
|
||||
|
||||
if (!batchBuffer.isEmpty() && waitingForFirstSampleInFormat) {
|
||||
// This is the first buffer in a new format, the output format must be updated.
|
||||
outputFormat = Assertions.checkNotNull(inputFormat);
|
||||
onOutputPassthroughFormatChanged(outputFormat);
|
||||
waitingForFirstSampleInFormat = false;
|
||||
}
|
||||
|
||||
if (formatChange) {
|
||||
onInputFormatChanged(formatHolder);
|
||||
}
|
||||
|
||||
if (batchBuffer.isEndOfStream()) {
|
||||
inputStreamEnded = true;
|
||||
}
|
||||
|
||||
if (batchBuffer.isEmpty()) {
|
||||
return false; // The buffer could not be filled, there is nothing more to do.
|
||||
}
|
||||
batchBuffer.flip(); // Buffer at least partially full, it can now be processed.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the buffer with multiple access unit from the source. Has otherwise the same semantic as
|
||||
* {@link #readSource(FormatHolder, DecoderInputBuffer, boolean)}. Will stop early on format
|
||||
* change, EOS or source starvation.
|
||||
*
|
||||
* @return If the format has changed.
|
||||
*/
|
||||
private boolean readBatchFromSource(FormatHolder formatHolder, BatchBuffer batchBuffer) {
|
||||
while (!batchBuffer.isFull() && !batchBuffer.isEndOfStream()) {
|
||||
@SampleStream.ReadDataResult
|
||||
int result =
|
||||
readSource(
|
||||
formatHolder, batchBuffer.getNextAccessUnitBuffer(), /* formatRequired= */ false);
|
||||
switch (result) {
|
||||
case C.RESULT_FORMAT_READ:
|
||||
return true;
|
||||
case C.RESULT_NOTHING_READ:
|
||||
return false;
|
||||
case C.RESULT_BUFFER_READ:
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(); // Unsupported result
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isMediaCodecException(IllegalStateException error) {
|
||||
if (Util.SDK_INT >= 21 && isMediaCodecExceptionV21(error)) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -514,7 +514,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||
setOutputSurfaceV23(codec, surface);
|
||||
} else {
|
||||
releaseCodec();
|
||||
maybeInitCodec();
|
||||
maybeInitCodecOrPassthrough();
|
||||
}
|
||||
}
|
||||
if (surface != null && surface != dummySurface) {
|
||||
|
|
@ -753,7 +753,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||
protected boolean processOutputBuffer(
|
||||
long positionUs,
|
||||
long elapsedRealtimeUs,
|
||||
MediaCodec codec,
|
||||
@Nullable MediaCodec codec,
|
||||
ByteBuffer buffer,
|
||||
int bufferIndex,
|
||||
int bufferFlags,
|
||||
|
|
@ -763,6 +763,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||
boolean isLastBuffer,
|
||||
Format format)
|
||||
throws ExoPlaybackException {
|
||||
Assertions.checkNotNull(codec); // Can not render video without codec
|
||||
|
||||
if (initialPositionUs == C.TIME_UNSET) {
|
||||
initialPositionUs = positionUs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* 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 com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Unit tests for {@link BatchBuffer}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class BatchBufferTest {
|
||||
|
||||
/** Bigger than {@link BatchBuffer#BATCH_SIZE_BYTES} */
|
||||
private static final int BUFFER_SIZE_LARGER_THAN_BATCH_SIZE_BYTES = 100 * 1000 * 1000;
|
||||
/** Smaller than {@link BatchBuffer#BATCH_SIZE_BYTES} */
|
||||
private static final int BUFFER_SIZE_MUCH_SMALLER_THAN_BATCH_SIZE_BYTES = 100;
|
||||
|
||||
private static final byte[] TEST_ACCESS_UNIT =
|
||||
TestUtil.buildTestData(BUFFER_SIZE_MUCH_SMALLER_THAN_BATCH_SIZE_BYTES);
|
||||
private static final byte[] TEST_HUGE_ACCESS_UNIT =
|
||||
TestUtil.buildTestData(BUFFER_SIZE_LARGER_THAN_BATCH_SIZE_BYTES);
|
||||
|
||||
private final BatchBuffer batchBuffer = new BatchBuffer();
|
||||
|
||||
@Test
|
||||
public void newBatchBuffer_isEmpty() {
|
||||
assertIsCleared(batchBuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clear_empty_isEmpty() {
|
||||
batchBuffer.clear();
|
||||
|
||||
assertIsCleared(batchBuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clear_afterInsertingAccessUnit_isEmpty() {
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
batchBuffer.clear();
|
||||
|
||||
assertIsCleared(batchBuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_addsAccessUnit() {
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
assertThat(batchBuffer.getAccessUnitCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_untilFull_isFullAndNotEmpty() {
|
||||
fillBatchBuffer(batchBuffer);
|
||||
|
||||
assertThat(batchBuffer.isEmpty()).isFalse();
|
||||
assertThat(batchBuffer.isFull()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_whenFull_throws() {
|
||||
batchBuffer.setMaxAccessUnitCount(1);
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
assertThrows(IllegalStateException.class, batchBuffer::commitNextAccessUnit);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_whenAccessUnitIsDecodeOnly_isDecodeOnly() {
|
||||
batchBuffer.getNextAccessUnitBuffer().setFlags(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
assertThat(batchBuffer.isDecodeOnly()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_whenAccessUnitIsEndOfStream_isEndOfSteam() {
|
||||
batchBuffer.getNextAccessUnitBuffer().setFlags(C.BUFFER_FLAG_END_OF_STREAM);
|
||||
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
assertThat(batchBuffer.isEndOfStream()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_whenAccessUnitIsKeyFrame_isKeyFrame() {
|
||||
batchBuffer.getNextAccessUnitBuffer().setFlags(C.BUFFER_FLAG_KEY_FRAME);
|
||||
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
assertThat(batchBuffer.isKeyFrame()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_withData_dataIsCopiedInTheBatch() {
|
||||
batchBuffer.getNextAccessUnitBuffer().ensureSpaceForWrite(TEST_ACCESS_UNIT.length);
|
||||
batchBuffer.getNextAccessUnitBuffer().data.put(TEST_ACCESS_UNIT);
|
||||
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
batchBuffer.flip();
|
||||
|
||||
assertThat(batchBuffer.getAccessUnitCount()).isEqualTo(1);
|
||||
assertThat(batchBuffer.data).isEqualTo(ByteBuffer.wrap(TEST_ACCESS_UNIT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_nextAccessUnit_isClear() {
|
||||
batchBuffer.getNextAccessUnitBuffer().ensureSpaceForWrite(TEST_ACCESS_UNIT.length);
|
||||
batchBuffer.getNextAccessUnitBuffer().data.put(TEST_ACCESS_UNIT);
|
||||
batchBuffer.getNextAccessUnitBuffer().setFlags(C.BUFFER_FLAG_KEY_FRAME);
|
||||
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
DecoderInputBuffer nextAccessUnit = batchBuffer.getNextAccessUnitBuffer();
|
||||
assertThat(nextAccessUnit.data).isNotNull();
|
||||
assertThat(nextAccessUnit.data.position()).isEqualTo(0);
|
||||
assertThat(nextAccessUnit.isKeyFrame()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_twice_bothAccessUnitAreConcatenated() {
|
||||
// Commit TEST_ACCESS_UNIT
|
||||
batchBuffer.getNextAccessUnitBuffer().ensureSpaceForWrite(TEST_ACCESS_UNIT.length);
|
||||
batchBuffer.getNextAccessUnitBuffer().data.put(TEST_ACCESS_UNIT);
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
// Commit TEST_ACCESS_UNIT again
|
||||
batchBuffer.getNextAccessUnitBuffer().ensureSpaceForWrite(TEST_ACCESS_UNIT.length);
|
||||
batchBuffer.getNextAccessUnitBuffer().data.put(TEST_ACCESS_UNIT);
|
||||
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
batchBuffer.flip();
|
||||
|
||||
byte[] expected = TestUtil.joinByteArrays(TEST_ACCESS_UNIT, TEST_ACCESS_UNIT);
|
||||
assertThat(batchBuffer.data).isEqualTo(ByteBuffer.wrap(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void commitNextAccessUnit_whenAccessUnitIsHugeAndBatchBufferNotEmpty_isMarkedPending() {
|
||||
batchBuffer.getNextAccessUnitBuffer().ensureSpaceForWrite(TEST_ACCESS_UNIT.length);
|
||||
batchBuffer.getNextAccessUnitBuffer().data.put(TEST_ACCESS_UNIT);
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
batchBuffer.getNextAccessUnitBuffer().ensureSpaceForWrite(TEST_HUGE_ACCESS_UNIT.length);
|
||||
batchBuffer.getNextAccessUnitBuffer().data.put(TEST_HUGE_ACCESS_UNIT);
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
batchBuffer.batchWasConsumed();
|
||||
batchBuffer.flip();
|
||||
|
||||
assertThat(batchBuffer.getAccessUnitCount()).isEqualTo(1);
|
||||
assertThat(batchBuffer.data).isEqualTo(ByteBuffer.wrap(TEST_HUGE_ACCESS_UNIT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batchWasConsumed_whenNotEmpty_isEmpty() {
|
||||
fillBatchBuffer(batchBuffer);
|
||||
|
||||
batchBuffer.batchWasConsumed();
|
||||
|
||||
assertIsCleared(batchBuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batchWasConsumed_whenFull_isEmpty() {
|
||||
fillBatchBuffer(batchBuffer);
|
||||
|
||||
batchBuffer.batchWasConsumed();
|
||||
|
||||
assertIsCleared(batchBuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMaxAccessUnitCount_whenSetToAPositiveValue_returnsIt() {
|
||||
batchBuffer.setMaxAccessUnitCount(20);
|
||||
|
||||
assertThat(batchBuffer.getMaxAccessUnitCount()).isEqualTo(20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMaxAccessUnitCount_whenSetToNegative_throws() {
|
||||
assertThrows(IllegalArgumentException.class, () -> batchBuffer.setMaxAccessUnitCount(-19));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMaxAccessUnitCount_whenSetToZero_throws() {
|
||||
assertThrows(IllegalArgumentException.class, () -> batchBuffer.setMaxAccessUnitCount(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMaxAccessUnitCount_whenSetToTheNumberOfAccessUnitInTheBatch_isFull() {
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
batchBuffer.setMaxAccessUnitCount(1);
|
||||
|
||||
assertThat(batchBuffer.isFull()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void batchWasConsumed_whenAccessUnitIsPending_pendingAccessUnitIsInTheBatch() {
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
batchBuffer.getNextAccessUnitBuffer().setFlags(C.BUFFER_FLAG_DECODE_ONLY);
|
||||
batchBuffer.getNextAccessUnitBuffer().ensureSpaceForWrite(TEST_ACCESS_UNIT.length);
|
||||
batchBuffer.getNextAccessUnitBuffer().data.put(TEST_ACCESS_UNIT);
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
|
||||
batchBuffer.batchWasConsumed();
|
||||
batchBuffer.flip();
|
||||
|
||||
assertThat(batchBuffer.getAccessUnitCount()).isEqualTo(1);
|
||||
assertThat(batchBuffer.isDecodeOnly()).isTrue();
|
||||
assertThat(batchBuffer.data).isEqualTo(ByteBuffer.wrap(TEST_ACCESS_UNIT));
|
||||
}
|
||||
|
||||
private static void fillBatchBuffer(BatchBuffer batchBuffer) {
|
||||
int maxAccessUnit = batchBuffer.getMaxAccessUnitCount();
|
||||
while (!batchBuffer.isFull()) {
|
||||
assertThat(maxAccessUnit--).isNotEqualTo(0);
|
||||
batchBuffer.commitNextAccessUnit();
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertIsCleared(BatchBuffer batchBuffer) {
|
||||
assertThat(batchBuffer.getFirstAccessUnitTimeUs()).isEqualTo(C.TIME_UNSET);
|
||||
assertThat(batchBuffer.getLastAccessUnitTimeUs()).isEqualTo(C.TIME_UNSET);
|
||||
assertThat(batchBuffer.getAccessUnitCount()).isEqualTo(0);
|
||||
assertThat(batchBuffer.isEmpty()).isTrue();
|
||||
assertThat(batchBuffer.isFull()).isFalse();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue