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