mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
9bc20d23a8
commit
035fab225d
@ -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();
|
||||
}
|
||||
|
@ -551,7 +551,7 @@ public final class ExoPlayerTest extends TestCase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipToKeyframeBefore(long timeUs) {
|
||||
public void skipData(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public final class EmptySampleStream implements SampleStream {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipToKeyframeBefore(long timeUs) {
|
||||
public void skipData(long positionUs) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user