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:
* 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)).

View File

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

View File

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

View File

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

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.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);
}
}

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.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);
});
}

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.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.

View File

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

View File

@ -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}.
*
* <p>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) {

View File

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

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;
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.
*
* <p>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<Integer, Integer> oldCodecProfileLevel =
MediaCodecUtil.getCodecProfileAndLevel(oldFormat);
@ -423,18 +433,30 @@ public final class MediaCodecInfo {
int newProfile = newCodecProfileLevel.first;
if (oldProfile == 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)
&& !needsAdaptationFlushWorkaround(mimeType)) {
return KEEP_CODEC_RESULT_YES_WITH_FLUSH;
if (!oldFormat.initializationDataEquals(newFormat)) {
discardReasons |= DISCARD_REASON_INITIALIZATION_DATA_CHANGED;
}
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;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO;
import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION;
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_DRM_SESSION_CHANGED;
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_OPERATING_RATE_CHANGED;
import static com.google.android.exoplayer2.decoder.DecoderReuseEvaluation.DISCARD_REASON_REUSE_NOT_IMPLEMENTED;
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.checkState;
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.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.DrmSession;
import com.google.android.exoplayer2.drm.DrmSession.DrmSessionException;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
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.source.MediaPeriod;
import com.google.android.exoplayer2.source.SampleStream;
@ -710,7 +715,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (codec != null
&& codecDrainAction != DRAIN_ACTION_REINITIALIZE
&& 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}.
* @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
protected void onInputFormatChanged(FormatHolder formatHolder) throws ExoPlaybackException {
@Nullable
protected DecoderReuseEvaluation onInputFormatChanged(FormatHolder formatHolder)
throws ExoPlaybackException {
waitingForFirstSampleInFormat = true;
Format newFormat = checkNotNull(formatHolder.format);
setSourceDrmSession(formatHolder.drmSession);
@ -1385,7 +1394,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (bypassEnabled) {
bypassDrainAndReinitialize = true;
return; // Need to drain batch buffer first.
return null; // Need to drain batch buffer first.
}
if (codec == null) {
@ -1393,66 +1402,86 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
availableCodecInfos = null;
}
maybeInitCodecOrBypass();
return;
return null;
}
// 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
// session may need to be updated.
MediaCodecAdapter oldCodec = codec;
Format oldFormat = codecInputFormat;
if (drmNeedsCodecReinitialization(codecInfo, newFormat, codecDrmSession, sourceDrmSession)) {
drainAndReinitializeCodec();
return;
return new DecoderReuseEvaluation(
codecInfo.name,
oldFormat,
newFormat,
REUSE_RESULT_NO,
DISCARD_REASON_DRM_SESSION_CHANGED);
}
boolean drainAndUpdateCodecDrmSession = sourceDrmSession != codecDrmSession;
Assertions.checkState(!drainAndUpdateCodecDrmSession || Util.SDK_INT >= 23);
switch (canKeepCodec(codec, codecInfo, codecInputFormat, newFormat)) {
case KEEP_CODEC_RESULT_NO:
DecoderReuseEvaluation evaluation = canReuseCodec(codecInfo, oldFormat, newFormat);
@DecoderDiscardReasons int overridingDiscardReasons = 0;
switch (evaluation.result) {
case REUSE_RESULT_NO:
drainAndReinitializeCodec();
break;
case KEEP_CODEC_RESULT_YES_WITH_FLUSH:
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
// Codec re-initialization triggered.
case REUSE_RESULT_YES_WITH_FLUSH:
if (!updateCodecOperatingRate(newFormat)) {
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
} else {
codecInputFormat = newFormat;
if (drainAndUpdateCodecDrmSession) {
drainAndUpdateCodecDrmSessionV23();
} else {
drainAndFlushCodec();
if (!drainAndUpdateCodecDrmSessionV23()) {
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
}
} else if (!drainAndFlushCodec()) {
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
}
}
break;
case KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION:
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
// Codec re-initialization triggered.
case REUSE_RESULT_YES_WITH_RECONFIGURATION:
if (!updateCodecOperatingRate(newFormat)) {
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
} else {
codecReconfigured = true;
codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING;
codecNeedsAdaptationWorkaroundBuffer =
codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_ALWAYS
|| (codecAdaptationWorkaroundMode == ADAPTATION_WORKAROUND_MODE_SAME_RESOLUTION
&& newFormat.width == codecInputFormat.width
&& newFormat.height == codecInputFormat.height);
&& newFormat.width == oldFormat.width
&& newFormat.height == oldFormat.height);
codecInputFormat = newFormat;
if (drainAndUpdateCodecDrmSession) {
drainAndUpdateCodecDrmSessionV23();
if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
}
}
break;
case KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION:
if (updateOperatingRateOrReinitializeCodec(newFormat)) {
// Codec re-initialization triggered.
case REUSE_RESULT_YES_WITHOUT_RECONFIGURATION:
if (!updateCodecOperatingRate(newFormat)) {
overridingDiscardReasons |= DISCARD_REASON_OPERATING_RATE_CHANGED;
} else {
codecInputFormat = newFormat;
if (drainAndUpdateCodecDrmSession) {
drainAndUpdateCodecDrmSessionV23();
if (drainAndUpdateCodecDrmSession && !drainAndUpdateCodecDrmSessionV23()) {
overridingDiscardReasons |= DISCARD_REASON_WORKAROUND;
}
}
break;
default:
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.
*
* <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 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;
}
/**

View File

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

View File

@ -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}.
*
* <p>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.

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),

View File

@ -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(),