Commit a9c94185 by Dustin

Working!

parent 33d22a12
Showing with 320 additions and 68 deletions
...@@ -5,14 +5,16 @@ import com.google.android.exoplayer2.util.MimeTypes; ...@@ -5,14 +5,16 @@ import com.google.android.exoplayer2.util.MimeTypes;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class AudioFormat { public class AudioFormat {
public static short WAVE_FORMAT_PCM = 1; public static final short WAVE_FORMAT_PCM = 1;
public static short WAVE_FORMAT_MPEGLAYER3 = 0x55; private static final short WAVE_FORMAT_MPEGLAYER3 = 0x55;
public static short WAVE_FORMAT_DVM = 0x2000; //AC3 private static final short WAVE_FORMAT_AAC = 0xff;
public static short WAVE_FORMAT_DTS2 = 0x2001; //DTS private static final short WAVE_FORMAT_DVM = 0x2000; //AC3
private static final short WAVE_FORMAT_DTS2 = 0x2001; //DTS
private static final SparseArray<String> FORMAT_MAP = new SparseArray<>(); private static final SparseArray<String> FORMAT_MAP = new SparseArray<>();
static { static {
FORMAT_MAP.put(WAVE_FORMAT_PCM, MimeTypes.AUDIO_RAW); FORMAT_MAP.put(WAVE_FORMAT_PCM, MimeTypes.AUDIO_RAW);
FORMAT_MAP.put(WAVE_FORMAT_MPEGLAYER3, MimeTypes.AUDIO_MPEG); FORMAT_MAP.put(WAVE_FORMAT_MPEGLAYER3, MimeTypes.AUDIO_MPEG);
FORMAT_MAP.put(WAVE_FORMAT_AAC, MimeTypes.AUDIO_AAC);
FORMAT_MAP.put(WAVE_FORMAT_DVM, MimeTypes.AUDIO_AC3); FORMAT_MAP.put(WAVE_FORMAT_DVM, MimeTypes.AUDIO_AC3);
FORMAT_MAP.put(WAVE_FORMAT_DTS2, MimeTypes.AUDIO_DTS); FORMAT_MAP.put(WAVE_FORMAT_DTS2, MimeTypes.AUDIO_DTS);
} }
...@@ -24,7 +26,7 @@ public class AudioFormat { ...@@ -24,7 +26,7 @@ public class AudioFormat {
this.byteBuffer = byteBuffer; this.byteBuffer = byteBuffer;
} }
public String getCodec() { public String getMimeType() {
return FORMAT_MAP.get(getFormatTag() & 0xffff); return FORMAT_MAP.get(getFormatTag() & 0xffff);
} }
...@@ -38,12 +40,24 @@ public class AudioFormat { ...@@ -38,12 +40,24 @@ public class AudioFormat {
return byteBuffer.getInt(4); return byteBuffer.getInt(4);
} }
// 8 - nAvgBytesPerSec(uint) // 8 - nAvgBytesPerSec(uint)
// 12 - nBlockAlign(ushort) public int getBlockAlign() {
return byteBuffer.getShort(12);
}
public short getBitsPerSample() { public short getBitsPerSample() {
return byteBuffer.getShort(14); return byteBuffer.getShort(14);
} }
public short getCbSize() { public int getCbSize() {
return byteBuffer.getShort(16); return byteBuffer.getShort(16) & 0xffff;
}
public byte[] getCodecData() {
final int size = getCbSize();
final ByteBuffer temp = byteBuffer.duplicate();
temp.clear();
temp.position(18);
temp.limit(18 + size);
final byte[] data = new byte[size];
temp.get(data);
return data;
} }
//TODO: Deal with WAVEFORMATEXTENSIBLE //TODO: Deal with WAVEFORMATEXTENSIBLE
} }
...@@ -2,13 +2,13 @@ package com.google.android.exoplayer2.extractor.avi; ...@@ -2,13 +2,13 @@ package com.google.android.exoplayer2.extractor.avi;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class AviHeader extends ResidentBox { public class AviHeaderBox extends ResidentBox {
public static final int AVIF_HASINDEX = 0x10; public static final int AVIF_HASINDEX = 0x10;
static final int AVIH = 'a' | ('v' << 8) | ('i' << 16) | ('h' << 24); static final int AVIH = 'a' | ('v' << 8) | ('i' << 16) | ('h' << 24);
//AVIMAINHEADER //AVIMAINHEADER
AviHeader(int type, int size, ByteBuffer byteBuffer) { AviHeaderBox(int type, int size, ByteBuffer byteBuffer) {
super(type, size, byteBuffer); super(type, size, byteBuffer);
} }
......
package com.google.android.exoplayer2.extractor.avi;
import android.util.SparseArray;
import androidx.annotation.NonNull;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.SeekPoint;
import com.google.android.exoplayer2.util.Log;
public class AviSeekMap implements SeekMap {
final AviTrack videoTrack;
/**
* Number of frames per index
* i.e. videoFrameOffsetMap[1] is frame 1 * seekIndexFactor
*/
final int seekIndexFactor;
//Map from the Video Frame index to the offset
final int[] videoFrameOffsetMap;
//Holds a map of video frameIds to audioFrameIds for each audioId
final SparseArray<int[]> audioIdMap;
final long moviOffset;
final long duration;
public AviSeekMap(AviTrack videoTrack, int seekIndexFactor, int[] videoFrameOffsetMap,
SparseArray<int[]> audioIdMap, long moviOffset, long duration) {
this.videoTrack = videoTrack;
this.seekIndexFactor = seekIndexFactor;
this.videoFrameOffsetMap = videoFrameOffsetMap;
this.audioIdMap = audioIdMap;
this.moviOffset = moviOffset;
this.duration = duration;
}
@Override
public boolean isSeekable() {
return true;
}
@Override
public long getDurationUs() {
return duration;
}
private int getSeekFrameIndex(long timeUs) {
final int reqFrame = (int)(timeUs / videoTrack.usPerSample);
int reqFrameIndex = reqFrame / seekIndexFactor;
if (reqFrameIndex >= videoFrameOffsetMap.length) {
reqFrameIndex = videoFrameOffsetMap.length - 1;
}
return reqFrameIndex;
}
@NonNull
@Override
public SeekPoints getSeekPoints(long timeUs) {
final int seekFrameIndex = getSeekFrameIndex(timeUs);
int offset = videoFrameOffsetMap[seekFrameIndex];
final long outUs = seekFrameIndex * seekIndexFactor * videoTrack.usPerSample;
final long position = offset + moviOffset;
Log.d(AviExtractor.TAG, "SeekPoint: us=" + outUs + " pos=" + position);
return new SeekPoints(new SeekPoint(outUs, position));
}
public void setFrames(final long position, final long timeUs, final SparseArray<AviTrack> idTrackMap) {
final int seekFrameIndex = getSeekFrameIndex(timeUs);
videoTrack.frame = seekFrameIndex * seekIndexFactor;
for (int i=0;i<audioIdMap.size();i++) {
final int audioId = audioIdMap.keyAt(i);
final int[] video2AudioFrameMap = audioIdMap.get(audioId);
final AviTrack audioTrack = idTrackMap.get(audioId);
audioTrack.frame = video2AudioFrameMap[seekFrameIndex];
}
}
}
package com.google.android.exoplayer2.extractor.avi;
import android.util.SparseIntArray;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
/**
* Collection of info about a track
*/
public class AviTrack {
final int id;
@NonNull
final TrackOutput trackOutput;
@NonNull
final StreamHeaderBox streamHeaderBox;
long usPerSample;
/**
* True indicates all frames are key frames (e.g. Audio, MJPEG)
*/
boolean allKeyFrames;
/**
* Key is frame number value is offset
*/
@Nullable
int[] keyFrames;
/**
* Current frame in the stream
* This needs to be updated on seek
* TODO: Should be offset from StreamHeaderBox.getStart()
*/
transient int frame;
/**
*
* @param trackOutput
*/
AviTrack(int id, @NonNull TrackOutput trackOutput, @NonNull StreamHeaderBox streamHeaderBox) {
this.id = id;
this.trackOutput = trackOutput;
this.streamHeaderBox = streamHeaderBox;
this.usPerSample = streamHeaderBox.getUsPerSample();
this.allKeyFrames = streamHeaderBox.isAudio() || (MimeTypes.IMAGE_JPEG.equals(streamHeaderBox.getMimeType()));
}
public boolean isKeyFrame() {
if (allKeyFrames) {
return true;
}
return keyFrames != null && Arrays.binarySearch(keyFrames, frame) >= 0;
}
public void setKeyFrames(int[] keyFrames) {
this.keyFrames = keyFrames;
}
public long getUs() {
return frame * usPerSample;
}
public void advance() {
frame++;
}
public boolean isVideo() {
return streamHeaderBox.isVideo();
}
public boolean isAudio() {
return streamHeaderBox.isAudio();
}
}
...@@ -65,17 +65,4 @@ public class AviUtil { ...@@ -65,17 +65,4 @@ public class AviUtil {
} }
return null; return null;
} }
@Nullable
static IAviList getSubList(List<? extends Box> list, int listType) {
for (Box box : list) {
if (IAviList.class.isInstance(box)) {
final IAviList aviList = (IAviList) box;
if (aviList.getListType() == listType) {
return aviList;
}
}
}
return null;
}
} }
package com.google.android.exoplayer2.extractor.avi; 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 { public class Box {
private final long size; private final long size;
private final int type; private final int type;
......
package com.google.android.exoplayer2.extractor.avi;
import androidx.annotation.NonNull;
import java.nio.ByteBuffer;
public class BoxFactory {
@NonNull
public ResidentBox createBox(final int type, final int size, final ByteBuffer byteBuffer) {
final ByteBuffer boxBuffer = AviExtractor.allocate(size);
AviUtil.copy(byteBuffer, boxBuffer, size);
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);
}
}
}
package com.google.android.exoplayer2.extractor.avi;
public interface IAviList {
int LIST = 'L' | ('I' << 8) | ('S' << 16) | ('T' << 24);
//Header List
int TYPE_HDRL = 'h' | ('d' << 8) | ('r' << 16) | ('l' << 24);
int getListType();
}
...@@ -5,15 +5,18 @@ import java.nio.ByteBuffer; ...@@ -5,15 +5,18 @@ import java.nio.ByteBuffer;
/** /**
* An AVI LIST box, memory resident * An AVI LIST box, memory resident
*/ */
public class ResidentList extends ResidentBox implements IAviList { public class ListBox extends ResidentBox {
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; private final int listType;
ResidentList(int type, int size, ByteBuffer byteBuffer) { ListBox(int type, int size, ByteBuffer byteBuffer) {
super(type, size, byteBuffer); super(type, size, byteBuffer);
listType = byteBuffer.getInt(0); listType = byteBuffer.getInt(0);
} }
@Override
public int getListType() { public int getListType() {
return listType; return listType;
} }
......
...@@ -13,25 +13,28 @@ import java.nio.ByteOrder; ...@@ -13,25 +13,28 @@ import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* 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 = 2*1024; final private static int MAX_RESIDENT = 64*1024;
final ByteBuffer byteBuffer; final ByteBuffer byteBuffer;
private Class<? extends ResidentBox> getClass(final int type) { // private Class<? extends ResidentBox> getClass(final int type) {
switch (type) { // switch (type) {
case AviHeader.AVIH: // case AviHeaderBox.AVIH:
return AviHeader.class; // return AviHeaderBox.class;
case IAviList.LIST: // case ListBox.LIST:
return ResidentList.class; // return ListBox.class;
case StreamHeader.STRH: // case StreamHeaderBox.STRH:
return StreamHeader.class; // return StreamHeaderBox.class;
case StreamFormat.STRF: // case StreamFormatBox.STRF:
return StreamFormat.class; // return StreamFormatBox.class;
default: // default:
return ResidentBox.class; // return ResidentBox.class;
} // }
} // }
ResidentBox(int type, int size, ByteBuffer byteBuffer) { ResidentBox(int type, int size, ByteBuffer byteBuffer) {
super(type, size); super(type, size);
...@@ -92,20 +95,14 @@ public class ResidentBox extends Box { ...@@ -92,20 +95,14 @@ public class ResidentBox extends Box {
} }
@NonNull @NonNull
public List<ResidentBox> getBoxList() { public List<ResidentBox> getBoxList(final BoxFactory boxFactory) {
final ByteBuffer temp = getByteBuffer(); final ByteBuffer temp = getByteBuffer();
temp.position(4); temp.position(4);
final List<ResidentBox> list = new ArrayList<>(); final List<ResidentBox> list = new ArrayList<>();
while (temp.hasRemaining()) { while (temp.hasRemaining()) {
final int type = temp.getInt(); final int type = temp.getInt();
final int size = temp.getInt(); final int size = temp.getInt();
final Class<? extends ResidentBox> clazz = getClass(type); final ResidentBox residentBox = boxFactory.createBox(type, size, temp);
final ByteBuffer boxBuffer = AviExtractor.allocate(size);
AviUtil.copy(temp, boxBuffer, size);
final ResidentBox residentBox = newInstance(type, size, boxBuffer, clazz);
if (residentBox == null) {
break;
}
list.add(residentBox); list.add(residentBox);
} }
return list; return list;
......
...@@ -3,10 +3,10 @@ package com.google.android.exoplayer2.extractor.avi; ...@@ -3,10 +3,10 @@ package com.google.android.exoplayer2.extractor.avi;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class StreamFormat extends ResidentBox { public class StreamFormatBox extends ResidentBox {
public static final int STRF = 's' | ('t' << 8) | ('r' << 16) | ('f' << 24); public static final int STRF = 's' | ('t' << 8) | ('r' << 16) | ('f' << 24);
StreamFormat(int type, int size, ByteBuffer byteBuffer) { StreamFormatBox(int type, int size, ByteBuffer byteBuffer) {
super(type, size, byteBuffer); super(type, size, byteBuffer);
} }
......
...@@ -4,7 +4,10 @@ import android.util.SparseArray; ...@@ -4,7 +4,10 @@ import android.util.SparseArray;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
public class StreamHeader extends ResidentBox { /**
* AVISTREAMHEADER
*/
public class StreamHeaderBox extends ResidentBox {
public static final int STRH = 's' | ('t' << 8) | ('r' << 16) | ('h' << 24); public static final int STRH = 's' | ('t' << 8) | ('r' << 16) | ('h' << 24);
//Audio Stream //Audio Stream
...@@ -16,15 +19,23 @@ public class StreamHeader extends ResidentBox { ...@@ -16,15 +19,23 @@ public class StreamHeader extends ResidentBox {
private static final SparseArray<String> STREAM_MAP = new SparseArray<>(); private static final SparseArray<String> STREAM_MAP = new SparseArray<>();
static { static {
STREAM_MAP.put('M' | ('P' << 8) | ('4' << 16) | ('2' << 24), MimeTypes.VIDEO_MP4V); //Although other types are technically supported, AVI is almost exclusively MP4V and MJPEG
STREAM_MAP.put('3' | ('V' << 8) | ('I' << 16) | ('D' << 24), MimeTypes.VIDEO_MP4V); final String mimeType = MimeTypes.VIDEO_MP4V;
STREAM_MAP.put('x' | ('v' << 8) | ('i' << 16) | ('d' << 24), MimeTypes.VIDEO_MP4V); //final String mimeType = MimeTypes.VIDEO_H263;
STREAM_MAP.put('X' | ('V' << 8) | ('I' << 16) | ('D' << 24), MimeTypes.VIDEO_MP4V);
//Doesn't seem to be supported on Android
//STREAM_MAP.put('M' | ('P' << 8) | ('4' << 16) | ('2' << 24), MimeTypes.VIDEO_MP4);
STREAM_MAP.put('H' | ('2' << 8) | ('6' << 16) | ('4' << 24), MimeTypes.VIDEO_H264);
STREAM_MAP.put('a' | ('v' << 8) | ('c' << 16) | ('1' << 24), MimeTypes.VIDEO_H264);
STREAM_MAP.put('A' | ('V' << 8) | ('C' << 16) | ('1' << 24), MimeTypes.VIDEO_H264);
STREAM_MAP.put('3' | ('V' << 8) | ('I' << 16) | ('D' << 24), mimeType);
STREAM_MAP.put('x' | ('v' << 8) | ('i' << 16) | ('d' << 24), mimeType);
STREAM_MAP.put('X' | ('V' << 8) | ('I' << 16) | ('D' << 24), mimeType);
STREAM_MAP.put('m' | ('j' << 8) | ('p' << 16) | ('g' << 24), MimeTypes.IMAGE_JPEG); STREAM_MAP.put('m' | ('j' << 8) | ('p' << 16) | ('g' << 24), MimeTypes.IMAGE_JPEG);
} }
StreamHeader(int type, int size, ByteBuffer byteBuffer) { StreamHeaderBox(int type, int size, ByteBuffer byteBuffer) {
super(type, size, byteBuffer); super(type, size, byteBuffer);
} }
...@@ -40,7 +51,15 @@ public class StreamHeader extends ResidentBox { ...@@ -40,7 +51,15 @@ public class StreamHeader extends ResidentBox {
return getRate() / (float)getScale(); return getRate() / (float)getScale();
} }
public String getCodec() { /**
* How long each sample covers
* @return
*/
public long getUsPerSample() {
return getScale() * 1_000_000L / getRate();
}
public String getMimeType() {
return STREAM_MAP.get(getFourCC()); return STREAM_MAP.get(getFourCC());
} }
...@@ -67,8 +86,15 @@ public class StreamHeader extends ResidentBox { ...@@ -67,8 +86,15 @@ public class StreamHeader extends ResidentBox {
public int getRate() { public int getRate() {
return byteBuffer.getInt(24); return byteBuffer.getInt(24);
} }
//28 - dwStart public int getStart() {
return byteBuffer.getInt(28);
}
public long getLength() { public long getLength() {
return byteBuffer.getInt(32) & AviUtil.UINT_MASK; return byteBuffer.getInt(32) & AviUtil.UINT_MASK;
} }
//36 - dwSuggestedBufferSize
//40 - dwQuality
public int getSampleSize() {
return byteBuffer.getInt(44);
}
} }
package com.google.android.exoplayer2.extractor.avi;
import androidx.annotation.NonNull;
import java.util.Arrays;
public class UnboundedIntArray {
@NonNull
int[] array;
//unint
int size =0;
public UnboundedIntArray() {
this(8);
}
public UnboundedIntArray(int size) {
if (size < 0) {
throw new IllegalArgumentException("Initial size must be positive: " + size);
}
array = new int[size];
}
public void add(int v) {
if (size == array.length) {
grow();
}
array[size++] = v;
}
public int getSize() {
return size;
}
public void pack() {
array = Arrays.copyOf(array, size);
}
protected void grow() {
int increase = Math.max(array.length /4, 1);
array = Arrays.copyOf(array, increase + array.length + size);
}
/**
* Only works if values are in sequential order
* @param v
* @return
*/
public int indexOf(int v) {
return Arrays.binarySearch(array, v);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment