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:
christosts 2023-08-22 17:09:01 +01:00 committed by Ian Baker
parent 36084eef05
commit 398809e4e2
8 changed files with 212 additions and 182 deletions

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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; 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 * <p>Use this class instead of a {@link java.util.Deque} to avoid boxing long primitives to {@link
* Integer} instances. * Long} instances.
*/ */
@UnstableApi @UnstableApi
/* package */ final class IntArrayQueue { public final class LongArrayQueue {
/** Default capacity needs to be a power of 2. */ /** Default initial capacity. */
private static final int DEFAULT_INITIAL_CAPACITY = 16; public static final int DEFAULT_INITIAL_CAPACITY = 16;
private int headIndex; private int headIndex;
private int tailIndex; private int tailIndex;
private int size; private int size;
private int[] data; private long[] data;
private int wrapAroundMask; 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; headIndex = 0;
tailIndex = -1; tailIndex = -1;
size = 0; size = 0;
data = new int[DEFAULT_INITIAL_CAPACITY]; data = new long[minCapacity];
wrapAroundMask = data.length - 1; wrapAroundMask = data.length - 1;
} }
/** Add a new item to the queue. */ /** Add a new item to the queue. */
public void add(int value) { public void add(long value) {
if (size == data.length) { if (size == data.length) {
doubleArraySize(); doubleArraySize();
} }
@ -60,18 +78,31 @@ import java.util.NoSuchElementException;
* *
* @throws NoSuchElementException if the queue is empty. * @throws NoSuchElementException if the queue is empty.
*/ */
public int remove() { public long remove() {
if (size == 0) { if (size == 0) {
throw new NoSuchElementException(); throw new NoSuchElementException();
} }
int value = data[headIndex]; long value = data[headIndex];
headIndex = (headIndex + 1) & wrapAroundMask; headIndex = (headIndex + 1) & wrapAroundMask;
size--; size--;
return value; 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. */ /** Returns the number of items in the queue. */
public int size() { public int size() {
return size; return size;
@ -90,7 +121,8 @@ import java.util.NoSuchElementException;
} }
/** Returns the length of the backing array. */ /** Returns the length of the backing array. */
public int capacity() { @VisibleForTesting
/* package */ int capacity() {
return data.length; return data.length;
} }
@ -100,7 +132,7 @@ import java.util.NoSuchElementException;
throw new IllegalStateException(); throw new IllegalStateException();
} }
int[] newData = new int[newCapacity]; long[] newData = new long[newCapacity];
int itemsToRight = data.length - headIndex; int itemsToRight = data.length - headIndex;
int itemsToLeft = headIndex; int itemsToLeft = headIndex;
System.arraycopy(data, headIndex, newData, 0, itemsToRight); System.arraycopy(data, headIndex, newData, 0, itemsToRight);

View File

@ -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();
}
}

View File

