Correctly handle a SampleStream ending without providing a format

I'm going to introduce an EmptySampleStream that will be used in
some cases in conjunction as part of 608/EMSG support. This change
avoids EmptySampleStream having to provide a dummy format.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148383831
This commit is contained in:
olly 2017-02-23 13:30:19 -08:00 committed by Oliver Woodman
parent ef2541e654
commit d99cb28e6a
15 changed files with 155 additions and 104 deletions

View File

@ -65,6 +65,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher;
private final FormatHolder formatHolder;
private final DecoderInputBuffer flagsOnlyBuffer;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private DecoderCounters decoderCounters;
@ -149,6 +150,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
joiningDeadlineMs = -1;
clearLastReportedVideoSize();
formatHolder = new FormatHolder();
flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
outputMode = VpxDecoder.OUTPUT_MODE_NONE;
}
@ -165,10 +167,22 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return;
}
// Try and read a format if we don't have one already.
if (format == null && !readFormat()) {
// We can't make progress without one.
if (format == null) {
// We don't have a format yet, so try and read one.
flagsOnlyBuffer.clear();
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(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
outputStreamEnded = true;
return;
} else {
// We still don't have a format and can't make progress without one.
return;
}
}
if (isRendererAvailable()) {
@ -327,7 +341,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
// We've already read an encrypted sample into buffer, and are waiting for keys.
result = C.RESULT_BUFFER_READ;
} else {
result = readSource(formatHolder, inputBuffer);
result = readSource(formatHolder, inputBuffer, false);
}
if (result == C.RESULT_NOTHING_READ) {
@ -485,15 +499,6 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
}
}
private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
return true;
}
return false;
}
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format;
format = newFormat;

View File

@ -513,8 +513,9 @@ public final class ExoPlayerTest extends TestCase {
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
if (buffer == null || !readFormat) {
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (formatRequired || !readFormat) {
formatHolder.format = format;
readFormat = true;
return C.RESULT_FORMAT_READ;
@ -571,7 +572,7 @@ public final class ExoPlayerTest extends TestCase {
FormatHolder formatHolder = new FormatHolder();
DecoderInputBuffer buffer =
new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL);
int result = readSource(formatHolder, buffer);
int result = readSource(formatHolder, buffer, false);
if (result == C.RESULT_FORMAT_READ) {
formatReadCount++;
assertEquals(expectedFormat, formatHolder.format);

View File

@ -262,13 +262,16 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. May be null if the
* caller requires that the format of the stream be read even if it's not changing.
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
* @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}.
*/
protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer) {
int result = stream.readData(formatHolder, buffer);
protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
int result = stream.readData(formatHolder, buffer, formatRequired);
if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) {
readEndOfStream = true;

View File

@ -34,6 +34,7 @@ import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil;
@ -67,12 +68,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
*/
private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher;
private final AudioTrack audioTrack;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private final FormatHolder formatHolder;
private final DecoderInputBuffer flagsOnlyBuffer;
private DecoderCounters decoderCounters;
private Format inputFormat;
@ -142,11 +143,12 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys,
BufferProcessor... bufferProcessors) {
super(C.TRACK_TYPE_AUDIO);
this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioTrack = new AudioTrack(audioCapabilities, bufferProcessors, new AudioTrackListener());
this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
flagsOnlyBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
audioTrackNeedsConfigure = true;
}
@ -187,9 +189,22 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
// Try and read a format if we don't have one already.
if (inputFormat == null && !readFormat()) {
// We can't make progress without one.
if (inputFormat == null) {
// We don't have a format yet, so try and read one.
flagsOnlyBuffer.clear();
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(flagsOnlyBuffer.isEndOfStream());
inputStreamEnded = true;
processEndOfStream();
return;
} else {
// We still don't have a format and can't make progress without one.
return;
}
}
// If we don't have a decoder yet, we need to instantiate one.
@ -284,8 +299,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} else {
outputBuffer.release();
outputBuffer = null;
outputStreamEnded = true;
audioTrack.playToEndOfStream();
processEndOfStream();
}
return false;
}
@ -334,7 +348,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
// We've already read an encrypted sample into buffer, and are waiting for keys.
result = C.RESULT_BUFFER_READ;
} else {
result = readSource(formatHolder, inputBuffer);
result = readSource(formatHolder, inputBuffer, false);
}
if (result == C.RESULT_NOTHING_READ) {
@ -375,6 +389,15 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
}
private void processEndOfStream() throws ExoPlaybackException {
outputStreamEnded = true;
try {
audioTrack.playToEndOfStream();
} catch (AudioTrack.WriteException e) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
}
}
private void flushDecoder() throws ExoPlaybackException {
waitingForKeys = false;
if (decoderReinitializationState != REINITIALIZATION_STATE_NONE) {
@ -523,15 +546,6 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
decoderReceivedBuffers = false;
}
private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
return true;
}
return false;
}
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = inputFormat;
inputFormat = newFormat;

