Play standalone MPEG-TS files.
- Have TsExtractor report a SeekMap to the output. - Implement TsExtractor.reset to reset extractor state. - Add 1x sample.
This commit is contained in:
parent
cfab852096
commit
efb9ff1fe7
@ -46,8 +46,9 @@ public class DemoUtil {
|
||||
public static final int TYPE_HLS = 2;
|
||||
public static final int TYPE_MP4 = 3;
|
||||
public static final int TYPE_MP3 = 4;
|
||||
public static final int TYPE_AAC = 5;
|
||||
public static final int TYPE_OTHER = 6;
|
||||
public static final int TYPE_TS = 5;
|
||||
public static final int TYPE_AAC = 6;
|
||||
public static final int TYPE_OTHER = 7;
|
||||
|
||||
private static final CookieManager defaultCookieManager;
|
||||
|
||||
|
@ -30,6 +30,7 @@ import com.google.android.exoplayer.demo.player.UnsupportedDrmException;
|
||||
import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
|
||||
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
|
||||
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
|
||||
import com.google.android.exoplayer.extractor.ts.TsExtractor;
|
||||
import com.google.android.exoplayer.metadata.GeobMetadata;
|
||||
import com.google.android.exoplayer.metadata.PrivMetadata;
|
||||
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
||||
@ -235,6 +236,9 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
||||
case DemoUtil.TYPE_MP3:
|
||||
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
||||
new Mp3Extractor());
|
||||
case DemoUtil.TYPE_TS:
|
||||
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
||||
new TsExtractor());
|
||||
case DemoUtil.TYPE_AAC:
|
||||
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
||||
new AdtsExtractor());
|
||||
|
@ -135,6 +135,9 @@ import java.util.Locale;
|
||||
new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
|
||||
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
|
||||
DemoUtil.TYPE_AAC),
|
||||
new Sample("Apple TS 10s", "https://devimages.apple.com.edgekey.net/streaming/examples/"
|
||||
+ "bipbop_4x3/gear1/fileSequence0.ts",
|
||||
DemoUtil.TYPE_TS),
|
||||
new Sample("Big Buck Bunny (MP4 Video)",
|
||||
"http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube"
|
||||
+ "&sparams=ip,ipbits,expire&ip=0.0.0.0&ipbits=0&expire=19000000000&signature="
|
||||
|
@ -58,6 +58,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
state = STATE_FINDING_SYNC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
state = STATE_FINDING_SYNC;
|
||||
bytesRead = 0;
|
||||
|
@ -58,8 +58,8 @@ public class AdtsExtractor implements Extractor, SeekMap {
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
adtsReader.seek();
|
||||
firstPacket = true;
|
||||
adtsReader.seek();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,12 +62,7 @@ import java.util.Collections;
|
||||
state = STATE_FINDING_SYNC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the reader that a seek has occurred.
|
||||
* <p>
|
||||
* The data passed to the next invocation of {@link #consume(ParsableByteArray, long, boolean)}
|
||||
* should not be treated as a continuation of the data passed to previous calls.
|
||||
*/
|
||||
@Override
|
||||
public void seek() {
|
||||
state = STATE_FINDING_SYNC;
|
||||
bytesRead = 0;
|
||||
|
@ -32,6 +32,15 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the reader that a seek has occurred.
|
||||
* <p>
|
||||
* Following a call to this method, the data passed to the next invocation of
|
||||
* {@link #consume(ParsableByteArray, long, boolean)} will not be a continuation of the data that
|
||||
* was previously passed. Hence the reader should reset any internal state.
|
||||
*/
|
||||
public abstract void seek();
|
||||
|
||||
/**
|
||||
* Consumes (possibly partial) payload data.
|
||||
*
|
||||
|
@ -63,22 +63,26 @@ import java.util.List;
|
||||
2f
|
||||
};
|
||||
|
||||
// State that should not be reset on seek.
|
||||
private boolean hasOutputFormat;
|
||||
|
||||
// State that should be reset on seek.
|
||||
private final SeiReader seiReader;
|
||||
private final boolean[] prefixFlags;
|
||||
private final NalUnitTargetBuffer sps;
|
||||
private final NalUnitTargetBuffer pps;
|
||||
private final NalUnitTargetBuffer sei;
|
||||
private final ParsableByteArray seiWrapper;
|
||||
|
||||
private boolean hasOutputFormat;
|
||||
private int scratchEscapeCount;
|
||||
private int[] scratchEscapePositions;
|
||||
|
||||
private boolean writingSample;
|
||||
private long totalBytesWritten;
|
||||
|
||||
// Per sample state that gets reset at the start of each sample.
|
||||
private boolean isKeyframe;
|
||||
private long samplePosition;
|
||||
private long sampleTimeUs;
|
||||
private long totalBytesWritten;
|
||||
|
||||
// Scratch variables to avoid allocations.
|
||||
private final ParsableByteArray seiWrapper;
|
||||
private int[] scratchEscapePositions;
|
||||
|
||||
public H264Reader(TrackOutput output, SeiReader seiReader) {
|
||||
super(output);
|
||||
@ -91,6 +95,17 @@ import java.util.List;
|
||||
scratchEscapePositions = new int[10];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
seiReader.seek();
|
||||
H264Util.clearPrefixFlags(prefixFlags);
|
||||
sps.reset();
|
||||
pps.reset();
|
||||
sei.reset();
|
||||
writingSample = false;
|
||||
totalBytesWritten = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
||||
while (data.bytesLeft() > 0) {
|
||||
@ -128,9 +143,9 @@ import java.util.List;
|
||||
writingSample = false;
|
||||
}
|
||||
writingSample = true;
|
||||
samplePosition = totalBytesWritten - bytesWrittenPastNalUnit;
|
||||
sampleTimeUs = pesTimeUs;
|
||||
isKeyframe = false;
|
||||
sampleTimeUs = pesTimeUs;
|
||||
samplePosition = totalBytesWritten - bytesWrittenPastNalUnit;
|
||||
} else if (nalUnitType == NAL_UNIT_TYPE_IDR) {
|
||||
isKeyframe = true;
|
||||
}
|
||||
@ -317,7 +332,7 @@ import java.util.List;
|
||||
*/
|
||||
private int unescapeStream(byte[] data, int limit) {
|
||||
int position = 0;
|
||||
scratchEscapeCount = 0;
|
||||
int scratchEscapeCount = 0;
|
||||
while (position < limit) {
|
||||
position = findNextUnescapeIndex(data, position, limit);
|
||||
if (position < limit) {
|
||||
@ -378,6 +393,17 @@ import java.util.List;
|
||||
nalData[3] = (byte) targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the buffer, clearing any data that it holds.
|
||||
*/
|
||||
public void reset() {
|
||||
isFilling = false;
|
||||
isCompleted = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the buffer currently holds a complete NAL unit of the target type.
|
||||
*/
|
||||
public boolean isCompleted() {
|
||||
return isCompleted;
|
||||
}
|
||||
|
@ -25,7 +25,10 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
*/
|
||||
/* package */ class Id3Reader extends ElementaryStreamReader {
|
||||
|
||||
// State that should be reset on seek.
|
||||
private boolean writingSample;
|
||||
|
||||
// Per sample state that gets reset at the start of each sample.
|
||||
private long sampleTimeUs;
|
||||
private int sampleSize;
|
||||
|
||||
@ -34,6 +37,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
output.format(MediaFormat.createId3Format());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
writingSample = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
||||
if (startOfPacket) {
|
||||
|
@ -34,6 +34,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
output.format(MediaFormat.createEia608Format());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray seiBuffer, long pesTimeUs, boolean startOfPacket) {
|
||||
// Skip the NAL prefix and type.
|
||||
|
@ -20,6 +20,7 @@ import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||
import com.google.android.exoplayer.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer.extractor.SeekMap;
|
||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||
|
||||
@ -32,7 +33,7 @@ import java.io.IOException;
|
||||
/**
|
||||
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
||||
*/
|
||||
public final class TsExtractor implements Extractor {
|
||||
public final class TsExtractor implements Extractor, SeekMap {
|
||||
|
||||
private static final String TAG = "TsExtractor";
|
||||
|
||||
@ -74,14 +75,21 @@ public final class TsExtractor implements Extractor {
|
||||
lastPts = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
// Extractor implementation.
|
||||
|
||||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
this.output = output;
|
||||
output.seekMap(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
throw new UnsupportedOperationException();
|
||||
timestampOffsetUs = 0;
|
||||
lastPts = Long.MIN_VALUE;
|
||||
for (int i = 0; i < tsPayloadReaders.size(); i++) {
|
||||
tsPayloadReaders.valueAt(i).seek();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,6 +133,20 @@ public final class TsExtractor implements Extractor {
|
||||
return RESULT_CONTINUE;
|
||||
}
|
||||
|
||||
// SeekMap implementation.
|
||||
|
||||
@Override
|
||||
public boolean isSeekable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition(long timeUs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Internals.
|
||||
|
||||
/**
|
||||
* Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound.
|
||||
*
|
||||
@ -157,6 +179,22 @@ public final class TsExtractor implements Extractor {
|
||||
*/
|
||||
private abstract static class TsPayloadReader {
|
||||
|
||||
/**
|
||||
* Notifies the reader that a seek has occurred.
|
||||
* <p>
|
||||
* Following a call to this method, the data passed to the next invocation of
|
||||
* {@link #consume(ParsableByteArray, boolean, ExtractorOutput)} will not be a continuation of
|
||||
* the data that was previously passed. Hence the reader should reset any internal state.
|
||||
*/
|
||||
public abstract void seek();
|
||||
|
||||
/**
|
||||
* Consumes the payload of a TS packet.
|
||||
*
|
||||
* @param data The TS packet. The position will be set to the start of the payload.
|
||||
* @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet.
|
||||
* @param output The output to which parsed data should be written.
|
||||
*/
|
||||
public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||
ExtractorOutput output);
|
||||
|
||||
@ -173,6 +211,11 @@ public final class TsExtractor implements Extractor {
|
||||
patScratch = new ParsableBitArray(new byte[4]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||
ExtractorOutput output) {
|
||||
@ -213,6 +256,11 @@ public final class TsExtractor implements Extractor {
|
||||
pmtScratch = new ParsableBitArray(new byte[5]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||
ExtractorOutput output) {
|
||||
@ -311,9 +359,7 @@ public final class TsExtractor implements Extractor {
|
||||
|
||||
private boolean ptsFlag;
|
||||
private int extendedHeaderLength;
|
||||
|
||||
private int payloadSize;
|
||||
|
||||
private long timeUs;
|
||||
|
||||
public PesReader(ElementaryStreamReader pesPayloadReader) {
|
||||
@ -322,6 +368,14 @@ public final class TsExtractor implements Extractor {
|
||||
state = STATE_FINDING_HEADER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek() {
|
||||
state = STATE_FINDING_HEADER;
|
||||
bytesRead = 0;
|
||||
bodyStarted = false;
|
||||
pesPayloadReader.seek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||
ExtractorOutput output) {
|
||||
|
@ -144,6 +144,17 @@ public final class H264Util {
|
||||
return endOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears prefix flags, as used by {@link #findNalUnit(byte[], int, int, boolean[])}.
|
||||
*
|
||||
* @param prefixFlags The flags to clear.
|
||||
*/
|
||||
public static void clearPrefixFlags(boolean[] prefixFlags) {
|
||||
prefixFlags[0] = false;
|
||||
prefixFlags[1] = false;
|
||||
prefixFlags[2] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned integer into an integer. This method is suitable for use when it can be
|
||||
* assumed that the top bit will always be set to zero.
|
||||
@ -162,17 +173,6 @@ public final class H264Util {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears prefix flags, as used by {@link #findNalUnit(byte[], int, int, boolean[])}.
|
||||
*
|
||||
* @param prefixFlags The flags to clear.
|
||||
*/
|
||||
private static void clearPrefixFlags(boolean[] prefixFlags) {
|
||||
prefixFlags[0] = false;
|
||||
prefixFlags[1] = false;
|
||||
prefixFlags[2] = false;
|
||||
}
|
||||
|
||||
private H264Util() {
|
||||
// Prevent instantiation.
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user