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:
parent
1fce55f6fe
commit
c497b78ffe
@ -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)) {
|
||||
|
@ -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.
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user