Skip to content

Commit

Permalink
Drain embedded track sample queues when not enabled.
Browse files Browse the repository at this point in the history
I think it's likely we'll revert back to discarding media
in sync with the playback position for ExtractorMediaSource
and HlsMediaSource too, where the tracks are muxed with ones
we're requesting anyway.

Note: discardBuffer is named as it is because it'll also be
used to discard for enabled tracks soon, as a result of the
remaining TODO in ChunkSampleStream. For enabled tracks the
discard will also be conditional on the samples having been
consumed, obviously.

Issue: #2362
Issue: #2176

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=149525857
  • Loading branch information
ojw28 committed Mar 10, 2017
1 parent 09471de commit 45c7fe9
Show file tree
Hide file tree
Showing 11 changed files with 136 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,11 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
return 0;
}

@Override
public void discardBuffer(long positionUs) {
// Do nothing.
}

@Override
public long readDiscontinuity() {
assertTrue(preparedPeriod);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ private void doSomeWork() throws ExoPlaybackException, IOException {
TraceUtil.beginSection("doSomeWork");

updatePlaybackPositions();
playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs);

boolean allRenderersEnded = true;
boolean allRenderersReadyOrEnded = true;
for (Renderer renderer : enabledRenderers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
return enablePositionUs - startUs;
}

@Override
public void discardBuffer(long positionUs) {
mediaPeriod.discardBuffer(positionUs + startUs);
}

@Override
public long readDiscontinuity() {
if (pendingInitialDiscontinuity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
return positionUs;
}

@Override
public void discardBuffer(long positionUs) {
// Do nothing.
}

@Override
public boolean continueLoading(long playbackPositionUs) {
if (loadingFinished || (prepared && enabledTrackCount == 0)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ interface Callback extends SequenceableLoader.Callback<MediaPeriod> {
long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, long positionUs);

/**
* Discards buffered media up to the specified position.
*
* @param positionUs The position in microseconds.
*/
void discardBuffer(long positionUs);

/**
* Attempts to read a discontinuity.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
return positionUs;
}

@Override
public void discardBuffer(long positionUs) {
for (MediaPeriod period : enabledPeriods) {
period.discardBuffer(positionUs);
}
}

@Override
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
return positionUs;
}

@Override
public void discardBuffer(long positionUs) {
// Do nothing.
}

@Override
public boolean continueLoading(long positionUs) {
if (loadingFinished || loader.isLoading()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S

private final int primaryTrackType;
private final int[] embeddedTrackTypes;
private final boolean[] embeddedTracksSelected;
private final T chunkSource;
private final SequenceableLoader.Callback<ChunkSampleStream<T>> callback;
private final EventDispatcher eventDispatcher;
Expand All @@ -49,7 +50,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
private final LinkedList<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> readOnlyMediaChunks;
private final DefaultTrackOutput primarySampleQueue;
private final EmbeddedSampleStream[] embeddedSampleStreams;
private final DefaultTrackOutput[] embeddedSampleQueues;
private final BaseMediaChunkOutput mediaChunkOutput;

private Format primaryDownstreamTrackFormat;
Expand Down Expand Up @@ -84,7 +85,8 @@ public ChunkSampleStream(int primaryTrackType, int[] embeddedTrackTypes, T chunk
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);

int embeddedTrackCount = embeddedTrackTypes == null ? 0 : embeddedTrackTypes.length;
embeddedSampleStreams = newEmbeddedSampleStreamArray(embeddedTrackCount);
embeddedSampleQueues = new DefaultTrackOutput[embeddedTrackCount];
embeddedTracksSelected = new boolean[embeddedTrackCount];
int[] trackTypes = new int[1 + embeddedTrackCount];
DefaultTrackOutput[] sampleQueues = new DefaultTrackOutput[1 + embeddedTrackCount];

Expand All @@ -93,9 +95,10 @@ public ChunkSampleStream(int primaryTrackType, int[] embeddedTrackTypes, T chunk
sampleQueues[0] = primarySampleQueue;

for (int i = 0; i < embeddedTrackCount; i++) {
DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator);
embeddedSampleQueues[i] = trackOutput;
sampleQueues[i + 1] = trackOutput;
trackTypes[i + 1] = embeddedTrackTypes[i];
sampleQueues[i + 1] = new DefaultTrackOutput(allocator);
embeddedSampleStreams[i] = new EmbeddedSampleStream(sampleQueues[i + 1]);
}

mediaChunkOutput = new BaseMediaChunkOutput(trackTypes, sampleQueues);
Expand All @@ -104,26 +107,36 @@ public ChunkSampleStream(int primaryTrackType, int[] embeddedTrackTypes, T chunk
}

/**
* Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}.
*/
public static boolean isPrimarySampleStream(SampleStream sampleStream) {
return sampleStream instanceof ChunkSampleStream;
}

/**
* Returns whether a {@link SampleStream} is for an embedded track of a {@link ChunkSampleStream}.
* Discards buffered media for embedded tracks that are not currently selected, up to the
* specified position.
*
* @param positionUs The position to discard up to, in microseconds.
*/
public static boolean isEmbeddedSampleStream(SampleStream sampleStream) {
return sampleStream instanceof ChunkSampleStream.EmbeddedSampleStream;
public void discardUnselectedEmbeddedTracksTo(long positionUs) {
for (int i = 0; i < embeddedSampleQueues.length; i++) {
if (!embeddedTracksSelected[i]) {
embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true);
}
}
}

/**
* Returns the {@link SampleStream} for the embedded track with the specified type.
* Selects the embedded track, returning a new {@link EmbeddedSampleStream} from which the track's
* samples can be consumed. {@link EmbeddedSampleStream#release()} must be called on the returned
* stream when the track is no longer required, and before calling this method again to obtain
* another stream for the same track.
*
* @param positionUs The current playback position in microseconds.
* @param trackType The type of the embedded track to enable.
* @return The {@link EmbeddedSampleStream} for the embedded track.
*/
public SampleStream getEmbeddedSampleStream(int trackType) {
for (int i = 0; i < embeddedTrackTypes.length; i++) {
public EmbeddedSampleStream selectEmbeddedTrack(long positionUs, int trackType) {
for (int i = 0; i < embeddedSampleQueues.length; i++) {
if (embeddedTrackTypes[i] == trackType) {
return embeddedSampleStreams[i];
Assertions.checkState(!embeddedTracksSelected[i]);
embeddedTracksSelected[i] = true;
embeddedSampleQueues[i].skipToKeyframeBefore(positionUs, true);
return new EmbeddedSampleStream(this, embeddedSampleQueues[i], i);
}
}
// Should never happen.
Expand Down Expand Up @@ -179,8 +192,8 @@ public void seekToUs(long positionUs) {
}
// TODO: For this to work correctly, the embedded streams must not discard anything from their
// sample queues beyond the current read position of the primary stream.
for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) {
embeddedSampleStream.skipToKeyframeBefore(positionUs);
for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.skipToKeyframeBefore(positionUs);
}
} else {
// We failed, and need to restart.
Expand All @@ -191,8 +204,8 @@ public void seekToUs(long positionUs) {
loader.cancelLoading();
} else {
primarySampleQueue.reset(true);
for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) {
embeddedSampleStream.reset(true);
for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset(true);
}
}
}
Expand All @@ -205,8 +218,8 @@ public void seekToUs(long positionUs) {
*/
public void release() {
primarySampleQueue.disable();
for (EmbeddedSampleStream embeddedSampleStream : embeddedSampleStreams) {
embeddedSampleStream.disable();
for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.disable();
}
loader.release();
}
Expand All @@ -232,7 +245,6 @@ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
// TODO: For embedded streams that aren't being used, we need to drain their queues here.
discardDownstreamMediaChunks(primarySampleQueue.getReadIndex());
return primarySampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
lastSeekPositionUs);
Expand Down Expand Up @@ -264,8 +276,8 @@ public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDura
loadable.bytesLoaded());
if (!released) {
primarySampleQueue.reset(true);
for (EmbeddedSampleStream embeddedStream : embeddedSampleStreams) {
embeddedStream.reset(true);
for (DefaultTrackOutput embeddedSampleQueue : embeddedSampleQueues) {
embeddedSampleQueue.reset(true);
}
callback.onContinueLoadingRequested(this);
}
Expand All @@ -284,8 +296,8 @@ public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDuration
BaseMediaChunk removed = mediaChunks.removeLast();
Assertions.checkState(removed == loadable);
primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0));
for (int i = 0; i < embeddedSampleStreams.length; i++) {
embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1));
for (int i = 0; i < embeddedSampleQueues.length; i++) {
embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1));
}
if (mediaChunks.isEmpty()) {
pendingResetPositionUs = lastSeekPositionUs;
Expand Down Expand Up @@ -406,25 +418,28 @@ private boolean discardUpstreamMediaChunks(int queueLength) {
loadingFinished = false;
}
primarySampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex(0));
for (int i = 0; i < embeddedSampleStreams.length; i++) {
embeddedSampleStreams[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1));
for (int i = 0; i < embeddedSampleQueues.length; i++) {
embeddedSampleQueues[i].discardUpstreamSamples(removed.getFirstSampleIndex(i + 1));
}
eventDispatcher.upstreamDiscarded(primaryTrackType, startTimeUs, endTimeUs);
return true;
}

