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_HLS = 2;
|
||||||
public static final int TYPE_MP4 = 3;
|
public static final int TYPE_MP4 = 3;
|
||||||
public static final int TYPE_MP3 = 4;
|
public static final int TYPE_MP3 = 4;
|
||||||
public static final int TYPE_AAC = 5;
|
public static final int TYPE_TS = 5;
|
||||||
public static final int TYPE_OTHER = 6;
|
public static final int TYPE_AAC = 6;
|
||||||
|
public static final int TYPE_OTHER = 7;
|
||||||
|
|
||||||
private static final CookieManager defaultCookieManager;
|
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.mp3.Mp3Extractor;
|
||||||
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
|
import com.google.android.exoplayer.extractor.mp4.Mp4Extractor;
|
||||||
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
|
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.GeobMetadata;
|
||||||
import com.google.android.exoplayer.metadata.PrivMetadata;
|
import com.google.android.exoplayer.metadata.PrivMetadata;
|
||||||
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
import com.google.android.exoplayer.metadata.TxxxMetadata;
|
||||||
@ -235,6 +236,9 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||||||
case DemoUtil.TYPE_MP3:
|
case DemoUtil.TYPE_MP3:
|
||||||
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
||||||
new Mp3Extractor());
|
new Mp3Extractor());
|
||||||
|
case DemoUtil.TYPE_TS:
|
||||||
|
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
||||||
|
new TsExtractor());
|
||||||
case DemoUtil.TYPE_AAC:
|
case DemoUtil.TYPE_AAC:
|
||||||
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
return new ExtractorRendererBuilder(userAgent, contentUri, debugTextView,
|
||||||
new AdtsExtractor());
|
new AdtsExtractor());
|
||||||
|
@ -135,6 +135,9 @@ import java.util.Locale;
|
|||||||
new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
|
new Sample("Apple AAC 10s", "https://devimages.apple.com.edgekey.net/"
|
||||||
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
|
+ "streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
|
||||||
DemoUtil.TYPE_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)",
|
new Sample("Big Buck Bunny (MP4 Video)",
|
||||||
"http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube"
|
"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="
|
+ "&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;
|
state = STATE_FINDING_SYNC;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
state = STATE_FINDING_SYNC;
|
state = STATE_FINDING_SYNC;
|
||||||
bytesRead = 0;
|
bytesRead = 0;
|
||||||
|
@ -58,8 +58,8 @@ public class AdtsExtractor implements Extractor, SeekMap {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
adtsReader.seek();
|
|
||||||
firstPacket = true;
|
firstPacket = true;
|
||||||
|
adtsReader.seek();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,12 +62,7 @@ import java.util.Collections;
|
|||||||
state = STATE_FINDING_SYNC;
|
state = STATE_FINDING_SYNC;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
public void seek() {
|
public void seek() {
|
||||||
state = STATE_FINDING_SYNC;
|
state = STATE_FINDING_SYNC;
|
||||||
bytesRead = 0;
|
bytesRead = 0;
|
||||||
|
@ -32,6 +32,15 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
this.output = output;
|
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.
|
* Consumes (possibly partial) payload data.
|
||||||
*
|
*
|
||||||
|
@ -63,22 +63,26 @@ import java.util.List;
|
|||||||
2f
|
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 SeiReader seiReader;
|
||||||
private final boolean[] prefixFlags;
|
private final boolean[] prefixFlags;
|
||||||
private final NalUnitTargetBuffer sps;
|
private final NalUnitTargetBuffer sps;
|
||||||
private final NalUnitTargetBuffer pps;
|
private final NalUnitTargetBuffer pps;
|
||||||
private final NalUnitTargetBuffer sei;
|
private final NalUnitTargetBuffer sei;
|
||||||
private final ParsableByteArray seiWrapper;
|
|
||||||
|
|
||||||
private boolean hasOutputFormat;
|
|
||||||
private int scratchEscapeCount;
|
|
||||||
private int[] scratchEscapePositions;
|
|
||||||
|
|
||||||
private boolean writingSample;
|
private boolean writingSample;
|
||||||
|
private long totalBytesWritten;
|
||||||
|
|
||||||
|
// Per sample state that gets reset at the start of each sample.
|
||||||
private boolean isKeyframe;
|
private boolean isKeyframe;
|
||||||
private long samplePosition;
|
private long samplePosition;
|
||||||
private long sampleTimeUs;
|
private long sampleTimeUs;
|
||||||
private long totalBytesWritten;
|
|
||||||
|
// Scratch variables to avoid allocations.
|
||||||
|
private final ParsableByteArray seiWrapper;
|
||||||
|
private int[] scratchEscapePositions;
|
||||||
|
|
||||||
public H264Reader(TrackOutput output, SeiReader seiReader) {
|
public H264Reader(TrackOutput output, SeiReader seiReader) {
|
||||||
super(output);
|
super(output);
|
||||||
@ -91,6 +95,17 @@ import java.util.List;
|
|||||||
scratchEscapePositions = new int[10];
|
scratchEscapePositions = new int[10];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
seiReader.seek();
|
||||||
|
H264Util.clearPrefixFlags(prefixFlags);
|
||||||
|
sps.reset();
|
||||||
|
pps.reset();
|
||||||
|
sei.reset();
|
||||||
|
writingSample = false;
|
||||||
|
totalBytesWritten = 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
@ -128,9 +143,9 @@ import java.util.List;
|
|||||||
writingSample = false;
|
writingSample = false;
|
||||||
}
|
}
|
||||||
writingSample = true;
|
writingSample = true;
|
||||||
samplePosition = totalBytesWritten - bytesWrittenPastNalUnit;
|
|
||||||
sampleTimeUs = pesTimeUs;
|
|
||||||
isKeyframe = false;
|
isKeyframe = false;
|
||||||
|
sampleTimeUs = pesTimeUs;
|
||||||
|
samplePosition = totalBytesWritten - bytesWrittenPastNalUnit;
|
||||||
} else if (nalUnitType == NAL_UNIT_TYPE_IDR) {
|
} else if (nalUnitType == NAL_UNIT_TYPE_IDR) {
|
||||||
isKeyframe = true;
|
isKeyframe = true;
|
||||||
}
|
}
|
||||||
@ -317,7 +332,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
private int unescapeStream(byte[] data, int limit) {
|
private int unescapeStream(byte[] data, int limit) {
|
||||||
int position = 0;
|
int position = 0;
|
||||||
scratchEscapeCount = 0;
|
int scratchEscapeCount = 0;
|
||||||
while (position < limit) {
|
while (position < limit) {
|
||||||
position = findNextUnescapeIndex(data, position, limit);
|
position = findNextUnescapeIndex(data, position, limit);
|
||||||
if (position < limit) {
|
if (position < limit) {
|
||||||
@ -378,6 +393,17 @@ import java.util.List;
|
|||||||
nalData[3] = (byte) targetType;
|
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() {
|
public boolean isCompleted() {
|
||||||
return isCompleted;
|
return isCompleted;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,10 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
*/
|
*/
|
||||||
/* package */ class Id3Reader extends ElementaryStreamReader {
|
/* package */ class Id3Reader extends ElementaryStreamReader {
|
||||||
|
|
||||||
|
// State that should be reset on seek.
|
||||||
private boolean writingSample;
|
private boolean writingSample;
|
||||||
|
|
||||||
|
// Per sample state that gets reset at the start of each sample.
|
||||||
private long sampleTimeUs;
|
private long sampleTimeUs;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
|
|
||||||
@ -34,6 +37,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
output.format(MediaFormat.createId3Format());
|
output.format(MediaFormat.createId3Format());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
writingSample = false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
||||||
if (startOfPacket) {
|
if (startOfPacket) {
|
||||||
|
@ -34,6 +34,11 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
output.format(MediaFormat.createEia608Format());
|
output.format(MediaFormat.createEia608Format());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray seiBuffer, long pesTimeUs, boolean startOfPacket) {
|
public void consume(ParsableByteArray seiBuffer, long pesTimeUs, boolean startOfPacket) {
|
||||||
// Skip the NAL prefix and type.
|
// 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.ExtractorInput;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer.extractor.PositionHolder;
|
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.ParsableBitArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
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.
|
* 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";
|
private static final String TAG = "TsExtractor";
|
||||||
|
|
||||||
@ -74,14 +75,21 @@ public final class TsExtractor implements Extractor {
|
|||||||
lastPts = Long.MIN_VALUE;
|
lastPts = Long.MIN_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extractor implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
|
output.seekMap(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
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
|
@Override
|
||||||
@ -125,6 +133,20 @@ public final class TsExtractor implements Extractor {
|
|||||||
return RESULT_CONTINUE;
|
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.
|
* 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 {
|
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,
|
public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
ExtractorOutput output);
|
ExtractorOutput output);
|
||||||
|
|
||||||
@ -173,6 +211,11 @@ public final class TsExtractor implements Extractor {
|
|||||||
patScratch = new ParsableBitArray(new byte[4]);
|
patScratch = new ParsableBitArray(new byte[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
ExtractorOutput output) {
|
ExtractorOutput output) {
|
||||||
@ -213,6 +256,11 @@ public final class TsExtractor implements Extractor {
|
|||||||
pmtScratch = new ParsableBitArray(new byte[5]);
|
pmtScratch = new ParsableBitArray(new byte[5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
ExtractorOutput output) {
|
ExtractorOutput output) {
|
||||||
@ -311,9 +359,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
private boolean ptsFlag;
|
private boolean ptsFlag;
|
||||||
private int extendedHeaderLength;
|
private int extendedHeaderLength;
|
||||||
|
|
||||||
private int payloadSize;
|
private int payloadSize;
|
||||||
|
|
||||||
private long timeUs;
|
private long timeUs;
|
||||||
|
|
||||||
public PesReader(ElementaryStreamReader pesPayloadReader) {
|
public PesReader(ElementaryStreamReader pesPayloadReader) {
|
||||||
@ -322,6 +368,14 @@ public final class TsExtractor implements Extractor {
|
|||||||
state = STATE_FINDING_HEADER;
|
state = STATE_FINDING_HEADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
state = STATE_FINDING_HEADER;
|
||||||
|
bytesRead = 0;
|
||||||
|
bodyStarted = false;
|
||||||
|
pesPayloadReader.seek();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
||||||
ExtractorOutput output) {
|
ExtractorOutput output) {
|
||||||
|
@ -144,6 +144,17 @@ public final class H264Util {
|
|||||||
return endOffset;
|
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
|
* 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.
|
* assumed that the top bit will always be set to zero.
|
||||||
@ -162,17 +173,6 @@ public final class H264Util {
|
|||||||
return result;
|
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() {
|
private H264Util() {
|
||||||
// Prevent instantiation.
|
// Prevent instantiation.
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user