Skip to content

Commit

Permalink
Add buffer flag for last sample to improve buffered position calculat…
Browse files Browse the repository at this point in the history
…ion.

The buffered position is currently based on the mimimum queued timestamp of
all AV tracks. If the tracks have unequal lengths, one track continues loading
without bounds as the "buffered position" will always stay at the shorter
track's duration.

This change adds an optional buffer flag to mark the last sample of the
stream. This is set in the Mp4Extractor only so far. ExtractorMediaSource
uses this flag to ignore AV streams in the buffered duration calculation if
they already finished loading.

Issue:#3670
PiperOrigin-RevId: 229359899
  • Loading branch information
tonihei authored and ojw28 committed Jan 15, 2019
1 parent fcda01e commit b97b35e
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 19 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
([#5351](https://github.com/google/ExoPlayer/issues/5351)).
* Downloading/Caching: Improve cache performance
([#4253](https://github.com/google/ExoPlayer/issues/4253)).
* Fix issue where uneven track durations in MP4 streams can cause OOM problems
([#3670](https://github.com/google/ExoPlayer/issues/3670)).

### 2.9.3 ###

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,8 @@ private C() {}

/**
* Flags which can apply to a buffer containing a media sample. Possible flag values are {@link
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_ENCRYPTED} and
* {@link #BUFFER_FLAG_DECODE_ONLY}.
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_LAST_SAMPLE},
* {@link #BUFFER_FLAG_ENCRYPTED} and {@link #BUFFER_FLAG_DECODE_ONLY}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
Expand All @@ -470,6 +470,7 @@ private C() {}
value = {
BUFFER_FLAG_KEY_FRAME,
BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_LAST_SAMPLE,
BUFFER_FLAG_ENCRYPTED,
BUFFER_FLAG_DECODE_ONLY
})
Expand All @@ -482,6 +483,8 @@ private C() {}
* Flag for empty buffers that signal that the end of the stream was reached.
*/
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
/** Indicates that a buffer is known to contain the last media sample of the stream. */
public static final int BUFFER_FLAG_LAST_SAMPLE = 1 << 29; // 0x20000000
/** Indicates that a buffer is (at least partially) encrypted. */
public static final int BUFFER_FLAG_ENCRYPTED = 1 << 30; // 0x40000000
/** Indicates that a buffer should be decoded but not rendered. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public TrackSampleTable(
this.flags = flags;
this.durationUs = durationUs;
sampleCount = offsets.length;
if (flags.length > 0) {
flags[flags.length - 1] |= C.BUFFER_FLAG_LAST_SAMPLE;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,18 +356,19 @@ public long getBufferedPositionUs() {
} else if (isPendingReset()) {
return pendingResetPositionUs;
}
long largestQueuedTimestampUs;
long largestQueuedTimestampUs = C.TIME_UNSET;
if (haveAudioVideoTracks) {
// Ignore non-AV tracks, which may be sparse or poorly interleaved.
largestQueuedTimestampUs = Long.MAX_VALUE;
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) {
if (trackIsAudioVideoFlags[i]) {
if (trackIsAudioVideoFlags[i] && !sampleQueues[i].isLastSampleQueued()) {
largestQueuedTimestampUs = Math.min(largestQueuedTimestampUs,
sampleQueues[i].getLargestQueuedTimestampUs());
}
}
} else {
}
if (largestQueuedTimestampUs == C.TIME_UNSET) {
largestQueuedTimestampUs = getLargestQueuedTimestampUs();
}
return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public static final class SampleExtrasHolder {

private long largestDiscardedTimestampUs;
private long largestQueuedTimestampUs;
private boolean isLastSampleQueued;
private boolean upstreamKeyframeRequired;
private boolean upstreamFormatRequired;
private Format upstreamFormat;
Expand Down Expand Up @@ -93,6 +94,7 @@ public void reset(boolean resetUpstreamFormat) {
upstreamKeyframeRequired = true;
largestDiscardedTimestampUs = Long.MIN_VALUE;
largestQueuedTimestampUs = Long.MIN_VALUE;
isLastSampleQueued = false;
if (resetUpstreamFormat) {
upstreamFormat = null;
upstreamFormatRequired = true;
Expand All @@ -118,6 +120,7 @@ public long discardUpstreamSamples(int discardFromIndex) {
Assertions.checkArgument(0 <= discardCount && discardCount <= (length - readPosition));
length -= discardCount;
largestQueuedTimestampUs = Math.max(largestDiscardedTimestampUs, getLargestTimestamp(length));
isLastSampleQueued = discardCount == 0 && isLastSampleQueued;
if (length == 0) {
return 0;
} else {
Expand Down Expand Up @@ -186,6 +189,19 @@ public synchronized long getLargestQueuedTimestampUs() {
return largestQueuedTimestampUs;
}

/**
* Returns whether the last sample of the stream has knowingly been queued. A return value of
* {@code false} means that the last sample had not been queued or that it's unknown whether the
* last sample has been queued.
*
* <p>Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not
* considered as having been queued. Samples that were dequeued from the front of the queue are
* considered as having been queued.
*/
public synchronized boolean isLastSampleQueued() {
return isLastSampleQueued;
}

/** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */
public synchronized long getFirstTimestampUs() {
return length == 0 ? Long.MIN_VALUE : timesUs[relativeFirstIndex];
Expand Down Expand Up @@ -224,7 +240,7 @@ public synchronized int read(FormatHolder formatHolder, DecoderInputBuffer buffe
boolean formatRequired, boolean loadingFinished, Format downstreamFormat,
SampleExtrasHolder extrasHolder) {
if (!hasNextSample()) {
if (loadingFinished) {
if (loadingFinished || isLastSampleQueued) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} else if (upstreamFormat != null
Expand Down Expand Up @@ -388,7 +404,9 @@ public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlag
upstreamKeyframeRequired = false;
}
Assertions.checkState(!upstreamFormatRequired);
commitSampleTimestamp(timeUs);

isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0;
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);

int relativeEndIndex = getRelativeIndex(length);
timesUs[relativeEndIndex] = timeUs;
Expand Down Expand Up @@ -439,10 +457,6 @@ public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlag
}
}

public synchronized void commitSampleTimestamp(long timeUs) {
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs);
}

/**
* Attempts to discard samples from the end of the queue to allow samples starting from the
* specified timestamp to be spliced in. Samples will not be discarded prior to the read position.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@ public long getLargestQueuedTimestampUs() {
return metadataQueue.getLargestQueuedTimestampUs();
}

/**
* Returns whether the last sample of the stream has knowingly been queued. A return value of
* {@code false} means that the last sample had not been queued or that it's unknown whether the
* last sample has been queued.
*/
public boolean isLastSampleQueued() {
return metadataQueue.isLastSampleQueued();
}

/** Returns the timestamp of the first sample, or {@link Long#MIN_VALUE} if the queue is empty. */
public long getFirstTimestampUs() {
return metadataQueue.getFirstTimestampUs();
Expand Down
4 changes: 2 additions & 2 deletions library/core/src/test/assets/mp4/sample.mp4.0.dump
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
flags = 0
flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
Expand Down Expand Up @@ -352,6 +352,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 44:
time = 1065678
flags = 1
flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true
4 changes: 2 additions & 2 deletions library/core/src/test/assets/mp4/sample.mp4.1.dump
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
flags = 0
flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
Expand Down Expand Up @@ -304,6 +304,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 32:
time = 1065678
flags = 1
flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true
4 changes: 2 additions & 2 deletions library/core/src/test/assets/mp4/sample.mp4.2.dump
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
flags = 0
flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
Expand Down Expand Up @@ -244,6 +244,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 17:
time = 1065678
flags = 1
flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true
4 changes: 2 additions & 2 deletions library/core/src/test/assets/mp4/sample.mp4.3.dump
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ track 0:
data = length 530, hash C98BC6A8
sample 29:
time = 934266
flags = 0
flags = 536870912
data = length 568, hash 4FE5C8EA
track 1:
format:
Expand Down Expand Up @@ -184,6 +184,6 @@ track 1:
data = length 229, hash FFF98DF0
sample 2:
time = 1065678
flags = 1
flags = 536870913
data = length 6, hash 31B22286
tracksEnded = true

0 comments on commit b97b35e

Please sign in to comment.