From 9b1f1b1ba1269206ef12c049acf65c6299463fe9 Mon Sep 17 00:00:00 2001 From: Dean Wheatley Date: Fri, 26 Apr 2024 13:51:31 +1000 Subject: [PATCH] Do not flush eac3(joc) and ac4 decoders on reuse eac3(joc) and ac4 decoders (for the same profile and level content) do not need to be flushed to be reused for the next compatible track or across adapative bitrate transitions. This change allows for gapless playback on devices with Dolby decoders that require internal re-initialization on flush. This change introduces a codec check in canReuseCodec, in addition to the existing mimeType check, as within the same mimeType, such as AC-4, there can be codec changes (such as between AC-4 ac-4.02.01.04 for Atmos Objective Based Immersive and ac-4.02.02.00 for AC-4 Immersive Stereo) that may require decoder reinitialization for correct decoding. --- .../exoplayer/DecoderReuseEvaluation.java | 3 + .../exoplayer/mediacodec/MediaCodecInfo.java | 16 +++ .../mediacodec/MediaCodecInfoTest.java | 129 ++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java index 95771c00ce8..1a49dbdbe07 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java @@ -137,6 +137,9 @@ public final class DecoderReuseEvaluation { /** The audio bypass mode is possible. */ public static final int DISCARD_REASON_AUDIO_BYPASS_POSSIBLE = 1 << 15; + /** The codec is changing. */ + public static final int DISCARD_REASON_CODEC_CHANGED = 1 << 16; + /** The name of the decoder. */ public final String decoderName; diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java index 97cd968143f..4db0b78a1f9 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfo.java @@ -19,6 +19,7 @@ import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_ENCODING_CHANGED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED; +import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_CODEC_CHANGED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_INITIALIZATION_DATA_CHANGED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_MIME_TYPE_CHANGED; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED; @@ -439,6 +440,9 @@ public DecoderReuseEvaluation canReuseCodec(Format oldFormat, Format newFormat) if (!Objects.equals(oldFormat.sampleMimeType, newFormat.sampleMimeType)) { discardReasons |= DISCARD_REASON_MIME_TYPE_CHANGED; } + if (!Objects.equals(oldFormat.codecs, newFormat.codecs)) { + discardReasons |= DISCARD_REASON_CODEC_CHANGED; + } if (isVideo) { if (oldFormat.rotationDegrees != newFormat.rotationDegrees) { @@ -516,6 +520,18 @@ public DecoderReuseEvaluation canReuseCodec(Format oldFormat, Format newFormat) } } + // For eac3, eac3-joc and ac4 formats, adaptation is possible without reconfiguration or + // flushing. + if (discardReasons == 0 && (MimeTypes.AUDIO_E_AC3_JOC.equals(mimeType) + || MimeTypes.AUDIO_E_AC3.equals(mimeType) || MimeTypes.AUDIO_AC4.equals(mimeType))) { + return new DecoderReuseEvaluation( + name, + oldFormat, + newFormat, + REUSE_RESULT_YES_WITHOUT_RECONFIGURATION, + /* discardReasons= */ 0); + } + if (!oldFormat.initializationDataEquals(newFormat)) { discardReasons |= DISCARD_REASON_INITIALIZATION_DATA_CHANGED; } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfoTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfoTest.java index ded29cb8f5c..e0df47b03e9 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfoTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/MediaCodecInfoTest.java @@ -16,6 +16,9 @@ package androidx.media3.exoplayer.mediacodec; import static androidx.media3.common.MimeTypes.AUDIO_AAC; +import static androidx.media3.common.MimeTypes.AUDIO_AC4; +import static androidx.media3.common.MimeTypes.AUDIO_E_AC3; +import static androidx.media3.common.MimeTypes.AUDIO_E_AC3_JOC; import static androidx.media3.common.MimeTypes.VIDEO_AV1; import static androidx.media3.common.MimeTypes.VIDEO_H264; import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED; @@ -74,6 +77,30 @@ public final class MediaCodecInfoTest { .setInitializationData(ImmutableList.of(new byte[] {4, 4, 1, 0, 0})) .build(); + private static final Format FORMAT_EAC3 = + new Format.Builder() + .setSampleMimeType(AUDIO_E_AC3) + .setChannelCount(6) + .setSampleRate(48000) + .setAverageBitrate(5000) + .build(); + + private static final Format FORMAT_EAC3JOC = + new Format.Builder() + .setSampleMimeType(AUDIO_E_AC3_JOC) + .setChannelCount(12) + .setSampleRate(48000) + .setAverageBitrate(5000) + .build(); + + private static final Format FORMAT_AC4 = + new Format.Builder() + .setSampleMimeType(AUDIO_AC4) + .setChannelCount(21) + .setSampleRate(48000) + .setAverageBitrate(5000) + .build(); + @Test public void canReuseCodec_withDifferentMimeType_returnsNo() { MediaCodecInfo codecInfo = buildH264CodecInfo(/* adaptive= */ true); @@ -318,6 +345,63 @@ public void canReuseCodec_differentVideoCrop_returnsNo() { DISCARD_REASON_WORKAROUND)); } + @Test + public void canReuseCodec_eac3_returnsYesWithoutReconfiguration() { + MediaCodecInfo codecInfo = buildEac3CodecInfo(); + + Format variantFormat = + FORMAT_EAC3 + .buildUpon() + .setInitializationData(ImmutableList.of(new byte[] {0})) + .build(); + assertThat(codecInfo.canReuseCodec(FORMAT_EAC3, variantFormat)) + .isEqualTo( + new DecoderReuseEvaluation( + codecInfo.name, + FORMAT_EAC3, + variantFormat, + DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION, + /* discardReasons= */ 0)); + } + + @Test + public void canReuseCodec_eac3joc_returnsYesWithoutReconfiguration() { + MediaCodecInfo codecInfo = buildEac3JocCodecInfo(); + + Format variantFormat = + FORMAT_EAC3JOC + .buildUpon() + .setInitializationData(ImmutableList.of(new byte[] {0})) + .build(); + assertThat(codecInfo.canReuseCodec(FORMAT_EAC3JOC, variantFormat)) + .isEqualTo( + new DecoderReuseEvaluation( + codecInfo.name, + FORMAT_EAC3JOC, + variantFormat, + DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION, + /* discardReasons= */ 0)); + } + + @Test + public void canReuseCodec_ac4_returnsYesWithoutReconfiguration() { + MediaCodecInfo codecInfo = buildAc4CodecInfo(); + + Format variantFormat = + FORMAT_AC4 + .buildUpon() + .setInitializationData(ImmutableList.of(new byte[] {0})) + .build(); + assertThat(codecInfo.canReuseCodec(FORMAT_AC4, variantFormat)) + .isEqualTo( + new DecoderReuseEvaluation( + codecInfo.name, + FORMAT_AC4, + variantFormat, + DecoderReuseEvaluation.REUSE_RESULT_YES_WITHOUT_RECONFIGURATION, + /* discardReasons= */ 0)); + } + private static MediaCodecInfo buildH264CodecInfo(boolean adaptive) { return new MediaCodecInfo( "h264", @@ -348,6 +432,51 @@ private static MediaCodecInfo buildAacCodecInfo() { /* detachedSurfaceSupported= */ false); } + private static MediaCodecInfo buildEac3CodecInfo() { + return new MediaCodecInfo( + "eac3joc", + AUDIO_E_AC3, + AUDIO_E_AC3, + /* capabilities= */ null, + /* hardwareAccelerated= */ false, + /* softwareOnly= */ true, + /* vendor= */ true, + /* adaptive= */ false, + /* tunneling= */ false, + /* secure= */ false, + /* detachedSurfaceSupported= */ false); + } + + private static MediaCodecInfo buildEac3JocCodecInfo() { + return new MediaCodecInfo( + "eac3joc", + AUDIO_E_AC3_JOC, + AUDIO_E_AC3_JOC, + /* capabilities= */ null, + /* hardwareAccelerated= */ false, + /* softwareOnly= */ true, + /* vendor= */ true, + /* adaptive= */ false, + /* tunneling= */ false, + /* secure= */ false, + /* detachedSurfaceSupported= */ false); + } + + private static MediaCodecInfo buildAc4CodecInfo() { + return new MediaCodecInfo( + "ac4", + AUDIO_AC4, + AUDIO_AC4, + /* capabilities= */ null, + /* hardwareAccelerated= */ false, + /* softwareOnly= */ true, + /* vendor= */ true, + /* adaptive= */ false, + /* tunneling= */ false, + /* secure= */ false, + /* detachedSurfaceSupported= */ false); + } + private static ColorInfo buildHdrColorInfo(@C.ColorSpace int colorSpace) { return new ColorInfo.Builder() .setColorSpace(colorSpace)