Rename skipToKeyframeBefore -> skipData and allow skipping to EOS

This change also ensures that format changes are read whilst the
renderer is enabled but without a codec. This is necessary to
ensure the drm session is updated (or replaced).

Updating the format is also needed so that the up-to-date format is
used in the case that the codec is initialized later due to the
surface being set. Previously, if an ABR change occurred between
the format being read and the surface being attached, we would
instantiate the codec and then immediately have to reconfigure it
when as a result of reading the up-to-date format. For a non-adaptive
codec this resulted in the codec being immediately released and
instantiated again!

Issue: #2582

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=151608096
This commit is contained in:
olly 2017-03-29 12:11:57 -07:00 committed by Oliver Woodman
parent 9bc20d23a8
commit 035fab225d
15 changed files with 149 additions and 47 deletions

View File

@ -150,7 +150,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
joiningDeadlineMs = C.TIME_UNSET;
clearReportedVideoSize();
formatHolder = new FormatHolder();
flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
outputMode = VpxDecoder.OUTPUT_MODE_NONE;
}
@ -185,6 +185,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
}
}
// We have a format.
if (isRendererAvailable()) {
drmSession = pendingDrmSession;
ExoMediaCrypto mediaCrypto = null;
@ -222,7 +223,20 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
throw ExoPlaybackException.createForRenderer(e, getIndex());
}
} else {
skipToKeyframeBefore(positionUs);
skipSource(positionUs);
// We need to read any format changes despite not having a codec so that drmSession can be
// updated, and so that we have the most recent format should the codec be initialized. We may
// also reach the end of the stream. Note that readSource will not read a sample into a
// flags-only buffer.
flagsOnlyBuffer.clear();
int result = readSource(formatHolder, flagsOnlyBuffer, false);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
outputStreamEnded = true;
}
}
decoderCounters.ensureUpdated();
}

View File

@ -551,7 +551,7 @@ public final class ExoPlayerTest extends TestCase {
}
@Override
public void skipToKeyframeBefore(long timeUs) {
public void skipData(long positionUs) {
// Do nothing.
}

View File

@ -296,6 +296,16 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
return result;
}
/**
* Attempts to skip to the keyframe before the specified position, or to the end of the stream if
* {@code positionUs} is beyond it.
*
* @param positionUs The position in microseconds.
*/
protected void skipSource(long positionUs) {
stream.skipData(positionUs - streamOffsetUs);
}
/**
* Returns whether the upstream source is ready.
*
@ -305,13 +315,4 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
return readEndOfStream ? streamIsFinal : stream.isReady();
}
/**
* Attempts to skip to the keyframe before the specified time.
*
* @param timeUs The specified time.
*/
protected void skipToKeyframeBefore(long timeUs) {
stream.skipToKeyframeBefore(timeUs - streamOffsetUs);
}
}

View File

@ -146,7 +146,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, audioProcessors, new AudioTrackListener());
formatHolder = new FormatHolder();
flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
audioTrackNeedsConfigure = true;
}

View File

