Simplified UnboundedIntArray

(cherry picked from commit b4cb20cd31e44fe641aac94fee7e69456e48356c)
This commit is contained in:
Dustin 2022-02-17 07:31:54 -07:00
parent e14617fc32
commit 0b629e4be8
6 changed files with 64 additions and 198 deletions

View file

@ -31,6 +31,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@ -449,8 +450,8 @@ public class AviExtractor implements Extractor {
final int size = indexByteBuffer.getInt();
if ((flags & AVIIF_KEYFRAME) == AVIIF_KEYFRAME) {
if (chunkHandler.isVideo()) {
int indexSize = seekIndexes[videoId].getSize();
if (indexSize == 0 || chunkHandler.chunks - seekIndexes[videoId].get(indexSize - 1) >= chunksPerKeyFrame) {
int indexSize = seekIndexes[videoId].size;
if (indexSize == 0 || chunkHandler.chunks - seekIndexes[videoId].array[indexSize - 1] >= chunksPerKeyFrame) {
keyFrameOffsetsDiv2.add(offset / 2);
for (@Nullable ChunkHandler seekTrack : chunkHandlers) {
if (seekTrack != null) {
@ -469,13 +470,17 @@ public class AviExtractor implements Extractor {
if (videoTrack.chunks == keyFrameCounts[videoTrack.getId()]) {
videoTrack.setKeyFrames(ChunkHandler.ALL_KEY_FRAMES);
} else {
videoTrack.setKeyFrames(seekIndexes[videoId].getArray());
videoTrack.setKeyFrames(seekIndexes[videoId].pack());
}
//Work-around a bug where the offset is from the start of the file, not "movi"
final long seekOffset = firstEntry.getInt(8) > moviOffset ? 0L : moviOffset;
int[][] seekIndexArrays = new int[seekIndexes.length][];
for (int i=0;i<seekIndexes.length;i++) {
seekIndexArrays[i] = seekIndexes[i].pack();
}
final AviSeekMap seekMap = new AviSeekMap(videoId, videoTrack.clock.durationUs, videoTrack.chunks,
keyFrameOffsetsDiv2.getArray(), seekIndexes, seekOffset);
keyFrameOffsetsDiv2.pack(), seekIndexArrays, seekOffset);
i("Video chunks=" + videoTrack.chunks + " us=" + seekMap.getDurationUs());
@ -610,6 +615,38 @@ public class AviExtractor implements Extractor {
//Intentionally blank
}
/**
* Optimized unbounded array of ints.
* Used primarily to create Index (SeekMap) data.
*/
@VisibleForTesting
static class UnboundedIntArray {
@NonNull
@VisibleForTesting
int[] array = new int[8];
@VisibleForTesting
int size = 0;
public void add(int v) {
if (size == array.length) {
grow();
}
array[size++] = v;
}
public int[] pack() {
if (size != array.length) {
array = Arrays.copyOf(array, size);
}
return array;
}
private void grow() {
int increase = Math.max(array.length /4, 1);
array = Arrays.copyOf(array, increase + array.length + size);
}
}
@VisibleForTesting
void setChunkHandlers(ChunkHandler[] chunkHandlers) {
this.chunkHandlers = chunkHandlers;
@ -642,18 +679,10 @@ public class AviExtractor implements Extractor {
}
private static void w(String message) {
try {
Log.w(TAG, message);
} catch (RuntimeException e) {
//Catch not mocked for tests
}
Log.w(TAG, message);
}
private static void i(String message) {
try {
Log.i(TAG, message);
} catch (RuntimeException e) {
//Catch not mocked for tests
}
Log.i(TAG, message);
}
}

View file

@ -39,15 +39,12 @@ public class AviSeekMap implements SeekMap {
final long seekOffset;
public AviSeekMap(int videoId, long usDuration, int videoChunks, int[] keyFrameOffsetsDiv2,
UnboundedIntArray[] seekIndexes, long seekOffset) {
int[][] seekIndexes, long seekOffset) {
this.videoId = videoId;
this.videoUsPerChunk = usDuration / videoChunks;
this.duration = usDuration;
this.keyFrameOffsetsDiv2 = keyFrameOffsetsDiv2;
this.seekIndexes = new int[seekIndexes.length][];
for (int i=0;i<seekIndexes.length;i++) {
this.seekIndexes[i] = seekIndexes[i].getArray();
}
this.seekIndexes = seekIndexes;
this.seekOffset = seekOffset;
}

View file

@ -1,84 +0,0 @@
/*
* Copyright (C) 2022 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.extractor.avi;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import java.util.Arrays;
/**
* Optimized unbounded array of ints.
* Used primarily to create Index (SeekMap) data.
*/
public class UnboundedIntArray {
@NonNull
@VisibleForTesting
int[] array;
//uint
private int size = 0;
public UnboundedIntArray() {
this(8);
}
public UnboundedIntArray(int size) {
if (size < 0) {
throw new IllegalArgumentException("Initial size must be positive: " + size);
}
array = new int[size];
}
public void add(int v) {
if (size == array.length) {
grow();
}
array[size++] = v;
}
public int get(final int index) {
if (index >= size) {
throw new ArrayIndexOutOfBoundsException(index + ">=" + size);
}
return array[index];
}
public int getSize() {
return size;
}
public void pack() {
if (size != array.length) {
array = Arrays.copyOf(array, size);
}
}
protected void grow() {
int increase = Math.max(array.length /4, 1);
array = Arrays.copyOf(array, increase + array.length + size);
}
public int[] getArray() {
pack();
return array;
}
/**
* Only works if values are in sequential order
*/
public int indexOf(int v) {
return Arrays.binarySearch(array, v);
}
}

View file

@ -594,4 +594,18 @@ public class AviExtractorTest {
final FakeTrackOutput fakeTrackOutput = (FakeTrackOutput) chunkHandler.trackOutput;
Assert.assertEquals(size, fakeTrackOutput.getSampleData(0).length);
}
@Test
public void unboundIntArray_add_givenExceedsCapacity() {
final AviExtractor.UnboundedIntArray unboundedIntArray = new AviExtractor.UnboundedIntArray();
final int testLen = unboundedIntArray.array.length + 1;
for (int i=0; i < testLen; i++) {
unboundedIntArray.add(i);
}
Assert.assertEquals(testLen, unboundedIntArray.size);
for (int i=0; i < testLen; i++) {
Assert.assertEquals(i, unboundedIntArray.array[i]);
}
}
}

View file

@ -118,14 +118,12 @@ public class DataHelper {
public static AviSeekMap getAviSeekMap() {
final int[] keyFrameOffsetsDiv2= {4, 1024};
final UnboundedIntArray videoArray = new UnboundedIntArray();
videoArray.add(0);
videoArray.add(4);
final UnboundedIntArray audioArray = new UnboundedIntArray();
audioArray.add(0);
audioArray.add(128);
final int[] videoArray = new int[2];
videoArray[1] = 4;
final int[] audioArray = new int[2];
audioArray[1] = 128;
return new AviSeekMap(0, 100L, 8, keyFrameOffsetsDiv2,
new UnboundedIntArray[]{videoArray, audioArray}, MOVI_OFFSET);
new int[][]{videoArray, audioArray}, MOVI_OFFSET);
}
private static void putIndex(final ByteBuffer byteBuffer, int chunkId, int flags, int offset,

View file

@ -1,88 +0,0 @@
/*
* Copyright (C) 2022 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.extractor.avi;
import org.junit.Assert;
import org.junit.Test;
public class UnboundedIntArrayTest {
@Test
public void add_givenInt() {
final UnboundedIntArray unboundedIntArray = new UnboundedIntArray();
unboundedIntArray.add(4);
Assert.assertEquals(1, unboundedIntArray.getSize());
Assert.assertEquals(unboundedIntArray.getArray()[0], 4);
}
@Test
public void indexOf_givenOrderSet() {
final UnboundedIntArray unboundedIntArray = new UnboundedIntArray();
unboundedIntArray.add(2);
unboundedIntArray.add(4);
unboundedIntArray.add(5);
unboundedIntArray.add(8);
Assert.assertEquals(2, unboundedIntArray.indexOf(5));
Assert.assertTrue(unboundedIntArray.indexOf(6) < 0);
}
@Test
public void grow_givenSizeOfOne() {
final UnboundedIntArray unboundedIntArray = new UnboundedIntArray(1);
unboundedIntArray.add(0);
Assert.assertEquals(1, unboundedIntArray.getSize());
unboundedIntArray.add(1);
Assert.assertTrue(unboundedIntArray.getSize() > 1);
}
@Test
public void pack_givenSizeOfOne() {
final UnboundedIntArray unboundedIntArray = new UnboundedIntArray(8);
unboundedIntArray.add(1);
unboundedIntArray.add(2);
Assert.assertEquals(8, unboundedIntArray.array.length);
unboundedIntArray.pack();
Assert.assertEquals(2, unboundedIntArray.array.length);
}
@Test
public void illegalArgument_givenNegativeSize() {
try {
new UnboundedIntArray(-1);
Assert.fail();
} catch (IllegalArgumentException e) {
//Intentionally blank
}
}
@Test
public void get_givenValidIndex() {
final UnboundedIntArray unboundedIntArray = new UnboundedIntArray(4);
unboundedIntArray.add(1);
unboundedIntArray.add(2);
Assert.assertEquals(1, unboundedIntArray.get(0));
}
@Test
public void get_givenOutOfBounds() {
final UnboundedIntArray unboundedIntArray = new UnboundedIntArray(4);
try {
unboundedIntArray.get(0);
Assert.fail();
} catch (ArrayIndexOutOfBoundsException e) {
//Intentionally blank
}
}
}