@ -35,6 +35,7 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlProgram;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; 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 boolean allInputsEnded; // Whether all inputSources have signaled end of input.
private final TexturePool outputTexturePool; private final TexturePool outputTexturePool;
private final Queue<Long> outputTextureTimestamps; // Synchronized with outputTexturePool. private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool.
private final Queue<Long> syncObjects; // Synchronized with outputTexturePool. private final LongArrayQueue syncObjects; // Synchronized with outputTexturePool.
// Only used on the GL Thread. // Only used on the GL Thread.
private @MonotonicNonNull EGLContext eglContext; private @MonotonicNonNull EGLContext eglContext;
@ -111,8 +112,8 @@ public final class DefaultVideoCompositor implements VideoCompositor {
inputSources = new ArrayList<>(); inputSources = new ArrayList<>();
outputTexturePool = outputTexturePool =
new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity); new TexturePool(/* useHighPrecisionColorComponents= */ false, textureOutputCapacity);
outputTextureTimestamps = new ArrayDeque<>(textureOutputCapacity); outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity);
syncObjects = new ArrayDeque<>(textureOutputCapacity); syncObjects = new LongArrayQueue(textureOutputCapacity);
boolean ownsExecutor = executorService == null; boolean ownsExecutor = executorService == null;
ExecutorService instanceExecutorService = ExecutorService instanceExecutorService =
@ -374,7 +375,7 @@ public final class DefaultVideoCompositor implements VideoCompositor {
private synchronized void releaseOutputFrameInternal(long presentationTimeUs) private synchronized void releaseOutputFrameInternal(long presentationTimeUs)
throws VideoFrameProcessingException, GlUtil.GlException { throws VideoFrameProcessingException, GlUtil.GlException {
while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity() while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity()
&& checkNotNull(outputTextureTimestamps.peek()) <= presentationTimeUs) { && outputTextureTimestamps.element() <= presentationTimeUs) {
outputTexturePool.freeTexture(); outputTexturePool.freeTexture();
outputTextureTimestamps.remove(); outputTextureTimestamps.remove();
GlUtil.deleteSyncObject(syncObjects.remove()); GlUtil.deleteSyncObject(syncObjects.remove());

View File

@ -41,10 +41,10 @@ import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.util.GlUtil; import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
@ -88,8 +88,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final VideoFrameProcessor.Listener videoFrameProcessorListener; private final VideoFrameProcessor.Listener videoFrameProcessorListener;
private final Queue<Pair<GlTextureInfo, Long>> availableFrames; private final Queue<Pair<GlTextureInfo, Long>> availableFrames;
private final TexturePool outputTexturePool; private final TexturePool outputTexturePool;
private final Queue<Long> outputTextureTimestamps; // Synchronized with outputTexturePool. private final LongArrayQueue outputTextureTimestamps; // Synchronized with outputTexturePool.
private final Queue<Long> syncObjects; private final LongArrayQueue syncObjects;
@Nullable private final DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener; @Nullable private final DefaultVideoFrameProcessor.TextureOutputListener textureOutputListener;
private int inputWidth; private int inputWidth;
@ -148,8 +148,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
boolean useHighPrecisionColorComponents = ColorInfo.isTransferHdr(outputColorInfo); boolean useHighPrecisionColorComponents = ColorInfo.isTransferHdr(outputColorInfo);
outputTexturePool = new TexturePool(useHighPrecisionColorComponents, textureOutputCapacity); outputTexturePool = new TexturePool(useHighPrecisionColorComponents, textureOutputCapacity);
outputTextureTimestamps = new ArrayDeque<>(textureOutputCapacity); outputTextureTimestamps = new LongArrayQueue(textureOutputCapacity);
syncObjects = new ArrayDeque<>(textureOutputCapacity); syncObjects = new LongArrayQueue(textureOutputCapacity);
} }
@Override @Override
@ -227,7 +227,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void releaseOutputFrameInternal(long presentationTimeUs) throws GlUtil.GlException { private void releaseOutputFrameInternal(long presentationTimeUs) throws GlUtil.GlException {
checkState(textureOutputListener != null); checkState(textureOutputListener != null);
while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity() while (outputTexturePool.freeTextureCount() < outputTexturePool.capacity()
&& checkNotNull(outputTextureTimestamps.peek()) <= presentationTimeUs) { && outputTextureTimestamps.element() <= presentationTimeUs) {
outputTexturePool.freeTexture(); outputTexturePool.freeTexture();
outputTextureTimestamps.remove(); outputTextureTimestamps.remove();
GlUtil.deleteSyncObject(syncObjects.remove()); GlUtil.deleteSyncObject(syncObjects.remove());

View File

@ -50,6 +50,7 @@ dependencies {
api project(modulePrefix + 'lib-extractor') api project(modulePrefix + 'lib-extractor')
api project(modulePrefix + 'lib-database') api project(modulePrefix + 'lib-database')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.collection:collection:' + androidxCollectionVersion
implementation 'androidx.core:core:' + androidxCoreVersion implementation 'androidx.core:core:' + androidxCoreVersion
implementation 'androidx.exifinterface:exifinterface:1.3.6' implementation 'androidx.exifinterface:exifinterface:1.3.6'
compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version

View File

@ -26,6 +26,7 @@ import android.os.HandlerThread;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.collection.CircularIntArray;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -39,10 +40,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private @MonotonicNonNull Handler handler; private @MonotonicNonNull Handler handler;
@GuardedBy("lock") @GuardedBy("lock")
private final IntArrayQueue availableInputBuffers; private final CircularIntArray availableInputBuffers;
@GuardedBy("lock") @GuardedBy("lock")
private final IntArrayQueue availableOutputBuffers; private final CircularIntArray availableOutputBuffers;
@GuardedBy("lock") @GuardedBy("lock")
private final ArrayDeque<MediaCodec.BufferInfo> bufferInfos; private final ArrayDeque<MediaCodec.BufferInfo> bufferInfos;
@ -81,8 +82,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* package */ AsynchronousMediaCodecCallback(HandlerThread callbackThread) { /* package */ AsynchronousMediaCodecCallback(HandlerThread callbackThread) {
this.lock = new Object(); this.lock = new Object();
this.callbackThread = callbackThread; this.callbackThread = callbackThread;
this.availableInputBuffers = new IntArrayQueue(); this.availableInputBuffers = new CircularIntArray();
this.availableOutputBuffers = new IntArrayQueue(); this.availableOutputBuffers = new CircularIntArray();
this.bufferInfos = new ArrayDeque<>(); this.bufferInfos = new ArrayDeque<>();
this.formats = new ArrayDeque<>(); this.formats = new ArrayDeque<>();
} }
@ -132,7 +133,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} else { } else {
return availableInputBuffers.isEmpty() return availableInputBuffers.isEmpty()
? MediaCodec.INFO_TRY_AGAIN_LATER ? MediaCodec.INFO_TRY_AGAIN_LATER
: availableInputBuffers.remove(); : availableInputBuffers.popFirst();
} }
} }
} }
@ -152,7 +153,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (availableOutputBuffers.isEmpty()) { if (availableOutputBuffers.isEmpty()) {
return MediaCodec.INFO_TRY_AGAIN_LATER; return MediaCodec.INFO_TRY_AGAIN_LATER;
} else { } else {
int bufferIndex = availableOutputBuffers.remove(); int bufferIndex = availableOutputBuffers.popFirst();
if (bufferIndex >= 0) { if (bufferIndex >= 0) {
checkStateNotNull(currentFormat); checkStateNotNull(currentFormat);
MediaCodec.BufferInfo nextBufferInfo = bufferInfos.remove(); MediaCodec.BufferInfo nextBufferInfo = bufferInfos.remove();
@ -204,7 +205,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void onInputBufferAvailable(MediaCodec codec, int index) { public void onInputBufferAvailable(MediaCodec codec, int index) {
synchronized (lock) { synchronized (lock) {
availableInputBuffers.add(index); availableInputBuffers.addLast(index);
} }
} }
@ -215,7 +216,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
addOutputFormat(pendingOutputFormat); addOutputFormat(pendingOutputFormat);
pendingOutputFormat = null; pendingOutputFormat = null;
} }
availableOutputBuffers.add(index); availableOutputBuffers.addLast(index);
bufferInfos.add(info); bufferInfos.add(info);
} }
} }
@ -278,7 +279,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@GuardedBy("lock") @GuardedBy("lock")
private void addOutputFormat(MediaFormat mediaFormat) { private void addOutputFormat(MediaFormat mediaFormat) {
availableOutputBuffers.add(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED); availableOutputBuffers.addLast(MediaCodec.INFO_OUTPUT_FORMAT_CHANGED);
formats.add(mediaFormat); formats.add(mediaFormat);
} }

