mirror of
https://github.com/androidx/media.git
synced 2025-05-04 22:20:47 +08:00
Fix issue where reading mime type wrong in video. More tests
This commit is contained in:
parent
7ea2d75fcd
commit
f1d007e68c
@ -1,10 +1,11 @@
|
|||||||
package com.google.android.exoplayer2.extractor.avi;
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public class AudioFormat {
|
public class AudioFormat implements IStreamFormat {
|
||||||
public static final short WAVE_FORMAT_PCM = 1;
|
public static final short WAVE_FORMAT_PCM = 1;
|
||||||
static final short WAVE_FORMAT_AAC = 0xff;
|
static final short WAVE_FORMAT_AAC = 0xff;
|
||||||
private static final short WAVE_FORMAT_MPEGLAYER3 = 0x55;
|
private static final short WAVE_FORMAT_MPEGLAYER3 = 0x55;
|
||||||
@ -60,5 +61,16 @@ public class AudioFormat {
|
|||||||
temp.get(data);
|
temp.get(data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllKeyFrames() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @C.TrackType int getTrackType() {
|
||||||
|
return C.TRACK_TYPE_AUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Deal with WAVEFORMATEXTENSIBLE
|
//TODO: Deal with WAVEFORMATEXTENSIBLE
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.google.android.exoplayer2.extractor.avi;
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
@ -37,6 +38,12 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Greatly simplified way to calculate the picOrder
|
||||||
|
* Full logic is here
|
||||||
|
* https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/video/h264_poc.cc
|
||||||
|
* @param nalTypeOffset
|
||||||
|
*/
|
||||||
void updatePicCountClock(final int nalTypeOffset) {
|
void updatePicCountClock(final int nalTypeOffset) {
|
||||||
final ParsableNalUnitBitArray in = new ParsableNalUnitBitArray(buffer, nalTypeOffset + 1, buffer.length);
|
final ParsableNalUnitBitArray in = new ParsableNalUnitBitArray(buffer, nalTypeOffset + 1, buffer.length);
|
||||||
//slide_header()
|
//slide_header()
|
||||||
@ -63,7 +70,8 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
|||||||
picCountClock.setIndex(picCountClock.getIndex());
|
picCountClock.setIndex(picCountClock.getIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
private int readSps(ExtractorInput input, int nalTypeOffset) throws IOException {
|
@VisibleForTesting
|
||||||
|
int readSps(ExtractorInput input, int nalTypeOffset) throws IOException {
|
||||||
final int spsStart = nalTypeOffset + 1;
|
final int spsStart = nalTypeOffset + 1;
|
||||||
nalTypeOffset = seekNextNal(input, spsStart);
|
nalTypeOffset = seekNextNal(input, spsStart);
|
||||||
spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos);
|
spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos);
|
||||||
|
@ -39,6 +39,21 @@ public class AviExtractor implements Extractor {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static long alignPosition(long position) {
|
||||||
|
if ((position & 1) == 1) {
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void alignInput(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) {
|
||||||
|
input.skipFully(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static final String TAG = "AviExtractor";
|
static final String TAG = "AviExtractor";
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final int PEEK_BYTES = 28;
|
static final int PEEK_BYTES = 28;
|
||||||
@ -81,28 +96,14 @@ public class AviExtractor implements Extractor {
|
|||||||
//At the start of the movi tag
|
//At the start of the movi tag
|
||||||
private long moviOffset;
|
private long moviOffset;
|
||||||
private long moviEnd;
|
private long moviEnd;
|
||||||
private AviSeekMap aviSeekMap;
|
@VisibleForTesting
|
||||||
|
AviSeekMap aviSeekMap;
|
||||||
|
|
||||||
// private long indexOffset; //Usually chunkStart
|
// private long indexOffset; //Usually chunkStart
|
||||||
|
|
||||||
//If partial read
|
//If partial read
|
||||||
private transient AviTrack chunkHandler;
|
private transient AviTrack chunkHandler;
|
||||||
|
|
||||||
static void alignInput(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) {
|
|
||||||
input.skipFully(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static long alignPosition(long position) {
|
|
||||||
if ((position & 1) == 1) {
|
|
||||||
position++;
|
|
||||||
}
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param input
|
* @param input
|
||||||
@ -161,7 +162,20 @@ public class AviExtractor implements Extractor {
|
|||||||
return byteBuffer;
|
return byteBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSeekMap(AviSeekMap aviSeekMap) {
|
@VisibleForTesting
|
||||||
|
static int getStreamId(int chunkId) {
|
||||||
|
final int upperChar = chunkId & 0xff;
|
||||||
|
if (Character.isDigit(upperChar)) {
|
||||||
|
final int lowerChar = (chunkId >> 8) & 0xff;
|
||||||
|
if (Character.isDigit(upperChar)) {
|
||||||
|
return (lowerChar & 0xf) + ((upperChar & 0xf) * 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void setSeekMap(AviSeekMap aviSeekMap) {
|
||||||
this.aviSeekMap = aviSeekMap;
|
this.aviSeekMap = aviSeekMap;
|
||||||
output.seekMap(aviSeekMap);
|
output.seekMap(aviSeekMap);
|
||||||
}
|
}
|
||||||
@ -191,16 +205,17 @@ public class AviExtractor implements Extractor {
|
|||||||
this.output = output;
|
this.output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseStream(final ListBox streamList, int streamId) {
|
@VisibleForTesting
|
||||||
|
AviTrack parseStream(final ListBox streamList, int streamId) {
|
||||||
final StreamHeaderBox streamHeader = streamList.getChild(StreamHeaderBox.class);
|
final StreamHeaderBox streamHeader = streamList.getChild(StreamHeaderBox.class);
|
||||||
final StreamFormatBox streamFormat = streamList.getChild(StreamFormatBox.class);
|
final StreamFormatBox streamFormat = streamList.getChild(StreamFormatBox.class);
|
||||||
if (streamHeader == null) {
|
if (streamHeader == null) {
|
||||||
Log.w(TAG, "Missing Stream Header");
|
Log.w(TAG, "Missing Stream Header");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
if (streamFormat == null) {
|
if (streamFormat == null) {
|
||||||
Log.w(TAG, "Missing Stream Format");
|
Log.w(TAG, "Missing Stream Format");
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
final Format.Builder builder = new Format.Builder();
|
final Format.Builder builder = new Format.Builder();
|
||||||
builder.setId(streamId);
|
builder.setId(streamId);
|
||||||
@ -212,31 +227,33 @@ public class AviExtractor implements Extractor {
|
|||||||
if (streamName != null) {
|
if (streamName != null) {
|
||||||
builder.setLabel(streamName.getName());
|
builder.setLabel(streamName.getName());
|
||||||
}
|
}
|
||||||
|
final AviTrack aviTrack;
|
||||||
if (streamHeader.isVideo()) {
|
if (streamHeader.isVideo()) {
|
||||||
final String mimeType = streamHeader.getMimeType();
|
final VideoFormat videoFormat = streamFormat.getVideoFormat();
|
||||||
|
final String mimeType = videoFormat.getMimeType();
|
||||||
if (mimeType == null) {
|
if (mimeType == null) {
|
||||||
Log.w(TAG, "Unknown FourCC: " + toString(streamHeader.getFourCC()));
|
Log.w(TAG, "Unknown FourCC: " + toString(streamHeader.getFourCC()));
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
final VideoFormat videoFormat = streamFormat.getVideoFormat();
|
|
||||||
final TrackOutput trackOutput = output.track(streamId, C.TRACK_TYPE_VIDEO);
|
final TrackOutput trackOutput = output.track(streamId, C.TRACK_TYPE_VIDEO);
|
||||||
builder.setWidth(videoFormat.getWidth());
|
builder.setWidth(videoFormat.getWidth());
|
||||||
builder.setHeight(videoFormat.getHeight());
|
builder.setHeight(videoFormat.getHeight());
|
||||||
builder.setFrameRate(streamHeader.getFrameRate());
|
builder.setFrameRate(streamHeader.getFrameRate());
|
||||||
builder.setSampleMimeType(mimeType);
|
builder.setSampleMimeType(mimeType);
|
||||||
|
|
||||||
final AviTrack aviTrack = new AviTrack(streamId, streamHeader, trackOutput);
|
if (MimeTypes.VIDEO_H264.equals(mimeType)) {
|
||||||
if (MimeTypes.VIDEO_MP4V.equals(mimeType)) {
|
|
||||||
Mp4vChunkPeeker mp4vChunkPeeker = new Mp4vChunkPeeker(builder, trackOutput);
|
|
||||||
aviTrack.setChunkPeeker(mp4vChunkPeeker);
|
|
||||||
} else if (MimeTypes.VIDEO_H264.equals(mimeType)) {
|
|
||||||
final AvcChunkPeeker avcChunkPeeker = new AvcChunkPeeker(builder, trackOutput, streamHeader.getUsPerSample());
|
final AvcChunkPeeker avcChunkPeeker = new AvcChunkPeeker(builder, trackOutput, streamHeader.getUsPerSample());
|
||||||
aviTrack.setClock(avcChunkPeeker.getPicCountClock());
|
aviTrack = new AviTrack(streamId, videoFormat, avcChunkPeeker.getPicCountClock(), trackOutput);
|
||||||
aviTrack.setChunkPeeker(avcChunkPeeker);
|
aviTrack.setChunkPeeker(avcChunkPeeker);
|
||||||
|
} else {
|
||||||
|
aviTrack = new AviTrack(streamId, videoFormat,
|
||||||
|
new LinearClock(streamHeader.getUsPerSample()), trackOutput);
|
||||||
|
if (MimeTypes.VIDEO_MP4V.equals(mimeType)) {
|
||||||
|
aviTrack.setChunkPeeker(new Mp4vChunkPeeker(builder, trackOutput));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trackOutput.format(builder.build());
|
trackOutput.format(builder.build());
|
||||||
durationUs = streamHeader.getUsPerSample() * streamHeader.getLength();
|
durationUs = streamHeader.getUsPerSample() * streamHeader.getLength();
|
||||||
aviTracks[streamId] = aviTrack;
|
|
||||||
} else if (streamHeader.isAudio()) {
|
} else if (streamHeader.isAudio()) {
|
||||||
final AudioFormat audioFormat = streamFormat.getAudioFormat();
|
final AudioFormat audioFormat = streamFormat.getAudioFormat();
|
||||||
final TrackOutput trackOutput = output.track(streamId, C.TRACK_TYPE_AUDIO);
|
final TrackOutput trackOutput = output.track(streamId, C.TRACK_TYPE_AUDIO);
|
||||||
@ -257,8 +274,12 @@ public class AviExtractor implements Extractor {
|
|||||||
builder.setInitializationData(Collections.singletonList(audioFormat.getCodecData()));
|
builder.setInitializationData(Collections.singletonList(audioFormat.getCodecData()));
|
||||||
}
|
}
|
||||||
trackOutput.format(builder.build());
|
trackOutput.format(builder.build());
|
||||||
aviTracks[streamId] = new AviTrack(streamId, streamHeader, trackOutput);
|
aviTrack = new AviTrack(streamId, audioFormat, new LinearClock(streamHeader.getUsPerSample()),
|
||||||
|
trackOutput);
|
||||||
|
}else {
|
||||||
|
aviTrack = null;
|
||||||
}
|
}
|
||||||
|
return aviTrack;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int readTracks(ExtractorInput input) throws IOException {
|
private int readTracks(ExtractorInput input) throws IOException {
|
||||||
@ -278,7 +299,7 @@ public class AviExtractor implements Extractor {
|
|||||||
for (Box box : headerList.getChildren()) {
|
for (Box box : headerList.getChildren()) {
|
||||||
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;
|
||||||
parseStream(streamList, streamId);
|
aviTracks[streamId] = parseStream(streamList, streamId);
|
||||||
streamId++;
|
streamId++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -343,7 +364,8 @@ public class AviExtractor implements Extractor {
|
|||||||
for (int i=0;i<seekOffsets.length;i++) {
|
for (int i=0;i<seekOffsets.length;i++) {
|
||||||
seekOffsets[i] = new UnboundedIntArray();
|
seekOffsets[i] = new UnboundedIntArray();
|
||||||
}
|
}
|
||||||
final int seekFrameRate = (int)(videoTrack.streamHeaderBox.getFrameRate() * 2);
|
//TODO: Change this to min frame rate
|
||||||
|
final int seekFrameRate = (int)(1f/(videoTrack.getClock().usPerChunk / 1_000_000f) * 2);
|
||||||
|
|
||||||
final UnboundedIntArray keyFrameList = new UnboundedIntArray();
|
final UnboundedIntArray keyFrameList = new UnboundedIntArray();
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
@ -406,17 +428,6 @@ public class AviExtractor implements Extractor {
|
|||||||
setSeekMap(seekMap);
|
setSeekMap(seekMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getStreamId(int chunkId) {
|
|
||||||
final int upperChar = chunkId & 0xff;
|
|
||||||
if (Character.isDigit(upperChar)) {
|
|
||||||
final int lowerChar = (chunkId >> 8) & 0xff;
|
|
||||||
if (Character.isDigit(upperChar)) {
|
|
||||||
return (lowerChar & 0xf) + ((upperChar & 0xf) * 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private AviTrack getAviTrack(int chunkId) {
|
private AviTrack getAviTrack(int chunkId) {
|
||||||
final int streamId = getStreamId(chunkId);
|
final int streamId = getStreamId(chunkId);
|
||||||
|
@ -3,7 +3,6 @@ package com.google.android.exoplayer2.extractor.avi;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer2.extractor.SeekPoint;
|
import com.google.android.exoplayer2.extractor.SeekPoint;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
|
||||||
|
|
||||||
public class AviSeekMap implements SeekMap {
|
public class AviSeekMap implements SeekMap {
|
||||||
final long videoUsPerChunk;
|
final long videoUsPerChunk;
|
||||||
@ -58,7 +57,7 @@ public class AviSeekMap implements SeekMap {
|
|||||||
int offset = seekOffsets[videoStreamId][seekFrameIndex];
|
int offset = seekOffsets[videoStreamId][seekFrameIndex];
|
||||||
final long outUs = seekFrameIndex * seekIndexFactor * videoUsPerChunk;
|
final long outUs = seekFrameIndex * seekIndexFactor * videoUsPerChunk;
|
||||||
final long position = offset + moviOffset;
|
final long position = offset + moviOffset;
|
||||||
Log.d(AviExtractor.TAG, "SeekPoint: us=" + outUs + " pos=" + position);
|
//Log.d(AviExtractor.TAG, "SeekPoint: us=" + outUs + " pos=" + position);
|
||||||
|
|
||||||
return new SeekPoints(new SeekPoint(outUs, position));
|
return new SeekPoints(new SeekPoint(outUs, position));
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
import com.google.android.exoplayer2.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -16,23 +15,22 @@ public class AviTrack {
|
|||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
final StreamHeaderBox streamHeaderBox;
|
final LinearClock clock;
|
||||||
|
|
||||||
@NonNull
|
|
||||||
LinearClock clock;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
ChunkPeeker chunkPeeker;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True indicates all frames are key frames (e.g. Audio, MJPEG)
|
* True indicates all frames are key frames (e.g. Audio, MJPEG)
|
||||||
*/
|
*/
|
||||||
boolean allKeyFrames;
|
final boolean allKeyFrames;
|
||||||
|
final @C.TrackType int trackType;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
final TrackOutput trackOutput;
|
||||||
|
|
||||||
boolean forceKeyFrame;
|
boolean forceKeyFrame;
|
||||||
|
|
||||||
@NonNull
|
@Nullable
|
||||||
TrackOutput trackOutput;
|
ChunkPeeker chunkPeeker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key is frame number value is offset
|
* Key is frame number value is offset
|
||||||
@ -43,22 +41,19 @@ public class AviTrack {
|
|||||||
transient int chunkSize;
|
transient int chunkSize;
|
||||||
transient int chunkRemaining;
|
transient int chunkRemaining;
|
||||||
|
|
||||||
AviTrack(int id, @NonNull StreamHeaderBox streamHeaderBox, @NonNull TrackOutput trackOutput) {
|
AviTrack(int id, @NonNull IStreamFormat streamFormat, @NonNull LinearClock clock,
|
||||||
|
@NonNull TrackOutput trackOutput) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.clock = clock;
|
||||||
|
this.allKeyFrames = streamFormat.isAllKeyFrames();
|
||||||
|
this.trackType = streamFormat.getTrackType();
|
||||||
this.trackOutput = trackOutput;
|
this.trackOutput = trackOutput;
|
||||||
this.streamHeaderBox = streamHeaderBox;
|
|
||||||
clock = new LinearClock(streamHeaderBox.getUsPerSample());
|
|
||||||
this.allKeyFrames = streamHeaderBox.isAudio() || (MimeTypes.VIDEO_MJPEG.equals(streamHeaderBox.getMimeType()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinearClock getClock() {
|
public LinearClock getClock() {
|
||||||
return clock;
|
return clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setClock(LinearClock clock) {
|
|
||||||
this.clock = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChunkPeeker(ChunkPeeker chunkPeeker) {
|
public void setChunkPeeker(ChunkPeeker chunkPeeker) {
|
||||||
this.chunkPeeker = chunkPeeker;
|
this.chunkPeeker = chunkPeeker;
|
||||||
}
|
}
|
||||||
@ -78,8 +73,6 @@ public class AviTrack {
|
|||||||
if (keyFrames != null) {
|
if (keyFrames != null) {
|
||||||
return Arrays.binarySearch(keyFrames, clock.getIndex()) >= 0;
|
return Arrays.binarySearch(keyFrames, clock.getIndex()) >= 0;
|
||||||
}
|
}
|
||||||
//Hack: Exo needs at least one frame before it starts playback
|
|
||||||
//return clock.getIndex() == 0;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,11 +85,11 @@ public class AviTrack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isVideo() {
|
public boolean isVideo() {
|
||||||
return streamHeaderBox.isVideo();
|
return trackType == C.TRACK_TYPE_VIDEO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAudio() {
|
public boolean isAudio() {
|
||||||
return streamHeaderBox.isAudio();
|
return trackType == C.TRACK_TYPE_AUDIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean newChunk(int tag, int size, ExtractorInput input) throws IOException {
|
public boolean newChunk(int tag, int size, ExtractorInput input) throws IOException {
|
||||||
@ -114,7 +107,13 @@ public class AviTrack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean resume(ExtractorInput input) throws IOException {
|
/**
|
||||||
|
* Resume a partial read of a chunk
|
||||||
|
* @param input
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
boolean resume(ExtractorInput input) throws IOException {
|
||||||
chunkRemaining -= trackOutput.sampleData(input, chunkRemaining, false);
|
chunkRemaining -= trackOutput.sampleData(input, chunkRemaining, false);
|
||||||
if (chunkRemaining == 0) {
|
if (chunkRemaining == 0) {
|
||||||
done(chunkSize);
|
done(chunkSize);
|
||||||
@ -124,6 +123,10 @@ public class AviTrack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Done reading a chunk
|
||||||
|
* @param size
|
||||||
|
*/
|
||||||
void done(final int size) {
|
void done(final int size) {
|
||||||
trackOutput.sampleMetadata(
|
trackOutput.sampleMetadata(
|
||||||
clock.getUs(), (isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0), size, 0, null);
|
clock.getUs(), (isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0), size, 0, null);
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
|
||||||
|
public interface IStreamFormat {
|
||||||
|
String getMimeType();
|
||||||
|
boolean isAllKeyFrames();
|
||||||
|
@C.TrackType int getTrackType();
|
||||||
|
}
|
@ -4,6 +4,8 @@ package com.google.android.exoplayer2.extractor.avi;
|
|||||||
* Properly calculates the frame time for H264 frames using PicCount
|
* Properly calculates the frame time for H264 frames using PicCount
|
||||||
*/
|
*/
|
||||||
public class PicCountClock extends LinearClock {
|
public class PicCountClock extends LinearClock {
|
||||||
|
//I believe this is 2 because there is a bottom pic order and a top pic order
|
||||||
|
private static final int STEP = 2;
|
||||||
//The frame as a calculated from the picCount
|
//The frame as a calculated from the picCount
|
||||||
private int picIndex;
|
private int picIndex;
|
||||||
private int lastPicCount;
|
private int lastPicCount;
|
||||||
@ -19,7 +21,7 @@ public class PicCountClock extends LinearClock {
|
|||||||
|
|
||||||
public void setMaxPicCount(int maxPicCount) {
|
public void setMaxPicCount(int maxPicCount) {
|
||||||
this.maxPicCount = maxPicCount;
|
this.maxPicCount = maxPicCount;
|
||||||
posHalf = maxPicCount / 2; //Not sure why pics are 2x
|
posHalf = maxPicCount / STEP;
|
||||||
negHalf = -posHalf;
|
negHalf = -posHalf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ public class PicCountClock extends LinearClock {
|
|||||||
} else if (delta > posHalf) {
|
} else if (delta > posHalf) {
|
||||||
delta -= maxPicCount;
|
delta -= maxPicCount;
|
||||||
}
|
}
|
||||||
picIndex += delta / 2;
|
picIndex += delta / STEP;
|
||||||
lastPicCount = picCount;
|
lastPicCount = picCount;
|
||||||
if (maxPicIndex < picIndex) {
|
if (maxPicIndex < picIndex) {
|
||||||
maxPicIndex = picIndex;
|
maxPicIndex = picIndex;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package com.google.android.exoplayer2.extractor.avi;
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
import android.util.SparseArray;
|
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,31 +14,6 @@ public class StreamHeaderBox extends ResidentBox {
|
|||||||
//Videos Stream
|
//Videos Stream
|
||||||
static final int VIDS = 'v' | ('i' << 8) | ('d' << 16) | ('s' << 24);
|
static final int VIDS = 'v' | ('i' << 8) | ('d' << 16) | ('s' << 24);
|
||||||
|
|
||||||
static final int XVID = 'X' | ('V' << 8) | ('I' << 16) | ('D' << 24);
|
|
||||||
|
|
||||||
private static final SparseArray<String> STREAM_MAP = new SparseArray<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
//Although other types are technically supported, AVI is almost exclusively MP4V and MJPEG
|
|
||||||
final String mimeType = MimeTypes.VIDEO_MP4V;
|
|
||||||
//final String mimeType = MimeTypes.VIDEO_H263;
|
|
||||||
|
|
||||||
//I've never seen an Android devices that actually supports MP42
|
|
||||||
STREAM_MAP.put('M' | ('P' << 8) | ('4' << 16) | ('2' << 24), MimeTypes.BASE_TYPE_VIDEO+"/mp42");
|
|
||||||
//Samsung seems to support the rare MP43.
|
|
||||||
STREAM_MAP.put('M' | ('P' << 8) | ('4' << 16) | ('3' << 24), MimeTypes.BASE_TYPE_VIDEO+"/mp43");
|
|
||||||
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(XVID, mimeType);
|
|
||||||
STREAM_MAP.put('D' | ('X' << 8) | ('5' << 16) | ('0' << 24), mimeType);
|
|
||||||
STREAM_MAP.put('d' | ('i' << 8) | ('v' << 16) | ('x' << 24), mimeType);
|
|
||||||
|
|
||||||
STREAM_MAP.put('m' | ('j' << 8) | ('p' << 16) | ('g' << 24), MimeTypes.VIDEO_MJPEG);
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamHeaderBox(int type, int size, ByteBuffer byteBuffer) {
|
StreamHeaderBox(int type, int size, ByteBuffer byteBuffer) {
|
||||||
super(type, size, byteBuffer);
|
super(type, size, byteBuffer);
|
||||||
}
|
}
|
||||||
@ -64,11 +37,6 @@ public class StreamHeaderBox extends ResidentBox {
|
|||||||
return getScale() * 1_000_000L / getRate();
|
return getScale() * 1_000_000L / getRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMimeType() {
|
|
||||||
return STREAM_MAP.get(getFourCC());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int getSteamType() {
|
public int getSteamType() {
|
||||||
return byteBuffer.getInt(0);
|
return byteBuffer.getInt(0);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,38 @@
|
|||||||
package com.google.android.exoplayer2.extractor.avi;
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class VideoFormat implements IStreamFormat {
|
||||||
|
|
||||||
|
static final int XVID = 'X' | ('V' << 8) | ('I' << 16) | ('D' << 24);
|
||||||
|
|
||||||
|
private static final HashMap<Integer, String> STREAM_MAP = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
//Although other types are technically supported, AVI is almost exclusively MP4V and MJPEG
|
||||||
|
final String mimeType = MimeTypes.VIDEO_MP4V;
|
||||||
|
//final String mimeType = MimeTypes.VIDEO_H263;
|
||||||
|
|
||||||
|
//I've never seen an Android devices that actually supports MP42
|
||||||
|
STREAM_MAP.put('M' | ('P' << 8) | ('4' << 16) | ('2' << 24), MimeTypes.BASE_TYPE_VIDEO+"/mp42");
|
||||||
|
//Samsung seems to support the rare MP43.
|
||||||
|
STREAM_MAP.put('M' | ('P' << 8) | ('4' << 16) | ('3' << 24), MimeTypes.BASE_TYPE_VIDEO+"/mp43");
|
||||||
|
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(XVID, mimeType);
|
||||||
|
STREAM_MAP.put('D' | ('X' << 8) | ('5' << 16) | ('0' << 24), mimeType);
|
||||||
|
STREAM_MAP.put('d' | ('i' << 8) | ('v' << 16) | ('x' << 24), mimeType);
|
||||||
|
|
||||||
|
STREAM_MAP.put('M' | ('J' << 8) | ('P' << 16) | ('G' << 24), MimeTypes.VIDEO_MJPEG);
|
||||||
|
STREAM_MAP.put('m' | ('j' << 8) | ('p' << 16) | ('g' << 24), MimeTypes.VIDEO_MJPEG);
|
||||||
|
}
|
||||||
|
|
||||||
public class VideoFormat {
|
|
||||||
private final ByteBuffer byteBuffer;
|
private final ByteBuffer byteBuffer;
|
||||||
|
|
||||||
public VideoFormat(final ByteBuffer byteBuffer) {
|
public VideoFormat(final ByteBuffer byteBuffer) {
|
||||||
@ -17,5 +47,23 @@ public class VideoFormat {
|
|||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
return byteBuffer.getInt(8);
|
return byteBuffer.getInt(8);
|
||||||
}
|
}
|
||||||
|
// 12 - biPlanes
|
||||||
|
// 14 - biBitCount
|
||||||
|
public int getCompression() {
|
||||||
|
return byteBuffer.getInt(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMimeType() {
|
||||||
|
return STREAM_MAP.get(getCompression());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllKeyFrames() {
|
||||||
|
return MimeTypes.VIDEO_MJPEG.equals(getMimeType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTrackType() {
|
||||||
|
return C.TRACK_TYPE_VIDEO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ public class AudioFormatTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getters_givenAacStreamFormat() throws IOException {
|
public void getters_givenAacStreamFormat() throws IOException {
|
||||||
final StreamFormatBox streamFormatBox = DataHelper.getAudioStreamFormat();
|
final StreamFormatBox streamFormatBox = DataHelper.getAacStreamFormat();
|
||||||
final AudioFormat audioFormat = streamFormatBox.getAudioFormat();
|
final AudioFormat audioFormat = streamFormatBox.getAudioFormat();
|
||||||
Assert.assertEquals(MimeTypes.AUDIO_AAC, audioFormat.getMimeType());
|
Assert.assertEquals(MimeTypes.AUDIO_AAC, audioFormat.getMimeType());
|
||||||
Assert.assertEquals(2, audioFormat.getChannels());
|
Assert.assertEquals(2, audioFormat.getChannels());
|
||||||
@ -21,5 +21,6 @@ public class AudioFormatTest {
|
|||||||
Assert.assertEquals(48000, audioFormat.getSamplesPerSecond());
|
Assert.assertEquals(48000, audioFormat.getSamplesPerSecond());
|
||||||
Assert.assertEquals(0, audioFormat.getBitsPerSample()); //Not meaningful for AAC
|
Assert.assertEquals(0, audioFormat.getBitsPerSample()); //Not meaningful for AAC
|
||||||
Assert.assertArrayEquals(CODEC_PRIVATE, audioFormat.getCodecData());
|
Assert.assertArrayEquals(CODEC_PRIVATE, audioFormat.getCodecData());
|
||||||
|
Assert.assertEquals(MimeTypes.AUDIO_AAC, audioFormat.getMimeType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
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 org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class AviExtractorRoboTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseStream_givenH264StreamList() throws IOException {
|
||||||
|
final AviExtractor aviExtractor = new AviExtractor();
|
||||||
|
final FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
|
||||||
|
aviExtractor.init(fakeExtractorOutput);
|
||||||
|
final ListBox streamList = DataHelper.getVideoStreamList();
|
||||||
|
aviExtractor.parseStream(streamList, 0);
|
||||||
|
FakeTrackOutput trackOutput = fakeExtractorOutput.track(0, C.TRACK_TYPE_VIDEO);
|
||||||
|
Assert.assertEquals(MimeTypes.VIDEO_H264, trackOutput.lastFormat.sampleMimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void parseStream_givenAacStreamList() throws IOException {
|
||||||
|
final AviExtractor aviExtractor = new AviExtractor();
|
||||||
|
final FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
|
||||||
|
aviExtractor.init(fakeExtractorOutput);
|
||||||
|
final ListBox streamList = DataHelper.getAacStreamList();
|
||||||
|
aviExtractor.parseStream(streamList, 0);
|
||||||
|
FakeTrackOutput trackOutput = fakeExtractorOutput.track(0, C.TRACK_TYPE_VIDEO);
|
||||||
|
Assert.assertEquals(MimeTypes.AUDIO_AAC, trackOutput.lastFormat.sampleMimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -98,4 +98,54 @@ public class AviExtractorTest {
|
|||||||
final int riff = 'R' | ('I' << 8) | ('F' << 16) | ('F' << 24);
|
final int riff = 'R' | ('I' << 8) | ('F' << 16) | ('F' << 24);
|
||||||
Assert.assertEquals("RIFF", AviExtractor.toString(riff));
|
Assert.assertEquals("RIFF", AviExtractor.toString(riff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void alignPosition_givenOddPosition() {
|
||||||
|
Assert.assertEquals(2, AviExtractor.alignPosition(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void alignPosition_givenEvenPosition() {
|
||||||
|
Assert.assertEquals(2, AviExtractor.alignPosition(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void alignInput_givenOddPosition() throws IOException {
|
||||||
|
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
|
||||||
|
setData(new byte[16]).build();
|
||||||
|
fakeExtractorInput.setPosition(1);
|
||||||
|
AviExtractor.alignInput(fakeExtractorInput);
|
||||||
|
Assert.assertEquals(2, fakeExtractorInput.getPosition());
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
|
||||||
|
public void alignInput_givenEvenPosition() throws IOException {
|
||||||
|
final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().
|
||||||
|
setData(new byte[16]).build();
|
||||||
|
fakeExtractorInput.setPosition(4);
|
||||||
|
AviExtractor.alignInput(fakeExtractorInput);
|
||||||
|
Assert.assertEquals(4, fakeExtractorInput.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setSeekMap_givenStubbedSeekMap() throws IOException {
|
||||||
|
final AviSeekMap aviSeekMap = DataHelper.getAviSeekMap();
|
||||||
|
final AviExtractor aviExtractor = new AviExtractor();
|
||||||
|
final FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput();
|
||||||
|
aviExtractor.init(fakeExtractorOutput);
|
||||||
|
aviExtractor.setSeekMap(aviSeekMap);
|
||||||
|
Assert.assertEquals(aviSeekMap, fakeExtractorOutput.seekMap);
|
||||||
|
Assert.assertEquals(aviSeekMap, aviExtractor.aviSeekMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStreamId_givenInvalidStreamId() {
|
||||||
|
Assert.assertEquals(-1, AviExtractor.getStreamId(AviExtractor.JUNK));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStreamId_givenValidStreamId() {
|
||||||
|
Assert.assertEquals(1, AviExtractor.getStreamId('0' | ('1' << 8) | ('d' << 16) | ('c' << 24)));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package com.google.android.exoplayer2.extractor.avi;
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
|
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
|
||||||
|
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
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.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class DataHelper {
|
public class DataHelper {
|
||||||
@ -31,7 +33,14 @@ public class DataHelper {
|
|||||||
return new StreamHeaderBox(StreamHeaderBox.STRH, buffer.length, byteBuffer);
|
return new StreamHeaderBox(StreamHeaderBox.STRH, buffer.length, byteBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StreamFormatBox getAudioStreamFormat() throws IOException {
|
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 StreamFormatBox getAacStreamFormat() throws IOException {
|
||||||
final byte[] buffer = getBytes("aac_stream_format.dump");
|
final byte[] buffer = getBytes("aac_stream_format.dump");
|
||||||
final ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
|
final ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
|
||||||
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
@ -45,6 +54,26 @@ public class DataHelper {
|
|||||||
return new StreamFormatBox(StreamFormatBox.STRF, buffer.length, byteBuffer);
|
return new StreamFormatBox(StreamFormatBox.STRF, buffer.length, byteBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ListBox getVideoStreamList() throws IOException {
|
||||||
|
final StreamHeaderBox streamHeaderBox = getVidsStreamHeader();
|
||||||
|
final StreamFormatBox streamFormatBox = getVideoStreamFormat();
|
||||||
|
final ArrayList<Box> list = new ArrayList<>(2);
|
||||||
|
list.add(streamHeaderBox);
|
||||||
|
list.add(streamFormatBox);
|
||||||
|
return new ListBox((int)(streamHeaderBox.getSize() + streamFormatBox.getSize()),
|
||||||
|
AviExtractor.STRL, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ListBox getAacStreamList() throws IOException {
|
||||||
|
final StreamHeaderBox streamHeaderBox = getAudioStreamHeader();
|
||||||
|
final StreamFormatBox streamFormatBox = getAacStreamFormat();
|
||||||
|
final ArrayList<Box> list = new ArrayList<>(2);
|
||||||
|
list.add(streamHeaderBox);
|
||||||
|
list.add(streamFormatBox);
|
||||||
|
return new ListBox((int)(streamHeaderBox.getSize() + streamFormatBox.getSize()),
|
||||||
|
AviExtractor.STRL, list);
|
||||||
|
}
|
||||||
|
|
||||||
public static StreamNameBox getStreamNameBox(final String name) {
|
public static StreamNameBox getStreamNameBox(final String name) {
|
||||||
byte[] bytes = name.getBytes();
|
byte[] bytes = name.getBytes();
|
||||||
bytes = Arrays.copyOf(bytes, bytes.length + 1);
|
bytes = Arrays.copyOf(bytes, bytes.length + 1);
|
||||||
@ -58,4 +87,18 @@ public class DataHelper {
|
|||||||
byteBuffer.put(nalType);
|
byteBuffer.put(nalType);
|
||||||
return byteBuffer;
|
return byteBuffer;
|
||||||
}
|
}
|
||||||
|
public static AviSeekMap getAviSeekMap() throws IOException {
|
||||||
|
|
||||||
|
final FakeTrackOutput output = new FakeTrackOutput(false);
|
||||||
|
final AviTrack videoTrack = new AviTrack(0,
|
||||||
|
DataHelper.getVideoStreamFormat().getVideoFormat(), new LinearClock(100), output);
|
||||||
|
final UnboundedIntArray videoArray = new UnboundedIntArray();
|
||||||
|
videoArray.add(0);
|
||||||
|
videoArray.add(1024);
|
||||||
|
final UnboundedIntArray audioArray = new UnboundedIntArray();
|
||||||
|
audioArray.add(0);
|
||||||
|
audioArray.add(128);
|
||||||
|
return new AviSeekMap(videoTrack,
|
||||||
|
new UnboundedIntArray[]{videoArray, audioArray}, 24, 0L, 0L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Most of this is covered by the PicOrderClockTest
|
||||||
|
*/
|
||||||
|
public class LinearClockTest {
|
||||||
|
@Test
|
||||||
|
public void advance() {
|
||||||
|
final LinearClock linearClock = new LinearClock(100L);
|
||||||
|
linearClock.setIndex(2);
|
||||||
|
Assert.assertEquals(200, linearClock.getUs());
|
||||||
|
linearClock.advance();
|
||||||
|
Assert.assertEquals(300, linearClock.getUs());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.google.android.exoplayer2.extractor.avi;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class PicCountClockTest {
|
||||||
|
@Test
|
||||||
|
public void us_givenTwoStepsForward() {
|
||||||
|
final PicCountClock picCountClock = new PicCountClock(100);
|
||||||
|
picCountClock.setMaxPicCount(16*2);
|
||||||
|
picCountClock.setPicCount(2*2);
|
||||||
|
Assert.assertEquals(2*100, picCountClock.getUs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void us_givenThreeStepsBackwards() {
|
||||||
|
final PicCountClock picCountClock = new PicCountClock(100);
|
||||||
|
picCountClock.setMaxPicCount(16*2);
|
||||||
|
picCountClock.setPicCount(4*2); // 400ms
|
||||||
|
Assert.assertEquals(400, picCountClock.getUs());
|
||||||
|
picCountClock.setPicCount(1*2);
|
||||||
|
Assert.assertEquals(1*100, picCountClock.getUs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setIndex_given3Chunks() {
|
||||||
|
final PicCountClock picCountClock = new PicCountClock(100);
|
||||||
|
picCountClock.setIndex(3);
|
||||||
|
Assert.assertEquals(3*100, picCountClock.getUs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void us_giveWrapBackwards() {
|
||||||
|
final PicCountClock picCountClock = new PicCountClock(100);
|
||||||
|
picCountClock.setMaxPicCount(16*2);
|
||||||
|
//Need to walk up no faster than maxPicCount / 2
|
||||||
|
picCountClock.setPicCount(7*2);
|
||||||
|
picCountClock.setPicCount(11*2);
|
||||||
|
picCountClock.setPicCount(15*2);
|
||||||
|
picCountClock.setPicCount(1*2);
|
||||||
|
Assert.assertEquals(17*100, picCountClock.getUs());
|
||||||
|
picCountClock.setPicCount(14*2);
|
||||||
|
Assert.assertEquals(14*100, picCountClock.getUs());
|
||||||
|
}
|
||||||
|
}
|
@ -19,11 +19,10 @@ public class StreamHeaderBoxTest {
|
|||||||
Assert.assertTrue(streamHeaderBox.isVideo());
|
Assert.assertTrue(streamHeaderBox.isVideo());
|
||||||
Assert.assertFalse(streamHeaderBox.isAudio());
|
Assert.assertFalse(streamHeaderBox.isAudio());
|
||||||
Assert.assertEquals(StreamHeaderBox.VIDS, streamHeaderBox.getSteamType());
|
Assert.assertEquals(StreamHeaderBox.VIDS, streamHeaderBox.getSteamType());
|
||||||
Assert.assertEquals(StreamHeaderBox.XVID, streamHeaderBox.getFourCC());
|
Assert.assertEquals(VideoFormat.XVID, streamHeaderBox.getFourCC());
|
||||||
Assert.assertEquals(0, streamHeaderBox.getInitialFrames());
|
Assert.assertEquals(0, streamHeaderBox.getInitialFrames());
|
||||||
Assert.assertEquals(FPS24, streamHeaderBox.getFrameRate(), 0.1);
|
Assert.assertEquals(FPS24, streamHeaderBox.getFrameRate(), 0.1);
|
||||||
Assert.assertEquals(US_SAMPLE24FPS, streamHeaderBox.getUsPerSample());
|
Assert.assertEquals(US_SAMPLE24FPS, streamHeaderBox.getUsPerSample());
|
||||||
Assert.assertEquals(MimeTypes.VIDEO_MP4V, streamHeaderBox.getMimeType());
|
|
||||||
Assert.assertEquals(11805L, streamHeaderBox.getLength());
|
Assert.assertEquals(11805L, streamHeaderBox.getLength());
|
||||||
Assert.assertEquals(0, streamHeaderBox.getSuggestedBufferSize());
|
Assert.assertEquals(0, streamHeaderBox.getSuggestedBufferSize());
|
||||||
}
|
}
|
||||||
|
BIN
testdata/src/test/assets/extractordumps/avi/auds_stream_header.dump
vendored
Normal file
BIN
testdata/src/test/assets/extractordumps/avi/auds_stream_header.dump
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user