Commit 38639e5a by tonihei

Ensure output format is updated in sync with stream changes.

MediaCodecRenderer currently has two independent paths to trigger
events at stream changes:
 1. Detection of the last output buffer of the old stream to trigger
    onProcessedStreamChange and setting the new output stream offset.
 2. Detection of the first input buffer of the new stream to trigger
    onOutputFormatChanged.
Both events are identical for most media. However, there are two
problematic cases:
  A. (1) happens after (2). This may happen if the declared media
     duration is shorter than the actual last sample timestamp.
  B. (2) is too late and there are output samples between (1) and (2).
     This can happen if the new media outputs samples with a timestamp
     less than the first input timestamp.

This can be made more robust by:
 - Keeping a separate formatQueue for each stream to avoid case A.
 - Force outputting the first format after a stream change to
   avoid case B.

Issue: google/ExoPlayer#8594

#minor-release

PiperOrigin-RevId: 512586838
(cherry picked from commit a02c8d85)
parent 095a0b5e
...@@ -297,7 +297,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -297,7 +297,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final DecoderInputBuffer buffer; private final DecoderInputBuffer buffer;
private final DecoderInputBuffer bypassSampleBuffer; private final DecoderInputBuffer bypassSampleBuffer;
private final BatchBuffer bypassBatchBuffer; private final BatchBuffer bypassBatchBuffer;
private final TimedValueQueue<Format> formatQueue;
private final ArrayList<Long> decodeOnlyPresentationTimestamps; private final ArrayList<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo; private final MediaCodec.BufferInfo outputBufferInfo;
private final ArrayDeque<OutputStreamInfo> pendingOutputStreamChanges; private final ArrayDeque<OutputStreamInfo> pendingOutputStreamChanges;
...@@ -357,6 +356,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -357,6 +356,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected DecoderCounters decoderCounters; protected DecoderCounters decoderCounters;
private OutputStreamInfo outputStreamInfo; private OutputStreamInfo outputStreamInfo;
private long lastProcessedOutputBufferTimeUs; private long lastProcessedOutputBufferTimeUs;
private boolean needToNotifyOutputFormatChangeAfterStreamChange;
/** /**
* @param trackType The {@link C.TrackType track type} that the renderer handles. * @param trackType The {@link C.TrackType track type} that the renderer handles.
...@@ -383,7 +383,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -383,7 +383,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
bypassSampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT); bypassSampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
bypassBatchBuffer = new BatchBuffer(); bypassBatchBuffer = new BatchBuffer();
formatQueue = new TimedValueQueue<>();
decodeOnlyPresentationTimestamps = new ArrayList<>(); decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo(); outputBufferInfo = new MediaCodec.BufferInfo();
currentPlaybackSpeed = 1f; currentPlaybackSpeed = 1f;
...@@ -595,13 +594,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -595,13 +594,15 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
protected final void updateOutputFormatForTime(long presentationTimeUs) protected final void updateOutputFormatForTime(long presentationTimeUs)
throws ExoPlaybackException { throws ExoPlaybackException {
boolean outputFormatChanged = false; boolean outputFormatChanged = false;
@Nullable Format format = formatQueue.pollFloor(presentationTimeUs); @Nullable Format format = outputStreamInfo.formatQueue.pollFloor(presentationTimeUs);
if (format == null && codecOutputMediaFormatChanged) { if (format == null
// If the codec's output MediaFormat has changed then there should be a corresponding Format && needToNotifyOutputFormatChangeAfterStreamChange
// change, which we've not found. Check the Format queue in case the corresponding && codecOutputMediaFormat != null) {
// presentation timestamp is greater than presentationTimeUs, which can happen for some codecs // After a stream change or after the initial start, there should be an input format change,
// [Internal ref: b/162719047]. // which we've not found. Check the Format queue in case the corresponding presentation
format = formatQueue.pollFirst(); // timestamp is greater than presentationTimeUs, which can happen for some codecs
// [Internal ref: b/162719047 and https://github.com/google/ExoPlayer/issues/8594].
format = outputStreamInfo.formatQueue.pollFirst();
} }
if (format != null) { if (format != null) {
outputFormat = format; outputFormat = format;
...@@ -610,6 +611,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -610,6 +611,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (outputFormatChanged || (codecOutputMediaFormatChanged && outputFormat != null)) { if (outputFormatChanged || (codecOutputMediaFormatChanged && outputFormat != null)) {
onOutputFormatChanged(outputFormat, codecOutputMediaFormat); onOutputFormatChanged(outputFormat, codecOutputMediaFormat);
codecOutputMediaFormatChanged = false; codecOutputMediaFormatChanged = false;
needToNotifyOutputFormatChangeAfterStreamChange = false;
} }
} }
...@@ -666,10 +668,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -666,10 +668,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// If there is a format change on the input side still pending propagation to the output, we // If there is a format change on the input side still pending propagation to the output, we
// need to queue a format next time a buffer is read. This is because we may not read a new // need to queue a format next time a buffer is read. This is because we may not read a new
// input format after the position reset. // input format after the position reset.
if (formatQueue.size() > 0) { if (outputStreamInfo.formatQueue.size() > 0) {
waitingForFirstSampleInFormat = true; waitingForFirstSampleInFormat = true;
} }
formatQueue.clear(); outputStreamInfo.formatQueue.clear();
pendingOutputStreamChanges.clear(); pendingOutputStreamChanges.clear();
} }
...@@ -1331,7 +1333,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1331,7 +1333,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
decodeOnlyPresentationTimestamps.add(presentationTimeUs); decodeOnlyPresentationTimestamps.add(presentationTimeUs);
} }
if (waitingForFirstSampleInFormat) { if (waitingForFirstSampleInFormat) {
formatQueue.add(presentationTimeUs, inputFormat); if (!pendingOutputStreamChanges.isEmpty()) {
pendingOutputStreamChanges.peekLast().formatQueue.add(presentationTimeUs, inputFormat);
} else {
outputStreamInfo.formatQueue.add(presentationTimeUs, inputFormat);
}
waitingForFirstSampleInFormat = false; waitingForFirstSampleInFormat = false;
} }
largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs); largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs);
...@@ -2027,6 +2033,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2027,6 +2033,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private void setOutputStreamInfo(OutputStreamInfo outputStreamInfo) { private void setOutputStreamInfo(OutputStreamInfo outputStreamInfo) {
this.outputStreamInfo = outputStreamInfo; this.outputStreamInfo = outputStreamInfo;
if (outputStreamInfo.streamOffsetUs != C.TIME_UNSET) { if (outputStreamInfo.streamOffsetUs != C.TIME_UNSET) {
needToNotifyOutputFormatChangeAfterStreamChange = true;
onOutputStreamOffsetUsChanged(outputStreamInfo.streamOffsetUs); onOutputStreamOffsetUsChanged(outputStreamInfo.streamOffsetUs);
} }
} }
...@@ -2485,12 +2492,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -2485,12 +2492,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
public final long previousStreamLastBufferTimeUs; public final long previousStreamLastBufferTimeUs;
public final long startPositionUs; public final long startPositionUs;
public final long streamOffsetUs; public final long streamOffsetUs;
public final TimedValueQueue<Format> formatQueue;
public OutputStreamInfo( public OutputStreamInfo(
long previousStreamLastBufferTimeUs, long startPositionUs, long streamOffsetUs) { long previousStreamLastBufferTimeUs, long startPositionUs, long streamOffsetUs) {
this.previousStreamLastBufferTimeUs = previousStreamLastBufferTimeUs; this.previousStreamLastBufferTimeUs = previousStreamLastBufferTimeUs;
this.startPositionUs = startPositionUs; this.startPositionUs = startPositionUs;
this.streamOffsetUs = streamOffsetUs; this.streamOffsetUs = streamOffsetUs;
this.formatQueue = new TimedValueQueue<>();
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment