diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 94d173da4e..6c0d929ca5 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -48,6 +48,12 @@ * DRM: * Fix playback failure when switching from PlayReady protected content to Widevine or Clearkey protected content in a playlist. +* Analytics: + * Pass a `DecoderReuseEvaluation` to `AnalyticsListener`'s + `onVideoInputFormatChanged` and `onAudioInputFormatChanged` methods. The + `DecoderReuseEvaluation` indicates whether it was possible to re-use an + existing decoder instance for the new format, and if not then the + reasons why. * IMA extension: * Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader` ([#7344](https://github.com/google/ExoPlayer/issues/7344)). diff --git a/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java b/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java index 7c558d24b2..a90ec1307c 100644 --- a/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java +++ b/extensions/av1/src/main/java/com/google/android/exoplayer2/ext/av1/Libgav1VideoRenderer.java @@ -15,12 +15,15 @@ */ package com.google.android.exoplayer2.ext.av1; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; + import android.os.Handler; import android.view.Surface; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; @@ -164,7 +167,13 @@ public class Libgav1VideoRenderer extends DecoderVideoRenderer { } @Override - protected boolean canKeepCodec(Format oldFormat, Format newFormat) { - return true; + protected DecoderReuseEvaluation canReuseDecoder( + String decoderName, Format oldFormat, Format newFormat) { + return new DecoderReuseEvaluation( + decoderName, + oldFormat, + newFormat, + REUSE_RESULT_YES_WITHOUT_RECONFIGURATION, + /* discardReasons= */ 0); } } diff --git a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java index d2f2fce639..19871898cb 100644 --- a/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java +++ b/extensions/ffmpeg/src/main/java/com/google/android/exoplayer2/ext/ffmpeg/FfmpegVideoRenderer.java @@ -15,6 +15,10 @@ */ package com.google.android.exoplayer2.ext.ffmpeg; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; + import android.os.Handler; import android.view.Surface; import androidx.annotation.Nullable; @@ -22,6 +26,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.decoder.Decoder; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; @@ -116,7 +121,15 @@ public final class FfmpegVideoRenderer extends DecoderVideoRenderer { } @Override - protected boolean canKeepCodec(Format oldFormat, Format newFormat) { - return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType); + protected DecoderReuseEvaluation canReuseDecoder( + String decoderName, Format oldFormat, Format newFormat) { + boolean sameMimeType = Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType); + // TODO: Ability to reuse the decoder may be MIME type dependent. + return new DecoderReuseEvaluation( + decoderName, + oldFormat, + newFormat, + sameMimeType ? REUSE_RESULT_YES_WITHOUT_RECONFIGURATION : REUSE_RESULT_NO, + sameMimeType ? 0 : DISCARD_REASON_MIME_TYPE_CHANGED); } } diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java index 61ebc8b0d9..2e17036fce 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/LibvpxVideoRenderer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.ext.vp9; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; import static java.lang.Runtime.getRuntime; import android.os.Handler; @@ -23,6 +24,7 @@ import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.TraceUtil; @@ -169,7 +171,13 @@ public class LibvpxVideoRenderer extends DecoderVideoRenderer { } @Override - protected boolean canKeepCodec(Format oldFormat, Format newFormat) { - return true; + protected DecoderReuseEvaluation canReuseDecoder( + String decoderName, Format oldFormat, Format newFormat) { + return new DecoderReuseEvaluation( + decoderName, + oldFormat, + newFormat, + REUSE_RESULT_YES_WITHOUT_RECONFIGURATION, + /* discardReasons= */ 0); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index c09a43feb6..e82e0dcba8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -36,6 +36,7 @@ import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.device.DeviceInfo; import com.google.android.exoplayer2.device.DeviceListener; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; @@ -2242,10 +2243,11 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public void onVideoInputFormatChanged(Format format) { + public void onVideoInputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { videoFormat = format; for (VideoRendererEventListener videoDebugListener : videoDebugListeners) { - videoDebugListener.onVideoInputFormatChanged(format); + videoDebugListener.onVideoInputFormatChanged(format, decoderReuseEvaluation); } } @@ -2337,10 +2339,11 @@ public class SimpleExoPlayer extends BasePlayer } @Override - public void onAudioInputFormatChanged(Format format) { + public void onAudioInputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { audioFormat = format; for (AudioRendererEventListener audioDebugListener : audioDebugListeners) { - audioDebugListener.onAudioInputFormatChanged(format); + audioDebugListener.onAudioInputFormatChanged(format, decoderReuseEvaluation); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java index b19ed00276..a11953421b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsCollector.java @@ -34,6 +34,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.drm.DrmSessionEventListener; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.MetadataOutput; @@ -195,11 +196,12 @@ public class AnalyticsCollector @SuppressWarnings("deprecation") @Override - public final void onAudioInputFormatChanged(Format format) { + public final void onAudioInputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { EventTime eventTime = generateReadingMediaPeriodEventTime(); listeners.sendEvent( listener -> { - listener.onAudioInputFormatChanged(eventTime, format); + listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation); listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format); }); } @@ -298,11 +300,12 @@ public class AnalyticsCollector @SuppressWarnings("deprecation") @Override - public final void onVideoInputFormatChanged(Format format) { + public final void onVideoInputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { EventTime eventTime = generateReadingMediaPeriodEventTime(); listeners.sendEvent( listener -> { - listener.onVideoInputFormatChanged(eventTime, format); + listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation); listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format); }); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java index 26e93cc982..87c6308990 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/analytics/AnalyticsListener.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.MediaLoadData; @@ -452,8 +453,8 @@ public interface AnalyticsListener { EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {} /** - * @deprecated Use {@link #onAudioInputFormatChanged} and {@link #onVideoInputFormatChanged} - * instead. + * @deprecated Use {@link #onAudioInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)} + * and {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}. instead. */ @Deprecated default void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {} @@ -482,13 +483,26 @@ public interface AnalyticsListener { default void onAudioDecoderInitialized( EventTime eventTime, String decoderName, long initializationDurationMs) {} + /** + * @deprecated Use {@link #onAudioInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}. + */ + @Deprecated + default void onAudioInputFormatChanged(EventTime eventTime, Format format) {} + /** * Called when the format of the media being consumed by an audio renderer changes. * * @param eventTime The event time. * @param format The new format. + * @param decoderReuseEvaluation The result of the evaluation to determine whether an existing + * decoder instance can be reused for the new format, or {@code null} if the renderer did not + * have a decoder. */ - default void onAudioInputFormatChanged(EventTime eventTime, Format format) {} + @SuppressWarnings("deprecation") + default void onAudioInputFormatChanged( + EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { + onAudioInputFormatChanged(eventTime, format); + } /** * Called when the audio position has increased for the first time since the last pause or @@ -589,13 +603,26 @@ public interface AnalyticsListener { default void onVideoDecoderInitialized( EventTime eventTime, String decoderName, long initializationDurationMs) {} + /** + * @deprecated Use {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}. + */ + @Deprecated + default void onVideoInputFormatChanged(EventTime eventTime, Format format) {} + /** * Called when the format of the media being consumed by a video renderer changes. * * @param eventTime The event time. * @param format The new format. + * @param decoderReuseEvaluation The result of the evaluation to determine whether an existing + * decoder instance can be reused for the new format, or {@code null} if the renderer did not + * have a decoder. */ - default void onVideoInputFormatChanged(EventTime eventTime, Format format) {} + @SuppressWarnings("deprecation") + default void onVideoInputFormatChanged( + EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { + onVideoInputFormatChanged(eventTime, format); + } /** * Called after video frames have been dropped. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java index 31c3b1f0b3..48d0e03144 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioRendererEventListener.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.decoder.DecoderCounters; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.util.Assertions; /** @@ -61,12 +62,23 @@ public interface AudioRendererEventListener { default void onAudioDecoderInitialized( String decoderName, long initializedTimestampMs, long initializationDurationMs) {} + /** @deprecated Use {@link #onAudioInputFormatChanged(Format, DecoderReuseEvaluation)}. */ + @Deprecated + default void onAudioInputFormatChanged(Format format) {} + /** * Called when the format of the media being consumed by the renderer changes. * * @param format The new format. + * @param decoderReuseEvaluation The result of the evaluation to determine whether an existing + * decoder instance can be reused for the new format, or {@code null} if the renderer did not + * have a decoder. */ - default void onAudioInputFormatChanged(Format format) {} + @SuppressWarnings("deprecation") + default void onAudioInputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { + onAudioInputFormatChanged(format); + } /** * Called when the audio position has increased for the first time since the last pause or @@ -167,9 +179,11 @@ public interface AudioRendererEventListener { } /** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */ - public void inputFormatChanged(Format format) { + public void inputFormatChanged( + Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) { if (handler != null) { - handler.post(() -> castNonNull(listener).onAudioInputFormatChanged(format)); + handler.post( + () -> castNonNull(listener).onAudioInputFormatChanged(format, decoderReuseEvaluation)); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java index 5495c3d97b..7ecf58f0b2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java @@ -15,6 +15,9 @@ */ package com.google.android.exoplayer2.audio; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; import static java.lang.Math.max; import android.media.audiofx.Virtualizer; @@ -38,6 +41,7 @@ import com.google.android.exoplayer2.decoder.Decoder; import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderException; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; @@ -347,14 +351,19 @@ public abstract class DecoderAudioRenderer< protected abstract Format getOutputFormat(T decoder); /** - * Returns whether the existing decoder can be kept for a new format. + * Evaluates whether the existing decoder can be reused for a new {@link Format}. * + *
The default implementation does not allow decoder reuse. + * + * @param decoderName The name of the decoder. * @param oldFormat The previous format. * @param newFormat The new format. - * @return Whether the existing decoder can be kept. + * @return The result of the evaluation. */ - protected boolean canKeepCodec(Format oldFormat, Format newFormat) { - return false; + protected DecoderReuseEvaluation canReuseDecoder( + String decoderName, Format oldFormat, Format newFormat) { + return new DecoderReuseEvaluation( + decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED); } private boolean drainOutputBuffer() @@ -655,10 +664,29 @@ public abstract class DecoderAudioRenderer< setSourceDrmSession(formatHolder.drmSession); Format oldFormat = inputFormat; inputFormat = newFormat; + encoderDelay = newFormat.encoderDelay; + encoderPadding = newFormat.encoderPadding; if (decoder == null) { maybeInitDecoder(); - } else if (sourceDrmSession != decoderDrmSession || !canKeepCodec(oldFormat, inputFormat)) { + eventDispatcher.inputFormatChanged(inputFormat, /* decoderReuseEvaluation= */ null); + return; + } + + DecoderReuseEvaluation evaluation; + if (sourceDrmSession != decoderDrmSession) { + evaluation = + new DecoderReuseEvaluation( + decoder.getName(), + oldFormat, + newFormat, + REUSE_RESULT_NO, + DISCARD_REASON_DRM_SESSION_CHANGED); + } else { + evaluation = canReuseDecoder(decoder.getName(), oldFormat, newFormat); + } + + if (evaluation.result == REUSE_RESULT_NO) { if (decoderReceivedBuffers) { // Signal end of stream and wait for any final output buffers before re-initialization. decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; @@ -669,10 +697,7 @@ public abstract class DecoderAudioRenderer< audioTrackNeedsConfigure = true; } } - - encoderDelay = inputFormat.encoderDelay; - encoderPadding = inputFormat.encoderPadding; - eventDispatcher.inputFormatChanged(inputFormat); + eventDispatcher.inputFormatChanged(inputFormat, evaluation); } private void onQueueInputBuffer(DecoderInputBuffer buffer) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 675516cb03..19988cd54b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -15,7 +15,8 @@ */ package com.google.android.exoplayer2.audio; -import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static java.lang.Math.max; @@ -41,9 +42,10 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispa import com.google.android.exoplayer2.audio.AudioSink.InitializationException; import com.google.android.exoplayer2.audio.AudioSink.WriteException; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons; import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; -import com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult; import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; @@ -328,13 +330,21 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - @KeepCodecResult - protected int canKeepCodec( - MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { + protected DecoderReuseEvaluation canReuseCodec( + MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { + DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat); + + @DecoderDiscardReasons int discardReasons = evaluation.discardReasons; if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) { - return KEEP_CODEC_RESULT_NO; + discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED; } - return codecInfo.canKeepCodec(oldFormat, newFormat); + + return new DecoderReuseEvaluation( + codecInfo.name, + oldFormat, + newFormat, + discardReasons != 0 ? REUSE_RESULT_NO : evaluation.result, + discardReasons); } @Override @@ -370,9 +380,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException { - super.onInputFormatChanged(formatHolder); - eventDispatcher.inputFormatChanged(formatHolder.format); + @Nullable + protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder) + throws ExoPlaybackException { + @Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder); + eventDispatcher.inputFormatChanged(formatHolder.format, evaluation); + return evaluation; } @Override @@ -664,7 +677,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media return maxInputSize; } for (Format streamFormat : streamFormats) { - if (codecInfo.canKeepCodec(format, streamFormat) != KEEP_CODEC_RESULT_NO) { + if (codecInfo.canReuseCodec(format, streamFormat).result != REUSE_RESULT_NO) { maxInputSize = max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat)); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderReuseEvaluation.java b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderReuseEvaluation.java new file mode 100644 index 0000000000..a23c92ec65 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/decoder/DecoderReuseEvaluation.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.decoder; + +import static com.google.android.exoplayer2.util.Assertions.checkArgument; +import static com.google.android.exoplayer2.util.Assertions.checkNotEmpty; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; + +import androidx.annotation.IntDef; +import androidx.annotation.Nullable; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.video.ColorInfo; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The result of an evaluation to determine whether a decoder can be reused for a new input format. + */ +public final class DecoderReuseEvaluation { + + /** Possible outcomes of the evaluation. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + REUSE_RESULT_NO, + REUSE_RESULT_YES_WITH_FLUSH, + REUSE_RESULT_YES_WITH_RECONFIGURATION, + REUSE_RESULT_YES_WITHOUT_RECONFIGURATION + }) + public @interface DecoderReuseResult {} + /** The decoder cannot be reused. */ + public static final int REUSE_RESULT_NO = 0; + /** The decoder can be reused, but must be flushed. */ + public static final int REUSE_RESULT_YES_WITH_FLUSH = 1; + /** + * The decoder can be reused. It does not need to be flushed, but must be reconfigured by + * prefixing the next input buffer with the new format's configuration data. + */ + public static final int REUSE_RESULT_YES_WITH_RECONFIGURATION = 2; + /** The decoder can be kept. It does not need to be flushed and no reconfiguration is required. */ + public static final int REUSE_RESULT_YES_WITHOUT_RECONFIGURATION = 3; + + /** Possible reasons why reuse is not possible. */ + @Documented + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = { + DISCARD_REASON_REUSE_NOT_IMPLEMENTED, + DISCARD_REASON_WORKAROUND, + DISCARD_REASON_APP_OVERRIDE, + DISCARD_REASON_MIME_TYPE_CHANGED, + DISCARD_REASON_OPERATING_RATE_CHANGED, + DISCARD_REASON_INITIALIZATION_DATA_CHANGED, + DISCARD_REASON_DRM_SESSION_CHANGED, + DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED, + DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED, + DISCARD_REASON_VIDEO_RESOLUTION_CHANGED, + DISCARD_REASON_VIDEO_ROTATION_CHANGED, + DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED, + DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED, + DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED, + DISCARD_REASON_AUDIO_ENCODING_CHANGED + }) + public @interface DecoderDiscardReasons {} + + /** Decoder reuse is not implemented. */ + public static final int DISCARD_REASON_REUSE_NOT_IMPLEMENTED = 1 << 0; + /** Decoder reuse is disabled by a workaround. */ + public static final int DISCARD_REASON_WORKAROUND = 1 << 1; + /** Decoder reuse is disabled by overriding behavior in application code. */ + public static final int DISCARD_REASON_APP_OVERRIDE = 1 << 2; + /** The sample MIME type is changing. */ + public static final int DISCARD_REASON_MIME_TYPE_CHANGED = 1 << 3; + /** The codec's operating rate is changing. */ + public static final int DISCARD_REASON_OPERATING_RATE_CHANGED = 1 << 4; + /** The format initialization data is changing. */ + public static final int DISCARD_REASON_INITIALIZATION_DATA_CHANGED = 1 << 5; + /** The new format may exceed the decoder's configured maximum sample size, in bytes. */ + public static final int DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED = 1 << 6; + /** The DRM session is changing. */ + public static final int DISCARD_REASON_DRM_SESSION_CHANGED = 1 << 7; + /** The new format may exceed the decoder's configured maximum resolution. */ + public static final int DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED = 1 << 8; + /** The video resolution is changing. */ + public static final int DISCARD_REASON_VIDEO_RESOLUTION_CHANGED = 1 << 9; + /** The video rotation is changing. */ + public static final int DISCARD_REASON_VIDEO_ROTATION_CHANGED = 1 << 10; + /** The video {@link ColorInfo} is changing. */ + public static final int DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED = 1 << 11; + /** The audio channel count is changing. */ + public static final int DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED = 1 << 12; + /** The audio sample rate is changing. */ + public static final int DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED = 1 << 13; + /** The audio encoding is changing. */ + public static final int DISCARD_REASON_AUDIO_ENCODING_CHANGED = 1 << 14; + + /** The name of the decoder. */ + public final String decoderName; + + /** The {@link Format} for which the decoder was previously configured. */ + public final Format oldFormat; + + /** The new {@link Format} being evaluated. */ + public final Format newFormat; + + /** The {@link DecoderReuseResult result} of the evaluation. */ + @DecoderReuseResult public final int result; + + /** + * {@link DecoderDiscardReasons Reasons} why the decoder cannot be reused. Always {@code 0} if + * reuse is possible. May also be {code 0} if reuse is not possible for an unspecified reason. + */ + @DecoderDiscardReasons public final int discardReasons; + + /** + * @param decoderName The name of the decoder. + * @param oldFormat The {@link Format} for which the decoder was previously configured. + * @param newFormat The new {@link Format} being evaluated. + * @param result The {@link DecoderReuseResult result} of the evaluation. + * @param discardReasons One or more {@link DecoderDiscardReasons reasons} why the decoder cannot + * be reused, or {@code 0} if reuse is possible. + */ + public DecoderReuseEvaluation( + String decoderName, + Format oldFormat, + Format newFormat, + @DecoderReuseResult int result, + @DecoderDiscardReasons int discardReasons) { + checkArgument(result == REUSE_RESULT_NO || discardReasons == 0); + this.decoderName = checkNotEmpty(decoderName); + this.oldFormat = checkNotNull(oldFormat); + this.newFormat = checkNotNull(newFormat); + this.result = result; + this.discardReasons = discardReasons; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + DecoderReuseEvaluation other = (DecoderReuseEvaluation) obj; + return result == other.result + && discardReasons == other.discardReasons + && decoderName.equals(other.decoderName) + && oldFormat.equals(other.oldFormat) + && newFormat.equals(other.newFormat); + } + + @Override + public int hashCode() { + int hashCode = 17; + hashCode = 31 * hashCode + result; + hashCode = 31 * hashCode + discardReasons; + hashCode = 31 * hashCode + decoderName.hashCode(); + hashCode = 31 * hashCode + oldFormat.hashCode(); + hashCode = 31 * hashCode + newFormat.hashCode(); + return hashCode; + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java index cad6cc72f7..1b35fc9887 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java @@ -15,6 +15,20 @@ */ package com.google.android.exoplayer2.mediacodec; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_ENCODING_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_INITIALIZATION_DATA_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_RESOLUTION_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_ROTATION_CHANGED; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_WORKAROUND; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH; +import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION; + import android.graphics.Point; import android.media.MediaCodec; import android.media.MediaCodecInfo.AudioCapabilities; @@ -22,18 +36,17 @@ import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCodecInfo.VideoCapabilities; import android.util.Pair; -import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons; +import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderReuseResult; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; /** Information about a {@link MediaCodec} for a given mime type. */ @SuppressWarnings("InlinedApi") @@ -47,28 +60,6 @@ public final class MediaCodecInfo { */ public static final int MAX_SUPPORTED_INSTANCES_UNKNOWN = -1; - /** The possible return values for {@link #canKeepCodec(Format, Format)}. */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - KEEP_CODEC_RESULT_NO, - KEEP_CODEC_RESULT_YES_WITH_FLUSH, - KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION, - KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION - }) - public @interface KeepCodecResult {} - /** The codec cannot be kept. */ - public static final int KEEP_CODEC_RESULT_NO = 0; - /** The codec can be kept, but must be flushed. */ - public static final int KEEP_CODEC_RESULT_YES_WITH_FLUSH = 1; - /** - * The codec can be kept. It does not need to be flushed, but must be reconfigured by prefixing - * the next input buffer with the new format's configuration data. - */ - public static final int KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION = 2; - /** The codec can be kept. It does not need to be flushed and no reconfiguration is required. */ - public static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 3; - /** * The name of the decoder. * @@ -361,7 +352,7 @@ public final class MediaCodecInfo { * @param isNewFormatComplete Whether {@code newFormat} is populated with format-specific * metadata. * @return Whether it is possible to adapt the decoder seamlessly. - * @deprecated Use {@link #canKeepCodec}. + * @deprecated Use {@link #canReuseCodec}. */ @Deprecated public boolean isSeamlessAdaptationSupported( @@ -369,49 +360,68 @@ public final class MediaCodecInfo { if (!isNewFormatComplete && oldFormat.colorInfo != null && newFormat.colorInfo == null) { newFormat = newFormat.buildUpon().setColorInfo(oldFormat.colorInfo).build(); } - @KeepCodecResult int keepCodecResult = canKeepCodec(oldFormat, newFormat); - return keepCodecResult == KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION - || keepCodecResult == KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION; + @DecoderReuseResult int reuseResult = canReuseCodec(oldFormat, newFormat).result; + return reuseResult == REUSE_RESULT_YES_WITH_RECONFIGURATION + || reuseResult == REUSE_RESULT_YES_WITHOUT_RECONFIGURATION; } /** - * Returns the extent to which it's possible to adapt an instance of this decoder that's currently - * decoding {@code oldFormat} to decode {@code newFormat} instead. + * Evaluates whether it's possible to reuse an instance of this decoder that's currently decoding + * {@code oldFormat} to decode {@code newFormat} instead. * *
For adaptation to succeed, the codec must also be configured with maximum values that are
* compatible with the new format.
*
* @param oldFormat The format being decoded.
* @param newFormat The new format.
- * @return The extent to which it's possible to adapt an instance of the decoder.
+ * @return The result of the evaluation.
*/
- @KeepCodecResult
- public int canKeepCodec(Format oldFormat, Format newFormat) {
+ public DecoderReuseEvaluation canReuseCodec(Format oldFormat, Format newFormat) {
+ @DecoderDiscardReasons int discardReasons = 0;
if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) {
- return KEEP_CODEC_RESULT_NO;
+ discardReasons |= DISCARD_REASON_MIME_TYPE_CHANGED;
}
if (isVideo) {
- if (oldFormat.rotationDegrees == newFormat.rotationDegrees
- && (adaptive
- || (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height))
- && Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)) {
- if (oldFormat.initializationDataEquals(newFormat)) {
- return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
- } else if (!needsAdaptationReconfigureWorkaround(name)) {
- return KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
- }
+ if (oldFormat.rotationDegrees != newFormat.rotationDegrees) {
+ discardReasons |= DISCARD_REASON_VIDEO_ROTATION_CHANGED;
+ }
+ if (!adaptive
+ && (oldFormat.width != newFormat.width || oldFormat.height != newFormat.height)) {
+ discardReasons |= DISCARD_REASON_VIDEO_RESOLUTION_CHANGED;
+ }
+ if (!Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)) {
+ discardReasons |= DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED;
+ }
+ if (needsAdaptationReconfigureWorkaround(name)
+ && !oldFormat.initializationDataEquals(newFormat)) {
+ discardReasons |= DISCARD_REASON_WORKAROUND;
+ }
+
+ if (discardReasons == 0) {
+ return new DecoderReuseEvaluation(
+ name,
+ oldFormat,
+ newFormat,
+ oldFormat.initializationDataEquals(newFormat)
+ ? REUSE_RESULT_YES_WITHOUT_RECONFIGURATION
+ : REUSE_RESULT_YES_WITH_RECONFIGURATION,
+ /* discardReasons= */ 0);
}
} else {
- if (oldFormat.channelCount != newFormat.channelCount
- || oldFormat.sampleRate != newFormat.sampleRate
- || oldFormat.pcmEncoding != newFormat.pcmEncoding) {
- return KEEP_CODEC_RESULT_NO;
+ if (oldFormat.channelCount != newFormat.channelCount) {
+ discardReasons |= DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
+ }
+ if (oldFormat.sampleRate != newFormat.sampleRate) {
+ discardReasons |= DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED;
+ }
+ if (oldFormat.pcmEncoding != newFormat.pcmEncoding) {
+ discardReasons |= DISCARD_REASON_AUDIO_ENCODING_CHANGED;
}
// Check whether we're adapting between two xHE-AAC formats, for which adaptation is possible
// without reconfiguration or flushing.
- if (MimeTypes.AUDIO_AAC.equals(mimeType)) {
+ if (discardReasons == 0 && MimeTypes.AUDIO_AAC.equals(mimeType)) {
@Nullable
Pair The default implementation returns {@link MediaCodecInfo#KEEP_CODEC_RESULT_NO}.
+ * The default implementation does not allow decoder reuse.
*
- * @param codec The existing {@link MediaCodecAdapter} instance.
* @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param oldFormat The {@link Format} for which the existing instance is configured.
* @param newFormat The new {@link Format}.
- * @return Whether the instance can be kept, and if it can whether it requires reconfiguration.
+ * @return The result of the evaluation.
*/
- @KeepCodecResult
- protected int canKeepCodec(
- MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
- return KEEP_CODEC_RESULT_NO;
+ protected DecoderReuseEvaluation canReuseCodec(
+ MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
+ return new DecoderReuseEvaluation(
+ codecInfo.name,
+ oldFormat,
+ newFormat,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
}
@Override
@@ -1608,25 +1640,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
*
* @param format The {@link Format} for which the operating rate should be configured.
* @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
- * @return Whether codec release and re-initialization was triggered, rather than the existing
- * codec being updated.
+ * @return False if codec release and re-initialization was triggered. True in all other cases.
*/
- private boolean updateOperatingRateOrReinitializeCodec(Format format)
- throws ExoPlaybackException {
+ private boolean updateCodecOperatingRate(Format format) throws ExoPlaybackException {
if (Util.SDK_INT < 23) {
- return false;
+ return true;
}
float newCodecOperatingRate =
getCodecOperatingRateV23(playbackSpeed, format, getStreamFormats());
if (codecOperatingRate == newCodecOperatingRate) {
// No change.
- return false;
+ return true;
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
// The only way to clear the operating rate is to instantiate a new codec instance. See
// [Internal ref: b/111543954].
drainAndReinitializeCodec();
- return true;
+ return false;
} else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET
|| newCodecOperatingRate > assumedMinimumCodecOperatingRate) {
// We need to set the operating rate, either because we've set it previously or because it's
@@ -1635,36 +1665,48 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, newCodecOperatingRate);
codec.setParameters(codecParameters);
codecOperatingRate = newCodecOperatingRate;
- return false;
+ return true;
}
- return false;
+ return true;
}
- /** Starts draining the codec for flush. */
- private void drainAndFlushCodec() {
+ /**
+ * Starts draining the codec for a flush, or to release and re-initialize the codec if flushing
+ * will not be possible. If no buffers have been queued to the codec then this method is a no-op.
+ *
+ * @return False if codec release and re-initialization was triggered due to the need to apply a
+ * flush workaround. True in all other cases.
+ */
+ private boolean drainAndFlushCodec() {
if (codecReceivedBuffers) {
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
+ return false;
} else {
codecDrainAction = DRAIN_ACTION_FLUSH;
}
}
+ return true;
}
/**
- * Starts draining the codec to update its DRM session. The update may occur immediately if no
- * buffers have been queued to the codec.
+ * Starts draining the codec to flush it and update its DRM session, or to release and
+ * re-initialize the codec if flushing will not be possible. If no buffers have been queued to the
+ * codec then this method updates the DRM session immediately without flushing the codec.
*
* @throws ExoPlaybackException If an error occurs updating the codec's DRM session.
+ * @return False if codec release and re-initialization was triggered due to the need to apply a
+ * flush workaround. True in all other cases.
*/
@TargetApi(23) // Only called when SDK_INT >= 23, but lint isn't clever enough to know.
- private void drainAndUpdateCodecDrmSessionV23() throws ExoPlaybackException {
+ private boolean drainAndUpdateCodecDrmSessionV23() throws ExoPlaybackException {
if (codecReceivedBuffers) {
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
codecDrainAction = DRAIN_ACTION_REINITIALIZE;
+ return false;
} else {
codecDrainAction = DRAIN_ACTION_FLUSH_AND_UPDATE_DRM_SESSION;
}
@@ -1672,6 +1714,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// Nothing has been queued to the decoder, so we can do the update immediately.
updateDrmSessionV23();
}
+ return true;
}
/**
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
index 32fc0be8ed..9225b575e6 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/util/EventLogger.java
@@ -34,6 +34,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.decoder.DecoderCounters;
+import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData;
@@ -328,7 +329,8 @@ public class EventLogger implements AnalyticsListener {
}
@Override
- public void onAudioInputFormatChanged(EventTime eventTime, Format format) {
+ public void onAudioInputFormatChanged(
+ EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
logd(eventTime, "audioInputFormat", Format.toLogString(format));
}
@@ -393,7 +395,8 @@ public class EventLogger implements AnalyticsListener {
}
@Override
- public void onVideoInputFormatChanged(EventTime eventTime, Format format) {
+ public void onVideoInputFormatChanged(
+ EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
logd(eventTime, "videoInputFormat", Format.toLogString(format));
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java
index c349813bdb..08961b9783 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/video/DecoderVideoRenderer.java
@@ -15,6 +15,9 @@
*/
package com.google.android.exoplayer2.video;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
import static java.lang.Math.max;
import android.os.Handler;
@@ -34,6 +37,7 @@ import com.google.android.exoplayer2.decoder.Decoder;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
@@ -97,9 +101,12 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
private Format inputFormat;
private Format outputFormat;
+
+ @Nullable
private Decoder<
VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException>
decoder;
+
private VideoDecoderInputBuffer inputBuffer;
private VideoDecoderOutputBuffer outputBuffer;
@Nullable private Surface surface;
@@ -366,7 +373,24 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
if (decoder == null) {
maybeInitDecoder();
- } else if (sourceDrmSession != decoderDrmSession || !canKeepCodec(oldFormat, inputFormat)) {
+ eventDispatcher.inputFormatChanged(inputFormat, /* decoderReuseEvaluation= */ null);
+ return;
+ }
+
+ DecoderReuseEvaluation evaluation;
+ if (sourceDrmSession != decoderDrmSession) {
+ evaluation =
+ new DecoderReuseEvaluation(
+ decoder.getName(),
+ oldFormat,
+ newFormat,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_DRM_SESSION_CHANGED);
+ } else {
+ evaluation = canReuseDecoder(decoder.getName(), oldFormat, newFormat);
+ }
+
+ if (evaluation.result == REUSE_RESULT_NO) {
if (decoderReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization.
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
@@ -376,8 +400,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
maybeInitDecoder();
}
}
-
- eventDispatcher.inputFormatChanged(inputFormat);
+ eventDispatcher.inputFormatChanged(inputFormat, evaluation);
}
/**
@@ -626,14 +649,18 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode);
/**
- * Returns whether the existing decoder can be kept for a new format.
+ * Evaluates whether the existing decoder can be reused for a new {@link Format}.
+ *
+ * The default implementation does not allow decoder reuse.
*
* @param oldFormat The previous format.
* @param newFormat The new format.
- * @return Whether the existing decoder can be kept.
+ * @return The result of the evaluation.
*/
- protected boolean canKeepCodec(Format oldFormat, Format newFormat) {
- return false;
+ protected DecoderReuseEvaluation canReuseDecoder(
+ String decoderName, Format oldFormat, Format newFormat) {
+ return new DecoderReuseEvaluation(
+ decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
}
// Internal methods.
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
index cebcece5ae..ab9f3af00b 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java
@@ -15,7 +15,9 @@
*/
package com.google.android.exoplayer2.video;
-import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
import static java.lang.Math.max;
import static java.lang.Math.min;
@@ -46,11 +48,12 @@ import com.google.android.exoplayer2.PlayerMessage.Target;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
+import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DecoderDiscardReasons;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
-import com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
@@ -566,15 +569,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
@Override
- @KeepCodecResult
- protected int canKeepCodec(
- MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
- if (newFormat.width > codecMaxValues.width
- || newFormat.height > codecMaxValues.height
- || getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) {
- return KEEP_CODEC_RESULT_NO;
+ protected DecoderReuseEvaluation canReuseCodec(
+ MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
+ DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
+
+ @DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
+ if (newFormat.width > codecMaxValues.width || newFormat.height > codecMaxValues.height) {
+ discardReasons |= DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED;
}
- return codecInfo.canKeepCodec(oldFormat, newFormat);
+ if (getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) {
+ discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
+ }
+
+ return new DecoderReuseEvaluation(
+ codecInfo.name,
+ oldFormat,
+ newFormat,
+ discardReasons != 0 ? REUSE_RESULT_NO : evaluation.result,
+ discardReasons);
}
@CallSuper
@@ -621,9 +633,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
@Override
- protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
- super.onInputFormatChanged(formatHolder);
- eventDispatcher.inputFormatChanged(formatHolder.format);
+ @Nullable
+ protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
+ throws ExoPlaybackException {
+ @Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder);
+ eventDispatcher.inputFormatChanged(formatHolder.format, evaluation);
+ return evaluation;
}
/**
@@ -1333,7 +1348,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// from format to avoid codec re-use being ruled out for only this reason.
streamFormat = streamFormat.buildUpon().setColorInfo(format.colorInfo).build();
}
- if (codecInfo.canKeepCodec(format, streamFormat) != KEEP_CODEC_RESULT_NO) {
+ if (codecInfo.canReuseCodec(format, streamFormat).result != REUSE_RESULT_NO) {
haveUnknownDimensions |=
(streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE);
maxWidth = max(maxWidth, streamFormat.width);
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
index c714f3ca21..aacd8ce8d4 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/video/VideoRendererEventListener.java
@@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
+import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.util.Assertions;
/**
@@ -52,12 +53,23 @@ public interface VideoRendererEventListener {
default void onVideoDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
+ /** @deprecated Use {@link #onVideoInputFormatChanged(Format, DecoderReuseEvaluation)}. */
+ @Deprecated
+ default void onVideoInputFormatChanged(Format format) {}
+
/**
* Called when the format of the media being consumed by the renderer changes.
*
* @param format The new format.
+ * @param decoderReuseEvaluation The result of the evaluation to determine whether an existing
+ * decoder instance can be reused for the new format, or {@code null} if the renderer did not
+ * have a decoder.
*/
- default void onVideoInputFormatChanged(Format format) {}
+ @SuppressWarnings("deprecation")
+ default void onVideoInputFormatChanged(
+ Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
+ onVideoInputFormatChanged(format);
+ }
/**
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
@@ -172,10 +184,15 @@ public interface VideoRendererEventListener {
}
}
- /** Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format)}. */
- public void inputFormatChanged(Format format) {
+ /**
+ * Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format,
+ * DecoderReuseEvaluation)}.
+ */
+ public void inputFormatChanged(
+ Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
if (handler != null) {
- handler.post(() -> castNonNull(listener).onVideoInputFormatChanged(format));
+ handler.post(
+ () -> castNonNull(listener).onVideoInputFormatChanged(format, decoderReuseEvaluation));
}
}
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java
index efef2b47b3..346d2773a6 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java
@@ -15,9 +15,15 @@
*/
package com.google.android.exoplayer2.mediacodec;
-import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
-import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_FLUSH;
-import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_RESOLUTION_CHANGED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_ROTATION_CHANGED;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_NO;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_FLUSH;
+import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.REUSE_RESULT_YES_WITH_RECONFIGURATION;
import static com.google.android.exoplayer2.util.MimeTypes.AUDIO_AAC;
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_AV1;
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_H264;
@@ -26,6 +32,7 @@ import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
@@ -71,7 +78,14 @@ public final class MediaCodecInfoTest {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
Format hdAv1Format = FORMAT_H264_HD.buildUpon().setSampleMimeType(VIDEO_AV1).build();
- assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdAv1Format)).isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdAv1Format))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_H264_HD,
+ hdAv1Format,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_MIME_TYPE_CHANGED));
}
@Test
@@ -79,24 +93,42 @@ public final class MediaCodecInfoTest {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
Format hdRotatedFormat = FORMAT_H264_HD.buildUpon().setRotationDegrees(90).build();
- assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdRotatedFormat))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdRotatedFormat))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_H264_HD,
+ hdRotatedFormat,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_VIDEO_ROTATION_CHANGED));
}
@Test
public void canKeepCodec_withResolutionChange_adaptiveCodec_returnsYesWithReconfiguration() {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
- assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K))
- .isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION);
+ assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_H264_HD,
+ FORMAT_H264_4K,
+ REUSE_RESULT_YES_WITH_RECONFIGURATION,
+ /* discardReasons= */ 0));
}
@Test
public void canKeepCodec_withResolutionChange_nonAdaptiveCodec_returnsNo() {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false);
- assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_H264_HD,
+ FORMAT_H264_4K,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_VIDEO_RESOLUTION_CHANGED));
}
@Test
@@ -105,8 +137,14 @@ public final class MediaCodecInfoTest {
Format hdVariantFormat =
FORMAT_H264_HD.buildUpon().setInitializationData(ImmutableList.of(new byte[] {0})).build();
- assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdVariantFormat))
- .isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION);
+ assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdVariantFormat))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_H264_HD,
+ hdVariantFormat,
+ REUSE_RESULT_YES_WITH_RECONFIGURATION,
+ /* discardReasons= */ 0));
}
@Test
@@ -115,8 +153,14 @@ public final class MediaCodecInfoTest {
Format hdrVariantFormat =
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
- assertThat(codecInfo.canKeepCodec(hdrVariantFormat, FORMAT_H264_4K))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(hdrVariantFormat, FORMAT_H264_4K))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ hdrVariantFormat,
+ FORMAT_H264_4K,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
}
@Test
@@ -125,8 +169,14 @@ public final class MediaCodecInfoTest {
Format hdrVariantFormat =
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
- assertThat(codecInfo.canKeepCodec(FORMAT_H264_4K, hdrVariantFormat))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(FORMAT_H264_4K, hdrVariantFormat))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_H264_4K,
+ hdrVariantFormat,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
}
@Test
@@ -137,18 +187,28 @@ public final class MediaCodecInfoTest {
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
Format hdrVariantFormat2 =
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT709)).build();
- assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
- assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(hdrVariantFormat1, hdrVariantFormat2))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ hdrVariantFormat1,
+ hdrVariantFormat2,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
}
@Test
public void canKeepCodec_audioWithDifferentChannelCounts_returnsNo() {
MediaCodecInfo codecInfo = buildAacCodecInfo();
- assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_AAC_STEREO,
+ FORMAT_AAC_SURROUND,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED));
}
@Test
@@ -156,8 +216,14 @@ public final class MediaCodecInfoTest {
MediaCodecInfo codecInfo = buildAacCodecInfo();
Format stereoVariantFormat = FORMAT_AAC_STEREO.buildUpon().setAverageBitrate(100).build();
- assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
- .isEqualTo(KEEP_CODEC_RESULT_YES_WITH_FLUSH);
+ assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_AAC_STEREO,
+ stereoVariantFormat,
+ REUSE_RESULT_YES_WITH_FLUSH,
+ /* discardReasons= */ 0));
}
@Test
@@ -169,8 +235,14 @@ public final class MediaCodecInfoTest {
.buildUpon()
.setInitializationData(ImmutableList.of(new byte[] {0}))
.build();
- assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
- .isEqualTo(KEEP_CODEC_RESULT_NO);
+ assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
+ .isEqualTo(
+ new DecoderReuseEvaluation(
+ codecInfo.name,
+ FORMAT_AAC_STEREO,
+ stereoVariantFormat,
+ REUSE_RESULT_NO,
+ DISCARD_REASON_INITIALIZATION_DATA_CHANGED));
}
@Test
diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
index 0853981443..9452550740 100644
--- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
+++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DebugRenderersFactory.java
@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
+import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
@@ -165,12 +166,15 @@ import java.util.ArrayList;
}
@Override
- protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
- super.onInputFormatChanged(formatHolder);
+ @Nullable
+ protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
+ throws ExoPlaybackException {
+ @Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder);
// Ensure timestamps of buffers queued after this format change are never inserted into the
// queue of expected output timestamps before those of buffers that have already been queued.
minimumInsertIndex = startIndex + queueSize;
inputFormatChanged = true;
+ return evaluation;
}
@Override
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
index 1e2f6159a5..f26d414db4 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeAudioRenderer.java
@@ -55,7 +55,7 @@ public class FakeAudioRenderer extends FakeRenderer {
@Override
protected void onFormatChanged(Format format) {
- eventDispatcher.inputFormatChanged(format);
+ eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
eventDispatcher.decoderInitialized(
/* decoderName= */ "fake.audio.decoder",
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),
diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java
index c1388b209d..f6c1bacea0 100644
--- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java
+++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeVideoRenderer.java
@@ -85,7 +85,7 @@ public class FakeVideoRenderer extends FakeRenderer {
@Override
protected void onFormatChanged(Format format) {
- eventDispatcher.inputFormatChanged(format);
+ eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
eventDispatcher.decoderInitialized(
/* decoderName= */ "fake.video.decoder",
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(),