Report reasons for not being able to reuse decoders

PiperOrigin-RevId: 342344090
This commit is contained in:
olly 2020-11-13 22:56:59 +00:00 committed by Ian Baker
parent 3ef609fa3b
commit c47e62209d
21 changed files with 708 additions and 206 deletions

View File

@ -48,6 +48,12 @@
* DRM: * DRM:
* Fix playback failure when switching from PlayReady protected content to * Fix playback failure when switching from PlayReady protected content to
Widevine or Clearkey protected content in a playlist. 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: * IMA extension:
* Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader` * Upgrade IMA SDK dependency to 3.21.0, and release the `AdsLoader`
([#7344](https://github.com/google/ExoPlayer/issues/7344)). ([#7344](https://github.com/google/ExoPlayer/issues/7344)).

View File

@ -15,12 +15,15 @@
*/ */
package com.google.android.exoplayer2.ext.av1; 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.os.Handler;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities; 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.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
@ -164,7 +167,13 @@ public class Libgav1VideoRenderer extends DecoderVideoRenderer {
} }
@Override @Override
protected boolean canKeepCodec(Format oldFormat, Format newFormat) { protected DecoderReuseEvaluation canReuseDecoder(
return true; String decoderName, Format oldFormat, Format newFormat) {
return new DecoderReuseEvaluation(
decoderName,
oldFormat,
newFormat,
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
/* discardReasons= */ 0);
} }
} }

View File

@ -15,6 +15,10 @@
*/ */
package com.google.android.exoplayer2.ext.ffmpeg; 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.os.Handler;
import android.view.Surface; import android.view.Surface;
import androidx.annotation.Nullable; 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.Format;
import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.decoder.Decoder; 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.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -116,7 +121,15 @@ public final class FfmpegVideoRenderer extends DecoderVideoRenderer {
} }
@Override @Override
protected boolean canKeepCodec(Format oldFormat, Format newFormat) { protected DecoderReuseEvaluation canReuseDecoder(
return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType); 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);
} }
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.ext.vp9; 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 static java.lang.Runtime.getRuntime;
import android.os.Handler; import android.os.Handler;
@ -23,6 +24,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.RendererCapabilities; 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.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
@ -169,7 +171,13 @@ public class LibvpxVideoRenderer extends DecoderVideoRenderer {
} }
@Override @Override
protected boolean canKeepCodec(Format oldFormat, Format newFormat) { protected DecoderReuseEvaluation canReuseDecoder(
return true; String decoderName, Format oldFormat, Format newFormat) {
return new DecoderReuseEvaluation(
decoderName,
oldFormat,
newFormat,
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
/* discardReasons= */ 0);
} }
} }

View File

@ -36,6 +36,7 @@ import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AuxEffectInfo; import com.google.android.exoplayer2.audio.AuxEffectInfo;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.DeviceInfo;
import com.google.android.exoplayer2.device.DeviceListener; import com.google.android.exoplayer2.device.DeviceListener;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
@ -2242,10 +2243,11 @@ public class SimpleExoPlayer extends BasePlayer
} }
@Override @Override
public void onVideoInputFormatChanged(Format format) { public void onVideoInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
videoFormat = format; videoFormat = format;
for (VideoRendererEventListener videoDebugListener : videoDebugListeners) { for (VideoRendererEventListener videoDebugListener : videoDebugListeners) {
videoDebugListener.onVideoInputFormatChanged(format); videoDebugListener.onVideoInputFormatChanged(format, decoderReuseEvaluation);
} }
} }
@ -2337,10 +2339,11 @@ public class SimpleExoPlayer extends BasePlayer
} }
@Override @Override
public void onAudioInputFormatChanged(Format format) { public void onAudioInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
audioFormat = format; audioFormat = format;
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) { for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
audioDebugListener.onAudioInputFormatChanged(format); audioDebugListener.onAudioInputFormatChanged(format, decoderReuseEvaluation);
} }
} }

View File

@ -34,6 +34,7 @@ import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataOutput; import com.google.android.exoplayer2.metadata.MetadataOutput;
@ -195,11 +196,12 @@ public class AnalyticsCollector
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public final void onAudioInputFormatChanged(Format format) { public final void onAudioInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( listeners.sendEvent(
listener -> { listener -> {
listener.onAudioInputFormatChanged(eventTime, format); listener.onAudioInputFormatChanged(eventTime, format, decoderReuseEvaluation);
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format); listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format);
}); });
} }
@ -298,11 +300,12 @@ public class AnalyticsCollector
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public final void onVideoInputFormatChanged(Format format) { public final void onVideoInputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
EventTime eventTime = generateReadingMediaPeriodEventTime(); EventTime eventTime = generateReadingMediaPeriodEventTime();
listeners.sendEvent( listeners.sendEvent(
listener -> { listener -> {
listener.onVideoInputFormatChanged(eventTime, format); listener.onVideoInputFormatChanged(eventTime, format, decoderReuseEvaluation);
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format); listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format);
}); });
} }

View File

@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.audio.AudioSink; import com.google.android.exoplayer2.audio.AudioSink;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.metadata.Metadata;
import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaLoadData;
@ -452,8 +453,8 @@ public interface AnalyticsListener {
EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {} EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {}
/** /**
* @deprecated Use {@link #onAudioInputFormatChanged} and {@link #onVideoInputFormatChanged} * @deprecated Use {@link #onAudioInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}
* instead. * and {@link #onVideoInputFormatChanged(EventTime, Format, DecoderReuseEvaluation)}. instead.
*/ */
@Deprecated @Deprecated
default void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {} default void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {}
@ -482,13 +483,26 @@ public interface AnalyticsListener {
default void onAudioDecoderInitialized( default void onAudioDecoderInitialized(
EventTime eventTime, String decoderName, long initializationDurationMs) {} 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. * Called when the format of the media being consumed by an audio renderer changes.
* *
* @param eventTime The event time. * @param eventTime The event time.
* @param format The new format. * @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 * 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( default void onVideoDecoderInitialized(
EventTime eventTime, String decoderName, long initializationDurationMs) {} 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. * Called when the format of the media being consumed by a video renderer changes.
* *
* @param eventTime The event time. * @param eventTime The event time.
* @param format The new format. * @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. * Called after video frames have been dropped.

View File

@ -27,6 +27,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
/** /**
@ -61,12 +62,23 @@ public interface AudioRendererEventListener {
default void onAudioDecoderInitialized( default void onAudioDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {} 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. * Called when the format of the media being consumed by the renderer changes.
* *
* @param format The new format. * @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 * 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)}. */ /** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */
public void inputFormatChanged(Format format) { public void inputFormatChanged(
Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
if (handler != null) { if (handler != null) {
handler.post(() -> castNonNull(listener).onAudioInputFormatChanged(format)); handler.post(
() -> castNonNull(listener).onAudioInputFormatChanged(format, decoderReuseEvaluation));
} }
} }

View File

@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.audio; 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 static java.lang.Math.max;
import android.media.audiofx.Virtualizer; 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.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderException; import com.google.android.exoplayer2.decoder.DecoderException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.decoder.SimpleOutputBuffer;
import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
@ -347,14 +351,19 @@ public abstract class DecoderAudioRenderer<
protected abstract Format getOutputFormat(T decoder); 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}.
* *
* <p>The default implementation does not allow decoder reuse.
*
* @param decoderName The name of the decoder.
* @param oldFormat The previous format. * @param oldFormat The previous format.
* @param newFormat The new 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) { protected DecoderReuseEvaluation canReuseDecoder(
return false; String decoderName, Format oldFormat, Format newFormat) {
return new DecoderReuseEvaluation(
decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
} }
private boolean drainOutputBuffer() private boolean drainOutputBuffer()
@ -655,10 +664,29 @@ public abstract class DecoderAudioRenderer<
setSourceDrmSession(formatHolder.drmSession); setSourceDrmSession(formatHolder.drmSession);
Format oldFormat = inputFormat; Format oldFormat = inputFormat;
inputFormat = newFormat; inputFormat = newFormat;
encoderDelay = newFormat.encoderDelay;
encoderPadding = newFormat.encoderPadding;
if (decoder == null) { if (decoder == null) {
maybeInitDecoder(); 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) { if (decoderReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization. // Signal end of stream and wait for any final output buffers before re-initialization.
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
@ -669,10 +697,7 @@ public abstract class DecoderAudioRenderer<
audioTrackNeedsConfigure = true; audioTrackNeedsConfigure = true;
} }
} }
eventDispatcher.inputFormatChanged(inputFormat, evaluation);
encoderDelay = inputFormat.encoderDelay;
encoderPadding = inputFormat.encoderPadding;
eventDispatcher.inputFormatChanged(inputFormat);
} }
private void onQueueInputBuffer(DecoderInputBuffer buffer) { private void onQueueInputBuffer(DecoderInputBuffer buffer) {

View File

@ -15,7 +15,8 @@
*/ */
package com.google.android.exoplayer2.audio; 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 com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.Math.max; 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.InitializationException;
import com.google.android.exoplayer2.audio.AudioSink.WriteException; import com.google.android.exoplayer2.audio.AudioSink.WriteException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; 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.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
@ -328,13 +330,21 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
@KeepCodecResult protected DecoderReuseEvaluation canReuseCodec(
protected int canKeepCodec( MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
@DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) { 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 @Override
@ -370,9 +380,12 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
} }
@Override @Override
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException { @Nullable
super.onInputFormatChanged(formatHolder); protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
eventDispatcher.inputFormatChanged(formatHolder.format); throws ExoPlaybackException {
@Nullable DecoderReuseEvaluation evaluation = super.onInputFormatChanged(formatHolder);
eventDispatcher.inputFormatChanged(formatHolder.format, evaluation);
return evaluation;
} }
@Override @Override
@ -664,7 +677,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
return maxInputSize; return maxInputSize;
} }
for (Format streamFormat : streamFormats) { 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)); maxInputSize = max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat));
} }
} }

View File

@ -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;
}
}

View File

@ -15,6 +15,20 @@
*/ */
package com.google.android.exoplayer2.mediacodec; 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.graphics.Point;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCodecInfo.AudioCapabilities; import android.media.MediaCodecInfo.AudioCapabilities;
@ -22,18 +36,17 @@ import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCodecInfo.CodecProfileLevel;
import android.media.MediaCodecInfo.VideoCapabilities; import android.media.MediaCodecInfo.VideoCapabilities;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.Format; 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.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; 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. */ /** Information about a {@link MediaCodec} for a given mime type. */
@SuppressWarnings("InlinedApi") @SuppressWarnings("InlinedApi")
@ -47,28 +60,6 @@ public final class MediaCodecInfo {
*/ */
public static final int MAX_SUPPORTED_INSTANCES_UNKNOWN = -1; 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. * The name of the decoder.
* *
@ -361,7 +352,7 @@ public final class MediaCodecInfo {
* @param isNewFormatComplete Whether {@code newFormat} is populated with format-specific * @param isNewFormatComplete Whether {@code newFormat} is populated with format-specific
* metadata. * metadata.
* @return Whether it is possible to adapt the decoder seamlessly. * @return Whether it is possible to adapt the decoder seamlessly.
* @deprecated Use {@link #canKeepCodec}. * @deprecated Use {@link #canReuseCodec}.
*/ */
@Deprecated @Deprecated
public boolean isSeamlessAdaptationSupported( public boolean isSeamlessAdaptationSupported(
@ -369,49 +360,68 @@ public final class MediaCodecInfo {
if (!isNewFormatComplete && oldFormat.colorInfo != null && newFormat.colorInfo == null) { if (!isNewFormatComplete && oldFormat.colorInfo != null && newFormat.colorInfo == null) {
newFormat = newFormat.buildUpon().setColorInfo(oldFormat.colorInfo).build(); newFormat = newFormat.buildUpon().setColorInfo(oldFormat.colorInfo).build();
} }
@KeepCodecResult int keepCodecResult = canKeepCodec(oldFormat, newFormat); @DecoderReuseResult int reuseResult = canReuseCodec(oldFormat, newFormat).result;
return keepCodecResult == KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION return reuseResult == REUSE_RESULT_YES_WITH_RECONFIGURATION
|| keepCodecResult == KEEP_CODEC_RESULT_YES_WITHOUT_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 * Evaluates whether it's possible to reuse an instance of this decoder that's currently decoding
* decoding {@code oldFormat} to decode {@code newFormat} instead. * {@code oldFormat} to decode {@code newFormat} instead.
* *
* <p>For adaptation to succeed, the codec must also be configured with maximum values that are * <p>For adaptation to succeed, the codec must also be configured with maximum values that are
* compatible with the new format. * compatible with the new format.
* *
* @param oldFormat The format being decoded. * @param oldFormat The format being decoded.
* @param newFormat The new format. * @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 DecoderReuseEvaluation canReuseCodec(Format oldFormat, Format newFormat) {
public int canKeepCodec(Format oldFormat, Format newFormat) { @DecoderDiscardReasons int discardReasons = 0;
if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) { if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) {
return KEEP_CODEC_RESULT_NO; discardReasons |= DISCARD_REASON_MIME_TYPE_CHANGED;
} }
if (isVideo) { if (isVideo) {
if (oldFormat.rotationDegrees == newFormat.rotationDegrees if (oldFormat.rotationDegrees != newFormat.rotationDegrees) {
&& (adaptive discardReasons |= DISCARD_REASON_VIDEO_ROTATION_CHANGED;
|| (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 (!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 { } else {
if (oldFormat.channelCount != newFormat.channelCount if (oldFormat.channelCount != newFormat.channelCount) {
|| oldFormat.sampleRate != newFormat.sampleRate discardReasons |= DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
|| oldFormat.pcmEncoding != newFormat.pcmEncoding) { }
return KEEP_CODEC_RESULT_NO; 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 // Check whether we're adapting between two xHE-AAC formats, for which adaptation is possible
// without reconfiguration or flushing. // without reconfiguration or flushing.
if (MimeTypes.AUDIO_AAC.equals(mimeType)) { if (discardReasons == 0 && MimeTypes.AUDIO_AAC.equals(mimeType)) {
@Nullable @Nullable
Pair<Integer, Integer> oldCodecProfileLevel = Pair<Integer, Integer> oldCodecProfileLevel =
MediaCodecUtil.getCodecProfileAndLevel(oldFormat); MediaCodecUtil.getCodecProfileAndLevel(oldFormat);
@ -423,18 +433,30 @@ public final class MediaCodecInfo {
int newProfile = newCodecProfileLevel.first; int newProfile = newCodecProfileLevel.first;
if (oldProfile == CodecProfileLevel.AACObjectXHE if (oldProfile == CodecProfileLevel.AACObjectXHE
&& newProfile == CodecProfileLevel.AACObjectXHE) { && newProfile == CodecProfileLevel.AACObjectXHE) {
return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION; return new DecoderReuseEvaluation(
name,
oldFormat,
newFormat,
REUSE_RESULT_YES_WITHOUT_RECONFIGURATION,
/* discardReasons= */ 0);
} }
} }
} }
if (oldFormat.initializationDataEquals(newFormat) if (!oldFormat.initializationDataEquals(newFormat)) {
&& !needsAdaptationFlushWorkaround(mimeType)) { discardReasons |= DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
return KEEP_CODEC_RESULT_YES_WITH_FLUSH; }
if (needsAdaptationFlushWorkaround(mimeType)) {
discardReasons |= DISCARD_REASON_WORKAROUND;
}
if (discardReasons == 0) {
return new DecoderReuseEvaluation(
name, oldFormat, newFormat, REUSE_RESULT_YES_WITH_FLUSH, /* discardReasons= */ 0);
} }
} }
return KEEP_CODEC_RESULT_NO; return new DecoderReuseEvaluation(name, oldFormat, newFormat, REUSE_RESULT_NO, discardReasons);
} }
/** /**

View File

@ -15,10 +15,14 @@
*/ */
package com.google.android.exoplayer2.mediacodec; package com.google.android.exoplayer2.mediacodec;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_DRM_SESSION_CHANGED;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_OPERATING_RATE_CHANGED;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_FLUSH; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION; 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 static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import static java.lang.Math.max; import static java.lang.Math.max;
@ -44,11 +48,12 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.DrmSession; import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KeepCodecResult;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
@ -710,7 +715,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (codec != null if (codec != null
&& codecDrainAction != DRAIN_ACTION_REINITIALIZE && codecDrainAction != DRAIN_ACTION_REINITIALIZE
&& getState() != STATE_DISABLED) { && getState() != STATE_DISABLED) {
updateOperatingRateOrReinitializeCodec(codecInputFormat); updateCodecOperatingRate(codecInputFormat);
} }
} }
@ -1375,9 +1380,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* *
* @param formatHolder A {@link FormatHolder} that holds the new {@link Format}. * @param formatHolder A {@link FormatHolder} that holds the new {@link Format}.
* @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}. * @throws ExoPlaybackException If an error occurs re-initializing the {@link MediaCodec}.
* @return The result of the evaluation to determine whether the existing decoder instance can be
* reused for the new format, or {@code null} if the renderer did not have a decoder.
*/ */
@CallSuper @CallSuper
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException { @Nullable
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
throws ExoPlaybackException {
waitingForFirstSampleInFormat = true; waitingForFirstSampleInFormat = true;
Format newFormat = checkNotNull(formatHolder.format); Format newFormat = checkNotNull(formatHolder.format);
setSourceDrmSession(formatHolder.drmSession); setSourceDrmSession(formatHolder.drmSession);
@ -1385,7 +1394,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (bypassEnabled) { if (bypassEnabled) {
bypassDrainAndReinitialize = true; bypassDrainAndReinitialize = true;
return; // Need to drain batch buffer first. return null; // Need to drain batch buffer first.
} }
if (codec == null) { if (codec == null) {
@ -1393,66 +1402,86 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
availableCodecInfos = null; availableCodecInfos = null;
} }
maybeInitCodecOrBypass(); maybeInitCodecOrBypass();
return; return null;
} }
// We have an existing codec that we may need to reconfigure, re-initialize, or release to // We have an existing codec that we may need to reconfigure, re-initialize, or release to
// switch to bypass. If the existing codec instance is kept then its operating rate and DRM // switch to bypass. If the existing codec instance is kept then its operating rate and DRM
// session may need to be updated. // session may need to be updated.
MediaCodecAdapter oldCodec = codec;
Format oldFormat = codecInputFormat;
if (drmNeedsCodecReinitialization(codecInfo, newFormat, codecDrmSession, sourceDrmSession)) { if (drmNeedsCodecReinitialization(codecInfo, newFormat, codecDrmSession, sourceDrmSession)) {
drainAndReinitializeCodec(); drainAndReinitializeCodec();
return; return new DecoderReuseEvaluation(
codecInfo.name,
oldFormat,
newFormat,
REUSE_RESULT_NO,
DISCARD_REASON_DRM_SESSION_CHANGED);
} }
boolean drainAndUpdateCodecDrmSession = sourceDrmSession != codecDrmSession; boolean drainAndUpdateCodecDrmSession = sourceDrmSession != codecDrmSession;
Assertions.checkState(!drainAndUpdateCodecDrmSession || Util.SDK_INT >= 23); Assertions.checkState(!drainAndUpdateCodecDrmSession || Util.SDK_INT >= 23);
switch (canKeepCodec(codec, codecInfo, codecInputFormat, newFormat)) { DecoderReuseEvaluation evaluation = canReuseCodec(codecInfo, oldFormat, newFormat);
case KEEP_CODEC_RESULT_NO: @DecoderDiscardReasons int overridingDiscardReasons = 0;
switch (evaluation.result) {
case REUSE_RESULT_NO:
drainAndReinitializeCodec(); drainAndReinitializeCodec();
break; break;
case KEEP_CODEC_RESULT_YES_WITH_FLUSH: case REUSE_RESULT_YES_WITH_FLUSH:
if (updateOperatingRateOrReinitializeCodec(newFormat)) { if (!updateCodecOperatingRate(newFormat)) {
// Codec re-initialization triggered. overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
} else { } else {
codecInputFormat = newFormat; codecInputFormat = newFormat;
if (drainAndUpdateCodecDrmSession) { if (drainAndUpdateCodecDrmSession) {
drainAndUpdateCodecDrmSessionV23(); if (!drainAndUpdateCodecDrmSessionV23()) {
} else { overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
drainAndFlushCodec(); }
} else if (!drainAndFlushCodec()) {
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
} }
} }
break; break;
case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION: case REUSE_RESULT_YES_WITH_RECONFIGURATION:
if (updateOperatingRateOrReinitializeCodec(newFormat)) { if (!updateCodecOperatingRate(newFormat)) {
// Codec re-initialization triggered. overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
} else { } else {
codecReconfigured = true; codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaroundBuffer =
codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION || (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
&& newFormat.width == codecInputFormat.width && newFormat.width == oldFormat.width
&& newFormat.height == codecInputFormat.height); && newFormat.height == oldFormat.height);
codecInputFormat = newFormat; codecInputFormat = newFormat;
if (drainAndUpdateCodecDrmSession) { if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
drainAndUpdateCodecDrmSessionV23(); overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
} }
} }
break; break;
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION: case REUSE_RESULT_YES_WITHOUT_RECONFIGURATION:
if (updateOperatingRateOrReinitializeCodec(newFormat)) { if (!updateCodecOperatingRate(newFormat)) {
// Codec re-initialization triggered. overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
} else { } else {
codecInputFormat = newFormat; codecInputFormat = newFormat;
if (drainAndUpdateCodecDrmSession) { if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
drainAndUpdateCodecDrmSessionV23(); overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
} }
} }
break; break;
default: default:
throw new IllegalStateException(); // Never happens. throw new IllegalStateException(); // Never happens.
} }
if (evaluation.result != REUSE_RESULT_NO
&& (codec != oldCodec || codecDrainAction == DRAIN_ACTION_REINITIALIZE)) {
// Initial evaluation indicated reuse was possible, but codec re-initialization was triggered.
// The reasons are indicated by overridingDiscardReasons.
return new DecoderReuseEvaluation(
codecInfo.name, oldFormat, newFormat, REUSE_RESULT_NO, overridingDiscardReasons);
}
return evaluation;
} }
/** /**
@ -1544,21 +1573,24 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
/** /**
* Determines whether the existing {@link MediaCodec} can be kept for a new {@link Format}, and if * Evaluates whether the existing {@link MediaCodec} can be kept for a new {@link Format}, and if
* it can whether it requires reconfiguration. * it can whether it requires reconfiguration.
* *
* <p>The default implementation returns {@link MediaCodecInfo#KEEP_CODEC_RESULT_NO}. * <p>The default implementation does not allow decoder reuse.
* *
* @param codec The existing {@link MediaCodecAdapter} instance.
* @param codecInfo A {@link MediaCodecInfo} describing the decoder. * @param codecInfo A {@link MediaCodecInfo} describing the decoder.
* @param oldFormat The {@link Format} for which the existing instance is configured. * @param oldFormat The {@link Format} for which the existing instance is configured.
* @param newFormat The new {@link Format}. * @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 DecoderReuseEvaluation canReuseCodec(
protected int canKeepCodec( MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { return new DecoderReuseEvaluation(
return KEEP_CODEC_RESULT_NO; codecInfo.name,
oldFormat,
newFormat,
REUSE_RESULT_NO,
DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
} }
@Override @Override
@ -1608,25 +1640,23 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
* *
* @param format The {@link Format} for which the operating rate should be configured. * @param format The {@link Format} for which the operating rate should be configured.
* @throws ExoPlaybackException If an error occurs releasing or initializing a codec. * @throws ExoPlaybackException If an error occurs releasing or initializing a codec.
* @return Whether codec release and re-initialization was triggered, rather than the existing * @return False if codec release and re-initialization was triggered. True in all other cases.
* codec being updated.
*/ */
private boolean updateOperatingRateOrReinitializeCodec(Format format) private boolean updateCodecOperatingRate(Format format) throws ExoPlaybackException {
throws ExoPlaybackException {
if (Util.SDK_INT < 23) { if (Util.SDK_INT < 23) {
return false; return true;
} }
float newCodecOperatingRate = float newCodecOperatingRate =
getCodecOperatingRateV23(playbackSpeed, format, getStreamFormats()); getCodecOperatingRateV23(playbackSpeed, format, getStreamFormats());
if (codecOperatingRate == newCodecOperatingRate) { if (codecOperatingRate == newCodecOperatingRate) {
// No change. // No change.
return false; return true;
} else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) { } else if (newCodecOperatingRate == CODEC_OPERATING_RATE_UNSET) {
// The only way to clear the operating rate is to instantiate a new codec instance. See // The only way to clear the operating rate is to instantiate a new codec instance. See
// [Internal ref: b/111543954]. // [Internal ref: b/111543954].
drainAndReinitializeCodec(); drainAndReinitializeCodec();
return true; return false;
} else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET } else if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET
|| newCodecOperatingRate > assumedMinimumCodecOperatingRate) { || newCodecOperatingRate > assumedMinimumCodecOperatingRate) {
// We need to set the operating rate, either because we've set it previously or because it's // 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); codecParameters.putFloat(MediaFormat.KEY_OPERATING_RATE, newCodecOperatingRate);
codec.setParameters(codecParameters); codec.setParameters(codecParameters);
codecOperatingRate = newCodecOperatingRate; 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) { if (codecReceivedBuffers) {
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM; codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) { if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
codecDrainAction = DRAIN_ACTION_REINITIALIZE; codecDrainAction = DRAIN_ACTION_REINITIALIZE;
return false;
} else { } else {
codecDrainAction = DRAIN_ACTION_FLUSH; codecDrainAction = DRAIN_ACTION_FLUSH;
} }
} }
return true;
} }
/** /**
* Starts draining the codec to update its DRM session. The update may occur immediately if no * Starts draining the codec to flush it and update its DRM session, or to release and
* buffers have been queued to the codec. * 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. * @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. @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) { if (codecReceivedBuffers) {
codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM; codecDrainState = DRAIN_STATE_SIGNAL_END_OF_STREAM;
if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) { if (codecNeedsFlushWorkaround || codecNeedsEosFlushWorkaround) {
codecDrainAction = DRAIN_ACTION_REINITIALIZE; codecDrainAction = DRAIN_ACTION_REINITIALIZE;
return false;
} else { } else {
codecDrainAction = DRAIN_ACTION_FLUSH_AND_UPDATE_DRM_SESSION; 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. // Nothing has been queued to the decoder, so we can do the update immediately.
updateDrmSessionV23(); updateDrmSessionV23();
} }
return true;
} }
/** /**

View File

@ -34,6 +34,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.AnalyticsListener;
import com.google.android.exoplayer2.audio.AudioAttributes; import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.decoder.DecoderCounters; 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.metadata.Metadata;
import com.google.android.exoplayer2.source.LoadEventInfo; import com.google.android.exoplayer2.source.LoadEventInfo;
import com.google.android.exoplayer2.source.MediaLoadData; import com.google.android.exoplayer2.source.MediaLoadData;
@ -328,7 +329,8 @@ public class EventLogger implements AnalyticsListener {
} }
@Override @Override
public void onAudioInputFormatChanged(EventTime eventTime, Format format) { public void onAudioInputFormatChanged(
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
logd(eventTime, "audioInputFormat", Format.toLogString(format)); logd(eventTime, "audioInputFormat", Format.toLogString(format));
} }
@ -393,7 +395,8 @@ public class EventLogger implements AnalyticsListener {
} }
@Override @Override
public void onVideoInputFormatChanged(EventTime eventTime, Format format) { public void onVideoInputFormatChanged(
EventTime eventTime, Format format, @Nullable DecoderReuseEvaluation decoderReuseEvaluation) {
logd(eventTime, "videoInputFormat", Format.toLogString(format)); logd(eventTime, "videoInputFormat", Format.toLogString(format));
} }

View File

@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.video; 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 static java.lang.Math.max;
import android.os.Handler; 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.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderException; import com.google.android.exoplayer2.decoder.DecoderException;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException; import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
import com.google.android.exoplayer2.drm.ExoMediaCrypto; import com.google.android.exoplayer2.drm.ExoMediaCrypto;
@ -97,9 +101,12 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
private Format inputFormat; private Format inputFormat;
private Format outputFormat; private Format outputFormat;
@Nullable
private Decoder< private Decoder<
VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException> VideoDecoderInputBuffer, ? extends VideoDecoderOutputBuffer, ? extends DecoderException>
decoder; decoder;
private VideoDecoderInputBuffer inputBuffer; private VideoDecoderInputBuffer inputBuffer;
private VideoDecoderOutputBuffer outputBuffer; private VideoDecoderOutputBuffer outputBuffer;
@Nullable private Surface surface; @Nullable private Surface surface;
@ -366,7 +373,24 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
if (decoder == null) { if (decoder == null) {
maybeInitDecoder(); 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) { if (decoderReceivedBuffers) {
// Signal end of stream and wait for any final output buffers before re-initialization. // Signal end of stream and wait for any final output buffers before re-initialization.
decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; decoderReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM;
@ -376,8 +400,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
maybeInitDecoder(); maybeInitDecoder();
} }
} }
eventDispatcher.inputFormatChanged(inputFormat, evaluation);
eventDispatcher.inputFormatChanged(inputFormat);
} }
/** /**
@ -626,14 +649,18 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
protected abstract void setDecoderOutputMode(@C.VideoOutputMode int outputMode); 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}.
*
* <p>The default implementation does not allow decoder reuse.
* *
* @param oldFormat The previous format. * @param oldFormat The previous format.
* @param newFormat The new 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) { protected DecoderReuseEvaluation canReuseDecoder(
return false; String decoderName, Format oldFormat, Format newFormat) {
return new DecoderReuseEvaluation(
decoderName, oldFormat, newFormat, REUSE_RESULT_NO, DISCARD_REASON_REUSE_NOT_IMPLEMENTED);
} }
// Internal methods. // Internal methods.

View File

@ -15,7 +15,9 @@
*/ */
package com.google.android.exoplayer2.video; 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.max;
import static java.lang.Math.min; 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.RendererCapabilities;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.drm.DrmInitData;
import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter; import com.google.android.exoplayer2.mediacodec.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException; import com.google.android.exoplayer2.mediacodec.MediaCodecDecoderException;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; 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.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
@ -566,15 +569,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
@Override @Override
@KeepCodecResult protected DecoderReuseEvaluation canReuseCodec(
protected int canKeepCodec( MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
MediaCodecAdapter codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
if (newFormat.width > codecMaxValues.width
|| newFormat.height > codecMaxValues.height @DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
|| getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) { if (newFormat.width > codecMaxValues.width || newFormat.height > codecMaxValues.height) {
return KEEP_CODEC_RESULT_NO; 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 @CallSuper
@ -621,9 +633,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
@Override @Override
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException { @Nullable
super.onInputFormatChanged(formatHolder); protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
eventDispatcher.inputFormatChanged(formatHolder.format); 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. // from format to avoid codec re-use being ruled out for only this reason.
streamFormat = streamFormat.buildUpon().setColorInfo(format.colorInfo).build(); 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 |= haveUnknownDimensions |=
(streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE);
maxWidth = max(maxWidth, streamFormat.width); maxWidth = max(maxWidth, streamFormat.width);

View File

@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
/** /**
@ -52,12 +53,23 @@ public interface VideoRendererEventListener {
default void onVideoDecoderInitialized( default void onVideoDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {} 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. * Called when the format of the media being consumed by the renderer changes.
* *
* @param format The new format. * @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 * 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) { if (handler != null) {
handler.post(() -> castNonNull(listener).onVideoInputFormatChanged(format)); handler.post(
() -> castNonNull(listener).onVideoInputFormatChanged(format, decoderReuseEvaluation));
} }
} }

View File

@ -15,9 +15,15 @@
*/ */
package com.google.android.exoplayer2.mediacodec; package com.google.android.exoplayer2.mediacodec;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_FLUSH; import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION; 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.AUDIO_AAC;
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_AV1; import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_AV1;
import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_H264; 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 androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderReuseEvaluation;
import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.ColorInfo;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import org.junit.Test; import org.junit.Test;
@ -71,7 +78,14 @@ public final class MediaCodecInfoTest {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true); MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
Format hdAv1Format = FORMAT_H264_HD.buildUpon().setSampleMimeType(VIDEO_AV1).build(); 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 @Test
@ -79,24 +93,42 @@ public final class MediaCodecInfoTest {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true); MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
Format hdRotatedFormat = FORMAT_H264_HD.buildUpon().setRotationDegrees(90).build(); Format hdRotatedFormat = FORMAT_H264_HD.buildUpon().setRotationDegrees(90).build();
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdRotatedFormat)) assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdRotatedFormat))
.isEqualTo(KEEP_CODEC_RESULT_NO); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_H264_HD,
hdRotatedFormat,
REUSE_RESULT_NO,
DISCARD_REASON_VIDEO_ROTATION_CHANGED));
} }
@Test @Test
public void canKeepCodec_withResolutionChange_adaptiveCodec_returnsYesWithReconfiguration() { public void canKeepCodec_withResolutionChange_adaptiveCodec_returnsYesWithReconfiguration() {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true); MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true);
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K)) assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_H264_HD,
FORMAT_H264_4K,
REUSE_RESULT_YES_WITH_RECONFIGURATION,
/* discardReasons= */ 0));
} }
@Test @Test
public void canKeepCodec_withResolutionChange_nonAdaptiveCodec_returnsNo() { public void canKeepCodec_withResolutionChange_nonAdaptiveCodec_returnsNo() {
MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false);
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, FORMAT_H264_4K)) assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, FORMAT_H264_4K))
.isEqualTo(KEEP_CODEC_RESULT_NO); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_H264_HD,
FORMAT_H264_4K,
REUSE_RESULT_NO,
DISCARD_REASON_VIDEO_RESOLUTION_CHANGED));
} }
@Test @Test
@ -105,8 +137,14 @@ public final class MediaCodecInfoTest {
Format hdVariantFormat = Format hdVariantFormat =
FORMAT_H264_HD.buildUpon().setInitializationData(ImmutableList.of(new byte[] {0})).build(); FORMAT_H264_HD.buildUpon().setInitializationData(ImmutableList.of(new byte[] {0})).build();
assertThat(codecInfo.canKeepCodec(FORMAT_H264_HD, hdVariantFormat)) assertThat(codecInfo.canReuseCodec(FORMAT_H264_HD, hdVariantFormat))
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_H264_HD,
hdVariantFormat,
REUSE_RESULT_YES_WITH_RECONFIGURATION,
/* discardReasons= */ 0));
} }
@Test @Test
@ -115,8 +153,14 @@ public final class MediaCodecInfoTest {
Format hdrVariantFormat = Format hdrVariantFormat =
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build(); FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
assertThat(codecInfo.canKeepCodec(hdrVariantFormat, FORMAT_H264_4K)) assertThat(codecInfo.canReuseCodec(hdrVariantFormat, FORMAT_H264_4K))
.isEqualTo(KEEP_CODEC_RESULT_NO); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
hdrVariantFormat,
FORMAT_H264_4K,
REUSE_RESULT_NO,
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
} }
@Test @Test
@ -125,8 +169,14 @@ public final class MediaCodecInfoTest {
Format hdrVariantFormat = Format hdrVariantFormat =
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build(); FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
assertThat(codecInfo.canKeepCodec(FORMAT_H264_4K, hdrVariantFormat)) assertThat(codecInfo.canReuseCodec(FORMAT_H264_4K, hdrVariantFormat))
.isEqualTo(KEEP_CODEC_RESULT_NO); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_H264_4K,
hdrVariantFormat,
REUSE_RESULT_NO,
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
} }
@Test @Test
@ -137,18 +187,28 @@ public final class MediaCodecInfoTest {
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build(); FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT601)).build();
Format hdrVariantFormat2 = Format hdrVariantFormat2 =
FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT709)).build(); FORMAT_H264_4K.buildUpon().setColorInfo(buildColorInfo(C.COLOR_SPACE_BT709)).build();
assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2)) assertThat(codecInfo.canReuseCodec(hdrVariantFormat1, hdrVariantFormat2))
.isEqualTo(KEEP_CODEC_RESULT_NO); .isEqualTo(
assertThat(codecInfo.canKeepCodec(hdrVariantFormat1, hdrVariantFormat2)) new DecoderReuseEvaluation(
.isEqualTo(KEEP_CODEC_RESULT_NO); codecInfo.name,
hdrVariantFormat1,
hdrVariantFormat2,
REUSE_RESULT_NO,
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED));
} }
@Test @Test
public void canKeepCodec_audioWithDifferentChannelCounts_returnsNo() { public void canKeepCodec_audioWithDifferentChannelCounts_returnsNo() {
MediaCodecInfo codecInfo = buildAacCodecInfo(); MediaCodecInfo codecInfo = buildAacCodecInfo();
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND)) assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND))
.isEqualTo(KEEP_CODEC_RESULT_NO); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_AAC_STEREO,
FORMAT_AAC_SURROUND,
REUSE_RESULT_NO,
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED));
} }
@Test @Test
@ -156,8 +216,14 @@ public final class MediaCodecInfoTest {
MediaCodecInfo codecInfo = buildAacCodecInfo(); MediaCodecInfo codecInfo = buildAacCodecInfo();
Format stereoVariantFormat = FORMAT_AAC_STEREO.buildUpon().setAverageBitrate(100).build(); Format stereoVariantFormat = FORMAT_AAC_STEREO.buildUpon().setAverageBitrate(100).build();
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat)) assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
.isEqualTo(KEEP_CODEC_RESULT_YES_WITH_FLUSH); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_AAC_STEREO,
stereoVariantFormat,
REUSE_RESULT_YES_WITH_FLUSH,
/* discardReasons= */ 0));
} }
@Test @Test
@ -169,8 +235,14 @@ public final class MediaCodecInfoTest {
.buildUpon() .buildUpon()
.setInitializationData(ImmutableList.of(new byte[] {0})) .setInitializationData(ImmutableList.of(new byte[] {0}))
.build(); .build();
assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat)) assertThat(codecInfo.canReuseCodec(FORMAT_AAC_STEREO, stereoVariantFormat))
.isEqualTo(KEEP_CODEC_RESULT_NO); .isEqualTo(
new DecoderReuseEvaluation(
codecInfo.name,
FORMAT_AAC_STEREO,
stereoVariantFormat,
REUSE_RESULT_NO,
DISCARD_REASON_INITIALIZATION_DATA_CHANGED));
} }
@Test @Test

View File

@ -30,6 +30,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.MediaCodecAdapter;
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
import com.google.android.exoplayer2.mediacodec.MediaCodecSelector; import com.google.android.exoplayer2.mediacodec.MediaCodecSelector;
@ -165,12 +166,15 @@ import java.util.ArrayList;
} }
@Override @Override
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException { @Nullable
super.onInputFormatChanged(formatHolder); 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 // 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. // queue of expected output timestamps before those of buffers that have already been queued.
minimumInsertIndex = startIndex + queueSize; minimumInsertIndex = startIndex + queueSize;
inputFormatChanged = true; inputFormatChanged = true;
return evaluation;
} }
@Override @Override

View File

@ -55,7 +55,7 @@ public class FakeAudioRenderer extends FakeRenderer {
@Override @Override
protected void onFormatChanged(Format format) { protected void onFormatChanged(Format format) {
eventDispatcher.inputFormatChanged(format); eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
eventDispatcher.decoderInitialized( eventDispatcher.decoderInitialized(
/* decoderName= */ "fake.audio.decoder", /* decoderName= */ "fake.audio.decoder",
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(), /* initializedTimestampMs= */ SystemClock.elapsedRealtime(),

View File

@ -85,7 +85,7 @@ public class FakeVideoRenderer extends FakeRenderer {
@Override @Override
protected void onFormatChanged(Format format) { protected void onFormatChanged(Format format) {
eventDispatcher.inputFormatChanged(format); eventDispatcher.inputFormatChanged(format, /* decoderReuseEvaluation= */ null);
eventDispatcher.decoderInitialized( eventDispatcher.decoderInitialized(
/* decoderName= */ "fake.video.decoder", /* decoderName= */ "fake.video.decoder",
/* initializedTimestampMs= */ SystemClock.elapsedRealtime(), /* initializedTimestampMs= */ SystemClock.elapsedRealtime(),