Commit c47e6220 by olly Committed by Ian Baker

Report reasons for not being able to reuse decoders

PiperOrigin-RevId: 342344090
parent 3ef609fa
Showing with 612 additions and 153 deletions
......@@ -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)).
......
......@@ -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);
}
}
......@@ -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);
}
}
......@@ -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);
}
}
......@@ -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);
}
}
......
......@@ -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);
});
}
......
......@@ -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) {}
......@@ -483,12 +484,25 @@ public interface AnalyticsListener {
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
......@@ -590,12 +604,25 @@ public interface AnalyticsListener {
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.
......
......@@ -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));
}
}
......
......@@ -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) {
......
......@@ -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));
}
}
......
/*
* 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;
}
}
......@@ -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));
}
......
......@@ -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.
......
......@@ -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;
}
if (getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) {
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);
}
@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);
......
......@@ -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));
}
}
......
......@@ -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
......
......@@ -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
......
......@@ -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(),
......
......@@ -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(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment