From b520b26f0faba2a4e69dc66a7553568eb675df33 Mon Sep 17 00:00:00 2001 From: Dustin Date: Sat, 29 Jan 2022 10:59:55 -0700 Subject: [PATCH] More AviExtractor tests --- .../extractor/avi/AviExtractor.java | 21 ++--- .../extractor/avi/AviHeaderBox.java | 16 +--- .../android/exoplayer2/extractor/avi/Box.java | 5 -- .../extractor/avi/StreamHeaderBox.java | 8 +- .../extractor/avi/AviExtractorRoboTest.java | 20 +++++ .../extractor/avi/AviExtractorTest.java | 83 ++++++++++++++---- .../exoplayer2/extractor/avi/DataHelper.java | 52 ++++++++--- .../extractor/avi/StreamHeaderBoxTest.java | 5 +- .../avi/auds_stream_header.dump | Bin 56 -> 0 bytes .../avi/vids_stream_header.dump | Bin 64 -> 0 bytes 10 files changed, 147 insertions(+), 63 deletions(-) delete mode 100644 testdata/src/test/assets/extractordumps/avi/auds_stream_header.dump delete mode 100644 testdata/src/test/assets/extractordumps/avi/vids_stream_header.dump diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java index a48025afb5..72fea3cb6c 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java @@ -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; } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviHeaderBox.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviHeaderBox.java index 8f6dfcedfc..84b6557d0b 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviHeaderBox.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviHeaderBox.java @@ -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 } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/Box.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/Box.java index c5c10731f9..94ed3d4028 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/Box.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/Box.java @@ -19,9 +19,4 @@ public class Box { public int getType() { return type; } - - boolean simpleAssert(final int expected) { - return getType() == expected; - } - } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBox.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBox.java index fffc43f016..8781d32adb 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBox.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBox.java @@ -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 diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorRoboTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorRoboTest.java index 315e504d7d..97b4932795 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorRoboTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorRoboTest.java @@ -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)); + } + } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorTest.java index f969979b8a..68f4a6a6f4 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/AviExtractorTest.java @@ -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); + } +} \ No newline at end of file diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java index 26c55b49fc..be72b48450 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java @@ -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; + } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBoxTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBoxTest.java index 7bc7edcb72..e36ac29cb3 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBoxTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamHeaderBoxTest.java @@ -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()); } } diff --git a/testdata/src/test/assets/extractordumps/avi/auds_stream_header.dump b/testdata/src/test/assets/extractordumps/avi/auds_stream_header.dump deleted file mode 100644 index 4224a479f6a2583261db19a002c6b20147260cab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56 hcmYc+O(|wT0*pX52shY41RDzUSRLdcY>+q%005N^1MvU= diff --git a/testdata/src/test/assets/extractordumps/avi/vids_stream_header.dump b/testdata/src/test/assets/extractordumps/avi/vids_stream_header.dump deleted file mode 100644 index d7e95e2df3548a89f42564ff78c958ae8fef93be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 ocmXTROeu~C^K@ZA0xy^u7*@nW1Z4G)B#@XVm>3u?FfuRz02cHH$N&HU