Make FLV video seekable by a seekMap.

This commit is contained in:
Will 2020-05-14 17:14:47 +08:00
parent 535e14cb4d
commit 99960acec3
2 changed files with 67 additions and 6 deletions

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.flv;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ChunkIndex;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -29,6 +30,7 @@ import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@ -83,6 +85,7 @@ public final class FlvExtractor implements Extractor {
private int tagDataSize; private int tagDataSize;
private long tagTimestampUs; private long tagTimestampUs;
private boolean outputSeekMap; private boolean outputSeekMap;
private boolean seekMapIsSeekable;
private @MonotonicNonNull AudioTagPayloadReader audioReader; private @MonotonicNonNull AudioTagPayloadReader audioReader;
private @MonotonicNonNull VideoTagPayloadReader videoReader; private @MonotonicNonNull VideoTagPayloadReader videoReader;
@ -133,7 +136,12 @@ public final class FlvExtractor implements Extractor {
@Override @Override
public void seek(long position, long timeUs) { public void seek(long position, long timeUs) {
state = STATE_READING_FLV_HEADER; if (seekMapIsSeekable) {
state = STATE_READING_TAG_HEADER;
} else {
state = STATE_READING_FLV_HEADER;
mediaTagTimestampOffsetUs = C.TIME_UNSET;
}
outputFirstSample = false; outputFirstSample = false;
bytesToNextTagHeader = 0; bytesToNextTagHeader = 0;
} }
@ -263,11 +271,13 @@ public final class FlvExtractor implements Extractor {
wasSampleOutput = videoReader.consume(prepareTagData(input), timestampUs); wasSampleOutput = videoReader.consume(prepareTagData(input), timestampUs);
} else if (tagType == TAG_TYPE_SCRIPT_DATA && !outputSeekMap) { } else if (tagType == TAG_TYPE_SCRIPT_DATA && !outputSeekMap) {
wasSampleOutput = metadataReader.consume(prepareTagData(input), timestampUs); wasSampleOutput = metadataReader.consume(prepareTagData(input), timestampUs);
long durationUs = metadataReader.getDurationUs(); SeekMap seekMap = buildSeekMap(metadataReader.getSeekMapTimes(),
if (durationUs != C.TIME_UNSET) { metadataReader.getSeekMapFilePositions(),
extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); metadataReader.getDurationUs(),
outputSeekMap = true; input.getLength());
} seekMapIsSeekable = seekMap.isSeekable();
extractorOutput.seekMap(seekMap);
outputSeekMap = true;
} else { } else {
input.skipFully(tagDataSize); input.skipFully(tagDataSize);
wasConsumed = false; wasConsumed = false;
@ -301,6 +311,32 @@ public final class FlvExtractor implements Extractor {
} }
} }
private SeekMap buildSeekMap(List<Double> times, List<Double> filePositions, long durationUs,
long flvDataSize) {
if (durationUs == C.TIME_UNSET
|| times == null || times.size() == 0
|| filePositions == null || filePositions.size() != times.size()) {
// Key frames information is missing or incomplete.
return new SeekMap.Unseekable(durationUs);
}
int keyFrameSize = times.size();
int[] sizes = new int[keyFrameSize];
long[] offsets = new long[keyFrameSize];
long[] durationsUs = new long[keyFrameSize];
long[] timesUs = new long[keyFrameSize];
for (int i = 0; i < keyFrameSize; i++) {
timesUs[i] = (long) (times.get(i) * C.MICROS_PER_SECOND);
offsets[i] = (long) (filePositions.get(i) + 0);
}
for (int i = 0; i < keyFrameSize - 1; i++) {
sizes[i] = (int) (offsets[i + 1] - offsets[i]);
durationsUs[i] = timesUs[i + 1] - timesUs[i];
}
sizes[keyFrameSize - 1] = (int) (flvDataSize - sizes[keyFrameSize - 2]);
durationsUs[keyFrameSize - 1] = durationUs - timesUs[keyFrameSize - 1];
return new ChunkIndex(sizes, offsets, durationsUs, timesUs);
}
private long getCurrentTimestampUs() { private long getCurrentTimestampUs() {
return outputFirstSample return outputFirstSample
? (mediaTagTimestampOffsetUs + tagTimestampUs) ? (mediaTagTimestampOffsetUs + tagTimestampUs)

View File

@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -32,6 +33,9 @@ import java.util.Map;
private static final String NAME_METADATA = "onMetaData"; private static final String NAME_METADATA = "onMetaData";
private static final String KEY_DURATION = "duration"; private static final String KEY_DURATION = "duration";
private static final String KEY_KEY_FRAMES = "keyframes";
private static final String KEY_FILE_POSITIONS = "filepositions";
private static final String KEY_TIMES = "times";
// AMF object types // AMF object types
private static final int AMF_TYPE_NUMBER = 0; private static final int AMF_TYPE_NUMBER = 0;
@ -45,6 +49,9 @@ import java.util.Map;
private long durationUs; private long durationUs;
private List<Double> seekMapFilePositions;
private List<Double> seekMapTimes;
public ScriptTagPayloadReader() { public ScriptTagPayloadReader() {
super(new DummyTrackOutput()); super(new DummyTrackOutput());
durationUs = C.TIME_UNSET; durationUs = C.TIME_UNSET;
@ -89,6 +96,16 @@ import java.util.Map;
durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND); durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND);
} }
} }
if (metadata.containsKey(KEY_KEY_FRAMES)) {
Object frames = metadata.get(KEY_KEY_FRAMES);
if (frames instanceof Map) {
Map framesMap = (Map) metadata.get(KEY_KEY_FRAMES);
if (framesMap.size() > 0) {
seekMapFilePositions = (List<Double>) framesMap.get(KEY_FILE_POSITIONS);
seekMapTimes = (List<Double>) framesMap.get(KEY_TIMES);
}
}
}
return false; return false;
} }
@ -224,4 +241,12 @@ import java.util.Map;
return null; return null;
} }
} }
public List<Double> getSeekMapFilePositions() {
return seekMapFilePositions;
}
public List<Double> getSeekMapTimes() {
return seekMapTimes;
}
} }