@SuppressWarnings("unchecked")
private static <T extends ChunkSource> ChunkSampleStream<T>.EmbeddedSampleStream[]
newEmbeddedSampleStreamArray(int length) {
return new ChunkSampleStream.EmbeddedSampleStream[length];
}
/**
* A {@link SampleStream} embedded in a {@link ChunkSampleStream}.
*/
public final class EmbeddedSampleStream implements SampleStream {

private final class EmbeddedSampleStream implements SampleStream {
public final ChunkSampleStream<T> parent;

private final DefaultTrackOutput sampleQueue;
private final int index;

public EmbeddedSampleStream(DefaultTrackOutput sampleQueue) {
public EmbeddedSampleStream(ChunkSampleStream<T> parent, DefaultTrackOutput sampleQueue,
int index) {
this.parent = parent;
this.sampleQueue = sampleQueue;
this.index = index;
}

@Override
Expand Down Expand Up @@ -452,16 +467,9 @@ public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
lastSeekPositionUs);
}

public void reset(boolean enable) {
sampleQueue.reset(enable);
}

public void disable() {
sampleQueue.disable();
}

public void discardUpstreamSamples(int discardFromIndex) {
sampleQueue.discardUpstreamSamples(discardFromIndex);
public void release() {
Assertions.checkState(embeddedTracksSelected[index]);
embeddedTracksSelected[index] = false;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream;
import com.google.android.exoplayer2.source.chunk.ChunkSampleStream.EmbeddedSampleStream;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
Expand Down Expand Up @@ -125,7 +126,7 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
HashMap<Integer, ChunkSampleStream<DashChunkSource>> primarySampleStreams = new HashMap<>();
// First pass for primary tracks.
for (int i = 0; i < selections.length; i++) {
if (ChunkSampleStream.isPrimarySampleStream(streams[i])) {
if (streams[i] instanceof ChunkSampleStream) {
@SuppressWarnings("unchecked")
ChunkSampleStream<DashChunkSource> stream = (ChunkSampleStream<DashChunkSource>) streams[i];
if (selections[i] == null || !mayRetainStreamFlags[i]) {
Expand All @@ -149,26 +150,31 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
}
// Second pass for embedded tracks.
for (int i = 0; i < selections.length; i++) {
if (ChunkSampleStream.isEmbeddedSampleStream(streams[i])) {
// Always clear even if the selection is unchanged, since the parent primary sample stream
// may have been replaced.
if ((streams[i] instanceof EmbeddedSampleStream || streams[i] instanceof EmptySampleStream)
&& (selections[i] == null || !mayRetainStreamFlags[i])) {
// The stream is for an embedded track and is either no longer selected or needs replacing.
releaseIfEmbeddedSampleStream(streams[i]);
streams[i] = null;
}
if (streams[i] == null && selections[i] != null) {
// We need to consider replacing the stream even if it's non-null because the primary stream
// may have been replaced, selected or deselected.
if (selections[i] != null) {
int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup());
if (trackGroupIndex >= adaptationSetCount) {
EmbeddedTrackInfo embeddedTrackInfo =
embeddedTrackInfos[trackGroupIndex - adaptationSetCount];
int embeddedTrackIndex = trackGroupIndex - adaptationSetCount;
EmbeddedTrackInfo embeddedTrackInfo = embeddedTrackInfos[embeddedTrackIndex];
int adaptationSetIndex = embeddedTrackInfo.adaptationSetIndex;
ChunkSampleStream<DashChunkSource> primarySampleStream =
primarySampleStreams.get(adaptationSetIndex);
if (primarySampleStream != null) {
streams[i] = primarySampleStream.getEmbeddedSampleStream(embeddedTrackInfo.trackType);
} else {
// The primary track in which this one is embedded is not selected.
streams[i] = new EmptySampleStream();
ChunkSampleStream<?> primaryStream = primarySampleStreams.get(adaptationSetIndex);
SampleStream stream = streams[i];
boolean mayRetainStream = primaryStream == null ? stream instanceof EmptySampleStream
: (stream instanceof EmbeddedSampleStream
&& ((EmbeddedSampleStream) stream).parent == primaryStream);
if (!mayRetainStream) {
releaseIfEmbeddedSampleStream(stream);
streams[i] = primaryStream == null ? new EmptySampleStream()
: primaryStream.selectEmbeddedTrack(positionUs, embeddedTrackInfo.trackType);
streamResetFlags[i] = true;
}
streamResetFlags[i] = true;
}
}
}
Expand All @@ -178,6 +184,13 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
return positionUs;
}

@Override
public void discardBuffer(long positionUs) {
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
sampleStream.discardUnselectedEmbeddedTracksTo(positionUs);
}
}

@Override
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
Expand Down Expand Up @@ -321,6 +334,12 @@ private static ChunkSampleStream<DashChunkSource>[] newSampleStreamArray(int len
return new ChunkSampleStream[length];
}

private static void releaseIfEmbeddedSampleStream(SampleStream sampleStream) {
if (sampleStream instanceof EmbeddedSampleStream) {
((EmbeddedSampleStream) sampleStream).release();
}
}

private static final class EmbeddedTrackInfo {

public final int adaptationSetIndex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamF
return positionUs;
}

@Override
public void discardBuffer(long positionUs) {
// Do nothing.
}

@Override
public boolean continueLoading(long positionUs) {
return sequenceableLoader.continueLoading(positionUs);
Expand Down
Loading

0 comments on commit 45c7fe9

Please sign in to comment.