More efficient header parsing

This commit is contained in:
Dustin 2022-01-18 15:28:10 -07:00
parent a9c9418591
commit 1a1dc44b84
6 changed files with 142 additions and 64 deletions

View file

@ -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<ResidentBox> streams, int i, int type) {
private static Box peekNext(final List<Box> 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<ResidentBox> headerChildren = headerList.getBoxList(boxFactory);
final List<Box> 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<ResidentBox> streamChildren = streamList.getBoxList(boxFactory);
final List<Box> streamChildren = streamList.getChildren();
for (int i=0;i<streamChildren.size();i++) {
final ResidentBox residentBox = streamChildren.get(i);
final Box residentBox = streamChildren.get(i);
if (residentBox instanceof StreamHeaderBox) {
final StreamHeaderBox streamHeader = (StreamHeaderBox) residentBox;
final StreamFormatBox streamFormat = (StreamFormatBox) peekNext(streamChildren, i, StreamFormatBox.STRF);
@ -208,10 +202,10 @@ public class AviExtractor implements Extractor {
i++;
if (streamHeader.isVideo()) {
final VideoFormat videoFormat = streamFormat.getVideoFormat();
final ResidentBox codecBox = (ResidentBox) peekNext(streamChildren, i, STRD);
final StreamDataBox codecBox = (StreamDataBox) peekNext(streamChildren, i, StreamDataBox.STRD);
final List<byte[]> codecData;
if (codecBox != null) {
codecData = Collections.singletonList(codecBox.byteBuffer.array());
codecData = Collections.singletonList(codecBox.getData());
i++;
} else {
codecData = null;

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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<Box> children;
ListBox(int size, int listType, List<Box> 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<Box> getChildren() {
return new ArrayList<>(children);
}
// static List<ResidentBox> realizeChildren(final ByteBuffer byteBuffer, final BoxFactory boxFactory) {
// final List<ResidentBox> 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<Box> 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);
}
}

View file

@ -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<? extends ResidentBox> 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<ResidentBox> getBoxList(final BoxFactory boxFactory) {
final ByteBuffer temp = getByteBuffer();
temp.position(4);
final List<ResidentBox> 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;
}
}

View file

@ -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;
}
}