Commit c497b78f by Oliver Woodman

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
parent 1fce55f6
...@@ -160,6 +160,9 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -160,6 +160,9 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
Assertions.checkState(prepared); Assertions.checkState(prepared);
Assertions.checkState(enabledTrackCount > 0); Assertions.checkState(enabledTrackCount > 0);
downstreamPositionUs = playbackPositionUs; downstreamPositionUs = playbackPositionUs;
if (!extractors.isEmpty()) {
discardSamplesForDisabledTracks(extractors.getFirst(), downstreamPositionUs);
}
return continueBufferingInternal(); return continueBufferingInternal();
} }
...@@ -168,7 +171,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -168,7 +171,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
if (isPendingReset() || extractors.isEmpty()) { if (isPendingReset() || extractors.isEmpty()) {
return false; return false;
} }
boolean haveSamples = extractors.getFirst().hasSamples(); boolean haveSamples = prepared && haveSamplesForEnabledTracks(extractors.getFirst());
if (!haveSamples) { if (!haveSamples) {
maybeThrowLoadableException(); maybeThrowLoadableException();
} }
...@@ -192,7 +195,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -192,7 +195,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
TsExtractor extractor = extractors.getFirst(); 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. // We're finished reading from the extractor for all tracks, and so can discard it.
extractors.removeFirst().release(); extractors.removeFirst().release();
extractor = extractors.getFirst(); extractor = extractors.getFirst();
...@@ -315,6 +318,23 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -315,6 +318,23 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
maybeStartLoading(); 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 { private void maybeThrowLoadableException() throws IOException {
if (currentLoadableException != null && (currentLoadableExceptionFatal if (currentLoadableException != null && (currentLoadableExceptionFatal
|| currentLoadableExceptionCount > minLoadableRetryCount)) { || currentLoadableExceptionCount > minLoadableRetryCount)) {
......
...@@ -187,19 +187,14 @@ public final class TsExtractor { ...@@ -187,19 +187,14 @@ public final class TsExtractor {
} }
/** /**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for any * Discards samples for the specified track up to the specified time.
* track.
* *
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)} * @param track The track from which samples should be discarded.
* for any track. False otherwise. * @param timeUs The time up to which samples should be discarded, in microseconds.
*/ */
public boolean hasSamples() { public void discardUntil(int track, long timeUs) {
for (int i = 0; i < sampleQueues.size(); i++) { Assertions.checkState(prepared);
if (hasSamples(i)) { sampleQueues.valueAt(track).discardUntil(timeUs);
return true;
}
}
return false;
} }
/** /**
...@@ -519,7 +514,7 @@ public final class TsExtractor { ...@@ -519,7 +514,7 @@ public final class TsExtractor {
private final ConcurrentLinkedQueue<Sample> internalQueue; private final ConcurrentLinkedQueue<Sample> internalQueue;
// Accessed only by the consuming thread. // Accessed only by the consuming thread.
private boolean readFirstFrame; private boolean needKeyframe;
private long lastReadTimeUs; private long lastReadTimeUs;
private long spliceOutTimeUs; private long spliceOutTimeUs;
...@@ -529,8 +524,9 @@ public final class TsExtractor { ...@@ -529,8 +524,9 @@ public final class TsExtractor {
protected SampleQueue(SamplePool samplePool) { protected SampleQueue(SamplePool samplePool) {
this.samplePool = samplePool; this.samplePool = samplePool;
internalQueue = new ConcurrentLinkedQueue<Sample>(); internalQueue = new ConcurrentLinkedQueue<Sample>();
spliceOutTimeUs = Long.MIN_VALUE; needKeyframe = true;
lastReadTimeUs = Long.MIN_VALUE; lastReadTimeUs = Long.MIN_VALUE;
spliceOutTimeUs = Long.MIN_VALUE;
} }
public boolean hasMediaFormat() { public boolean hasMediaFormat() {
...@@ -557,7 +553,7 @@ public final class TsExtractor { ...@@ -557,7 +553,7 @@ public final class TsExtractor {
Sample head = peek(); Sample head = peek();
if (head != null) { if (head != null) {
internalQueue.remove(); internalQueue.remove();
readFirstFrame = true; needKeyframe = false;
lastReadTimeUs = head.timeUs; lastReadTimeUs = head.timeUs;
} }
return head; return head;
...@@ -570,7 +566,7 @@ public final class TsExtractor { ...@@ -570,7 +566,7 @@ public final class TsExtractor {
*/ */
public Sample peek() { public Sample peek() {
Sample head = internalQueue.peek(); Sample head = internalQueue.peek();
if (!readFirstFrame) { if (needKeyframe) {
// Peeking discard of samples until we find a keyframe or run out of available samples. // Peeking discard of samples until we find a keyframe or run out of available samples.
while (head != null && !head.isKeyframe) { while (head != null && !head.isKeyframe) {
recycle(head); recycle(head);
...@@ -591,6 +587,24 @@ public final class TsExtractor { ...@@ -591,6 +587,24 @@ public final class TsExtractor {
} }
/** /**
* 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. * Clears the queue.
*/ */
public void release() { public void release() {
......
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