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