@ -63,6 +63,15 @@ public class DecoderInputBuffer extends Buffer {
@BufferReplacementMode private final int bufferReplacementMode;
/**
* Creates a new instance for which {@link #isFlagsOnly()} will return true.
*
* @return A new flags only input buffer.
*/
public static DecoderInputBuffer newFlagsOnlyInstance() {
return new DecoderInputBuffer(BUFFER_REPLACEMENT_MODE_DISABLED);
}
/**
* @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One
* of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and
@ -109,6 +118,14 @@ public class DecoderInputBuffer extends Buffer {
data = newData;
}
/**
* Returns whether the buffer is only able to hold flags, meaning {@link #data} is null and
* its replacement mode is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}.
*/
public final boolean isFlagsOnly() {
return data == null && bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DISABLED;
}
/**
* Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set.
*/

View File

@ -76,7 +76,6 @@ public final class DefaultTrackOutput implements TrackOutput {
private long totalBytesWritten;
private Allocation lastAllocation;
private int lastAllocationOffset;
private boolean needKeyframe;
private boolean pendingSplice;
private UpstreamFormatChangedListener upstreamFormatChangeListener;
@ -92,7 +91,6 @@ public final class DefaultTrackOutput implements TrackOutput {
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
state = new AtomicInteger();
lastAllocationOffset = allocationLength;
needKeyframe = true;
}
// Called by the consuming thread, but only when there is no loading thread.
@ -227,6 +225,16 @@ public final class DefaultTrackOutput implements TrackOutput {
return infoQueue.getLargestQueuedTimestampUs();
}
/**
* Skips all samples currently in the buffer.
*/
public void skipAll() {
long nextOffset = infoQueue.skipAll();
if (nextOffset != C.POSITION_UNSET) {
dropDownstreamTo(nextOffset);
}
}
/**
* Attempts to skip to the keyframe before or at the specified time. Succeeds only if the buffer
* contains a keyframe with a timestamp of {@code timeUs} or earlier. If
@ -523,12 +531,6 @@ public final class DefaultTrackOutput implements TrackOutput {
}
pendingSplice = false;
}
if (needKeyframe) {
if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) {
return;
}
needKeyframe = false;
}
timeUs += sampleOffsetUs;
long absoluteOffset = totalBytesWritten - size - offset;
infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey);
@ -558,7 +560,6 @@ public final class DefaultTrackOutput implements TrackOutput {
totalBytesWritten = 0;
lastAllocation = null;
lastAllocationOffset = allocationLength;
needKeyframe = true;
}
/**
@ -615,6 +616,7 @@ public final class DefaultTrackOutput implements TrackOutput {
private long largestDequeuedTimestampUs;
private long largestQueuedTimestampUs;
private boolean upstreamKeyframeRequired;
private boolean upstreamFormatRequired;
private Format upstreamFormat;
private int upstreamSourceId;
@ -631,6 +633,7 @@ public final class DefaultTrackOutput implements TrackOutput {
largestDequeuedTimestampUs = Long.MIN_VALUE;
largestQueuedTimestampUs = Long.MIN_VALUE;
upstreamFormatRequired = true;
upstreamKeyframeRequired = true;
}
public void clearSampleData() {
@ -638,6 +641,7 @@ public final class DefaultTrackOutput implements TrackOutput {
relativeReadIndex = 0;
relativeWriteIndex = 0;
queueSize = 0;
upstreamKeyframeRequired = true;
}
// Called by the consuming thread, but only when there is no loading thread.
@ -780,6 +784,10 @@ public final class DefaultTrackOutput implements TrackOutput {
return C.RESULT_FORMAT_READ;
}
if (buffer.isFlagsOnly()) {
return C.RESULT_NOTHING_READ;
}
buffer.timeUs = timesUs[relativeReadIndex];
buffer.setFlags(flags[relativeReadIndex]);
extrasHolder.size = sizes[relativeReadIndex];
@ -800,6 +808,24 @@ public final class DefaultTrackOutput implements TrackOutput {
return C.RESULT_BUFFER_READ;
}
/**
* Skips all samples in the buffer.
*
* @return The offset up to which data should be dropped, or {@link C#POSITION_UNSET} if no
* dropping of data is required.
*/
public synchronized long skipAll() {
if (queueSize == 0) {
return C.POSITION_UNSET;
}
int lastSampleIndex = (relativeReadIndex + queueSize - 1) % capacity;
relativeReadIndex = (relativeReadIndex + queueSize) % capacity;
absoluteReadIndex += queueSize;
queueSize = 0;
return offsets[lastSampleIndex] + sizes[lastSampleIndex];
}
/**
* Attempts to locate the keyframe before or at the specified time. If
* {@code allowTimeBeyondBuffer} is {@code false} then it is also required that {@code timeUs}
@ -842,9 +868,9 @@ public final class DefaultTrackOutput implements TrackOutput {
return C.POSITION_UNSET;
}
queueSize -= sampleCountToKeyframe;
relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity;
absoluteReadIndex += sampleCountToKeyframe;
queueSize -= sampleCountToKeyframe;
return offsets[relativeReadIndex];
}
@ -867,6 +893,12 @@ public final class DefaultTrackOutput implements TrackOutput {
public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset,
int size, byte[] encryptionKey) {
if (upstreamKeyframeRequired) {
if ((sampleFlags & C.BUFFER_FLAG_KEY_FRAME) == 0) {
return;
}
upstreamKeyframeRequired = false;
}
Assertions.checkState(!upstreamFormatRequired);
commitSampleTimestamp(timeUs);
timesUs[relativeWriteIndex] = timeUs;

View File

@ -169,6 +169,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
private final boolean playClearSamplesWithoutKeys;
private final DecoderInputBuffer buffer;
private final DecoderInputBuffer flagsOnlyBuffer;
private final FormatHolder formatHolder;
private final List<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo;
@ -227,6 +228,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
flagsOnlyBuffer = DecoderInputBuffer.newFlagsOnlyInstance();
formatHolder = new FormatHolder();
decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo();
@ -448,6 +450,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecReconfigurationState = RECONFIGURATION_STATE_NONE;
codecReinitializationState = REINITIALIZATION_STATE_NONE;
decoderCounters.decoderReleaseCount++;
buffer.data = null;
try {
codec.stop();
} finally {
@ -486,12 +489,12 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (format == null) {
// We don't have a format yet, so try and read one.
buffer.clear();
int result = readSource(formatHolder, buffer, true);
int result = readSource(formatHolder, flagsOnlyBuffer, true);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
// End of stream read having not read a format.
Assertions.checkState(buffer.isEndOfStream());
Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
processEndOfStream();
return;
@ -500,14 +503,28 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return;
}
}
// We have a format.
maybeInitCodec();
if (codec != null) {
TraceUtil.beginSection("drainAndFeed");
while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {}
while (feedInputBuffer()) {}
TraceUtil.endSection();
} else if (format != null) {
skipToKeyframeBefore(positionUs);
} else {
skipSource(positionUs);
// We need to read any format changes despite not having a codec so that drmSession can be
// updated, and so that we have the most recent format should the codec be initialized. We may
// also reach the end of the stream. Note that readSource will not read a sample into a
// flags-only buffer.
flagsOnlyBuffer.clear();
int result = readSource(formatHolder, flagsOnlyBuffer, false);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
} else if (result == C.RESULT_BUFFER_READ) {
Assertions.checkState(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
processEndOfStream();
}
}
decoderCounters.ensureUpdated();
}

View File

@ -262,8 +262,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
}
@Override
public void skipToKeyframeBefore(long timeUs) {
stream.skipToKeyframeBefore(startUs + timeUs);
public void skipData(long positionUs) {
stream.skipData(startUs + positionUs);
}
}

View File

@ -43,7 +43,7 @@ public final class EmptySampleStream implements SampleStream {
}
@Override
public void skipToKeyframeBefore(long timeUs) {
public void skipData(long positionUs) {
// Do nothing.
}

View File

@ -340,8 +340,13 @@ import java.io.IOException;
loadingFinished, lastSeekPositionUs);
}
/* package */ void skipToKeyframeBefore(int track, long timeUs) {
sampleQueues.valueAt(track).skipToKeyframeBefore(timeUs, true);
/* package */ void skipData(int track, long positionUs) {
DefaultTrackOutput sampleQueue = sampleQueues.valueAt(track);
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
sampleQueue.skipAll();
} else {
sampleQueue.skipToKeyframeBefore(positionUs, true);
}
}
// Loader.Callback implementation.
@ -569,8 +574,8 @@ import java.io.IOException;
}
@Override
public void skipToKeyframeBefore(long timeUs) {
ExtractorMediaPeriod.this.skipToKeyframeBefore(track, timeUs);
public void skipData(long positionUs) {
ExtractorMediaPeriod.this.skipData(track, positionUs);
}
}

View File

@ -66,10 +66,11 @@ public interface SampleStream {
int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired);
/**
* Attempts to skip to the keyframe before the specified time.
* Attempts to skip to the keyframe before the specified position, or to the end of the stream if
* {@code positionUs} is beyond it.
*
* @param timeUs The specified time.
* @param positionUs The specified time.
*/
void skipToKeyframeBefore(long timeUs);
void skipData(long positionUs);
}