View File

@ -37,13 +37,13 @@ import androidx.media3.common.SurfaceInfo;
import androidx.media3.common.VideoFrameProcessingException; import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.VideoFrameProcessor; import androidx.media3.common.VideoFrameProcessor;
import androidx.media3.common.VideoSize; import androidx.media3.common.VideoSize;
import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.Size; import androidx.media3.common.util.Size;
import androidx.media3.common.util.TimedValueQueue; import androidx.media3.common.util.TimedValueQueue;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -168,8 +168,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final Context context; private final Context context;
private final RenderControl renderControl; private final RenderControl renderControl;
private final VideoFrameProcessor videoFrameProcessor; private final VideoFrameProcessor videoFrameProcessor;
// TODO b/293447478 - Use a queue for primitive longs to avoid the cost of boxing to Long. private final LongArrayQueue processedFramesBufferTimestampsUs;
private final ArrayDeque<Long> processedFramesBufferTimestampsUs;
private final TimedValueQueue<Long> streamOffsets; private final TimedValueQueue<Long> streamOffsets;
private final TimedValueQueue<VideoSize> videoSizeChanges; private final TimedValueQueue<VideoSize> videoSizeChanges;
private final Handler handler; private final Handler handler;
@ -219,7 +218,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
throws VideoFrameProcessingException { throws VideoFrameProcessingException {
this.context = context; this.context = context;
this.renderControl = renderControl; this.renderControl = renderControl;
processedFramesBufferTimestampsUs = new ArrayDeque<>(); processedFramesBufferTimestampsUs = new LongArrayQueue();
streamOffsets = new TimedValueQueue<>(); streamOffsets = new TimedValueQueue<>();
videoSizeChanges = new TimedValueQueue<>(); videoSizeChanges = new TimedValueQueue<>();
// TODO b/226330223 - Investigate increasing frame count when frame dropping is // TODO b/226330223 - Investigate increasing frame count when frame dropping is
@ -362,7 +361,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void render(long positionUs, long elapsedRealtimeUs) { public void render(long positionUs, long elapsedRealtimeUs) {
while (!processedFramesBufferTimestampsUs.isEmpty()) { while (!processedFramesBufferTimestampsUs.isEmpty()) {
long bufferPresentationTimeUs = checkNotNull(processedFramesBufferTimestampsUs.peek()); long bufferPresentationTimeUs = processedFramesBufferTimestampsUs.element();
// check whether this buffer comes with a new stream offset. // check whether this buffer comes with a new stream offset.
if (maybeUpdateOutputStreamOffset(bufferPresentationTimeUs)) { if (maybeUpdateOutputStreamOffset(bufferPresentationTimeUs)) {
renderedFirstFrame = false; renderedFirstFrame = false;

View File

@ -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();
}
}