mirror of
https://github.com/androidx/media.git
synced 2025-05-03 21:57:46 +08:00
Enable MediaCodec asynchronous mode
Enable using MediaCodec in async mode. Expose experimental API to enable/disable the feature. PiperOrigin-RevId: 283309798
This commit is contained in:
parent
b68d19bceb
commit
aceba835cc
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* 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.mediacodec;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
import com.google.android.exoplayer2.util.IntArrayQueue;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
|
||||||
|
/** Handles the asynchronous callbacks from {@link android.media.MediaCodec.Callback}. */
|
||||||
|
@RequiresApi(21)
|
||||||
|
/* package */ final class MediaCodecAsyncCallback extends MediaCodec.Callback {
|
||||||
|
private final IntArrayQueue availableInputBuffers;
|
||||||
|
private final IntArrayQueue availableOutputBuffers;
|
||||||
|
private final ArrayDeque<MediaCodec.BufferInfo> bufferInfos;
|
||||||
|
private final ArrayDeque<MediaFormat> formats;
|
||||||
|
@Nullable private MediaFormat currentFormat;
|
||||||
|
@Nullable private IllegalStateException mediaCodecException;
|
||||||
|
|
||||||
|
/** Creates a new MediaCodecAsyncCallback. */
|
||||||
|
public MediaCodecAsyncCallback() {
|
||||||
|
availableInputBuffers = new IntArrayQueue();
|
||||||
|
availableOutputBuffers = new IntArrayQueue();
|
||||||
|
bufferInfos = new ArrayDeque<>();
|
||||||
|
formats = new ArrayDeque<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next available input buffer index or {@link MediaCodec#INFO_TRY_AGAIN_LATER} if no
|
||||||
|
* such buffer exists.
|
||||||
|
*/
|
||||||
|
public int dequeueInputBufferIndex() {
|
||||||
|
return availableInputBuffers.isEmpty()
|
||||||
|
? MediaCodec.INFO_TRY_AGAIN_LATER
|
||||||
|
: availableInputBuffers.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next available output buffer index. If the next available output is a MediaFormat
|
||||||
|
* change, it will return {@link MediaCodec#INFO_OUTPUT_FORMAT_CHANGED} and you should call {@link
|
||||||
|
* #getOutputFormat()} to get the format. If there is no available output, this method will return
|
||||||
|
* {@link MediaCodec#INFO_TRY_AGAIN_LATER}.
|
||||||
|
*/
|
||||||
|
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
if (availableOutputBuffers.isEmpty()) {
|
||||||
|
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||||
|
} else {
|
||||||
|
int bufferIndex = availableOutputBuffers.remove();
|
||||||
|
if (bufferIndex >= 0) {
|
||||||
|
MediaCodec.BufferInfo nextBufferInfo = bufferInfos.remove();
|
||||||
|
bufferInfo.set(
|
||||||
|
nextBufferInfo.offset,
|
||||||
|
nextBufferInfo.size,
|
||||||
|
nextBufferInfo.presentationTimeUs,
|
||||||
|
nextBufferInfo.flags);
|
||||||
|
} else if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||||
|
currentFormat = formats.remove();
|
||||||
|
}
|
||||||
|
return bufferIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link MediaFormat} signalled by the underlying {@link MediaCodec}.
|
||||||
|
*
|
||||||
|
* <p>Call this <b>after</b> {@link #dequeueOutputBufferIndex} returned {@link
|
||||||
|
* MediaCodec#INFO_OUTPUT_FORMAT_CHANGED}.
|
||||||
|
*
|
||||||
|
* @throws {@link IllegalStateException} if you call this method before before {
|
||||||
|
* @link #dequeueOutputBufferIndex} returned {@link MediaCodec#INFO_OUTPUT_FORMAT_CHANGED}.
|
||||||
|
*/
|
||||||
|
public MediaFormat getOutputFormat() throws IllegalStateException {
|
||||||
|
if (currentFormat == null) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and throws an {@link IllegalStateException} if an error was previously set on this
|
||||||
|
* instance via {@link #onError}.
|
||||||
|
*/
|
||||||
|
public void maybeThrowMediaCodecException() throws IllegalStateException {
|
||||||
|
IllegalStateException exception = mediaCodecException;
|
||||||
|
mediaCodecException = null;
|
||||||
|
|
||||||
|
if (exception != null) {
|
||||||
|
throw exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the MediaCodecAsyncCallback. This method removes all available input and output buffers
|
||||||
|
* and any error that was previously set.
|
||||||
|
*/
|
||||||
|
public void flush() {
|
||||||
|
availableInputBuffers.clear();
|
||||||
|
availableOutputBuffers.clear();
|
||||||
|
bufferInfos.clear();
|
||||||
|
formats.clear();
|
||||||
|
mediaCodecException = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int i) {
|
||||||
|
availableInputBuffers.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOutputBufferAvailable(
|
||||||
|
@NonNull MediaCodec mediaCodec, int i, @NonNull MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
availableOutputBuffers.add(i);
|
||||||
|
bufferInfos.add(bufferInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
|
||||||
|
onMediaCodecError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOutputFormatChanged(
|
||||||
|
@NonNull MediaCodec mediaCodec, @NonNull MediaFormat mediaFormat) {
|
||||||
|
availableOutputBuffers.add(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||||
|
formats.add(mediaFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting()
|
||||||
|
void onMediaCodecError(IllegalStateException e) {
|
||||||
|
mediaCodecException = e;
|
||||||
|
}
|
||||||
|
}
|
@ -23,10 +23,13 @@ import android.media.MediaCrypto;
|
|||||||
import android.media.MediaCryptoException;
|
import android.media.MediaCryptoException;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import com.google.android.exoplayer2.BaseRenderer;
|
import com.google.android.exoplayer2.BaseRenderer;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
@ -289,6 +292,51 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
ADAPTATION_WORKAROUND_MODE_ALWAYS
|
ADAPTATION_WORKAROUND_MODE_ALWAYS
|
||||||
})
|
})
|
||||||
private @interface AdaptationWorkaroundMode {}
|
private @interface AdaptationWorkaroundMode {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstracts {@link MediaCodec} operations that differ whether a {@link MediaCodec} is used in
|
||||||
|
* synchronous or asynchronous mode.
|
||||||
|
*/
|
||||||
|
private interface MediaCodecAdapter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next available input buffer index from the underlying {@link MediaCodec} or
|
||||||
|
* {@link MediaCodec#INFO_TRY_AGAIN_LATER} if no such buffer exists.
|
||||||
|
*
|
||||||
|
* @throws {@link IllegalStateException} if the underling {@link MediaCodec} raised an error.
|
||||||
|
*/
|
||||||
|
int dequeueInputBufferIndex();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next available output buffer index from the underlying {@link MediaCodec}. If the
|
||||||
|
* next available output is a MediaFormat change, it will return {@link
|
||||||
|
* MediaCodec#INFO_OUTPUT_FORMAT_CHANGED} and you should call {@link #getOutputFormat()} to get
|
||||||
|
* the format. If there is no available output, this method will return {@link
|
||||||
|
* MediaCodec#INFO_TRY_AGAIN_LATER}.
|
||||||
|
*
|
||||||
|
* @throws {@link IllegalStateException} if the underling {@link MediaCodec} raised an error.
|
||||||
|
*/
|
||||||
|
int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link MediaFormat} that was output from the {@link MediaCodec}.
|
||||||
|
*
|
||||||
|
* <p>Call this method if a previous call to {@link #dequeueOutputBufferIndex} returned {@link
|
||||||
|
* MediaCodec#INFO_OUTPUT_FORMAT_CHANGED}.
|
||||||
|
*/
|
||||||
|
MediaFormat getOutputFormat();
|
||||||
|
|
||||||
|
/** Flushes the {@code MediaCodecAdapter}. */
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shutdown the {@code MediaCodecAdapter}.
|
||||||
|
*
|
||||||
|
* <p>Note: it does not release the underlying codec.
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The adaptation workaround is never used.
|
* The adaptation workaround is never used.
|
||||||
*/
|
*/
|
||||||
@ -336,6 +384,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
private long renderTimeLimitMs;
|
private long renderTimeLimitMs;
|
||||||
private float rendererOperatingRate;
|
private float rendererOperatingRate;
|
||||||
@Nullable private MediaCodec codec;
|
@Nullable private MediaCodec codec;
|
||||||
|
@Nullable private MediaCodecAdapter codecAdapter;
|
||||||
@Nullable private Format codecFormat;
|
@Nullable private Format codecFormat;
|
||||||
private float codecOperatingRate;
|
private float codecOperatingRate;
|
||||||
@Nullable private ArrayDeque<MediaCodecInfo> availableCodecInfos;
|
@Nullable private ArrayDeque<MediaCodecInfo> availableCodecInfos;
|
||||||
@ -375,6 +424,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
private boolean skipMediaCodecStopOnRelease;
|
private boolean skipMediaCodecStopOnRelease;
|
||||||
private boolean pendingOutputEndOfStream;
|
private boolean pendingOutputEndOfStream;
|
||||||
|
|
||||||
|
private boolean useMediaCodecInAsyncMode;
|
||||||
|
|
||||||
protected DecoderCounters decoderCounters;
|
protected DecoderCounters decoderCounters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -451,6 +502,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
skipMediaCodecStopOnRelease = enabled;
|
skipMediaCodecStopOnRelease = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the underlying {@link MediaCodec} in asynchronous mode to obtain available input and output
|
||||||
|
* buffers.
|
||||||
|
*
|
||||||
|
* <p>This method is experimental, and will be renamed or removed in a future release. It should
|
||||||
|
* only be called before the renderer is used.
|
||||||
|
*
|
||||||
|
* @param enabled enable of disable the feature.
|
||||||
|
*/
|
||||||
|
public void experimental_setUseMediaCodecInAsyncMode(boolean enabled) {
|
||||||
|
useMediaCodecInAsyncMode = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int supportsMixedMimeTypeAdaptation() {
|
public final int supportsMixedMimeTypeAdaptation() {
|
||||||
return ADAPTIVE_NOT_SEAMLESS;
|
return ADAPTIVE_NOT_SEAMLESS;
|
||||||
@ -664,6 +728,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
codec = null;
|
codec = null;
|
||||||
|
if (codecAdapter != null) {
|
||||||
|
codecAdapter.shutdown();
|
||||||
|
codecAdapter = null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (mediaCrypto != null) {
|
if (mediaCrypto != null) {
|
||||||
mediaCrypto.release();
|
mediaCrypto.release();
|
||||||
@ -763,7 +831,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
codec.flush();
|
codecAdapter.flush();
|
||||||
resetInputBuffer();
|
resetInputBuffer();
|
||||||
resetOutputBuffer();
|
resetOutputBuffer();
|
||||||
codecHotswapDeadlineMs = C.TIME_UNSET;
|
codecHotswapDeadlineMs = C.TIME_UNSET;
|
||||||
@ -908,10 +976,18 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
if (codecOperatingRate <= assumedMinimumCodecOperatingRate) {
|
if (codecOperatingRate <= assumedMinimumCodecOperatingRate) {
|
||||||
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
codecOperatingRate = CODEC_OPERATING_RATE_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MediaCodecAdapter codecAdapter = null;
|
||||||
try {
|
try {
|
||||||
codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
codecInitializingTimestamp = SystemClock.elapsedRealtime();
|
||||||
TraceUtil.beginSection("createCodec:" + codecName);
|
TraceUtil.beginSection("createCodec:" + codecName);
|
||||||
codec = MediaCodec.createByCodecName(codecName);
|
codec = MediaCodec.createByCodecName(codecName);
|
||||||
|
if (useMediaCodecInAsyncMode && Util.SDK_INT >= 21) {
|
||||||
|
codecAdapter = new AsynchronousMediaCodecAdapter(codec);
|
||||||
|
} else {
|
||||||
|
codecAdapter = new SynchronousMediaCodecAdapter(codec, getDequeueOutputBufferTimeoutUs());
|
||||||
|
}
|
||||||
|
|
||||||
TraceUtil.endSection();
|
TraceUtil.endSection();
|
||||||
TraceUtil.beginSection("configureCodec");
|
TraceUtil.beginSection("configureCodec");
|
||||||
configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate);
|
configureCodec(codecInfo, codec, inputFormat, crypto, codecOperatingRate);
|
||||||
@ -922,6 +998,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
||||||
getCodecBuffers(codec);
|
getCodecBuffers(codec);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if (codecAdapter != null) {
|
||||||
|
codecAdapter.shutdown();
|
||||||
|
}
|
||||||
if (codec != null) {
|
if (codec != null) {
|
||||||
resetCodecBuffers();
|
resetCodecBuffers();
|
||||||
codec.release();
|
codec.release();
|
||||||
@ -930,6 +1009,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.codec = codec;
|
this.codec = codec;
|
||||||
|
this.codecAdapter = codecAdapter;
|
||||||
this.codecInfo = codecInfo;
|
this.codecInfo = codecInfo;
|
||||||
this.codecOperatingRate = codecOperatingRate;
|
this.codecOperatingRate = codecOperatingRate;
|
||||||
codecFormat = inputFormat;
|
codecFormat = inputFormat;
|
||||||
@ -1036,7 +1116,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (inputIndex < 0) {
|
if (inputIndex < 0) {
|
||||||
inputIndex = codec.dequeueInputBuffer(0);
|
inputIndex = codecAdapter.dequeueInputBufferIndex();
|
||||||
if (inputIndex < 0) {
|
if (inputIndex < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1489,8 +1569,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
int outputIndex;
|
int outputIndex;
|
||||||
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
|
if (codecNeedsEosOutputExceptionWorkaround && codecReceivedEos) {
|
||||||
try {
|
try {
|
||||||
outputIndex =
|
outputIndex = codecAdapter.dequeueOutputBufferIndex(outputBufferInfo);
|
||||||
codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
|
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
processEndOfStream();
|
processEndOfStream();
|
||||||
if (outputStreamEnded) {
|
if (outputStreamEnded) {
|
||||||
@ -1500,8 +1579,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outputIndex =
|
outputIndex = codecAdapter.dequeueOutputBufferIndex(outputBufferInfo);
|
||||||
codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputIndex < 0) {
|
if (outputIndex < 0) {
|
||||||
@ -1599,7 +1677,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
|
|
||||||
/** Processes a new output {@link MediaFormat}. */
|
/** Processes a new output {@link MediaFormat}. */
|
||||||
private void processOutputFormat() throws ExoPlaybackException {
|
private void processOutputFormat() throws ExoPlaybackException {
|
||||||
MediaFormat mediaFormat = codec.getOutputFormat();
|
MediaFormat mediaFormat = codecAdapter.getOutputFormat();
|
||||||
if (codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER
|
if (codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER
|
||||||
&& mediaFormat.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
|
&& mediaFormat.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT
|
||||||
&& mediaFormat.getInteger(MediaFormat.KEY_HEIGHT)
|
&& mediaFormat.getInteger(MediaFormat.KEY_HEIGHT)
|
||||||
@ -1944,4 +2022,123 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
&& "OMX.MTK.AUDIO.DECODER.MP3".equals(name);
|
&& "OMX.MTK.AUDIO.DECODER.MP3".equals(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(21)
|
||||||
|
private static class AsynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||||
|
|
||||||
|
private MediaCodecAsyncCallback mediaCodecAsyncCallback;
|
||||||
|
private final Handler handler;
|
||||||
|
private final MediaCodec codec;
|
||||||
|
@Nullable private IllegalStateException internalException;
|
||||||
|
private boolean flushing;
|
||||||
|
|
||||||
|
public AsynchronousMediaCodecAdapter(MediaCodec codec) {
|
||||||
|
mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
|
||||||
|
handler = new Handler(Looper.myLooper());
|
||||||
|
this.codec = codec;
|
||||||
|
this.codec.setCallback(mediaCodecAsyncCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueInputBufferIndex() {
|
||||||
|
if (flushing) {
|
||||||
|
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||||
|
} else {
|
||||||
|
maybeThrowException();
|
||||||
|
return mediaCodecAsyncCallback.dequeueInputBufferIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
if (flushing) {
|
||||||
|
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||||
|
} else {
|
||||||
|
maybeThrowException();
|
||||||
|
return mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaFormat getOutputFormat() {
|
||||||
|
return mediaCodecAsyncCallback.getOutputFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
clearPendingFlushState();
|
||||||
|
flushing = true;
|
||||||
|
codec.flush();
|
||||||
|
handler.post(this::onCompleteFlush);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
clearPendingFlushState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCompleteFlush() {
|
||||||
|
flushing = false;
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
try {
|
||||||
|
codec.start();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// Catch IllegalStateException directly so that we don't have to wrap it
|
||||||
|
internalException = e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
internalException = new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeThrowException() throws IllegalStateException {
|
||||||
|
maybeThrowInternalException();
|
||||||
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeThrowInternalException() {
|
||||||
|
if (internalException != null) {
|
||||||
|
IllegalStateException e = internalException;
|
||||||
|
internalException = null;
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear state related to pending flush events. */
|
||||||
|
private void clearPendingFlushState() {
|
||||||
|
handler.removeCallbacksAndMessages(null);
|
||||||
|
internalException = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
|
||||||
|
private final MediaCodec codec;
|
||||||
|
private final long dequeueOutputBufferTimeoutMs;
|
||||||
|
|
||||||
|
public SynchronousMediaCodecAdapter(MediaCodec mediaCodec, long dequeueOutputBufferTimeoutMs) {
|
||||||
|
this.codec = mediaCodec;
|
||||||
|
this.dequeueOutputBufferTimeoutMs = dequeueOutputBufferTimeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueInputBufferIndex() {
|
||||||
|
return codec.dequeueInputBuffer(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int dequeueOutputBufferIndex(MediaCodec.BufferInfo bufferInfo) {
|
||||||
|
return codec.dequeueOutputBuffer(bufferInfo, dequeueOutputBufferTimeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaFormat getOutputFormat() {
|
||||||
|
return codec.getOutputFormat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
codec.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array-based unbounded queue for int primitives with amortized O(1) add and remove.
|
||||||
|
*
|
||||||
|
* <p>Use this class instead of a {@link java.util.Deque} to avoid boxing int primitives to {@link
|
||||||
|
* Integer} instances.
|
||||||
|
*/
|
||||||
|
public final class IntArrayQueue {
|
||||||
|
|
||||||
|
/** Default capacity needs to be a power of 2. */
|
||||||
|
private static int DEFAULT_INITIAL_CAPACITY = 16;
|
||||||
|
|
||||||
|
private int headIndex;
|
||||||
|
private int tailIndex;
|
||||||
|
private int size;
|
||||||
|
private int[] data;
|
||||||
|
private int wrapAroundMask;
|
||||||
|
|
||||||
|
public IntArrayQueue() {
|
||||||
|
headIndex = 0;
|
||||||
|
tailIndex = -1;
|
||||||
|
size = 0;
|
||||||
|
data = new int[DEFAULT_INITIAL_CAPACITY];
|
||||||
|
wrapAroundMask = data.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a new item to the queue. */
|
||||||
|
public void add(int value) {
|
||||||
|
if (size == data.length) {
|
||||||
|
doubleArraySize();
|
||||||
|
}
|
||||||
|
|
||||||
|
tailIndex = (tailIndex + 1) & wrapAroundMask;
|
||||||
|
data[tailIndex] = value;
|
||||||
|
size++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an item from the queue.
|
||||||
|
*
|
||||||
|
* @throws {@link NoSuchElementException} if the queue is empty.
|
||||||
|
*/
|
||||||
|
public int remove() {
|
||||||
|
if (size == 0) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int value = data[headIndex];
|
||||||
|
headIndex = (headIndex + 1) & wrapAroundMask;
|
||||||
|
size--;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the number of items in the queue. */
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the queue is empty. */
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clears the queue. */
|
||||||
|
public void clear() {
|
||||||
|
headIndex = 0;
|
||||||
|
tailIndex = -1;
|
||||||
|
size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the length of the backing array. */
|
||||||
|
public int capacity() {
|
||||||
|
return data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doubleArraySize() {
|
||||||
|
int newCapacity = data.length << 1;
|
||||||
|
if (newCapacity < 0) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] newData = new int[newCapacity];
|
||||||
|
int itemsToRight = data.length - headIndex;
|
||||||
|
int itemsToLeft = headIndex;
|
||||||
|
System.arraycopy(data, headIndex, newData, 0, itemsToRight);
|
||||||
|
System.arraycopy(data, 0, newData, itemsToRight, itemsToLeft);
|
||||||
|
|
||||||
|
headIndex = 0;
|
||||||
|
tailIndex = size - 1;
|
||||||
|
data = newData;
|
||||||
|
wrapAroundMask = data.length - 1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* 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.mediacodec;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import android.media.MediaCodec;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit tests for {@link MediaCodecAsyncCallback}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class MediaCodecAsyncCallbackTest {
|
||||||
|
|
||||||
|
private MediaCodecAsyncCallback mediaCodecAsyncCallback;
|
||||||
|
private MediaCodec codec;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws IOException {
|
||||||
|
mediaCodecAsyncCallback = new MediaCodecAsyncCallback();
|
||||||
|
codec = MediaCodec.createByCodecName("h264");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeInputBufferIndex_afterCreation_returnsTryAgain() {
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueInputBufferIndex())
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeInputBufferIndex_returnsEnqueuedBuffers() {
|
||||||
|
// Send two input buffers to the mediaCodecAsyncCallback.
|
||||||
|
mediaCodecAsyncCallback.onInputBufferAvailable(codec, 0);
|
||||||
|
mediaCodecAsyncCallback.onInputBufferAvailable(codec, 1);
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueInputBufferIndex()).isEqualTo(0);
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueInputBufferIndex()).isEqualTo(1);
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueInputBufferIndex())
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeInputBufferIndex_afterFlush_returnsTryAgain() {
|
||||||
|
// Send two input buffers to the mediaCodecAsyncCallback and then flush().
|
||||||
|
mediaCodecAsyncCallback.onInputBufferAvailable(codec, 0);
|
||||||
|
mediaCodecAsyncCallback.onInputBufferAvailable(codec, 1);
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueInputBufferIndex())
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeInputBufferIndex_afterFlushAndNewInputBuffer_returnsEnqueuedBuffer() {
|
||||||
|
// Send two input buffers to the mediaCodecAsyncCallback, then flush(), then send
|
||||||
|
// another input buffer.
|
||||||
|
mediaCodecAsyncCallback.onInputBufferAvailable(codec, 0);
|
||||||
|
mediaCodecAsyncCallback.onInputBufferAvailable(codec, 1);
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
mediaCodecAsyncCallback.onInputBufferAvailable(codec, 2);
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueInputBufferIndex()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeOutputBufferIndex_afterCreation_returnsTryAgain() {
|
||||||
|
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueOutputBufferIndex(outBufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeOutputBufferIndex_returnsEnqueuedBuffers() {
|
||||||
|
// Send two output buffers to the mediaCodecAsyncCallback.
|
||||||
|
MediaCodec.BufferInfo bufferInfo1 = new MediaCodec.BufferInfo();
|
||||||
|
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, 0, bufferInfo1);
|
||||||
|
|
||||||
|
MediaCodec.BufferInfo bufferInfo2 = new MediaCodec.BufferInfo();
|
||||||
|
bufferInfo2.set(1, 1, 1, 1);
|
||||||
|
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, 1, bufferInfo2);
|
||||||
|
|
||||||
|
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueOutputBufferIndex(outBufferInfo)).isEqualTo(0);
|
||||||
|
assertThat(areEqual(outBufferInfo, bufferInfo1)).isTrue();
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueOutputBufferIndex(outBufferInfo)).isEqualTo(1);
|
||||||
|
assertThat(areEqual(outBufferInfo, bufferInfo2)).isTrue();
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueOutputBufferIndex(outBufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeOutputBufferIndex_afterFlush_returnsTryAgain() {
|
||||||
|
// Send two output buffers to the mediaCodecAsyncCallback and then flush().
|
||||||
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
||||||
|
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueOutputBufferIndex(outBufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_TRY_AGAIN_LATER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dequeOutputBufferIndex_afterFlushAndNewOutputBuffers_returnsEnqueueBuffer() {
|
||||||
|
// Send two output buffers to the mediaCodecAsyncCallback, then flush(), then send
|
||||||
|
// another output buffer.
|
||||||
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, 0, bufferInfo);
|
||||||
|
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, 1, bufferInfo);
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
mediaCodecAsyncCallback.onOutputBufferAvailable(codec, 2, bufferInfo);
|
||||||
|
MediaCodec.BufferInfo outBufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueOutputBufferIndex(outBufferInfo)).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOutputFormat_onNewInstance_raisesException() {
|
||||||
|
try {
|
||||||
|
mediaCodecAsyncCallback.getOutputFormat();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOutputFormat_afterOnOutputFormatCalled_returnsFormat() {
|
||||||
|
MediaFormat format = new MediaFormat();
|
||||||
|
mediaCodecAsyncCallback.onOutputFormatChanged(codec, format);
|
||||||
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo))
|
||||||
|
.isEqualTo(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||||
|
assertThat(mediaCodecAsyncCallback.getOutputFormat()).isEqualTo(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOutputFormat_afterFlush_raisesCurrentFormat() {
|
||||||
|
MediaFormat format = new MediaFormat();
|
||||||
|
mediaCodecAsyncCallback.onOutputFormatChanged(codec, format);
|
||||||
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
mediaCodecAsyncCallback.dequeueOutputBufferIndex(bufferInfo);
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
|
||||||
|
assertThat(mediaCodecAsyncCallback.getOutputFormat()).isEqualTo(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maybeThrowExoPlaybackException_withoutErrorFromCodec_doesNotThrow() {
|
||||||
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maybeThrowExoPlaybackException_withErrorFromCodec_Throws() {
|
||||||
|
IllegalStateException exception = new IllegalStateException();
|
||||||
|
mediaCodecAsyncCallback.onMediaCodecError(exception);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maybeThrowExoPlaybackException_doesNotThrowTwice() {
|
||||||
|
IllegalStateException exception = new IllegalStateException();
|
||||||
|
mediaCodecAsyncCallback.onMediaCodecError(exception);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
|
fail();
|
||||||
|
} catch (IllegalStateException expected) {
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void maybeThrowExoPlaybackException_afterFlush_doesNotThrow() {
|
||||||
|
IllegalStateException exception = new IllegalStateException();
|
||||||
|
mediaCodecAsyncCallback.onMediaCodecError(exception);
|
||||||
|
mediaCodecAsyncCallback.flush();
|
||||||
|
|
||||||
|
mediaCodecAsyncCallback.maybeThrowMediaCodecException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares if two {@link android.media.MediaCodec.BufferInfo} are equal by inspecting {@link
|
||||||
|
* android.media.MediaCodec.BufferInfo#flags}, {@link android.media.MediaCodec.BufferInfo#size},
|
||||||
|
* {@link android.media.MediaCodec.BufferInfo#presentationTimeUs} and {@link
|
||||||
|
* android.media.MediaCodec.BufferInfo#offset}.
|
||||||
|
*/
|
||||||
|
private static boolean areEqual(MediaCodec.BufferInfo lhs, MediaCodec.BufferInfo rhs) {
|
||||||
|
return lhs.flags == rhs.flags
|
||||||
|
&& lhs.offset == rhs.offset
|
||||||
|
&& lhs.presentationTimeUs == rhs.presentationTimeUs
|
||||||
|
&& lhs.size == rhs.size;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Tests for {@link IntArrayQueue}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class IntArrayQueueTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void add_willDoubleCapacity() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
int capacity = queue.capacity();
|
||||||
|
|
||||||
|
for (int i = 0; i <= capacity; i++) {
|
||||||
|
queue.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(queue.capacity()).isEqualTo(2 * capacity);
|
||||||
|
assertThat(queue.size()).isEqualTo(capacity + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isEmpty_returnsTrueAfterConstruction() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
|
||||||
|
assertThat(queue.isEmpty()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isEmpty_returnsFalseAfterAddition() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
queue.add(0);
|
||||||
|
|
||||||
|
assertThat(queue.isEmpty()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isEmpty_returnsFalseAfterRemoval() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
queue.add(0);
|
||||||
|
queue.remove();
|
||||||
|
|
||||||
|
assertThat(queue.isEmpty()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void remove_onEmptyQueue_throwsException() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
|
||||||
|
try {
|
||||||
|
queue.remove();
|
||||||
|
fail();
|
||||||
|
} catch (NoSuchElementException expected) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void remove_returnsCorrectItem() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
int value = 20;
|
||||||
|
queue.add(value);
|
||||||
|
|
||||||
|
assertThat(queue.remove()).isEqualTo(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void remove_untilIsEmpty() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
for (int i = 0; i < 1024; i++) {
|
||||||
|
queue.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
int expectedRemoved = 0;
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
if (expectedRemoved == 15) {
|
||||||
|
System.out.println("foo");
|
||||||
|
}
|
||||||
|
int removed = queue.remove();
|
||||||
|
assertThat(removed).isEqualTo(expectedRemoved++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void remove_withResize_returnsCorrectItem() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
int nextToAdd = 0;
|
||||||
|
|
||||||
|
while (queue.size() < queue.capacity()) {
|
||||||
|
queue.add(nextToAdd++);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.remove();
|
||||||
|
queue.remove();
|
||||||
|
|
||||||
|
// This will force the queue to wrap-around and then resize
|
||||||
|
int howManyToResize = queue.capacity() - queue.size() + 1;
|
||||||
|
for (int i = 0; i < howManyToResize; i++) {
|
||||||
|
queue.add(nextToAdd++);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(queue.remove()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clear_resetsQueue() {
|
||||||
|
IntArrayQueue queue = new IntArrayQueue();
|
||||||
|
|
||||||
|
// Add items until array re-sizes twice (capacity grows by 4)
|
||||||
|
for (int i = 0; i < 1024; i++) {
|
||||||
|
queue.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.clear();
|
||||||
|
|
||||||
|
assertThat(queue.size()).isEqualTo(0);
|
||||||
|
assertThat(queue.isEmpty()).isTrue();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user