Skip to content

Commit

Permalink
Ensure output format is updated in sync with stream changes.
Browse files Browse the repository at this point in the history
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: #8594

#minor-release

PiperOrigin-RevId: 512586838
(cherry picked from commit a02c8d8)
  • Loading branch information
tonihei committed Feb 28, 2023
1 parent 095a0b5 commit 38639e5
Show file tree
Hide file tree
Showing 2 changed files with 344 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,6 @@ private static String buildCustomDiagnosticInfo(int errorCode) {
private final DecoderInputBuffer buffer;
private final DecoderInputBuffer bypassSampleBuffer;
private final BatchBuffer bypassBatchBuffer;
private final TimedValueQueue<Format> formatQueue;
private final ArrayList<Long> decodeOnlyPresentationTimestamps;
private final MediaCodec.BufferInfo outputBufferInfo;
private final ArrayDeque<OutputStreamInfo> pendingOutputStreamChanges;
Expand Down Expand Up @@ -357,6 +356,7 @@ private static String buildCustomDiagnosticInfo(int errorCode) {
protected DecoderCounters decoderCounters;
private OutputStreamInfo outputStreamInfo;
private long lastProcessedOutputBufferTimeUs;
private boolean needToNotifyOutputFormatChangeAfterStreamChange;

/**
* @param trackType The {@link C.TrackType track type} that the renderer handles.
Expand All @@ -383,7 +383,6 @@ public MediaCodecRenderer(
buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
bypassSampleBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
bypassBatchBuffer = new BatchBuffer();
formatQueue = new TimedValueQueue<>();
decodeOnlyPresentationTimestamps = new ArrayList<>();
outputBufferInfo = new MediaCodec.BufferInfo();
currentPlaybackSpeed = 1f;
Expand Down Expand Up @@ -595,13 +594,15 @@ protected final void setPendingPlaybackException(ExoPlaybackException exception)
protected final void updateOutputFormatForTime(long presentationTimeUs)
throws ExoPlaybackException {
boolean outputFormatChanged = false;
@Nullable Format format = formatQueue.pollFloor(presentationTimeUs);
if (format == null && codecOutputMediaFormatChanged) {
// If the codec's output MediaFormat has changed then there should be a corresponding Format
// change, which we've not found. Check the Format queue in case the corresponding
// presentation timestamp is greater than presentationTimeUs, which can happen for some codecs
// [Internal ref: b/162719047].
format = formatQueue.pollFirst();
@Nullable Format format = outputStreamInfo.formatQueue.pollFloor(presentationTimeUs);
if (format == null
&& needToNotifyOutputFormatChangeAfterStreamChange
&& codecOutputMediaFormat != null) {
// After a stream change or after the initial start, there should be an input format change,
// which we've not found. Check the Format queue in case the corresponding presentation
// 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) {
outputFormat = format;
Expand All @@ -610,6 +611,7 @@ protected final void updateOutputFormatForTime(long presentationTimeUs)
if (outputFormatChanged || (codecOutputMediaFormatChanged && outputFormat != null)) {
onOutputFormatChanged(outputFormat, codecOutputMediaFormat);
codecOutputMediaFormatChanged = false;
needToNotifyOutputFormatChangeAfterStreamChange = false;
}
}

Expand Down Expand Up @@ -666,10 +668,10 @@ protected void onPositionReset(long positionUs, boolean joining) throws ExoPlayb
// 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
// input format after the position reset.
if (formatQueue.size() > 0) {
if (outputStreamInfo.formatQueue.size() > 0) {
waitingForFirstSampleInFormat = true;
}
formatQueue.clear();
outputStreamInfo.formatQueue.clear();
pendingOutputStreamChanges.clear();
}

Expand Down Expand Up @@ -1331,7 +1333,11 @@ private boolean feedInputBuffer() throws ExoPlaybackException {
decodeOnlyPresentationTimestamps.add(presentationTimeUs);
}
if (waitingForFirstSampleInFormat) {
formatQueue.add(presentationTimeUs, inputFormat);
if (!pendingOutputStreamChanges.isEmpty()) {
pendingOutputStreamChanges.peekLast().formatQueue.add(presentationTimeUs, inputFormat);
} else {
outputStreamInfo.formatQueue.add(presentationTimeUs, inputFormat);
}
waitingForFirstSampleInFormat = false;
}
largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs);
Expand Down Expand Up @@ -2027,6 +2033,7 @@ protected final long getOutputStreamOffsetUs() {
private void setOutputStreamInfo(OutputStreamInfo outputStreamInfo) {
this.outputStreamInfo = outputStreamInfo;
if (outputStreamInfo.streamOffsetUs != C.TIME_UNSET) {
needToNotifyOutputFormatChangeAfterStreamChange = true;
onOutputStreamOffsetUsChanged(outputStreamInfo.streamOffsetUs);
}
}
Expand Down Expand Up @@ -2485,12 +2492,14 @@ private static final class OutputStreamInfo {
public final long previousStreamLastBufferTimeUs;
public final long startPositionUs;
public final long streamOffsetUs;
public final TimedValueQueue<Format> formatQueue;

public OutputStreamInfo(
long previousStreamLastBufferTimeUs, long startPositionUs, long streamOffsetUs) {
this.previousStreamLastBufferTimeUs = previousStreamLastBufferTimeUs;
this.startPositionUs = startPositionUs;
this.streamOffsetUs = streamOffsetUs;
this.formatQueue = new TimedValueQueue<>();
}
}

Expand Down

0 comments on commit 38639e5

Please sign in to comment.