Fix memory leak in TsExtractor when not all tracks are enabled.

Previously samples belonging to disabled tracks would just
accumulate in an arbitrarily long queue in TsExtractor. We
need to actively throw samples away from disabled tracks up
to the current playback position, so as to prevent this.

Issue: #174
This commit is contained in:
Oliver Woodman 2014-12-19 12:13:46 +00:00
parent 1fce55f6fe
commit c497b78ffe
2 changed files with 51 additions and 17 deletions

View File

@ -160,6 +160,9 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
Assertions.checkState(prepared);
Assertions.checkState(enabledTrackCount > 0);
downstreamPositionUs = playbackPositionUs;
if (!extractors.isEmpty()) {
discardSamplesForDisabledTracks(extractors.getFirst(), downstreamPositionUs);
}
return continueBufferingInternal();
}
@ -168,7 +171,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if (isPendingReset() || extractors.isEmpty()) {
return false;
}
boolean haveSamples = extractors.getFirst().hasSamples();
boolean haveSamples = prepared && haveSamplesForEnabledTracks(extractors.getFirst());
if (!haveSamples) {
maybeThrowLoadableException();
}
@ -192,7 +195,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
}
TsExtractor extractor = extractors.getFirst();
while (extractors.size() > 1 && !extractor.hasSamples()) {
while (extractors.size() > 1 && !haveSamplesForEnabledTracks(extractor)) {
// We're finished reading from the extractor for all tracks, and so can discard it.
extractors.removeFirst().release();
extractor = extractors.getFirst();
@ -315,6 +318,23 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
maybeStartLoading();
}
private void discardSamplesForDisabledTracks(TsExtractor extractor, long timeUs) {
for (int i = 0; i < trackEnabledStates.length; i++) {
if (!trackEnabledStates[i]) {
extractor.discardUntil(i, timeUs);
}
}
}
private boolean haveSamplesForEnabledTracks(TsExtractor extractor) {
for (int i = 0; i < trackEnabledStates.length; i++) {
if (trackEnabledStates[i] && extractor.hasSamples(i)) {
return true;
}
}
return false;
}
private void maybeThrowLoadableException() throws IOException {
if (currentLoadableException != null && (currentLoadableExceptionFatal
|| currentLoadableExceptionCount > minLoadableRetryCount)) {

View File

@ -187,19 +187,14 @@ public final class TsExtractor {
}
/**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for any
* track.
* Discards samples for the specified track up to the specified time.
*
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
* for any track. False otherwise.
* @param track The track from which samples should be discarded.
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public boolean hasSamples() {
for (int i = 0; i < sampleQueues.size(); i++) {
if (hasSamples(i)) {
return true;
}
}
return false;
public void discardUntil(int track, long timeUs) {
Assertions.checkState(prepared);
sampleQueues.valueAt(track).discardUntil(timeUs);
}
/**
@ -519,7 +514,7 @@ public final class TsExtractor {
private final ConcurrentLinkedQueue<Sample> internalQueue;
// Accessed only by the consuming thread.
private boolean readFirstFrame;
private boolean needKeyframe;
private long lastReadTimeUs;
private long spliceOutTimeUs;
@ -529,8 +524,9 @@ public final class TsExtractor {
protected SampleQueue(SamplePool samplePool) {
this.samplePool = samplePool;
internalQueue = new ConcurrentLinkedQueue<Sample>();
spliceOutTimeUs = Long.MIN_VALUE;
needKeyframe = true;
lastReadTimeUs = Long.MIN_VALUE;
spliceOutTimeUs = Long.MIN_VALUE;
}
public boolean hasMediaFormat() {
@ -557,7 +553,7 @@ public final class TsExtractor {
Sample head = peek();
if (head != null) {
internalQueue.remove();
readFirstFrame = true;
needKeyframe = false;
lastReadTimeUs = head.timeUs;
}
return head;
@ -570,7 +566,7 @@ public final class TsExtractor {
*/
public Sample peek() {
Sample head = internalQueue.peek();
if (!readFirstFrame) {
if (needKeyframe) {
// Peeking discard of samples until we find a keyframe or run out of available samples.
while (head != null && !head.isKeyframe) {
recycle(head);
@ -590,6 +586,24 @@ public final class TsExtractor {
return head;
}
/**
* Discards samples from the queue up to the specified time.
*
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public void discardUntil(long timeUs) {
Sample head = peek();
while (head != null && head.timeUs < timeUs) {
recycle(head);
internalQueue.remove();
head = internalQueue.peek();
// We're discarding at least one sample, so any subsequent read will need to start at
// a keyframe.
needKeyframe = true;
}
lastReadTimeUs = Long.MIN_VALUE;
}
/**
* Clears the queue.
*/