View File

@ -235,8 +235,10 @@ import java.util.Arrays;
}
@Override
public void skipToKeyframeBefore(long timeUs) {
// Do nothing.
public void skipData(long positionUs) {
if (positionUs > 0) {
streamState = STREAM_STATE_END_OF_STREAM;
}
}
}

View File

@ -251,8 +251,12 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
}
@Override
public void skipToKeyframeBefore(long timeUs) {
primarySampleQueue.skipToKeyframeBefore(timeUs, true);
public void skipData(long positionUs) {
if (loadingFinished && positionUs > primarySampleQueue.getLargestQueuedTimestampUs()) {
primarySampleQueue.skipAll();
} else {
primarySampleQueue.skipToKeyframeBefore(positionUs, true);
}
}
// Loader.Callback implementation.
@ -448,8 +452,12 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
}
@Override
public void skipToKeyframeBefore(long timeUs) {
sampleQueue.skipToKeyframeBefore(timeUs, true);
public void skipData(long positionUs) {
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
sampleQueue.skipAll();
} else {
sampleQueue.skipToKeyframeBefore(positionUs, true);
}
}
@Override

View File

@ -50,8 +50,8 @@ import java.io.IOException;
}
@Override
public void skipToKeyframeBefore(long timeUs) {
sampleStreamWrapper.skipToKeyframeBefore(group, timeUs);
public void skipData(long positionUs) {
sampleStreamWrapper.skipData(group, positionUs);
}
}

View File

@ -312,8 +312,13 @@ import java.util.LinkedList;
loadingFinished, lastSeekPositionUs);
}
/* package */ void skipToKeyframeBefore(int group, long timeUs) {
sampleQueues.valueAt(group).skipToKeyframeBefore(timeUs, true);
/* package */ void skipData(int group, long positionUs) {
DefaultTrackOutput sampleQueue = sampleQueues.valueAt(group);
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
sampleQueue.skipAll();
} else {
sampleQueue.skipToKeyframeBefore(positionUs, true);
}
}
private boolean finishedReadingChunk(HlsMediaChunk chunk) {