Replace Queue<Long> with a queue for long primitives
Replace Queue<Long> with LongArrayQueue which provides queue semantics for long primitives. LongArrayQueue is forked from IntArrayQueue which in turn was forked from Androidx CircularIntArray. IntArrayQueue is deleted and we now use CircularIntArray directly from Androidx Collection. PiperOrigin-RevId: 559129744
This commit is contained in:
parent
36084eef05
commit
398809e4e2
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
* Copyright 2023 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.
|
||||
@ -13,39 +13,57 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package androidx.media3.exoplayer.mediacodec;
|
||||
package androidx.media3.common.util;
|
||||
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Array-based unbounded queue for int primitives with amortized O(1) add and remove.
|
||||
* Array-based unbounded queue for long 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.
|
||||
* <p>Use this class instead of a {@link java.util.Deque} to avoid boxing long primitives to {@link
|
||||
* Long} instances.
|
||||
*/
|
||||
@UnstableApi
|
||||
/* package */ final class IntArrayQueue {
|
||||
public final class LongArrayQueue {
|
||||
|
||||
/** Default capacity needs to be a power of 2. */
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
/** Default initial capacity. */
|
||||
public static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
|
||||
private int headIndex;
|
||||
private int tailIndex;
|
||||
private int size;
|
||||
private int[] data;
|
||||
private long[] data;
|
||||
private int wrapAroundMask;
|
||||
|
||||
public IntArrayQueue() {
|
||||
/** Creates a queue with an initial capacity of {@link #DEFAULT_INITIAL_CAPACITY}. */
|
||||
public LongArrayQueue() {
|
||||
this(DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a queue with capacity for at least {@code minCapacity}
|
||||
*
|
||||
* @param minCapacity minCapacity the minimum capacity, between 1 and 2^30 inclusive
|
||||
*/
|
||||
public LongArrayQueue(int minCapacity) {
|
||||
checkArgument(minCapacity >= 0 && minCapacity <= (1 << 30));
|
||||
minCapacity = minCapacity == 0 ? 1 : minCapacity;
|
||||
// If capacity isn't a power of 2, round up to the next highest power of 2.
|
||||
if (Integer.bitCount(minCapacity) != 1) {
|
||||
minCapacity = Integer.highestOneBit(minCapacity - 1) << 1;
|
||||
}
|
||||
headIndex = 0;
|
||||
tailIndex = -1;
|
||||
size = 0;
|
||||
data = new int[DEFAULT_INITIAL_CAPACITY];
|
||||
data = new long[minCapacity];
|
||||
wrapAroundMask = data.length - 1;
|
||||
}
|
||||
|
||||
/** Add a new item to the queue. */
|
||||
public void add(int value) {
|
||||
public void add(long value) {
|
||||
if (size == data.length) {
|
||||
doubleArraySize();
|
||||
}
|
||||
@ -60,18 +78,31 @@ import java.util.NoSuchElementException;
|
||||
*
|
||||
* @throws NoSuchElementException if the queue is empty.
|
||||
*/
|
||||
public int remove() {
|
||||
public long remove() {
|
||||
if (size == 0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
int value = data[headIndex];
|
||||
long value = data[headIndex];
|
||||
headIndex = (headIndex + 1) & wrapAroundMask;
|
||||
size--;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves, but does not remove, the head of the queue.
|
||||
*
|
||||
* @throws NoSuchElementException if the queue is empty.
|
||||
*/
|
||||
public long element() {
|
||||
if (size == 0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return data[headIndex];
|
||||
}
|
||||
|
||||
/** Returns the number of items in the queue. */
|
||||
public int size() {
|
||||
return size;
|
||||
@ -90,7 +121,8 @@ import java.util.NoSuchElementException;
|
||||
}
|
||||
|
||||
/** Returns the length of the backing array. */
|
||||
public int capacity() {
|
||||
@VisibleForTesting
|
||||
/* package */ int capacity() {
|
||||
return data.length;
|
||||
}
|
||||
|
||||
@ -100,7 +132,7 @@ import java.util.NoSuchElementException;
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
int[] newData = new int[newCapacity];
|
||||
long[] newData = new long[newCapacity];
|
||||
int itemsToRight = data.length - headIndex;
|
||||
int itemsToLeft = headIndex;
|
||||
System.arraycopy(data, headIndex, newData, 0, itemsToRight);
|
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2023 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.common.util;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import java.util.NoSuchElementException;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Tests for {@link LongArrayQueue}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class LongArrayQueueTest {
|
||||
|
||||
@Test
|
||||
public void capacity() {
|
||||
LongArrayQueue queue = new LongArrayQueue(2);
|
||||
|
||||
assertThat(queue.capacity()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void capacity_setTo0_increasedTo1() {
|
||||
LongArrayQueue queue = new LongArrayQueue(0);
|
||||
|
||||
assertThat(queue.capacity()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void capacity_setToNextPowerOf2() {
|
||||
LongArrayQueue queue = new LongArrayQueue(6);
|
||||
|
||||
assertThat(queue.capacity()).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void capacity_invalidMinCapacity_throws() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new LongArrayQueue(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void add_beyondInitialCapacity_doublesCapacity() {
|
||||
LongArrayQueue queue = new LongArrayQueue(2);
|
||||
|
||||
queue.add(0);
|
||||
queue.add(1);
|
||||
queue.add(2);
|
||||
|
||||
assertThat(queue.size()).isEqualTo(3);
|
||||
assertThat(queue.capacity()).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmpty_afterConstruction_returnsTrue() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
assertThat(queue.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmpty_afterAddition_returnsFalse() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
queue.add(0);
|
||||
|
||||
assertThat(queue.isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isEmpty_afterRemoval_returnsTrue() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
queue.add(0);
|
||||
queue.remove();
|
||||
|
||||
assertThat(queue.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove_onEmptyQueue_throwsException() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
assertThrows(NoSuchElementException.class, queue::remove);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove_returnsCorrectItem() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
queue.add(20);
|
||||
|
||||
assertThat(queue.remove()).isEqualTo(20);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void element_withEmptyQueue_throws() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
assertThrows(NoSuchElementException.class, queue::element);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void element_returnsQueueHead() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
queue.add(5);
|
||||
|
||||
assertThat(queue.element()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clear_resetsQueue() {
|
||||
LongArrayQueue queue = new LongArrayQueue();
|
||||
|
||||
queue.add(123);
|
||||
queue.clear();
|
||||
|
||||
assertThat(queue.size()).isEqualTo(0);
|
||||
assertThat(queue.isEmpty()).isTrue();
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ import androidx.media3.common.VideoFrameProcessingException;
|
||||
import androidx.media3.common.util.GlProgram;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.LongArrayQueue;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -81,8 +82,8 @@ public final class DefaultVideoCompositor implements VideoCompositor {
|
||||
private boolean allInputsEnded; // Whether all inputSources have signaled end of input.
|
||||
|
||||
private final TexturePool outputTexturePool;
|
||||
private final Queue<Long> outputTextureTimestamps; // Synchronized with outputTexturePool.
|
||||
private final Queue<Long> syncObjects; // Synchronized with outputTexturePool.
|
||||
private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool.
|
||||
private final LongArrayQueue syncObjects; // Synchronized with outputTexturePool.
|
||||
|
||||
// Only used on the GL Thread.
|
||||
private @MonotonicNonNull EGLContext eglContext;
|
||||
@ -111,8 +112,8 @@ public final class DefaultVideoCompositor implements VideoCompositor {
|
||||
inputSources = new ArrayList<>();
|
||||
outputTexturePool =
|
||||
new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity);
|
||||
outputTextureTimestamps = new ArrayDeque<>(textureOutputCapacity);
|
||||
syncObjects = new ArrayDeque<>(textureOutputCapacity);
|
||||
outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity);
|
||||
syncObjects = new LongArrayQueue(textureOutputCapacity);
|
||||
|
||||
boolean ownsExecutor = executorService == null;
|
||||
ExecutorService instanceExecutorService =
|
||||
@ -374,7 +375,7 @@ public final class DefaultVideoCompositor implements VideoCompositor {
|
||||
private synchronized void releaseOutputFrameInternal(long presentationTimeUs)
|
||||
throws VideoFrameProcessingException, GlUtil.GlException {
|
||||
while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity()
|
||||
&& checkNotNull(outputTextureTimestamps.peek()) <= presentationTimeUs) {
|
||||
&& outputTextureTimestamps.element() <= presentationTimeUs) {
|
||||
outputTexturePool.freeTexture();
|
||||
outputTextureTimestamps.remove();
|
||||
GlUtil.deleteSyncObject(syncObjects.remove());
|
||||
|
@ -41,10 +41,10 @@ import androidx.media3.common.VideoFrameProcessingException;
|
||||
import androidx.media3.common.VideoFrameProcessor;
|
||||
import androidx.media3.common.util.GlUtil;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.LongArrayQueue;
|
||||
import androidx.media3.common.util.Size;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
@ -88,8 +88,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private final VideoFrameProcessor.Listener videoFrameProcessorListener;
|
||||
private final Queue<Pair<GlTextureInfo, Long>> availableFrames;
|
||||
private final TexturePool outputTexturePool;
|
||||
private final Queue<Long> outputTextureTimestamps; // Synchronized with outputTexturePool.
|
||||
private final Queue<Long> syncObjects;
|
||||
private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool.
|
||||
private final LongArrayQueue syncObjects;
|
||||
@Nullable private final DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener;
|
||||
|
||||
private int inputWidth;
|
||||
@ -148,8 +148,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
boolean useHighPrecisionColorComponents = ColorInfo.isTransferHdr(outputColorInfo);
|
||||
outputTexturePool = new TexturePool(useHighPrecisionColorComponents, textureOutputCapacity);
|
||||
outputTextureTimestamps = new ArrayDeque<>(textureOutputCapacity);
|
||||
syncObjects = new ArrayDeque<>(textureOutputCapacity);
|
||||
outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity);
|
||||
syncObjects = new LongArrayQueue(textureOutputCapacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -227,7 +227,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private void releaseOutputFrameInternal(long presentationTimeUs) throws GlUtil.GlException {
|
||||
checkState(textureOutputListener != null);
|
||||
while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity()
|
||||
&& checkNotNull(outputTextureTimestamps.peek()) <= presentationTimeUs) {
|
||||
&& outputTextureTimestamps.element() <= presentationTimeUs) {
|
||||
outputTexturePool.freeTexture();
|
||||
outputTextureTimestamps.remove();
|
||||
GlUtil.deleteSyncObject(syncObjects.remove());
|
||||
|
@ -50,6 +50,7 @@ dependencies {
|
||||
api project(modulePrefix + 'lib-extractor')
|
||||
api project(modulePrefix + 'lib-database')
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation 'androidx.collection:collection:' + androidxCollectionVersion
|
||||
implementation 'androidx.core:core:' + androidxCoreVersion
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.6'
|
||||
compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version
|
||||
|
@ -26,6 +26,7 @@ import android.os.HandlerThread;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.collection.CircularIntArray;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.util.ArrayDeque;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@ -39,10 +40,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private @MonotonicNonNull Handler handler;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final IntArrayQueue availableInputBuffers;
|
||||
private final CircularIntArray availableInputBuffers;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final IntArrayQueue availableOutputBuffers;
|
||||
private final CircularIntArray availableOutputBuffers;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final ArrayDeque<MediaCodec.BufferInfo> bufferInfos;
|
||||
@ -81,8 +82,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
/* package */ AsynchronousMediaCodecCallback(HandlerThread callbackThread) {
|
||||
this.lock = new Object();
|
||||
this.callbackThread = callbackThread;
|
||||
this.availableInputBuffers = new IntArrayQueue();
|
||||
this.availableOutputBuffers = new IntArrayQueue();
|
||||
this.availableInputBuffers = new CircularIntArray();
|
||||
this.availableOutputBuffers = new CircularIntArray();
|
||||
this.bufferInfos = new ArrayDeque<>();
|
||||
this.formats = new ArrayDeque<>();
|
||||
}
|
||||
@ -132,7 +133,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
} else {
|
||||
return availableInputBuffers.isEmpty()
|
||||
? MediaCodec.INFO_TRY_AGAIN_LATER
|
||||
: availableInputBuffers.remove();
|
||||
: availableInputBuffers.popFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,7 +153,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
if (availableOutputBuffers.isEmpty()) {
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
} else {
|
||||
int bufferIndex = availableOutputBuffers.remove();
|
||||
int bufferIndex = availableOutputBuffers.popFirst();
|
||||
if (bufferIndex >= 0) {
|
||||
checkStateNotNull(currentFormat);
|
||||
MediaCodec.BufferInfo nextBufferInfo = bufferInfos.remove();
|
||||
@ -204,7 +205,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@Override
|
||||
public void onInputBufferAvailable(MediaCodec codec, int index) {
|
||||
synchronized (lock) {
|
||||
availableInputBuffers.add(index);
|
||||
availableInputBuffers.addLast(index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,7 +216,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
addOutputFormat(pendingOutputFormat);
|
||||
pendingOutputFormat = null;
|
||||
}
|
||||
availableOutputBuffers.add(index);
|
||||
availableOutputBuffers.addLast(index);
|
||||
bufferInfos.add(info);
|
||||
}
|
||||
}
|
||||
@ -278,7 +279,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private void addOutputFormat(MediaFormat mediaFormat) {
|
||||
availableOutputBuffers.add(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
availableOutputBuffers.addLast(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
|
||||
formats.add(mediaFormat);
|
||||
}
|
||||
|
||||
|
@ -37,13 +37,13 @@ import androidx.media3.common.SurfaceInfo;
|
||||
import androidx.media3.common.VideoFrameProcessingException;
|
||||
import androidx.media3.common.VideoFrameProcessor;
|
||||
import androidx.media3.common.VideoSize;
|
||||
import androidx.media3.common.util.LongArrayQueue;
|
||||
import androidx.media3.common.util.Size;
|
||||
import androidx.media3.common.util.TimedValueQueue;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
@ -168,8 +168,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private final Context context;
|
||||
private final RenderControl renderControl;
|
||||
private final VideoFrameProcessor videoFrameProcessor;
|
||||
// TODO b/293447478 - Use a queue for primitive longs to avoid the cost of boxing to Long.
|
||||
private final ArrayDeque<Long> processedFramesBufferTimestampsUs;
|
||||
private final LongArrayQueue processedFramesBufferTimestampsUs;
|
||||
private final TimedValueQueue<Long> streamOffsets;
|
||||
private final TimedValueQueue<VideoSize> videoSizeChanges;
|
||||
private final Handler handler;
|
||||
@ -219,7 +218,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
throws VideoFrameProcessingException {
|
||||
this.context = context;
|
||||
this.renderControl = renderControl;
|
||||
processedFramesBufferTimestampsUs = new ArrayDeque<>();
|
||||
processedFramesBufferTimestampsUs = new LongArrayQueue();
|
||||
streamOffsets = new TimedValueQueue<>();
|
||||
videoSizeChanges = new TimedValueQueue<>();
|
||||
// TODO b/226330223 - Investigate increasing frame count when frame dropping is
|
||||
@ -362,7 +361,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@Override
|
||||
public void render(long positionUs, long elapsedRealtimeUs) {
|
||||
while (!processedFramesBufferTimestampsUs.isEmpty()) {
|
||||
long bufferPresentationTimeUs = checkNotNull(processedFramesBufferTimestampsUs.peek());
|
||||
long bufferPresentationTimeUs = processedFramesBufferTimestampsUs.element();
|
||||
// check whether this buffer comes with a new stream offset.
|
||||
if (maybeUpdateOutputStreamOffset(bufferPresentationTimeUs)) {
|
||||
renderedFirstFrame = false;
|
||||
|
@ -1,140 +0,0 @@
|
||||
/*
|
||||
* 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 androidx.media3.exoplayer.mediacodec;
|
||||
|
||||
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