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:
Oliver Woodman 2015-05-01 20:31:21 +01:00
parent cfab852096
commit efb9ff1fe7
12 changed files with 140 additions and 34 deletions

View File

@ -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;

View File

@ -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());

View File

@ -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="

View File

@ -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;

View File

@ -58,8 +58,8 @@ public class AdtsExtractor implements Extractor, SeekMap {
@Override
public void seek() {
adtsReader.seek();
firstPacket = true;
adtsReader.seek();
}
@Override

View File

@ -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;

View File

@ -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.
*

View File

@ -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;
}

View File

@ -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) {

View File

@ -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.

View File

@ -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) {

View File

@ -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.
}