mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
More efficient header parsing
This commit is contained in:
parent
a9c9418591
commit
1a1dc44b84
6 changed files with 142 additions and 64 deletions
|
|
@ -1,7 +1,6 @@
|
||||||
package com.google.android.exoplayer2.extractor.avi;
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseIntArray;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
|
@ -17,12 +16,10 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on the official MicroSoft spec
|
* 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',' '});
|
static final int AVI_ = AviUtil.toInt(new byte[]{'A','V','I',' '});
|
||||||
//Stream List
|
//Stream List
|
||||||
static final int STRL = 's' | ('t' << 8) | ('r' << 16) | ('l' << 24);
|
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
|
//movie data box
|
||||||
static final int MOVI = 'm' | ('o' << 8) | ('v' << 16) | ('i' << 24);
|
static final int MOVI = 'm' | ('o' << 8) | ('v' << 16) | ('i' << 24);
|
||||||
//Index
|
//Index
|
||||||
|
|
@ -144,20 +139,20 @@ public class AviExtractor implements Extractor {
|
||||||
if (inputLen != C.LENGTH_UNSET && inputLen != reportedLen) {
|
if (inputLen != C.LENGTH_UNSET && inputLen != reportedLen) {
|
||||||
Log.w(TAG, "Header length doesn't match stream length");
|
Log.w(TAG, "Header length doesn't match stream length");
|
||||||
}
|
}
|
||||||
int avi = byteBuffer.getInt();
|
final int avi = byteBuffer.getInt();
|
||||||
if (avi != AviExtractor.AVI_) {
|
if (avi != AviExtractor.AVI_) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final ListBox header = ListBox.getInstance(byteBuffer, input, ListBox.class);
|
final int list = byteBuffer.getInt();
|
||||||
if (header == null) {
|
if (list != ListBox.LIST) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (header.getListType() != ListBox.TYPE_HDRL) {
|
final int listSize = byteBuffer.getInt();
|
||||||
Log.e(TAG, "Expected " +AviUtil.toString(ListBox.TYPE_HDRL) + ", got: " +
|
final ListBox listBox = ListBox.newInstance(listSize, new BoxFactory(), input);
|
||||||
AviUtil.toString(header.getType()));
|
if (listBox.getListType() != ListBox.TYPE_HDRL) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return header;
|
return listBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
long getDuration() {
|
long getDuration() {
|
||||||
|
|
@ -173,7 +168,7 @@ public class AviExtractor implements Extractor {
|
||||||
this.output = output;
|
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) {
|
if (i + 1 < streams.size() && streams.get(i + 1).getType() == type) {
|
||||||
return streams.get(i + 1);
|
return streams.get(i + 1);
|
||||||
}
|
}
|
||||||
|
|
@ -185,8 +180,7 @@ public class AviExtractor implements Extractor {
|
||||||
if (headerList == null) {
|
if (headerList == null) {
|
||||||
throw new IOException("AVI Header List not found");
|
throw new IOException("AVI Header List not found");
|
||||||
}
|
}
|
||||||
final BoxFactory boxFactory = new BoxFactory();
|
final List<Box> headerChildren = headerList.getChildren();
|
||||||
final List<ResidentBox> headerChildren = headerList.getBoxList(boxFactory);
|
|
||||||
aviHeader = AviUtil.getBox(headerChildren, AviHeaderBox.class);
|
aviHeader = AviUtil.getBox(headerChildren, AviHeaderBox.class);
|
||||||
if (aviHeader == null) {
|
if (aviHeader == null) {
|
||||||
throw new IOException("AviHeader not found");
|
throw new IOException("AviHeader not found");
|
||||||
|
|
@ -198,9 +192,9 @@ public class AviExtractor implements Extractor {
|
||||||
for (Box box : headerChildren) {
|
for (Box box : headerChildren) {
|
||||||
if (box instanceof ListBox && ((ListBox) box).getListType() == STRL) {
|
if (box instanceof ListBox && ((ListBox) box).getListType() == STRL) {
|
||||||
final ListBox streamList = (ListBox) box;
|
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++) {
|
for (int i=0;i<streamChildren.size();i++) {
|
||||||
final ResidentBox residentBox = streamChildren.get(i);
|
final Box residentBox = streamChildren.get(i);
|
||||||
if (residentBox instanceof StreamHeaderBox) {
|
if (residentBox instanceof StreamHeaderBox) {
|
||||||
final StreamHeaderBox streamHeader = (StreamHeaderBox) residentBox;
|
final StreamHeaderBox streamHeader = (StreamHeaderBox) residentBox;
|
||||||
final StreamFormatBox streamFormat = (StreamFormatBox) peekNext(streamChildren, i, StreamFormatBox.STRF);
|
final StreamFormatBox streamFormat = (StreamFormatBox) peekNext(streamChildren, i, StreamFormatBox.STRF);
|
||||||
|
|
@ -208,10 +202,10 @@ public class AviExtractor implements Extractor {
|
||||||
i++;
|
i++;
|
||||||
if (streamHeader.isVideo()) {
|
if (streamHeader.isVideo()) {
|
||||||
final VideoFormat videoFormat = streamFormat.getVideoFormat();
|
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;
|
final List<byte[]> codecData;
|
||||||
if (codecBox != null) {
|
if (codecBox != null) {
|
||||||
codecData = Collections.singletonList(codecBox.byteBuffer.array());
|
codecData = Collections.singletonList(codecBox.getData());
|
||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
codecData = null;
|
codecData = null;
|
||||||
|
|
|
||||||
|
|
@ -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
|
* This is referred to as a Chunk in the MS spec, but that gets confusing with AV chunks
|
||||||
*/
|
*/
|
||||||
public class Box {
|
public class Box {
|
||||||
private final long size;
|
private final int size;
|
||||||
private final int type;
|
private final int type;
|
||||||
|
|
||||||
Box(int type, long size) {
|
Box(int type, int size) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSize() {
|
public long getSize() {
|
||||||
|
return size & AviUtil.UINT_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSizeInt() {
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,60 @@
|
||||||
package com.google.android.exoplayer2.extractor.avi;
|
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.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class BoxFactory {
|
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) {
|
public ResidentBox createBox(final int type, final int size, final ByteBuffer byteBuffer) {
|
||||||
final ByteBuffer boxBuffer = AviExtractor.allocate(size);
|
final ByteBuffer boxBuffer = AviExtractor.allocate(size);
|
||||||
AviUtil.copy(byteBuffer, boxBuffer, 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) {
|
switch (type) {
|
||||||
case AviHeaderBox.AVIH:
|
case AviHeaderBox.AVIH:
|
||||||
return new AviHeaderBox(type, size, boxBuffer);
|
return new AviHeaderBox(type, size, boxBuffer);
|
||||||
case ListBox.LIST:
|
|
||||||
return new ListBox(type, size, boxBuffer);
|
|
||||||
case StreamHeaderBox.STRH:
|
case StreamHeaderBox.STRH:
|
||||||
return new StreamHeaderBox(type, size, boxBuffer);
|
return new StreamHeaderBox(type, size, boxBuffer);
|
||||||
case StreamFormatBox.STRF:
|
case StreamFormatBox.STRF:
|
||||||
return new StreamFormatBox(type, size, boxBuffer);
|
return new StreamFormatBox(type, size, boxBuffer);
|
||||||
default:
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,28 @@
|
||||||
package com.google.android.exoplayer2.extractor.avi;
|
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.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An AVI LIST box, memory resident
|
* 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);
|
public static final int LIST = 'L' | ('I' << 8) | ('S' << 16) | ('T' << 24);
|
||||||
//Header List
|
//Header List
|
||||||
public static final int TYPE_HDRL = 'h' | ('d' << 8) | ('r' << 16) | ('l' << 24);
|
public static final int TYPE_HDRL = 'h' | ('d' << 8) | ('r' << 16) | ('l' << 24);
|
||||||
|
|
||||||
private final int listType;
|
private final int listType;
|
||||||
|
|
||||||
ListBox(int type, int size, ByteBuffer byteBuffer) {
|
final List<Box> children;
|
||||||
super(type, size, byteBuffer);
|
|
||||||
listType = byteBuffer.getInt(0);
|
ListBox(int size, int listType, List<Box> children) {
|
||||||
|
super(LIST, size);
|
||||||
|
this.listType = listType;
|
||||||
|
this.children = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getListType() {
|
public int getListType() {
|
||||||
|
|
@ -25,4 +33,56 @@ public class ListBox extends ResidentBox {
|
||||||
boolean assertType() {
|
boolean assertType() {
|
||||||
return simpleAssert(LIST);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,32 +10,15 @@ import java.nio.BufferOverflowException;
|
||||||
import java.nio.BufferUnderflowException;
|
import java.nio.BufferUnderflowException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A box that is resident in memory
|
* A box that is resident in memory
|
||||||
*/
|
*/
|
||||||
public class ResidentBox extends Box {
|
public class ResidentBox extends Box {
|
||||||
private static final String TAG = AviExtractor.TAG;
|
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;
|
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) {
|
ResidentBox(int type, int size, ByteBuffer byteBuffer) {
|
||||||
super(type, size);
|
super(type, size);
|
||||||
this.byteBuffer = byteBuffer;
|
this.byteBuffer = byteBuffer;
|
||||||
|
|
@ -81,7 +64,6 @@ public class ResidentBox extends Box {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns shallow copy of this ByteBuffer with the position at 0
|
* Returns shallow copy of this ByteBuffer with the position at 0
|
||||||
* @return
|
* @return
|
||||||
|
|
@ -93,18 +75,4 @@ public class ResidentBox extends Box {
|
||||||
clone.clear();
|
clone.clear();
|
||||||
return clone;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue