Migrate HLS over to new SampleQueue methods

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=161391296
This commit is contained in:
olly 2017-07-10 07:23:25 -07:00 committed by Oliver Woodman
parent 5ebbb6ef45
commit 06b3b3ca8d
5 changed files with 134 additions and 175 deletions

View File

@ -219,6 +219,10 @@ import java.util.Arrays;
if (!seekRequired) {
SampleQueue sampleQueue = sampleQueues[track];
sampleQueue.rewind();
// A seek can be avoided if we're able to advance to the current playback position in the
// sample queue, or if we haven't read anything from the queue since the previous seek
// (this case is common for sparse tracks such as metadata tracks). In all other cases a
// seek is required.
seekRequired = !sampleQueue.advanceTo(positionUs, true, true)
&& sampleQueue.getReadIndex() != 0;
}

View File

@ -83,11 +83,6 @@ import com.google.android.exoplayer2.util.Util;
relativeStartIndex = 0;
readPosition = 0;
upstreamKeyframeRequired = true;
}
// Called by the consuming thread, but only when there is no loading thread.
public void resetLargestParsedTimestamps() {
largestDiscardedTimestampUs = Long.MIN_VALUE;
largestQueuedTimestampUs = Long.MIN_VALUE;
}

View File

@ -29,7 +29,6 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A queue of media samples.
@ -52,16 +51,11 @@ public final class SampleQueue implements TrackOutput {
private static final int INITIAL_SCRATCH_SIZE = 32;
private static final int STATE_ENABLED = 0;
private static final int STATE_ENABLED_WRITING = 1;
private static final int STATE_DISABLED = 2;
private final Allocator allocator;
private final int allocationLength;
private final SampleMetadataQueue metadataQueue;
private final SampleExtrasHolder extrasHolder;
private final ParsableByteArray scratch;
private final AtomicInteger state;
// References into the linked list of allocations.
private AllocationNode firstAllocationNode;
@ -88,7 +82,6 @@ public final class SampleQueue implements TrackOutput {
metadataQueue = new SampleMetadataQueue();
extrasHolder = new SampleExtrasHolder();
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
state = new AtomicInteger();
firstAllocationNode = new AllocationNode(0, allocationLength);
readAllocationNode = firstAllocationNode;
writeAllocationNode = firstAllocationNode;
@ -100,20 +93,13 @@ public final class SampleQueue implements TrackOutput {
* Resets the output.
*/
public void reset() {
reset(true);
}
/**
* @deprecated Use {@link #reset()}. Don't disable sample queues.
*/
@Deprecated
public void reset(boolean enable) {
int previousState = state.getAndSet(enable ? STATE_ENABLED : STATE_DISABLED);
clearSampleData();
metadataQueue.resetLargestParsedTimestamps();
if (previousState == STATE_DISABLED) {
downstreamFormat = null;
}
metadataQueue.clearSampleData();
clearAllocationNodes(firstAllocationNode);
firstAllocationNode = new AllocationNode(0, allocationLength);
readAllocationNode = firstAllocationNode;
writeAllocationNode = firstAllocationNode;
totalBytesWritten = 0;
allocator.trim();
}
/**
@ -174,16 +160,6 @@ public final class SampleQueue implements TrackOutput {
// Called by the consuming thread.
/**
* @deprecated Don't disable sample queues.
*/
@Deprecated
public void disable() {
if (state.getAndSet(STATE_DISABLED) == STATE_ENABLED) {
clearSampleData();
}
}
/**
* Returns whether a sample is available to be read.
*/
@ -265,15 +241,6 @@ public final class SampleQueue implements TrackOutput {
discardDownstreamTo(metadataQueue.discardToEnd());
}
/**
* @deprecated Use {@link #advanceToEnd()} followed by {@link #discardToRead()}.
*/
@Deprecated
public void skipAll() {
advanceToEnd();
discardToRead();
}
/**
* Advances the read position to the end of the queue.
*/
@ -281,17 +248,6 @@ public final class SampleQueue implements TrackOutput {
metadataQueue.advanceToEnd();
}
/**
* @deprecated Use {@link #advanceTo(long, boolean, boolean)} followed by
* {@link #discardToRead()}.
*/
@Deprecated
public boolean skipToKeyframeBefore(long timeUs, boolean allowTimeBeyondBuffer) {
boolean success = advanceTo(timeUs, true, allowTimeBeyondBuffer);
discardToRead();
return success;
}
/**
* Attempts to advance the read position to the sample before or at the specified time.
*
@ -307,19 +263,6 @@ public final class SampleQueue implements TrackOutput {
return metadataQueue.advanceTo(timeUs, toKeyframe, allowTimeBeyondBuffer);
}
/**
* @deprecated Use {@link #read(FormatHolder, DecoderInputBuffer, boolean, boolean, long)}
* followed by {@link #discardToRead()}.
*/
@Deprecated
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired,
boolean loadingFinished, long decodeOnlyUntilUs) {
int result = read(formatHolder, buffer, formatRequired, loadingFinished,
decodeOnlyUntilUs);
discardToRead();
return result;
}
/**
* Attempts to read from the queue.
*
@ -558,17 +501,6 @@ public final class SampleQueue implements TrackOutput {
@Override
public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput)
throws IOException, InterruptedException {
if (!startWriteOperation()) {
int bytesSkipped = input.skip(length);
if (bytesSkipped == C.RESULT_END_OF_INPUT) {
if (allowEndOfInput) {
return C.RESULT_END_OF_INPUT;
}
throw new EOFException();
}
return bytesSkipped;
}
try {
length = preAppend(length);
int bytesAppended = input.read(writeAllocationNode.allocation.data,
writeAllocationNode.translateOffset(totalBytesWritten), length);
@ -580,17 +512,10 @@ public final class SampleQueue implements TrackOutput {
}
postAppend(bytesAppended);
return bytesAppended;
} finally {
endWriteOperation();
}
}
@Override
public void sampleData(ParsableByteArray buffer, int length) {
if (!startWriteOperation()) {
buffer.skipBytes(length);
return;
}
while (length > 0) {
int bytesAppended = preAppend(length);
buffer.readBytes(writeAllocationNode.allocation.data,
@ -598,7 +523,6 @@ public final class SampleQueue implements TrackOutput {
length -= bytesAppended;
postAppend(bytesAppended);
}
endWriteOperation();
}
@Override
@ -607,11 +531,6 @@ public final class SampleQueue implements TrackOutput {
if (pendingFormatAdjustment) {
format(lastUnadjustedFormat);
}
if (!startWriteOperation()) {
metadataQueue.commitSampleTimestamp(timeUs);
return;
}
try {
if (pendingSplice) {
if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !metadataQueue.attemptSplice(timeUs)) {
return;
@ -621,33 +540,10 @@ public final class SampleQueue implements TrackOutput {
timeUs += sampleOffsetUs;
long absoluteOffset = totalBytesWritten - size - offset;
metadataQueue.commitSample(timeUs, flags, absoluteOffset, size, cryptoData);
} finally {
endWriteOperation();
}
}
// Private methods.
private boolean startWriteOperation() {
return state.compareAndSet(STATE_ENABLED, STATE_ENABLED_WRITING);
}
private void endWriteOperation() {
if (!state.compareAndSet(STATE_ENABLED_WRITING, STATE_ENABLED)) {
clearSampleData();
}
}
private void clearSampleData() {
metadataQueue.clearSampleData();
clearAllocationNodes(firstAllocationNode);
firstAllocationNode = new AllocationNode(0, allocationLength);
readAllocationNode = firstAllocationNode;
writeAllocationNode = firstAllocationNode;
totalBytesWritten = 0;
allocator.trim();
}
/**
* Clears allocation nodes starting from {@code fromNode}.
*

View File

@ -53,6 +53,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private final Handler continueLoadingHandler;
private Callback callback;
private long preparePositionUs;
private int pendingPrepareCount;
private boolean seenFirstTrackSelection;
private TrackGroupArray trackGroups;
@ -84,8 +85,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
@Override
public void prepare(Callback callback, long positionUs) {
playlistTracker.addListener(this);
this.callback = callback;
playlistTracker.addListener(this);
preparePositionUs = positionUs;
buildAndPrepareSampleStreamWrappers(positionUs);
}
@ -123,7 +125,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
}
}
}
boolean selectedNewTracks = false;
// We'll always need to seek if this is a first selection to a position other than the prepare
// position.
boolean seekRequired = !seenFirstTrackSelection && positionUs != preparePositionUs;
streamWrapperIndices.clear();
// Select tracks for each child, copying the resulting streams back into a new streams array.
SampleStream[] newStreams = new SampleStream[selections.length];
@ -136,8 +140,8 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
childStreams[j] = streamChildIndices[j] == i ? streams[j] : null;
childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null;
}
selectedNewTracks |= sampleStreamWrappers[i].selectTracks(childSelections,
mayRetainStreamFlags, childStreams, streamResetFlags, !seenFirstTrackSelection);
seekRequired |= sampleStreamWrappers[i].selectTracks(childSelections, mayRetainStreamFlags,
childStreams, streamResetFlags, positionUs, seenFirstTrackSelection, seekRequired);
boolean wrapperEnabled = false;
for (int j = 0; j < selections.length; j++) {
if (selectionChildIndices[j] == i) {
@ -173,7 +177,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
}
sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers);
if (seenFirstTrackSelection && selectedNewTracks) {
if (seekRequired) {
seekToUs(positionUs);
// We'll need to reset renderers consuming from all streams due to the seek.
for (int i = 0; i < selections.length; i++) {
@ -188,7 +192,9 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
@Override
public void discardBuffer(long positionUs) {
// Do nothing.
for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) {
sampleStreamWrapper.discardBuffer(positionUs);
}
}
@Override

View File

@ -47,7 +47,7 @@ import java.util.LinkedList;
* {@link SampleStream}s from which the loaded media can be consumed.
*/
/* package */ final class HlsSampleStreamWrapper implements Loader.Callback<Chunk>,
SequenceableLoader, ExtractorOutput, UpstreamFormatChangedListener {
Loader.ReleaseCallback, SequenceableLoader, ExtractorOutput, UpstreamFormatChangedListener {
/**
* A callback to be notified of events.
@ -165,21 +165,42 @@ import java.util.LinkedList;
return trackGroups;
}
/**
* Called by the parent {@link HlsMediaPeriod} when a track selection occurs.
*
* @param selections The renderer track selections.
* @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained
* for each selection. A {@code true} value indicates that the selection is unchanged, and
* that the caller does not require that the sample stream be recreated.
* @param streams The existing sample streams, which will be updated to reflect the provided
* selections.
* @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that
* have been retained but with the requirement that the consuming renderer be reset.
* @param positionUs The current playback position in microseconds.
* @param seenFirstTrackSelection Whether we've already had the first track selection, meaning
* this is a subsequent selection.
* @param seekRequired Whether the parent {@link HlsMediaPeriod} is already guaranteed to perform
* a seek as part of the track selection
* @return Whether this wrapper requires the parent {@link HlsMediaPeriod} to perform a seek as
* part of the track selection.
*/
public boolean selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags,
SampleStream[] streams, boolean[] streamResetFlags, boolean isFirstTrackSelection) {
SampleStream[] streams, boolean[] streamResetFlags, long positionUs,
boolean seenFirstTrackSelection, boolean seekRequired) {
Assertions.checkState(prepared);
// Disable old tracks.
int oldEnabledTrackCount = enabledTrackCount;
// Deselect old tracks.
for (int i = 0; i < selections.length; i++) {
if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) {
int group = ((HlsSampleStream) streams[i]).group;
setTrackGroupEnabledState(group, false);
sampleQueues.valueAt(group).disable();
streams[i] = null;
}
}
// Enable new tracks.
// We'll always need to seek if we're making a selection having previously disabled all tracks.
seekRequired |= seenFirstTrackSelection && oldEnabledTrackCount == 0;
// Select new tracks.
TrackSelection primaryTrackSelection = null;
boolean selectedNewTracks = false;
for (int i = 0; i < selections.length; i++) {
if (streams[i] == null && selections[i] != null) {
TrackSelection selection = selections[i];
@ -191,37 +212,60 @@ import java.util.LinkedList;
}
streams[i] = new HlsSampleStream(this, group);
streamResetFlags[i] = true;
selectedNewTracks = true;
}
}
if (isFirstTrackSelection) {
// At the time of the first track selection all queues will be enabled, so we need to disable
// any that are no longer required.
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
if (!groupEnabledStates[i]) {
sampleQueues.valueAt(i).disable();
}
}
if (primaryTrackSelection != null && !mediaChunks.isEmpty()) {
primaryTrackSelection.updateSelectedTrack(0);
int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat);
if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
// The loaded preparation chunk does match the selection. We discard it.
seekTo(lastSeekPositionUs);
// If there's still a chance of avoiding a seek, try and seek within the sample queue.
if (!seekRequired) {
SampleQueue sampleQueue = sampleQueues.valueAt(group);
sampleQueue.rewind();
// A seek can be avoided if we're able to advance to the current playback position in the
// sample queue, or if we haven't read anything from the queue since the previous seek
// (this case is common for sparse tracks such as metadata tracks). In all other cases a
// seek is required.
seekRequired = !sampleQueue.advanceTo(positionUs, true, true)
&& sampleQueue.getReadIndex() != 0;
}
}
}
// Cancel requests if necessary.
if (enabledTrackCount == 0) {
chunkSource.reset();
downstreamTrackFormat = null;
mediaChunks.clear();
int sampleQueueCount = sampleQueues.size();
if (loader.isLoading()) {
// Discard as much as we can synchronously.
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).discardToEnd();
}
loader.cancelLoading();
} else {
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).reset();
}
}
return selectedNewTracks;
return false;
}
// If this is the first selection and the chunk loaded during preparation does not match the
// selection, we call seekTo to discard it. Note that if seekRequired is true then the wrapping
// HlsMediaPeriod will call seekTo regardless, and so we do not need to perform the selection
// check here.
if (!seekRequired && !seenFirstTrackSelection && primaryTrackSelection != null
&& !mediaChunks.isEmpty()) {
primaryTrackSelection.updateSelectedTrack(0);
int chunkIndex = chunkSource.getTrackGroup().indexOf(mediaChunks.getLast().trackFormat);
if (primaryTrackSelection.getSelectedIndexInTrackGroup() != chunkIndex) {
// The loaded preparation chunk does not match the selection, so discard it.
seekTo(positionUs);
}
}
return seekRequired;
}
public void discardBuffer(long positionUs) {
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).discardTo(positionUs, false, groupEnabledStates[i]);
}
}
public void seekTo(long positionUs) {
@ -234,7 +278,7 @@ import java.util.LinkedList;
} else {
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).reset(groupEnabledStates[i]);
sampleQueues.valueAt(i).reset();
}
}
}
@ -262,15 +306,27 @@ import java.util.LinkedList;
}
public void release() {
boolean releasedSynchronously = loader.release(this);
if (prepared && !releasedSynchronously) {
// Discard as much as we can synchronously. We only do this if we're prepared, since otherwise
// sampleQueues may still be being modified by the loading thread.
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).disable();
sampleQueues.valueAt(i).discardToEnd();
}
}
loader.release();
handler.removeCallbacksAndMessages(null);
released = true;
}
@Override
public void onLoaderReleased() {
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).reset();
}
}
public void setIsTimestampMaster(boolean isTimestampMaster) {
chunkSource.setIsTimestampMaster(isTimestampMaster);
}
@ -310,16 +366,16 @@ import java.util.LinkedList;
downstreamTrackFormat = trackFormat;
}
return sampleQueues.valueAt(group).readData(formatHolder, buffer, requireFormat,
loadingFinished, lastSeekPositionUs);
return sampleQueues.valueAt(group).read(formatHolder, buffer, requireFormat, loadingFinished,
lastSeekPositionUs);
}
/* package */ void skipData(int group, long positionUs) {
SampleQueue sampleQueue = sampleQueues.valueAt(group);
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
sampleQueue.skipAll();
sampleQueue.advanceToEnd();
} else {
sampleQueue.skipToKeyframeBefore(positionUs, true);
sampleQueue.advanceTo(positionUs, true, true);
}
}
@ -408,11 +464,13 @@ import java.util.LinkedList;
if (!released) {
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).reset(groupEnabledStates[i]);
sampleQueues.valueAt(i).reset();
}
if (enabledTrackCount > 0) {
callback.onContinueLoadingRequested(this);
}
}
}
@Override
public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs,