From 1a1dc44b84585288d9769ecb6e1178c43b4bab88 Mon Sep 17 00:00:00 2001 From: Dustin Date: Tue, 18 Jan 2022 15:28:10 -0700 Subject: [PATCH] More efficient header parsing --- .../extractor/avi/AviExtractor.java | 32 ++++----- .../android/exoplayer2/extractor/avi/Box.java | 8 ++- .../exoplayer2/extractor/avi/BoxFactory.java | 47 +++++++++++-- .../exoplayer2/extractor/avi/ListBox.java | 68 +++++++++++++++++-- .../exoplayer2/extractor/avi/ResidentBox.java | 34 +--------- .../extractor/avi/StreamDataBox.java | 17 +++++ 6 files changed, 142 insertions(+), 64 deletions(-) create mode 100644 library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamDataBox.java 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 c1f39af608..c89d92f0d8 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 @@ -1,7 +1,6 @@ package com.google.android.exoplayer2.extractor.avi; import android.util.SparseArray; -import android.util.SparseIntArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; @@ -17,12 +16,10 @@ import com.google.android.exoplayer2.util.MimeTypes; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; /** * Based on the official MicroSoft spec @@ -46,8 +43,6 @@ public class AviExtractor implements Extractor { static final int AVI_ = AviUtil.toInt(new byte[]{'A','V','I',' '}); //Stream List static final int STRL = 's' | ('t' << 8) | ('r' << 16) | ('l' << 24); - //Stream CODEC data - static final int STRD = 's' | ('t' << 8) | ('r' << 16) | ('d' << 24); //movie data box static final int MOVI = 'm' | ('o' << 8) | ('v' << 16) | ('i' << 24); //Index @@ -144,20 +139,20 @@ public class AviExtractor implements Extractor { if (inputLen != C.LENGTH_UNSET && inputLen != reportedLen) { Log.w(TAG, "Header length doesn't match stream length"); } - int avi = byteBuffer.getInt(); + final int avi = byteBuffer.getInt(); if (avi != AviExtractor.AVI_) { return null; } - final ListBox header = ListBox.getInstance(byteBuffer, input, ListBox.class); - if (header == null) { + final int list = byteBuffer.getInt(); + if (list != ListBox.LIST) { return null; } - if (header.getListType() != ListBox.TYPE_HDRL) { - Log.e(TAG, "Expected " +AviUtil.toString(ListBox.TYPE_HDRL) + ", got: " + - AviUtil.toString(header.getType())); + final int listSize = byteBuffer.getInt(); + final ListBox listBox = ListBox.newInstance(listSize, new BoxFactory(), input); + if (listBox.getListType() != ListBox.TYPE_HDRL) { return null; } - return header; + return listBox; } long getDuration() { @@ -173,7 +168,7 @@ public class AviExtractor implements Extractor { this.output = output; } - private static ResidentBox peekNext(final List streams, int i, int type) { + private static Box peekNext(final List streams, int i, int type) { if (i + 1 < streams.size() && streams.get(i + 1).getType() == type) { return streams.get(i + 1); } @@ -185,8 +180,7 @@ public class AviExtractor implements Extractor { if (headerList == null) { throw new IOException("AVI Header List not found"); } - final BoxFactory boxFactory = new BoxFactory(); - final List headerChildren = headerList.getBoxList(boxFactory); + final List headerChildren = headerList.getChildren(); aviHeader = AviUtil.getBox(headerChildren, AviHeaderBox.class); if (aviHeader == null) { throw new IOException("AviHeader not found"); @@ -198,9 +192,9 @@ public class AviExtractor implements Extractor { for (Box box : headerChildren) { if (box instanceof ListBox && ((ListBox) box).getListType() == STRL) { final ListBox streamList = (ListBox) box; - final List streamChildren = streamList.getBoxList(boxFactory); + final List streamChildren = streamList.getChildren(); for (int i=0;i codecData; if (codecBox != null) { - codecData = Collections.singletonList(codecBox.byteBuffer.array()); + codecData = Collections.singletonList(codecBox.getData()); i++; } else { codecData = null; 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 bb00c7043f..3cc891bc29 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 @@ -4,15 +4,19 @@ package com.google.android.exoplayer2.extractor.avi; * This is referred to as a Chunk in the MS spec, but that gets confusing with AV chunks */ public class Box { - private final long size; + private final int size; private final int type; - Box(int type, long size) { + Box(int type, int size) { this.type = type; this.size = size; } public long getSize() { + return size & AviUtil.UINT_MASK; + } + + public int getSizeInt() { return size; } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java index 8bf568d72d..5dc0314ee7 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java @@ -1,25 +1,60 @@ package com.google.android.exoplayer2.extractor.avi; -import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.extractor.ExtractorInput; +import java.io.IOException; import java.nio.ByteBuffer; +import java.util.Arrays; public class BoxFactory { - @NonNull + static int[] types = {AviHeaderBox.AVIH, StreamHeaderBox.STRH, StreamFormatBox.STRF, StreamDataBox.STRD}; + static { + Arrays.sort(types); + } + + public boolean isUnknown(final int type) { + return Arrays.binarySearch(types, type) < 0; + } + + @Nullable public ResidentBox createBox(final int type, final int size, final ByteBuffer byteBuffer) { final ByteBuffer boxBuffer = AviExtractor.allocate(size); AviUtil.copy(byteBuffer, boxBuffer, size); - + //TODO: Deal with list + switch (type) { + case AviHeaderBox.AVIH: + return new AviHeaderBox(type, size, boxBuffer); + case StreamHeaderBox.STRH: + return new StreamHeaderBox(type, size, boxBuffer); + case StreamFormatBox.STRF: + return new StreamFormatBox(type, size, boxBuffer); + case StreamDataBox.STRD: + return new StreamDataBox(type, size, boxBuffer); + default: + return null; + } + } + + private ResidentBox createBoxImpl(final int type, final int size, final ByteBuffer boxBuffer) { switch (type) { case AviHeaderBox.AVIH: return new AviHeaderBox(type, size, boxBuffer); - case ListBox.LIST: - return new ListBox(type, size, boxBuffer); case StreamHeaderBox.STRH: return new StreamHeaderBox(type, size, boxBuffer); case StreamFormatBox.STRF: return new StreamFormatBox(type, size, boxBuffer); default: - return new ResidentBox(type, size, boxBuffer); + return null; } } + + public ResidentBox createBox(final int type, final int size, ExtractorInput input) throws IOException { + if (isUnknown(type)) { + input.skipFully(size); + return null; + } + final ByteBuffer boxBuffer = AviExtractor.allocate(size); + input.readFully(boxBuffer.array(),0,size); + return createBoxImpl(type, size, boxBuffer); + } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ListBox.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ListBox.java index e9767bd818..c2ca4f343e 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ListBox.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ListBox.java @@ -1,20 +1,28 @@ package com.google.android.exoplayer2.extractor.avi; +import androidx.annotation.NonNull; +import com.google.android.exoplayer2.extractor.ExtractorInput; +import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; /** * An AVI LIST box, memory resident */ -public class ListBox extends ResidentBox { +public class ListBox extends Box { public static final int LIST = 'L' | ('I' << 8) | ('S' << 16) | ('T' << 24); //Header List public static final int TYPE_HDRL = 'h' | ('d' << 8) | ('r' << 16) | ('l' << 24); private final int listType; - ListBox(int type, int size, ByteBuffer byteBuffer) { - super(type, size, byteBuffer); - listType = byteBuffer.getInt(0); + final List children; + + ListBox(int size, int listType, List children) { + super(LIST, size); + this.listType = listType; + this.children = children; } public int getListType() { @@ -25,4 +33,56 @@ public class ListBox extends ResidentBox { boolean assertType() { return simpleAssert(LIST); } + + @NonNull + public List getChildren() { + return new ArrayList<>(children); + } + +// static List realizeChildren(final ByteBuffer byteBuffer, final BoxFactory boxFactory) { +// final List list = new ArrayList<>(); +// while (byteBuffer.hasRemaining()) { +// final int type = byteBuffer.getInt(); +// final int size = byteBuffer.getInt(); +// final ResidentBox residentBox = boxFactory.createBox(type, size, byteBuffer); +// list.add(residentBox); +// } +// return list; +// } + + /** + * Assume the input is pointing to the list type + * @param boxFactory + * @param input + * @return + * @throws IOException + */ + public static ListBox newInstance(final int listSize, BoxFactory boxFactory, + ExtractorInput input) throws IOException { + + final List list = new ArrayList<>(); + final ByteBuffer headerBuffer = AviExtractor.allocate(8); + byte [] bytes = headerBuffer.array(); + input.readFully(bytes, 0, 4); + final int listType = headerBuffer.getInt(); + + long endPos = input.getPosition() + listSize - 4; + while (input.getPosition() + 8 < endPos) { + headerBuffer.clear(); + input.readFully(bytes, 0, 8); + final int type = headerBuffer.getInt(); + final int size = headerBuffer.getInt(); + final Box box; + if (type == LIST) { + box = newInstance(size, boxFactory, input); + } else { + box = boxFactory.createBox(type, size, input); + } + + if (box != null) { + list.add(box); + } + } + return new ListBox(listSize, listType, list); + } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ResidentBox.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ResidentBox.java index d4283e314a..843300b8af 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ResidentBox.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/ResidentBox.java @@ -10,32 +10,15 @@ import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; /** * A box that is resident in memory */ public class ResidentBox extends Box { private static final String TAG = AviExtractor.TAG; - final private static int MAX_RESIDENT = 64*1024; + final private static int MAX_RESIDENT = 1024; final ByteBuffer byteBuffer; -// private Class getClass(final int type) { -// switch (type) { -// case AviHeaderBox.AVIH: -// return AviHeaderBox.class; -// case ListBox.LIST: -// return ListBox.class; -// case StreamHeaderBox.STRH: -// return StreamHeaderBox.class; -// case StreamFormatBox.STRF: -// return StreamFormatBox.class; -// default: -// return ResidentBox.class; -// } -// } - ResidentBox(int type, int size, ByteBuffer byteBuffer) { super(type, size); this.byteBuffer = byteBuffer; @@ -81,7 +64,6 @@ public class ResidentBox extends Box { } } - /** * Returns shallow copy of this ByteBuffer with the position at 0 * @return @@ -93,18 +75,4 @@ public class ResidentBox extends Box { clone.clear(); return clone; } - - @NonNull - public List getBoxList(final BoxFactory boxFactory) { - final ByteBuffer temp = getByteBuffer(); - temp.position(4); - final List list = new ArrayList<>(); - while (temp.hasRemaining()) { - final int type = temp.getInt(); - final int size = temp.getInt(); - final ResidentBox residentBox = boxFactory.createBox(type, size, temp); - list.add(residentBox); - } - return list; - } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamDataBox.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamDataBox.java new file mode 100644 index 0000000000..ce6b0260ae --- /dev/null +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamDataBox.java @@ -0,0 +1,17 @@ +package com.google.android.exoplayer2.extractor.avi; + +import java.nio.ByteBuffer; + +public class StreamDataBox extends ResidentBox { + //Stream CODEC data + static final int STRD = 's' | ('t' << 8) | ('r' << 16) | ('d' << 24); + + StreamDataBox(int type, int size, ByteBuffer byteBuffer) { + super(type, size, byteBuffer); + } + byte[] getData() { + byte[] data = new byte[byteBuffer.capacity()]; + System.arraycopy(byteBuffer.array(), 0, data, 0, data.length); + return data; + } +}