View File

@ -267,27 +267,26 @@ public final class DefaultTrackOutput implements TrackOutput {
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. May be null if the
* caller requires that the format of the stream be read even if it's not changing.
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
* @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
* @param loadingFinished True if an empty queue should be considered the end of the stream.
* @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will
* be set if the buffer's timestamp is less than this value.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}.
*/
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean loadingFinished,
long decodeOnlyUntilUs) {
switch (infoQueue.readData(formatHolder, buffer, downstreamFormat, extrasHolder)) {
case C.RESULT_NOTHING_READ:
if (loadingFinished) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
return C.RESULT_NOTHING_READ;
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired,
boolean loadingFinished, long decodeOnlyUntilUs) {
int result = infoQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
downstreamFormat, extrasHolder);
switch (result) {
case C.RESULT_FORMAT_READ:
downstreamFormat = formatHolder.format;
return C.RESULT_FORMAT_READ;
case C.RESULT_BUFFER_READ:
if (!buffer.isEndOfStream()) {
if (buffer.timeUs < decodeOnlyUntilUs) {
buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
}
@ -300,7 +299,10 @@ public final class DefaultTrackOutput implements TrackOutput {
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
// Advance the read head.
dropDownstreamTo(extrasHolder.nextOffset);
}
return C.RESULT_BUFFER_READ;
case C.RESULT_NOTHING_READ:
return C.RESULT_NOTHING_READ;
default:
throw new IllegalStateException();
}
@ -760,23 +762,34 @@ public final class DefaultTrackOutput implements TrackOutput {
* and the absolute position of the first byte that may still be required after the current
* sample has been read. May be null if the caller requires that the format of the stream be
* read even if it's not changing.
* @param formatRequired Whether the caller requires that the format of the stream be read even
* if it's not changing. A sample will never be read if set to true, however it is still
* possible for the end of stream or nothing to be read.
* @param loadingFinished True if an empty queue should be considered the end of the stream.
* @param downstreamFormat The current downstream {@link Format}. If the format of the next
* sample is different to the current downstream format then a format will be read.
* @param extrasHolder The holder into which extra sample information should be written.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ}
* or {@link C#RESULT_BUFFER_READ}.
*/
@SuppressWarnings("ReferenceEquality")
public synchronized int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
Format downstreamFormat, BufferExtrasHolder extrasHolder) {
boolean formatRequired, boolean loadingFinished, Format downstreamFormat,
BufferExtrasHolder extrasHolder) {
if (queueSize == 0) {
if (upstreamFormat != null && (buffer == null || upstreamFormat != downstreamFormat)) {
if (loadingFinished) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} else if (upstreamFormat != null
&& (formatRequired || upstreamFormat != downstreamFormat)) {
formatHolder.format = upstreamFormat;
return C.RESULT_FORMAT_READ;
}
} else {
return C.RESULT_NOTHING_READ;
}
}
if (buffer == null || formats[relativeReadIndex] != downstreamFormat) {
if (formatRequired || formats[relativeReadIndex] != downstreamFormat) {
formatHolder.format = formats[relativeReadIndex];
return C.RESULT_FORMAT_READ;
}

View File

@ -484,7 +484,21 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return;
}
if (format == null) {
readFormat();
// We don't have a format yet, so try and read one.
buffer.clear();
int result = readSource(formatHolder, buffer, 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());
inputStreamEnded = true;
processEndOfStream();
return;
} else {
// We still don't have a format and can't make progress without one.
return;
}
}
maybeInitCodec();
if (codec != null) {
@ -498,13 +512,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decoderCounters.ensureUpdated();
}
private void readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format);
}
}
protected void flushCodec() throws ExoPlaybackException {
codecHotswapDeadlineMs = C.TIME_UNSET;
inputIndex = C.INDEX_UNSET;
@ -594,7 +601,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING;
}
adaptiveReconfigurationBytes = buffer.data.position();
result = readSource(formatHolder, buffer);
result = readSource(formatHolder, buffer, false);
}
if (result == C.RESULT_NOTHING_READ) {

View File

@ -109,7 +109,7 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (!inputStreamEnded && pendingMetadata == null) {
buffer.clear();
int result = readSource(formatHolder, buffer);
int result = readSource(formatHolder, buffer, false);
if (result == C.RESULT_BUFFER_READ) {
if (buffer.isEndOfStream()) {
inputStreamEnded = true;

View File

@ -231,18 +231,16 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean requireFormat) {
if (pendingDiscontinuity) {
return C.RESULT_NOTHING_READ;
}
if (buffer == null) {
return stream.readData(formatHolder, null);
}
if (sentEos) {
buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
int result = stream.readData(formatHolder, buffer);
int result = stream.readData(formatHolder, buffer, requireFormat);
// TODO: Clear gapless playback metadata if a format was read (if applicable).
if (endUs != C.TIME_END_OF_SOURCE && ((result == C.RESULT_BUFFER_READ
&& buffer.timeUs >= endUs) || (result == C.RESULT_NOTHING_READ

View File

@ -325,13 +325,14 @@ import java.io.IOException;
loader.maybeThrowError();
}
/* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer) {
/* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (notifyReset || isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
return sampleQueues.valueAt(track).readData(formatHolder, buffer, loadingFinished,
lastSeekPositionUs);
return sampleQueues.valueAt(track).readData(formatHolder, buffer, formatRequired,
loadingFinished, lastSeekPositionUs);
}
// Loader.Callback implementation.
@ -552,8 +553,9 @@ import java.io.IOException;
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer);
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer, formatRequired);
}
@Override

View File

@ -45,20 +45,24 @@ public interface SampleStream {
/**
* Attempts to read from the stream.
* <p>
* If no data is available then {@link C#RESULT_NOTHING_READ} is returned. If the format of the
* media is changing or if {@code buffer == null} then {@code formatHolder} is populated and
* If the stream has ended then {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set on {@code buffer}
* and {@link C#RESULT_BUFFER_READ} is returned. Else if no data is available then
* {@link C#RESULT_NOTHING_READ} is returned. Else if the format of the media is changing or if
* {@code formatRequired} is set then {@code formatHolder} is populated and
* {@link C#RESULT_FORMAT_READ} is returned. Else {@code buffer} is populated and
* {@link C#RESULT_BUFFER_READ} is returned.
*
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
* end of the stream. If the end of the stream has been reached, the
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. May be null if the
* caller requires that the format of the stream be read even if it's not changing.
* {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
* @param formatRequired Whether the caller requires that the format of the stream be read even if
* it's not changing. A sample will never be read if set to true, however it is still possible
* for the end of stream or nothing to be read.
* @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or
* {@link C#RESULT_BUFFER_READ}.
*/
int readData(FormatHolder formatHolder, DecoderInputBuffer buffer);
int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired);
/**
* Attempts to skip to the keyframe before the specified time.

View File

@ -205,14 +205,15 @@ import java.util.Arrays;
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
if (buffer == null || streamState == STREAM_STATE_SEND_FORMAT) {
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean requireFormat) {
if (streamState == STREAM_STATE_END_OF_STREAM) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
} else if (requireFormat || streamState == STREAM_STATE_SEND_FORMAT) {
formatHolder.format = format;
streamState = STREAM_STATE_SEND_SAMPLE;
return C.RESULT_FORMAT_READ;
} else if (streamState == STREAM_STATE_END_OF_STREAM) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
return C.RESULT_BUFFER_READ;
}
Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE);

View File

@ -169,7 +169,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean formatRequired) {
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
@ -187,7 +188,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
currentChunk.startTimeUs);
}
downstreamTrackFormat = trackFormat;
return sampleQueue.readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
return sampleQueue.readData(formatHolder, buffer, formatRequired, loadingFinished,
lastSeekPositionUs);
}
@Override

View File

@ -45,8 +45,8 @@ import java.io.IOException;
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) {
return sampleStreamWrapper.readData(group, formatHolder, buffer);
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
return sampleStreamWrapper.readData(group, formatHolder, buffer, requireFormat);
}
@Override

View File

@ -290,7 +290,8 @@ import java.util.LinkedList;
chunkSource.maybeThrowError();
}
/* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) {
/* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean requireFormat) {
if (isPendingReset()) {
return C.RESULT_NOTHING_READ;
}
@ -307,8 +308,8 @@ import java.util.LinkedList;
}
downstreamTrackFormat = trackFormat;
return sampleQueues.valueAt(group).readData(formatHolder, buffer, loadingFinished,
lastSeekPositionUs);
return sampleQueues.valueAt(group).readData(formatHolder, buffer, requireFormat,
loadingFinished, lastSeekPositionUs);
}
/* package */ void skipToKeyframeBefore(int group, long timeUs) {

View File

@ -189,7 +189,7 @@ public final class TextRenderer extends BaseRenderer implements Callback {
}
}
// Try and read the next subtitle from the source.
int result = readSource(formatHolder, nextInputBuffer);
int result = readSource(formatHolder, nextInputBuffer, false);
if (result == C.RESULT_BUFFER_READ) {
if (nextInputBuffer.isEndOfStream()) {
inputStreamEnded = true;