From d436a69d8f50df98677a7ff7f619623394e9bada Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 26 Oct 2020 22:39:26 +0000 Subject: [PATCH] Centralize canKeepCodec logic Logic for determining if (and how) decoders can be adapted is currently split between renderers and MediaCodecInfo. This change centralizes the majority of the logic in MediaCodecInfo. This change also fixes a bug in MediaCodecAudioRenderer when computing max values for the codec. Previously, max values would not be increased to account for potential adaptation to another stream in the case that the codec needs to be flushed for the adaptation to occur. PiperOrigin-RevId: 339133416 --- .../audio/MediaCodecAudioRenderer.java | 38 +----- .../exoplayer2/mediacodec/MediaCodecInfo.java | 103 ++++++++++++--- .../mediacodec/MediaCodecRenderer.java | 35 ++--- .../video/MediaCodecVideoRenderer.java | 27 ++-- .../mediacodec/MediaCodecInfoTest.java | 122 ++++++++++++++++++ 5 files changed, 235 insertions(+), 90 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 55deaadd65..50e7723cd8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.audio; +import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static java.lang.Math.max; @@ -42,6 +43,7 @@ import com.google.android.exoplayer2.audio.AudioSink.WriteException; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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,40 +330,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media } @Override - protected @KeepCodecResult int canKeepCodec( + @KeepCodecResult + protected int canKeepCodec( MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) { return KEEP_CODEC_RESULT_NO; - } else if (codecInfo.isSeamlessAdaptationSupported( - oldFormat, newFormat, /* isNewFormatComplete= */ true)) { - return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION; - } else if (canKeepCodecWithFlush(oldFormat, newFormat)) { - return KEEP_CODEC_RESULT_YES_WITH_FLUSH; - } else { - return KEEP_CODEC_RESULT_NO; } - } - - /** - * Returns whether the codec can be flushed and reused when switching to a new format. Reuse is - * generally possible when the codec would be configured in an identical way after the format - * change (excluding {@link MediaFormat#KEY_MAX_INPUT_SIZE} and configuration that does not come - * from the {@link Format}). - * - * @param oldFormat The first format. - * @param newFormat The second format. - * @return Whether the codec can be flushed and reused when switching to a new format. - */ - protected boolean canKeepCodecWithFlush(Format oldFormat, Format newFormat) { - // Flush and reuse the codec if the audio format and initialization data matches. For Opus, we - // don't flush and reuse the codec because the decoder may discard samples after flushing, which - // would result in audio being dropped just after a stream change (see [Internal: b/143450854]). - return Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType) - && oldFormat.channelCount == newFormat.channelCount - && oldFormat.sampleRate == newFormat.sampleRate - && oldFormat.pcmEncoding == newFormat.pcmEncoding - && oldFormat.initializationDataEquals(newFormat) - && !MimeTypes.AUDIO_OPUS.equals(oldFormat.sampleMimeType); + return codecInfo.canKeepCodec(oldFormat, newFormat); } @Override @@ -698,8 +673,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media return maxInputSize; } for (Format streamFormat : streamFormats) { - if (codecInfo.isSeamlessAdaptationSupported( - format, streamFormat, /* isNewFormatComplete= */ false)) { + if (codecInfo.canKeepCodec(format, streamFormat) != KEEP_CODEC_RESULT_NO) { maxInputSize = max(maxInputSize, getCodecMaxInputSize(codecInfo, streamFormat)); } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java index 9a1daf9600..ced56b53f3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfo.java @@ -22,6 +22,7 @@ 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; @@ -30,6 +31,9 @@ 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") @@ -43,10 +47,32 @@ 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. - *

- * May be passed to {@link MediaCodec#createByCodecName(String)} to create an instance of the + * + *

May be passed to {@link MediaCodec#createByCodecName(String)} to create an instance of the * decoder. */ public final String name; @@ -300,11 +326,12 @@ public final class MediaCodecInfo { } /** - * Returns whether it may be possible to adapt to playing a different format when the codec is - * configured to play media in the specified {@code format}. For adaptation to succeed, the codec - * must also be configured with appropriate maximum values and {@link - * #isSeamlessAdaptationSupported(Format, Format, boolean)} must return {@code true} for the - * old/new formats. + * Returns whether it may be possible to adapt an instance of this decoder to playing a different + * format when the codec is configured to play media in the specified {@code format}. + * + *

For adaptation to succeed, the codec must also be configured with appropriate maximum values + * and {@link #isSeamlessAdaptationSupported(Format, Format, boolean)} must return {@code true} + * for the old/new formats. * * @param format The format of media for which the decoder will be configured. * @return Whether adaptation may be possible @@ -319,36 +346,66 @@ public final class MediaCodecInfo { } /** - * Returns whether it is possible to adapt the decoder seamlessly from {@code oldFormat} to {@code - * newFormat}. If {@code newFormat} may not be completely populated, pass {@code false} for {@code - * isNewFormatComplete}. + * Returns whether it is possible to adapt an instance of this decoder seamlessly from {@code + * oldFormat} to {@code newFormat}. If {@code newFormat} may not be completely populated, pass + * {@code false} for {@code isNewFormatComplete}. + * + *

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. * @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 public boolean isSeamlessAdaptationSupported( Format oldFormat, Format newFormat, boolean isNewFormatComplete) { + 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; + } + + /** + * 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. + * + *

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. + */ + @KeepCodecResult + public int canKeepCodec(Format oldFormat, Format newFormat) { if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) { - return false; + return KEEP_CODEC_RESULT_NO; } if (isVideo) { - return oldFormat.rotationDegrees == newFormat.rotationDegrees + if (oldFormat.rotationDegrees == newFormat.rotationDegrees && (adaptive || (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height)) - && ((!isNewFormatComplete && newFormat.colorInfo == null) - || Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)); + && Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)) { + return oldFormat.initializationDataEquals(newFormat) + ? KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION + : KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION; + } } else { if (oldFormat.channelCount != newFormat.channelCount - || oldFormat.sampleRate != newFormat.sampleRate) { - return false; + || oldFormat.sampleRate != newFormat.sampleRate + || oldFormat.pcmEncoding != newFormat.pcmEncoding) { + return KEEP_CODEC_RESULT_NO; } // Check whether we're adapting between two xHE-AAC formats, for which adaptation is possible - // without reconfiguration. + // without reconfiguration or flushing. if (MimeTypes.AUDIO_AAC.equals(mimeType)) { @Nullable Pair oldCodecProfileLevel = @@ -361,13 +418,21 @@ public final class MediaCodecInfo { int newProfile = newCodecProfileLevel.first; if (oldProfile == CodecProfileLevel.AACObjectXHE && newProfile == CodecProfileLevel.AACObjectXHE) { - return true; + return KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION; } } } - return false; + // For Opus, we don't flush and reuse the codec because the decoder may discard samples after + // flushing, which would result in audio being dropped just after a stream change (see + // [Internal: b/143450854]). For other formats, we allow reuse after flushing if the codec + // initialization data is unchanged. + if (!MimeTypes.AUDIO_OPUS.equals(mimeType) && oldFormat.initializationDataEquals(newFormat)) { + return KEEP_CODEC_RESULT_YES_WITH_FLUSH; + } } + + return KEEP_CODEC_RESULT_NO; } /** diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index 89bdefba58..b21d215277 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -15,6 +15,10 @@ */ 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.util.Assertions.checkState; import static java.lang.Math.max; @@ -43,6 +47,7 @@ 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; @@ -192,31 +197,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { // pending output streams that have fewer frames than the codec latency. private static final int MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT = 10; - /** - * The possible return values for {@link #canKeepCodec(MediaCodec, MediaCodecInfo, 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 - }) - protected @interface KeepCodecResult {} - /** The codec cannot be kept. */ - protected static final int KEEP_CODEC_RESULT_NO = 0; - /** The codec can be kept, but must be flushed. */ - protected 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. - */ - protected 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. */ - protected static final int KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION = 3; - @Documented @Retention(RetentionPolicy.SOURCE) @IntDef({ @@ -1574,7 +1554,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * Determines whether the existing {@link MediaCodec} can be kept for a new {@link Format}, and if * it can whether it requires reconfiguration. * - *

The default implementation returns {@link #KEEP_CODEC_RESULT_NO}. + *

The default implementation returns {@link MediaCodecInfo#KEEP_CODEC_RESULT_NO}. * * @param codec The existing {@link MediaCodec} instance. * @param codecInfo A {@link MediaCodecInfo} describing the decoder. @@ -1582,7 +1562,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { * @param newFormat The new {@link Format}. * @return Whether the instance can be kept, and if it can whether it requires reconfiguration. */ - protected @KeepCodecResult int canKeepCodec( + @KeepCodecResult + protected int canKeepCodec( MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { return KEEP_CODEC_RESULT_NO; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 55415b5d33..eb0cd994eb 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.video; +import static com.google.android.exoplayer2.mediacodec.MediaCodecInfo.KEEP_CODEC_RESULT_NO; import static java.lang.Math.max; import static java.lang.Math.min; @@ -49,6 +50,7 @@ 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; @@ -565,18 +567,15 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } @Override - protected @KeepCodecResult int canKeepCodec( + @KeepCodecResult + protected int canKeepCodec( MediaCodec codec, MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) { - if (codecInfo.isSeamlessAdaptationSupported( - oldFormat, newFormat, /* isNewFormatComplete= */ true) - && newFormat.width <= codecMaxValues.width - && newFormat.height <= codecMaxValues.height - && getMaxInputSize(codecInfo, newFormat) <= codecMaxValues.inputSize) { - return oldFormat.initializationDataEquals(newFormat) - ? KEEP_CODEC_RESULT_YES_WITHOUT_RECONFIGURATION - : KEEP_CODEC_RESULT_YES_WITH_RECONFIGURATION; + if (newFormat.width > codecMaxValues.width + || newFormat.height > codecMaxValues.height + || getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) { + return KEEP_CODEC_RESULT_NO; } - return KEEP_CODEC_RESULT_NO; + return codecInfo.canKeepCodec(oldFormat, newFormat); } @CallSuper @@ -1325,8 +1324,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } boolean haveUnknownDimensions = false; for (Format streamFormat : streamFormats) { - if (codecInfo.isSeamlessAdaptationSupported( - format, streamFormat, /* isNewFormatComplete= */ false)) { + if (format.colorInfo != null && streamFormat.colorInfo == null) { + // streamFormat likely has incomplete color information. Copy the complete color information + // 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) { haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE || streamFormat.height == Format.NO_VALUE); maxWidth = max(maxWidth, streamFormat.width); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java index 488f58b573..efef2b47b3 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/MediaCodecInfoTest.java @@ -15,6 +15,9 @@ */ 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.util.MimeTypes.AUDIO_AAC; import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_AV1; import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_H264; @@ -64,6 +67,114 @@ public final class MediaCodecInfoTest { .build(); @Test + public void canKeepCodec_withDifferentMimeType_returnsNo() { + 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); + } + + @Test + public void canKeepCodec_withRotation_returnsNo() { + 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); + } + + @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); + } + + @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); + } + + @Test + public void canKeepCodec_noResolutionChange_nonAdaptiveCodec_returnsYesWithReconfiguration() { + MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); + + 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); + } + + @Test + public void canKeepCodec_colorInfoOmittedFromNewFormat_returnsNo() { + MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); + + 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); + } + + @Test + public void canKeepCodec_colorInfoOmittedFromOldFormat_returnsNo() { + MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); + + 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); + } + + @Test + public void canKeepCodec_colorInfoChange_returnsNo() { + MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); + + Format hdrVariantFormat1 = + 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); + } + + @Test + public void canKeepCodec_audioWithDifferentChannelCounts_returnsNo() { + MediaCodecInfo codecInfo = buildAacCodecInfo(); + + assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, FORMAT_AAC_SURROUND)) + .isEqualTo(KEEP_CODEC_RESULT_NO); + } + + @Test + public void canKeepCodec_audioWithSameChannelCounts_returnsYesWithFlush() { + 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); + } + + @Test + public void canKeepCodec_audioWithDifferentInitializationData_returnsNo() { + MediaCodecInfo codecInfo = buildAacCodecInfo(); + + Format stereoVariantFormat = + FORMAT_AAC_STEREO + .buildUpon() + .setInitializationData(ImmutableList.of(new byte[] {0})) + .build(); + assertThat(codecInfo.canKeepCodec(FORMAT_AAC_STEREO, stereoVariantFormat)) + .isEqualTo(KEEP_CODEC_RESULT_NO); + } + + @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_withDifferentMimeType_returnsFalse() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true); @@ -75,6 +186,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_withRotation_returnsFalse() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true); @@ -86,6 +198,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_withResolutionChange_adaptiveCodec_returnsTrue() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true); @@ -96,6 +209,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_withResolutionChange_nonAdaptiveCodec_returnsFalse() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); @@ -106,6 +220,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_noResolutionChange_nonAdaptiveCodec_returnsTrue() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); @@ -118,6 +233,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_colorInfoOmittedFromCompleteNewFormat_returnsFalse() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); @@ -130,6 +246,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_colorInfoOmittedFromIncompleteNewFormat_returnsTrue() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); @@ -142,6 +259,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_colorInfoOmittedFromOldFormat_returnsFalse() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); @@ -154,6 +272,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_colorInfoChange_returnsFalse() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ false); @@ -172,6 +291,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_audioWithDifferentChannelCounts_returnsFalse() { MediaCodecInfo codecInfo = buildAacCodecInfo(); @@ -182,6 +302,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_audioWithSameChannelCounts_returnsFalse() { MediaCodecInfo codecInfo = buildAacCodecInfo(); @@ -193,6 +314,7 @@ public final class MediaCodecInfoTest { } @Test + @SuppressWarnings("deprecation") public void isSeamlessAdaptationSupported_audioWithDifferentInitializationData_returnsFalse() { MediaCodecInfo codecInfo = buildAacCodecInfo();