Commit aeb17e6a by Oliver Woodman

HLS improvements + steps towards ABR.

parent 9790430a
...@@ -45,7 +45,6 @@ public class HlsChunkSource { ...@@ -45,7 +45,6 @@ public class HlsChunkSource {
private final HlsMasterPlaylist masterPlaylist; private final HlsMasterPlaylist masterPlaylist;
private final HlsMediaPlaylistParser mediaPlaylistParser; private final HlsMediaPlaylistParser mediaPlaylistParser;
private long liveStartTimeUs;
/* package */ HlsMediaPlaylist mediaPlaylist; /* package */ HlsMediaPlaylist mediaPlaylist;
/* package */ boolean mediaPlaylistWasLive; /* package */ boolean mediaPlaylistWasLive;
/* package */ long lastMediaPlaylistLoadTimeMs; /* package */ long lastMediaPlaylistLoadTimeMs;
...@@ -168,25 +167,22 @@ public class HlsChunkSource { ...@@ -168,25 +167,22 @@ public class HlsChunkSource {
DataSpec dataSpec = new DataSpec(chunkUri, 0, C.LENGTH_UNBOUNDED, null); DataSpec dataSpec = new DataSpec(chunkUri, 0, C.LENGTH_UNBOUNDED, null);
long startTimeUs = segment.startTimeUs; long startTimeUs;
long endTimeUs = startTimeUs + (long) (segment.durationSecs * 1000000);
int nextChunkMediaSequence = chunkMediaSequence + 1; int nextChunkMediaSequence = chunkMediaSequence + 1;
if (mediaPlaylistWasLive) { if (mediaPlaylistWasLive) {
if (queue.isEmpty()) { if (queue.isEmpty()) {
liveStartTimeUs = startTimeUs;
startTimeUs = 0; startTimeUs = 0;
endTimeUs -= liveStartTimeUs;
} else { } else {
startTimeUs -= liveStartTimeUs; startTimeUs = queue.get(queue.size() - 1).endTimeUs;
endTimeUs -= liveStartTimeUs;
} }
} else { } else {
// Not live. // Not live.
startTimeUs = segment.startTimeUs;
if (chunkIndex == mediaPlaylist.segments.size() - 1) { if (chunkIndex == mediaPlaylist.segments.size() - 1) {
nextChunkMediaSequence = -1; nextChunkMediaSequence = -1;
} }
} }
long endTimeUs = startTimeUs + (long) (segment.durationSecs * 1000000);
DataSource dataSource; DataSource dataSource;
if (encryptedDataSource != null) { if (encryptedDataSource != null) {
...@@ -194,9 +190,8 @@ public class HlsChunkSource { ...@@ -194,9 +190,8 @@ public class HlsChunkSource {
} else { } else {
dataSource = upstreamDataSource; dataSource = upstreamDataSource;
} }
out.chunk = new TsChunk(dataSource, dataSpec, 0, 0, startTimeUs, endTimeUs,
out.chunk = new TsChunk(dataSource, dataSpec, 0, startTimeUs, endTimeUs, nextChunkMediaSequence, segment.discontinuity, false);
nextChunkMediaSequence, segment.discontinuity);
} }
private boolean shouldRerequestMediaPlaylist() { private boolean shouldRerequestMediaPlaylist() {
......
...@@ -48,10 +48,11 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -48,10 +48,11 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private static final long MAX_SAMPLE_INTERLEAVING_OFFSET_US = 5000000; private static final long MAX_SAMPLE_INTERLEAVING_OFFSET_US = 5000000;
private static final int NO_RESET_PENDING = -1; private static final int NO_RESET_PENDING = -1;
private final TsExtractor extractor; private final TsExtractor.SamplePool samplePool;
private final LoadControl loadControl; private final LoadControl loadControl;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final HlsChunkOperationHolder currentLoadableHolder; private final HlsChunkOperationHolder currentLoadableHolder;
private final LinkedList<TsExtractor> extractors;
private final LinkedList<TsChunk> mediaChunks; private final LinkedList<TsChunk> mediaChunks;
private final List<TsChunk> readOnlyHlsChunks; private final List<TsChunk> readOnlyHlsChunks;
private final int bufferSizeContribution; private final int bufferSizeContribution;
...@@ -84,7 +85,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -84,7 +85,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
this.bufferSizeContribution = bufferSizeContribution; this.bufferSizeContribution = bufferSizeContribution;
this.frameAccurateSeeking = frameAccurateSeeking; this.frameAccurateSeeking = frameAccurateSeeking;
this.remainingReleaseCount = downstreamRendererCount; this.remainingReleaseCount = downstreamRendererCount;
extractor = new TsExtractor(); samplePool = new TsExtractor.SamplePool();
extractors = new LinkedList<TsExtractor>();
currentLoadableHolder = new HlsChunkOperationHolder(); currentLoadableHolder = new HlsChunkOperationHolder();
mediaChunks = new LinkedList<TsChunk>(); mediaChunks = new LinkedList<TsChunk>();
readOnlyHlsChunks = Collections.unmodifiableList(mediaChunks); readOnlyHlsChunks = Collections.unmodifiableList(mediaChunks);
...@@ -100,6 +102,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -100,6 +102,10 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
loadControl.register(this, bufferSizeContribution); loadControl.register(this, bufferSizeContribution);
} }
continueBufferingInternal(); continueBufferingInternal();
if (extractors.isEmpty()) {
return false;
}
TsExtractor extractor = extractors.get(0);
if (extractor.isPrepared()) { if (extractor.isPrepared()) {
trackCount = extractor.getTrackCount(); trackCount = extractor.getTrackCount();
trackEnabledStates = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount];
...@@ -171,39 +177,38 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -171,39 +177,38 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
TsChunk mediaChunk = mediaChunks.getFirst(); TsChunk mediaChunk = mediaChunks.getFirst();
int currentVariant = mediaChunk.variantIndex;
TsExtractor extractor;
if (extractors.isEmpty()) {
extractor = new TsExtractor(mediaChunk.startTimeUs, samplePool);
extractors.addLast(extractor);
if (mediaChunk.discardFromFirstKeyframes) {
extractor.discardFromNextKeyframes();
}
} else {
extractor = extractors.getLast();
}
if (mediaChunk.isReadFinished() && mediaChunks.size() > 1) { if (mediaChunk.isReadFinished() && mediaChunks.size() > 1) {
discardDownstreamHlsChunk(); discardDownstreamHlsChunk();
mediaChunk = mediaChunks.getFirst(); mediaChunk = mediaChunks.getFirst();
} if (mediaChunk.discontinuity || mediaChunk.variantIndex != currentVariant) {
extractor = new TsExtractor(mediaChunk.startTimeUs, samplePool);
boolean haveSufficientSamples = false; extractors.addLast(extractor);
if (mediaChunk.hasPendingDiscontinuity()) { }
if (extractor.hasSamples()) { if (mediaChunk.discardFromFirstKeyframes) {
// There are samples from before the discontinuity yet to be read from the extractor, so extractor.discardFromNextKeyframes();
// we don't want to reset the extractor yet.
haveSufficientSamples = true;
} else {
extractor.reset(mediaChunk.startTimeUs);
mediaChunk.clearPendingDiscontinuity();
if (pendingDiscontinuities == null) {
// We're not prepared yet.
} else {
for (int i = 0; i < pendingDiscontinuities.length; i++) {
pendingDiscontinuities[i] = true;
}
}
} }
} }
if (!mediaChunk.hasPendingDiscontinuity()) { // Allow the extractor to consume from the current chunk.
// Allow the extractor to consume from the current chunk. NonBlockingInputStream inputStream = mediaChunk.getNonBlockingInputStream();
NonBlockingInputStream inputStream = mediaChunk.getNonBlockingInputStream(); boolean haveSufficientSamples = extractor.consumeUntil(inputStream,
haveSufficientSamples = extractor.consumeUntil(inputStream, downstreamPositionUs + MAX_SAMPLE_INTERLEAVING_OFFSET_US);
downstreamPositionUs + MAX_SAMPLE_INTERLEAVING_OFFSET_US); if (!haveSufficientSamples) {
// If we can't read any more, then we always say we have sufficient samples. // If we can't read any more, then we always say we have sufficient samples.
if (!haveSufficientSamples) { haveSufficientSamples = mediaChunk.isLastChunk() && mediaChunk.isReadFinished();
haveSufficientSamples = mediaChunk.isLastChunk() && mediaChunk.isReadFinished();
}
} }
if (!haveSufficientSamples && currentLoadableException != null) { if (!haveSufficientSamples && currentLoadableException != null) {
...@@ -223,7 +228,28 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -223,7 +228,28 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return DISCONTINUITY_READ; return DISCONTINUITY_READ;
} }
if (onlyReadDiscontinuity || isPendingReset() || !extractor.isPrepared()) { if (onlyReadDiscontinuity || isPendingReset()) {
return NOTHING_READ;
}
if (extractors.isEmpty()) {
return NOTHING_READ;
}
TsExtractor extractor = extractors.getFirst();
while (extractors.size() > 1 && !extractor.hasSamples()) {
// We're finished reading from the extractor for all tracks, and so can discard it.
extractors.removeFirst().clear();
extractor = extractors.getFirst();
}
int extractorIndex = 0;
while (extractors.size() > extractorIndex + 1 && !extractor.hasSamples(track)) {
// We're finished reading from the extractor for this particular track, so advance to the
// next one for the current read.
extractor = extractors.get(++extractorIndex);
}
if (!extractor.isPrepared()) {
return NOTHING_READ; return NOTHING_READ;
} }
...@@ -265,9 +291,9 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -265,9 +291,9 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if (mediaChunk == null) { if (mediaChunk == null) {
restartFrom(positionUs); restartFrom(positionUs);
} else { } else {
discardExtractors();
discardDownstreamHlsChunks(mediaChunk); discardDownstreamHlsChunks(mediaChunk);
mediaChunk.reset(); mediaChunk.resetReadPosition();
extractor.reset(mediaChunk.startTimeUs);
updateLoadControl(); updateLoadControl();
} }
} }
...@@ -494,13 +520,20 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -494,13 +520,20 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
TsChunk mediaChunk = (TsChunk) currentLoadable; TsChunk mediaChunk = (TsChunk) currentLoadable;
mediaChunks.add(mediaChunk); mediaChunks.add(mediaChunk);
if (isPendingReset()) { if (isPendingReset()) {
extractor.reset(mediaChunk.startTimeUs); discardExtractors();
pendingResetPositionUs = NO_RESET_PENDING; pendingResetPositionUs = NO_RESET_PENDING;
} }
} }
loader.startLoading(currentLoadable, this); loader.startLoading(currentLoadable, this);
} }
private void discardExtractors() {
for (int i = 0; i < extractors.size(); i++) {
extractors.get(i).clear();
}
extractors.clear();
}
/** /**
* Discards downstream media chunks until {@code untilChunk} if found. {@code untilChunk} is not * Discards downstream media chunks until {@code untilChunk} if found. {@code untilChunk} is not
* itself discarded. Null can be passed to discard all media chunks. * itself discarded. Null can be passed to discard all media chunks.
......
...@@ -24,6 +24,10 @@ import com.google.android.exoplayer.upstream.DataSpec; ...@@ -24,6 +24,10 @@ import com.google.android.exoplayer.upstream.DataSpec;
public final class TsChunk extends HlsChunk { public final class TsChunk extends HlsChunk {
/** /**
* The index of the variant in the master playlist.
*/
public final int variantIndex;
/**
* The start time of the media contained by the chunk. * The start time of the media contained by the chunk.
*/ */
public final long startTimeUs; public final long startTimeUs;
...@@ -38,44 +42,38 @@ public final class TsChunk extends HlsChunk { ...@@ -38,44 +42,38 @@ public final class TsChunk extends HlsChunk {
/** /**
* The encoding discontinuity indicator. * The encoding discontinuity indicator.
*/ */
private final boolean discontinuity; public final boolean discontinuity;
/**
private boolean pendingDiscontinuity; * For each track, whether samples from the first keyframe (inclusive) should be discarded.
*/
public final boolean discardFromFirstKeyframes;
/** /**
* @param dataSource A {@link DataSource} for loading the data. * @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded. * @param dataSpec Defines the data to be loaded.
* @param trigger The reason for this chunk being selected. * @param trigger The reason for this chunk being selected.
* @param variantIndex The index of the variant in the master playlist.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk. * @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
* @param discontinuity The encoding discontinuity indicator. * @param discontinuity The encoding discontinuity indicator.
* @param discardFromFirstKeyframes For each contained media stream, whether samples from the
* first keyframe (inclusive) should be discarded.
*/ */
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, long startTimeUs, public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, int variantIndex,
long endTimeUs, int nextChunkIndex, boolean discontinuity) { long startTimeUs, long endTimeUs, int nextChunkIndex, boolean discontinuity,
boolean discardFromFirstKeyframes) {
super(dataSource, dataSpec, trigger); super(dataSource, dataSpec, trigger);
this.variantIndex = variantIndex;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs; this.endTimeUs = endTimeUs;
this.nextChunkIndex = nextChunkIndex; this.nextChunkIndex = nextChunkIndex;
this.discontinuity = discontinuity; this.discontinuity = discontinuity;
this.pendingDiscontinuity = discontinuity; this.discardFromFirstKeyframes = discardFromFirstKeyframes;
} }
public boolean isLastChunk() { public boolean isLastChunk() {
return nextChunkIndex == -1; return nextChunkIndex == -1;
} }
public void reset() {
resetReadPosition();
pendingDiscontinuity = discontinuity;
}
public boolean hasPendingDiscontinuity() {
return pendingDiscontinuity;
}
public void clearPendingDiscontinuity() {
pendingDiscontinuity = false;
}
} }
...@@ -23,11 +23,13 @@ import com.google.android.exoplayer.util.CodecSpecificDataUtil; ...@@ -23,11 +23,13 @@ import com.google.android.exoplayer.util.CodecSpecificDataUtil;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaExtractor; import android.media.MediaExtractor;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.SparseArray; import android.util.SparseArray;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Queue; import java.util.Queue;
...@@ -47,26 +49,27 @@ public final class TsExtractor { ...@@ -47,26 +49,27 @@ public final class TsExtractor {
private static final int TS_STREAM_TYPE_H264 = 0x1B; private static final int TS_STREAM_TYPE_H264 = 0x1B;
private static final int TS_STREAM_TYPE_ID3 = 0x15; private static final int TS_STREAM_TYPE_ID3 = 0x15;
private static final int DEFAULT_BUFFER_SEGMENT_SIZE = 64 * 1024;
private final BitsArray tsPacketBuffer; private final BitsArray tsPacketBuffer;
private final SparseArray<PesPayloadReader> pesPayloadReaders; // Indexed by streamType private final SparseArray<PesPayloadReader> pesPayloadReaders; // Indexed by streamType
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
private final Queue<Sample> samplesPool; private final SamplePool samplePool;
private boolean prepared; private boolean prepared;
/* package */ boolean pendingTimestampOffsetUpdate; /* package */ boolean pendingFirstSampleTimestampAdjustment;
/* package */ long pendingTimestampOffsetUs; /* package */ long firstSampleTimestamp;
/* package */ long sampleTimestampOffsetUs; /* package */ long sampleTimestampOffsetUs;
/* package */ long largestParsedTimestampUs; /* package */ long largestParsedTimestampUs;
/* package */ boolean discardFromNextKeyframes;
public TsExtractor() { public TsExtractor(long firstSampleTimestamp, SamplePool samplePool) {
this.firstSampleTimestamp = firstSampleTimestamp;
this.samplePool = samplePool;
pendingFirstSampleTimestampAdjustment = true;
tsPacketBuffer = new BitsArray(); tsPacketBuffer = new BitsArray();
pesPayloadReaders = new SparseArray<PesPayloadReader>(); pesPayloadReaders = new SparseArray<PesPayloadReader>();
tsPayloadReaders = new SparseArray<TsPayloadReader>(); tsPayloadReaders = new SparseArray<TsPayloadReader>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader()); tsPayloadReaders.put(TS_PAT_PID, new PatReader());
samplesPool = new LinkedList<Sample>();
largestParsedTimestampUs = Long.MIN_VALUE; largestParsedTimestampUs = Long.MIN_VALUE;
} }
...@@ -105,22 +108,19 @@ public final class TsExtractor { ...@@ -105,22 +108,19 @@ public final class TsExtractor {
} }
/** /**
* Resets the extractor's internal state. * Flushes any pending or incomplete samples, returning them to the sample pool.
*/ */
public void reset(long nextSampleTimestampUs) { public void clear() {
prepared = false;
tsPacketBuffer.reset();
tsPayloadReaders.clear();
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
// Clear each reader before discarding it, so as to recycle any queued Sample objects.
for (int i = 0; i < pesPayloadReaders.size(); i++) { for (int i = 0; i < pesPayloadReaders.size(); i++) {
pesPayloadReaders.valueAt(i).clear(); pesPayloadReaders.valueAt(i).clear();
} }
pesPayloadReaders.clear(); }
// Configure for subsequent read operations.
pendingTimestampOffsetUpdate = true; /**
pendingTimestampOffsetUs = nextSampleTimestampUs; * For each track, whether to discard samples from the next keyframe (inclusive).
largestParsedTimestampUs = Long.MIN_VALUE; */
public void discardFromNextKeyframes() {
discardFromNextKeyframes = true;
} }
/** /**
...@@ -153,31 +153,43 @@ public final class TsExtractor { ...@@ -153,31 +153,43 @@ public final class TsExtractor {
*/ */
public boolean getSample(int track, SampleHolder out) { public boolean getSample(int track, SampleHolder out) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
Queue<Sample> queue = pesPayloadReaders.valueAt(track).samplesQueue; Queue<Sample> queue = pesPayloadReaders.valueAt(track).sampleQueue;
if (queue.isEmpty()) { if (queue.isEmpty()) {
return false; return false;
} }
Sample sample = queue.remove(); Sample sample = queue.remove();
convert(sample, out); convert(sample, out);
recycleSample(sample); samplePool.recycle(sample);
return true; return true;
} }
/** /**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)}. * Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for any
* track.
* *
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}. * @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
* False otherwise. * for any track. False otherwise.
*/ */
public boolean hasSamples() { public boolean hasSamples() {
for (int i = 0; i < pesPayloadReaders.size(); i++) { for (int i = 0; i < pesPayloadReaders.size(); i++) {
if (!pesPayloadReaders.valueAt(i).samplesQueue.isEmpty()) { if (hasSamples(i)) {
return true; return true;
} }
} }
return false; return false;
} }
/**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the
* specified track.
*
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
* for the specified track. False otherwise.
*/
public boolean hasSamples(int track) {
return !pesPayloadReaders.valueAt(track).sampleQueue.isEmpty();
}
private boolean checkPrepared() { private boolean checkPrepared() {
int pesPayloadReaderCount = pesPayloadReaders.size(); int pesPayloadReaderCount = pesPayloadReaders.size();
if (pesPayloadReaderCount == 0) { if (pesPayloadReaderCount == 0) {
...@@ -251,18 +263,6 @@ public final class TsExtractor { ...@@ -251,18 +263,6 @@ public final class TsExtractor {
out.timeUs = in.timeUs; out.timeUs = in.timeUs;
} }
/* package */ Sample getSample() {
if (samplesPool.isEmpty()) {
return new Sample(DEFAULT_BUFFER_SEGMENT_SIZE);
}
return samplesPool.remove();
}
/* package */ void recycleSample(Sample sample) {
sample.reset();
samplesPool.add(sample);
}
/** /**
* Parses payload data. * Parses payload data.
*/ */
...@@ -484,12 +484,14 @@ public final class TsExtractor { ...@@ -484,12 +484,14 @@ public final class TsExtractor {
*/ */
private abstract class PesPayloadReader { private abstract class PesPayloadReader {
public final Queue<Sample> samplesQueue; public final Queue<Sample> sampleQueue;
private MediaFormat mediaFormat; private MediaFormat mediaFormat;
private boolean foundFirstKeyframe;
private boolean foundLastKeyframe;
protected PesPayloadReader() { protected PesPayloadReader() {
this.samplesQueue = new LinkedList<Sample>(); this.sampleQueue = new LinkedList<Sample>();
} }
public boolean hasMediaFormat() { public boolean hasMediaFormat() {
...@@ -507,8 +509,8 @@ public final class TsExtractor { ...@@ -507,8 +509,8 @@ public final class TsExtractor {
public abstract void read(BitsArray pesBuffer, int pesPayloadSize, long pesTimeUs); public abstract void read(BitsArray pesBuffer, int pesPayloadSize, long pesTimeUs);
public void clear() { public void clear() {
while (!samplesQueue.isEmpty()) { while (!sampleQueue.isEmpty()) {
recycleSample(samplesQueue.remove()); samplePool.recycle(sampleQueue.remove());
} }
} }
...@@ -520,17 +522,31 @@ public final class TsExtractor { ...@@ -520,17 +522,31 @@ public final class TsExtractor {
* @param sampleTimeUs The sample time stamp. * @param sampleTimeUs The sample time stamp.
*/ */
protected void addSample(BitsArray buffer, int sampleSize, long sampleTimeUs, int flags) { protected void addSample(BitsArray buffer, int sampleSize, long sampleTimeUs, int flags) {
Sample sample = getSample(); Sample sample = samplePool.get();
addToSample(sample, buffer, sampleSize); addToSample(sample, buffer, sampleSize);
sample.flags = flags; sample.flags = flags;
sample.timeUs = sampleTimeUs; sample.timeUs = sampleTimeUs;
addSample(sample); addSample(sample);
} }
@SuppressLint("InlinedApi")
protected void addSample(Sample sample) { protected void addSample(Sample sample) {
boolean isKeyframe = (sample.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
if (isKeyframe) {
if (!foundFirstKeyframe) {
foundFirstKeyframe = true;
}
if (discardFromNextKeyframes) {
foundLastKeyframe = true;
}
}
adjustTimestamp(sample); adjustTimestamp(sample);
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs); if (foundFirstKeyframe && !foundLastKeyframe) {
samplesQueue.add(sample); largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sample.timeUs);
sampleQueue.add(sample);
} else {
samplePool.recycle(sample);
}
} }
protected void addToSample(Sample sample, BitsArray buffer, int size) { protected void addToSample(Sample sample, BitsArray buffer, int size) {
...@@ -542,9 +558,9 @@ public final class TsExtractor { ...@@ -542,9 +558,9 @@ public final class TsExtractor {
} }
private void adjustTimestamp(Sample sample) { private void adjustTimestamp(Sample sample) {
if (pendingTimestampOffsetUpdate) { if (pendingFirstSampleTimestampAdjustment) {
sampleTimestampOffsetUs = pendingTimestampOffsetUs - sample.timeUs; sampleTimestampOffsetUs = firstSampleTimestamp - sample.timeUs;
pendingTimestampOffsetUpdate = false; pendingFirstSampleTimestampAdjustment = false;
} }
sample.timeUs += sampleTimestampOffsetUs; sample.timeUs += sampleTimestampOffsetUs;
} }
...@@ -583,7 +599,7 @@ public final class TsExtractor { ...@@ -583,7 +599,7 @@ public final class TsExtractor {
if (currentSample != null) { if (currentSample != null) {
addSample(currentSample); addSample(currentSample);
} }
currentSample = getSample(); currentSample = samplePool.get();
pesPayloadSize -= readOneH264Frame(pesBuffer, false); pesPayloadSize -= readOneH264Frame(pesBuffer, false);
currentSample.timeUs = pesTimeUs; currentSample.timeUs = pesTimeUs;
...@@ -615,7 +631,7 @@ public final class TsExtractor { ...@@ -615,7 +631,7 @@ public final class TsExtractor {
public void clear() { public void clear() {
super.clear(); super.clear();
if (currentSample != null) { if (currentSample != null) {
recycleSample(currentSample); samplePool.recycle(currentSample);
currentSample = null; currentSample = null;
} }
} }
...@@ -742,8 +758,35 @@ public final class TsExtractor { ...@@ -742,8 +758,35 @@ public final class TsExtractor {
} }
/** /**
* Simplified version of SampleHolder for internal buffering. * A pool from which the extractor can obtain sample objects for internal use.
*/ */
public static class SamplePool {
private static final int DEFAULT_BUFFER_SEGMENT_SIZE = 64 * 1024;
private final ArrayList<Sample> samples;
public SamplePool() {
samples = new ArrayList<Sample>();
}
/* package */ Sample get() {
if (samples.isEmpty()) {
return new Sample(DEFAULT_BUFFER_SEGMENT_SIZE);
}
return samples.remove(samples.size() - 1);
}
/* package */ void recycle(Sample sample) {
sample.reset();
samples.add(sample);
}
}
/**
* Simplified version of SampleHolder for internal buffering.
*/
private static class Sample { private static class Sample {
public byte[] data; public byte[] data;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment