Use ArrayDeque for pending output stream changes.

The current logic uses manual array operations to keep track of pending
changes. Modernize this code by using an ArrayDeque and a data class.
This also allows to extend the output stream information in the future.

This also fixes a bug where a position reset accidentally assigns a pending
stream offset instead of keeping the current one.

#minor-release

PiperOrigin-RevId: 511787571
This commit is contained in:
tonihei 2023-02-23 16:02:14 +00:00
parent ecf168b359
commit f042012495

View File

@ -209,10 +209,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/ */
private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000; private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000;
// Generally there is zero or one pending output stream offset. We track more offsets to allow for
// pending output streams that have fewer frames than the codec latency.
private static final int MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT = 10;
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@ -307,9 +303,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private final TimedValueQueue<Format> formatQueue; 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 long[] pendingOutputStreamStartPositionsUs; private final ArrayDeque<OutputStreamInfo> pendingOutputStreamChanges;
private final long[] pendingOutputStreamOffsetsUs;
private final long[] pendingOutputStreamSwitchTimesUs;
private final OggOpusAudioPacketizer oggOpusAudioPacketizer; private final OggOpusAudioPacketizer oggOpusAudioPacketizer;
@Nullable private Format inputFormat; @Nullable private Format inputFormat;
@ -365,9 +359,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
private boolean pendingOutputEndOfStream; private boolean pendingOutputEndOfStream;
@Nullable private ExoPlaybackException pendingPlaybackException; @Nullable private ExoPlaybackException pendingPlaybackException;
protected DecoderCounters decoderCounters; protected DecoderCounters decoderCounters;
private long outputStreamStartPositionUs; private OutputStreamInfo outputStreamInfo;
private long outputStreamOffsetUs;
private int pendingOutputStreamOffsetCount;
/** /**
* @param trackType The {@link C.TrackType track type} that the renderer handles. * @param trackType The {@link C.TrackType track type} that the renderer handles.
@ -400,11 +392,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
currentPlaybackSpeed = 1f; currentPlaybackSpeed = 1f;
targetPlaybackSpeed = 1f; targetPlaybackSpeed = 1f;
renderTimeLimitMs = C.TIME_UNSET; renderTimeLimitMs = C.TIME_UNSET;
pendingOutputStreamStartPositionsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; pendingOutputStreamChanges = new ArrayDeque<>();
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT]; setOutputStreamInfo(OutputStreamInfo.UNSET);
pendingOutputStreamSwitchTimesUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
outputStreamStartPositionUs = C.TIME_UNSET;
setOutputStreamOffsetUs(C.TIME_UNSET);
// MediaCodec outputs audio buffers in native endian: // MediaCodec outputs audio buffers in native endian:
// https://developer.android.com/reference/android/media/MediaCodec#raw-audio-buffers // https://developer.android.com/reference/android/media/MediaCodec#raw-audio-buffers
// and code called from MediaCodecAudioRenderer.processOutputBuffer expects this endianness. // and code called from MediaCodecAudioRenderer.processOutputBuffer expects this endianness.
@ -651,23 +640,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs)
throws ExoPlaybackException { throws ExoPlaybackException {
if (this.outputStreamOffsetUs == C.TIME_UNSET) { if (outputStreamInfo.streamOffsetUs == C.TIME_UNSET) {
checkState(this.outputStreamStartPositionUs == C.TIME_UNSET); checkState(outputStreamInfo.startPositionUs == C.TIME_UNSET);
this.outputStreamStartPositionUs = startPositionUs; setOutputStreamInfo(
setOutputStreamOffsetUs(offsetUs); new OutputStreamInfo(
/* previousStreamLastBufferTimeUs= */ C.TIME_UNSET, startPositionUs, offsetUs));
} else { } else {
if (pendingOutputStreamOffsetCount == pendingOutputStreamOffsetsUs.length) { pendingOutputStreamChanges.add(
Log.w( new OutputStreamInfo(largestQueuedPresentationTimeUs, startPositionUs, offsetUs));
TAG,
"Too many stream changes, so dropping offset: "
+ pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]);
} else {
pendingOutputStreamOffsetCount++;
}
pendingOutputStreamStartPositionsUs[pendingOutputStreamOffsetCount - 1] = startPositionUs;
pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1] = offsetUs;
pendingOutputStreamSwitchTimesUs[pendingOutputStreamOffsetCount - 1] =
largestQueuedPresentationTimeUs;
} }
} }
@ -690,12 +670,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
waitingForFirstSampleInFormat = true; waitingForFirstSampleInFormat = true;
} }
formatQueue.clear(); formatQueue.clear();
if (pendingOutputStreamOffsetCount != 0) { pendingOutputStreamChanges.clear();
setOutputStreamOffsetUs(pendingOutputStreamOffsetsUs[pendingOutputStreamOffsetCount - 1]);
outputStreamStartPositionUs =
pendingOutputStreamStartPositionsUs[pendingOutputStreamOffsetCount - 1];
pendingOutputStreamOffsetCount = 0;
}
} }
@Override @Override
@ -709,9 +684,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
@Override @Override
protected void onDisabled() { protected void onDisabled() {
inputFormat = null; inputFormat = null;
outputStreamStartPositionUs = C.TIME_UNSET; setOutputStreamInfo(OutputStreamInfo.UNSET);
setOutputStreamOffsetUs(C.TIME_UNSET); pendingOutputStreamChanges.clear();
pendingOutputStreamOffsetCount = 0;
flushOrReleaseCodec(); flushOrReleaseCodec();
} }
@ -1614,29 +1588,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*/ */
@CallSuper @CallSuper
protected void onProcessedOutputBuffer(long presentationTimeUs) { protected void onProcessedOutputBuffer(long presentationTimeUs) {
while (pendingOutputStreamOffsetCount != 0 if (!pendingOutputStreamChanges.isEmpty()
&& presentationTimeUs >= pendingOutputStreamSwitchTimesUs[0]) { && presentationTimeUs >= pendingOutputStreamChanges.peek().previousStreamLastBufferTimeUs) {
outputStreamStartPositionUs = pendingOutputStreamStartPositionsUs[0]; setOutputStreamInfo(pendingOutputStreamChanges.poll());
setOutputStreamOffsetUs(pendingOutputStreamOffsetsUs[0]);
pendingOutputStreamOffsetCount--;
System.arraycopy(
pendingOutputStreamStartPositionsUs,
/* srcPos= */ 1,
pendingOutputStreamStartPositionsUs,
/* destPos= */ 0,
pendingOutputStreamOffsetCount);
System.arraycopy(
pendingOutputStreamOffsetsUs,
/* srcPos= */ 1,
pendingOutputStreamOffsetsUs,
/* destPos= */ 0,
pendingOutputStreamOffsetCount);
System.arraycopy(
pendingOutputStreamSwitchTimesUs,
/* srcPos= */ 1,
pendingOutputStreamSwitchTimesUs,
/* destPos= */ 0,
pendingOutputStreamOffsetCount);
onProcessedStreamChange(); onProcessedStreamChange();
} }
} }
@ -2083,13 +2037,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* boolean, Format)} to get the playback position with respect to the media. * boolean, Format)} to get the playback position with respect to the media.
*/ */
protected final long getOutputStreamOffsetUs() { protected final long getOutputStreamOffsetUs() {
return outputStreamOffsetUs; return outputStreamInfo.streamOffsetUs;
} }
private void setOutputStreamOffsetUs(long outputStreamOffsetUs) { private void setOutputStreamInfo(OutputStreamInfo outputStreamInfo) {
this.outputStreamOffsetUs = outputStreamOffsetUs; this.outputStreamInfo = outputStreamInfo;
if (outputStreamOffsetUs != C.TIME_UNSET) { if (outputStreamInfo.streamOffsetUs != C.TIME_UNSET) {
onOutputStreamOffsetUsChanged(outputStreamOffsetUs); onOutputStreamOffsetUsChanged(outputStreamInfo.streamOffsetUs);
} }
} }
@ -2543,6 +2497,26 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
&& "OMX.MTK.AUDIO.DECODER.MP3".equals(name); && "OMX.MTK.AUDIO.DECODER.MP3".equals(name);
} }
private static final class OutputStreamInfo {
public static final OutputStreamInfo UNSET =
new OutputStreamInfo(
/* previousStreamLastBufferTimeUs= */ C.TIME_UNSET,
/* startPositionUs= */ C.TIME_UNSET,
/* streamOffsetUs= */ C.TIME_UNSET);
public final long previousStreamLastBufferTimeUs;
public final long startPositionUs;
public final long streamOffsetUs;
public OutputStreamInfo(
long previousStreamLastBufferTimeUs, long startPositionUs, long streamOffsetUs) {
this.previousStreamLastBufferTimeUs = previousStreamLastBufferTimeUs;
this.startPositionUs = startPositionUs;
this.streamOffsetUs = streamOffsetUs;
}
}
@RequiresApi(31) @RequiresApi(31)
private static final class Api31 { private static final class Api31 {
private Api31() {} private Api31() {}