More AviExtractor tests

This commit is contained in:
Dustin 2022-01-29 10:59:55 -07:00
parent a17d36de12
commit b520b26f0f
10 changed files with 147 additions and 63 deletions

View File

@ -28,7 +28,7 @@ public class AviExtractor implements Extractor {
static final long MIN_KEY_FRAME_RATE_US = 2_000_000L;
static final long UINT_MASK = 0xffffffffL;
static long getUInt(ByteBuffer byteBuffer) {
static long getUInt(@NonNull ByteBuffer byteBuffer) {
return byteBuffer.getInt() & UINT_MASK;
}
@ -49,7 +49,7 @@ public class AviExtractor implements Extractor {
return position;
}
static void alignInput(ExtractorInput input) throws IOException {
static void alignInput(@NonNull ExtractorInput input) throws IOException {
// This isn't documented anywhere, but most files are aligned to even bytes
// and can have gaps of zeros
if ((input.getPosition() & 1) == 1) {
@ -57,15 +57,16 @@ public class AviExtractor implements Extractor {
}
}
static int checkAlign(final ExtractorInput input, PositionHolder seekPosition) {
static int alignPositionHolder(@NonNull ExtractorInput input, @NonNull PositionHolder seekPosition) {
final long position = input.getPosition();
if ((position & 1) ==1) {
if ((position & 1) == 1) {
seekPosition.position = position + 1;
return RESULT_SEEK;
}
return RESULT_CONTINUE;
}
@NonNull
static ByteBuffer allocate(int bytes) {
final byte[] buffer = new byte[bytes];
final ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
@ -141,7 +142,7 @@ public class AviExtractor implements Extractor {
* @param bytes Must be at least 20
*/
@Nullable
private ByteBuffer getAviBuffer(ExtractorInput input, int bytes) throws IOException {
static private ByteBuffer getAviBuffer(@NonNull ExtractorInput input, int bytes) throws IOException {
if (input.getLength() < bytes) {
return null;
}
@ -190,7 +191,7 @@ public class AviExtractor implements Extractor {
}
@Nullable
ListBox readHeaderList(ExtractorInput input) throws IOException {
static ListBox readHeaderList(ExtractorInput input) throws IOException {
final ByteBuffer byteBuffer = getAviBuffer(input, 20);
if (byteBuffer == null) {
return null;
@ -245,7 +246,7 @@ public class AviExtractor implements Extractor {
final VideoFormat videoFormat = streamFormat.getVideoFormat();
final String mimeType = videoFormat.getMimeType();
if (mimeType == null) {
Log.w(TAG, "Unknown FourCC: " + toString(streamHeader.getFourCC()));
Log.w(TAG, "Unknown FourCC: " + toString(videoFormat.getCompression()));
return null;
}
final TrackOutput trackOutput = output.track(streamId, C.TRACK_TYPE_VIDEO);
@ -478,11 +479,11 @@ public class AviExtractor implements Extractor {
return null;
}
int readSamples(ExtractorInput input, PositionHolder seekPosition) throws IOException {
int readSamples(@NonNull ExtractorInput input, @NonNull PositionHolder seekPosition) throws IOException {
if (chunkHandler != null) {
if (chunkHandler.resume(input)) {
chunkHandler = null;
return checkAlign(input, seekPosition);
return alignPositionHolder(input, seekPosition);
}
} else {
ByteBuffer byteBuffer = allocate(8);
@ -514,7 +515,7 @@ public class AviExtractor implements Extractor {
return RESULT_SEEK;
}
if (aviTrack.newChunk(chunkId, size, input)) {
return checkAlign(input, seekPosition);
return alignPositionHolder(input, seekPosition);
} else {
chunkHandler = aviTrack;
}

View File

@ -3,7 +3,8 @@ package com.google.android.exoplayer2.extractor.avi;
import java.nio.ByteBuffer;
public class AviHeaderBox extends ResidentBox {
private static final int AVIF_HASINDEX = 0x10;
static final int LEN = 0x38;
static final int AVIF_HASINDEX = 0x10;
private static final int AVIF_MUSTUSEINDEX = 0x20;
static final int AVIH = 'a' | ('v' << 8) | ('i' << 16) | ('h' << 24);
@ -46,15 +47,6 @@ public class AviHeaderBox extends ResidentBox {
}
// 28 - dwSuggestedBufferSize
// int getSuggestedBufferSize() {
// return byteBuffer.getInt(28);
// }
//
// int getWidth() {
// return byteBuffer.getInt(32);
// }
//
// int getHeight() {
// return byteBuffer.getInt(36);
// }
// 32 - dwWidth
// 36 - dwHeight
}

View File

@ -19,9 +19,4 @@ public class Box {
public int getType() {
return type;
}
boolean simpleAssert(final int expected) {
return getType() == expected;
}
}

View File

@ -37,13 +37,7 @@ public class StreamHeaderBox extends ResidentBox {
public int getSteamType() {
return byteBuffer.getInt(0);
}
/**
* Only meaningful for video
* @return FourCC
*/
public int getFourCC() {
return byteBuffer.getInt(4);
}
//4 - fourCC
//8 - dwFlags
//12 - wPriority
//14 - wLanguage

View File

@ -6,6 +6,7 @@ import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.Collections;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -35,4 +36,23 @@ public class AviExtractorRoboTest {
Assert.assertEquals(MimeTypes.AUDIO_AAC, trackOutput.lastFormat.sampleMimeType);
}
@Test
public void parseStream_givenNoStreamHeader() {
final AviExtractor aviExtractor = new AviExtractor();
final FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
aviExtractor.init(fakeExtractorOutput);
final ListBox streamList = new ListBox(128, AviExtractor.STRL, Collections.EMPTY_LIST);
Assert.assertNull(aviExtractor.parseStream(streamList, 0));
}
@Test
public void parseStream_givenNoStreamFormat() throws IOException {
final AviExtractor aviExtractor = new AviExtractor();
final FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
aviExtractor.init(fakeExtractorOutput);
final ListBox streamList = new ListBox(128, AviExtractor.STRL,
Collections.singletonList(DataHelper.getVidsStreamHeader()));
Assert.assertNull(aviExtractor.parseStream(streamList, 0));
}
}

View File

@ -1,5 +1,7 @@
package com.google.android.exoplayer2.extractor.avi;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
@ -9,6 +11,7 @@ import org.junit.Assert;
import org.junit.Test;
public class AviExtractorTest {
@Test
public void init_givenFakeExtractorOutput() {
AviExtractor aviExtractor = new AviExtractor();
@ -83,14 +86,7 @@ public class AviExtractorTest {
@Test
public void peek_givenOnlyRiffAvi_ListHdrlAvih() {
ByteBuffer byteBuffer = AviExtractor.allocate(AviExtractor.PEEK_BYTES);
byteBuffer.putInt(AviExtractor.RIFF);
byteBuffer.putInt(128);
byteBuffer.putInt(AviExtractor.AVI_);
byteBuffer.putInt(ListBox.LIST);
byteBuffer.putInt(64);
byteBuffer.putInt(ListBox.TYPE_HDRL);
byteBuffer.putInt(AviHeaderBox.AVIH);
final ByteBuffer byteBuffer = DataHelper.getAviHeader(AviExtractor.PEEK_BYTES, 128);
Assert.assertTrue(sniff(byteBuffer));
}
@ -118,6 +114,7 @@ public class AviExtractorTest {
AviExtractor.alignInput(fakeExtractorInput);
Assert.assertEquals(2, fakeExtractorInput.getPosition());
}
@Test
public void alignInput_givenEvenPosition() throws IOException {
@ -149,7 +146,8 @@ public class AviExtractorTest {
Assert.assertEquals(1, AviExtractor.getStreamId('0' | ('1' << 8) | ('d' << 16) | ('c' << 24)));
}
private void assertIdx1(AviSeekMap aviSeekMap, AviTrack videoTrack, int keyFrames, int keyFrameRate) {
private void assertIdx1(AviSeekMap aviSeekMap, AviTrack videoTrack, int keyFrames,
int keyFrameRate) {
Assert.assertEquals(keyFrames, videoTrack.keyFrames.length);
final int framesPerKeyFrame = 24 * 3;
@ -179,11 +177,13 @@ public class AviExtractorTest {
final AviTrack audioTrack = DataHelper.getAudioAviTrack(secs);
aviExtractor.setAviTracks(new AviTrack[]{videoTrack, audioTrack});
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().setData(idx1.array()).build();
aviExtractor.readIdx1(fakeExtractorInput, (int)fakeExtractorInput.getLength());
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder()
.setData(idx1.array()).build();
aviExtractor.readIdx1(fakeExtractorInput, (int) fakeExtractorInput.getLength());
final AviSeekMap aviSeekMap = aviExtractor.aviSeekMap;
assertIdx1(aviSeekMap, videoTrack, keyFrames, keyFrameRate);
}
@Test
public void readIdx1_givenNoVideo() throws IOException {
final AviExtractor aviExtractor = new AviExtractor();
@ -195,8 +195,9 @@ public class AviExtractorTest {
final AviTrack audioTrack = DataHelper.getAudioAviTrack(secs);
aviExtractor.setAviTracks(new AviTrack[]{audioTrack});
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().setData(idx1.array()).build();
aviExtractor.readIdx1(fakeExtractorInput, (int)fakeExtractorInput.getLength());
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder()
.setData(idx1.array()).build();
aviExtractor.readIdx1(fakeExtractorInput, (int) fakeExtractorInput.getLength());
Assert.assertTrue(fakeExtractorOutput.seekMap instanceof SeekMap.Unseekable);
}
@ -222,7 +223,7 @@ public class AviExtractorTest {
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
setData(junk.array()).build();
aviExtractor.readIdx1(fakeExtractorInput, (int)fakeExtractorInput.getLength());
aviExtractor.readIdx1(fakeExtractorInput, (int) fakeExtractorInput.getLength());
assertIdx1(aviExtractor.aviSeekMap, videoTrack, keyFrames, keyFrameRate);
}
@ -240,9 +241,59 @@ public class AviExtractorTest {
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
setData(idx1.array()).build();
aviExtractor.readIdx1(fakeExtractorInput, (int)fakeExtractorInput.getLength());
aviExtractor.readIdx1(fakeExtractorInput, (int) fakeExtractorInput.getLength());
//We should be throttled to 2 key frame per second
Assert.assertSame(AviTrack.ALL_KEY_FRAMES, videoTrack.keyFrames);
}
}
@Test
public void alignPositionHolder_givenOddPosition() {
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
setData(new byte[4]).build();
fakeExtractorInput.setPosition(1);
final PositionHolder positionHolder = new PositionHolder();
final int result = AviExtractor.alignPositionHolder(fakeExtractorInput, positionHolder);
Assert.assertEquals(Extractor.RESULT_SEEK, result);
Assert.assertEquals(2, positionHolder.position);
}
@Test
public void alignPositionHolder_givenEvenPosition() {
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
setData(new byte[4]).build();
fakeExtractorInput.setPosition(2);
final PositionHolder positionHolder = new PositionHolder();
final int result = AviExtractor.alignPositionHolder(fakeExtractorInput, positionHolder);
Assert.assertEquals(Extractor.RESULT_CONTINUE, result);
}
@Test
public void readHeaderList_givenBadHeader() throws IOException {
final FakeExtractorInput input = new FakeExtractorInput.Builder().setData(new byte[32]).build();
Assert.assertNull(AviExtractor.readHeaderList(input));
}
@Test
public void readHeaderList_givenNoHeaderList() throws IOException {
final ByteBuffer byteBuffer = DataHelper.getAviHeader(88, 0x44);
byteBuffer.putInt(0x14, AviExtractor.STRL); //Overwrite header list with stream list
final FakeExtractorInput input = new FakeExtractorInput.Builder().
setData(byteBuffer.array()).build();
Assert.assertNull(AviExtractor.readHeaderList(input));
}
@Test
public void readHeaderList_givenEmptyHeaderList() throws IOException {
final ByteBuffer byteBuffer = DataHelper.getAviHeader(88, 0x44);
byteBuffer.putInt(AviHeaderBox.LEN);
byteBuffer.put(DataHelper.createHeader());
final FakeExtractorInput input = new FakeExtractorInput.Builder().
setData(byteBuffer.array()).build();
final ListBox listBox = AviExtractor.readHeaderList(input);
Assert.assertEquals(1, listBox.getChildren().size());
Assert.assertTrue(listBox.getChildren().get(0) instanceof AviHeaderBox);
}
}

View File

@ -35,18 +35,22 @@ public class DataHelper {
}
}
public static StreamHeaderBox getVidsStreamHeader() throws IOException {
final byte[] buffer = getBytes("vids_stream_header.dump");
final ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
return new StreamHeaderBox(StreamHeaderBox.STRH, buffer.length, byteBuffer);
public static StreamHeaderBox getStreamHeader(int type, int scale, int rate, int length) {
final ByteBuffer byteBuffer = AviExtractor.allocate(0x40);
byteBuffer.putInt(type);
byteBuffer.putInt(20, scale);
byteBuffer.putInt(24, rate);
byteBuffer.putInt(32, length);
byteBuffer.putInt(36, (type == StreamHeaderBox.VIDS ? 128 : 16) * 1024); //Suggested buffer size
return new StreamHeaderBox(StreamHeaderBox.STRH, 0x40, byteBuffer);
}
public static StreamHeaderBox getAudioStreamHeader() throws IOException {
final byte[] buffer = getBytes("auds_stream_header.dump");
final ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
return new StreamHeaderBox(StreamHeaderBox.STRH, buffer.length, byteBuffer);
public static StreamHeaderBox getVidsStreamHeader() {
return getStreamHeader(StreamHeaderBox.VIDS, 1001, 24000, 9 * FPS);
}
public static StreamHeaderBox getAudioStreamHeader() {
return getStreamHeader(StreamHeaderBox.AUDS, 1, 44100, 9 * FPS);
}
public static StreamFormatBox getAacStreamFormat() throws IOException {
@ -154,4 +158,32 @@ public class DataHelper {
}
return byteBuffer;
}
/**
* Get the RIFF header up to AVI Header
* @param bufferSize
* @return
*/
public static ByteBuffer getAviHeader(int bufferSize, int headerListSize) {
ByteBuffer byteBuffer = AviExtractor.allocate(bufferSize);
byteBuffer.putInt(AviExtractor.RIFF);
byteBuffer.putInt(128);
byteBuffer.putInt(AviExtractor.AVI_);
byteBuffer.putInt(ListBox.LIST);
byteBuffer.putInt(headerListSize);
byteBuffer.putInt(ListBox.TYPE_HDRL);
byteBuffer.putInt(AviHeaderBox.AVIH);
return byteBuffer;
}
public static ByteBuffer createHeader() {
final ByteBuffer byteBuffer = ByteBuffer.allocate(AviHeaderBox.LEN);
byteBuffer.putInt((int)VIDEO_US);
byteBuffer.putLong(0); //skip 4+4
byteBuffer.putInt(AviHeaderBox.AVIF_HASINDEX);
byteBuffer.putInt(FPS * 5); //5 seconds
byteBuffer.putInt(24, 2); // Number of streams
byteBuffer.clear();
return byteBuffer;
}
}

View File

@ -19,10 +19,9 @@ public class StreamHeaderBoxTest {
Assert.assertTrue(streamHeaderBox.isVideo());
Assert.assertFalse(streamHeaderBox.isAudio());
Assert.assertEquals(StreamHeaderBox.VIDS, streamHeaderBox.getSteamType());
Assert.assertEquals(VideoFormat.XVID, streamHeaderBox.getFourCC());
Assert.assertEquals(0, streamHeaderBox.getInitialFrames());
Assert.assertEquals(FPS24, streamHeaderBox.getFrameRate(), 0.1);
Assert.assertEquals(11805L, streamHeaderBox.getLength());
Assert.assertEquals(0, streamHeaderBox.getSuggestedBufferSize());
Assert.assertEquals(9 * DataHelper.FPS, streamHeaderBox.getLength());
Assert.assertEquals(128 * 1024, streamHeaderBox.getSuggestedBufferSize());
}
}