From dae7abbfa6b638a69cb19442e3ff5a2c195ae96f Mon Sep 17 00:00:00 2001 From: christosts Date: Thu, 11 May 2023 14:06:16 +0100 Subject: [PATCH] End-to-end Robolectric playback test for audio capabilities This change adds end-to-end Robolectric playback tests which handle the scenario the player is playing audio via passthrough and AudioTrack raises the ERROR_DEAD_OBJECT error upon which the player attempts to recover by switching to another audio format. PiperOrigin-RevId: 531180183 --- .../exoplayer/audio/AudioCapabilities.java | 3 +- .../test/assets/media/mp4/sample_ac3_aac.mp4 | Bin 0 -> 17284 bytes .../robolectric/ShadowMediaCodecConfig.java | 241 ++++++++++++------ 3 files changed, 165 insertions(+), 79 deletions(-) create mode 100644 libraries/test_data/src/test/assets/media/mp4/sample_ac3_aac.mp4 diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java index c8cab506a0..499a2ae51c 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/AudioCapabilities.java @@ -32,6 +32,7 @@ import android.util.Pair; import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import androidx.annotation.VisibleForTesting; import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; @@ -48,7 +49,7 @@ import java.util.Arrays; public final class AudioCapabilities { private static final int DEFAULT_MAX_CHANNEL_COUNT = 8; - private static final int DEFAULT_SAMPLE_RATE_HZ = 48_000; + @VisibleForTesting /* package */ static final int DEFAULT_SAMPLE_RATE_HZ = 48_000; /** The minimum audio capabilities supported by all devices. */ public static final AudioCapabilities DEFAULT_AUDIO_CAPABILITIES = diff --git a/libraries/test_data/src/test/assets/media/mp4/sample_ac3_aac.mp4 b/libraries/test_data/src/test/assets/media/mp4/sample_ac3_aac.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..40b4f6040cc603142cf21cf724cf9207629edc5e GIT binary patch literal 17284 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 L9~#=s|Iqk9pb6_k literal 0 HcmV?d00001 diff --git a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java index 6e85eed2a0..fdb57338bd 100644 --- a/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java +++ b/libraries/test_utils_robolectric/src/main/java/androidx/media3/test/utils/robolectric/ShadowMediaCodecConfig.java @@ -15,15 +15,24 @@ */ package androidx.media3.test.utils.robolectric; +import static androidx.media3.common.util.Assertions.checkArgument; +import static androidx.media3.common.util.Assertions.checkState; +import static androidx.media3.common.util.Assertions.checkStateNotNull; + import android.media.MediaCodecInfo; +import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaFormat; +import androidx.media3.common.C; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.UnstableApi; import androidx.media3.exoplayer.mediacodec.MediaCodecUtil; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.primitives.Ints; import java.nio.ByteBuffer; -import java.util.List; +import java.util.HashSet; +import java.util.Set; import org.junit.rules.ExternalResource; import org.robolectric.shadows.MediaCodecInfoBuilder; import org.robolectric.shadows.ShadowMediaCodec; @@ -37,98 +46,129 @@ import org.robolectric.shadows.ShadowMediaCodecList; */ @UnstableApi public final class ShadowMediaCodecConfig extends ExternalResource { + private static final ImmutableMap ALL_SUPPORTED_CODECS = + createAllSupportedCodecs(); public static ShadowMediaCodecConfig forAllSupportedMimeTypes() { - return new ShadowMediaCodecConfig(); + return new ShadowMediaCodecConfig(ALL_SUPPORTED_CODECS.keySet()); + } + + public static ShadowMediaCodecConfig withNoDefaultSupportedMimeTypes() { + return new ShadowMediaCodecConfig(ImmutableSet.of()); + } + + private final Set supportedMimeTypes; + + private ShadowMediaCodecConfig(Set mimeTypes) { + supportedMimeTypes = new HashSet<>(mimeTypes); + } + + public void addSupportedMimeTypes(String... mimeTypes) { + for (String mimeType : mimeTypes) { + checkState(!supportedMimeTypes.contains(mimeType), "MIME type already added: " + mimeType); + checkArgument( + ALL_SUPPORTED_CODECS.containsKey(mimeType), "MIME type not supported: " + mimeType); + } + ImmutableSet addedMimeTypes = ImmutableSet.copyOf(mimeTypes); + supportedMimeTypes.addAll(addedMimeTypes); + configureCodecs(addedMimeTypes); } @Override protected void before() throws Throwable { - // Video codecs - MediaCodecInfo.CodecProfileLevel avcProfileLevel = - createProfileLevel( - MediaCodecInfo.CodecProfileLevel.AVCProfileHigh, - MediaCodecInfo.CodecProfileLevel.AVCLevel62); - configureCodec( - /* codecName= */ "exotest.video.avc", - MimeTypes.VIDEO_H264, - ImmutableList.of(avcProfileLevel), - ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); - MediaCodecInfo.CodecProfileLevel mpeg2ProfileLevel = - createProfileLevel( - MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain, - MediaCodecInfo.CodecProfileLevel.MPEG2LevelML); - configureCodec( - /* codecName= */ "exotest.video.mpeg2", - MimeTypes.VIDEO_MPEG2, - ImmutableList.of(mpeg2ProfileLevel), - ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); - configureCodec( - /* codecName= */ "exotest.video.vp9", - MimeTypes.VIDEO_VP9, - ImmutableList.of(), - ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)); - - // Audio codecs - configureCodec("exotest.audio.aac", MimeTypes.AUDIO_AAC); - configureCodec("exotest.audio.ac3", MimeTypes.AUDIO_AC3); - configureCodec("exotest.audio.ac4", MimeTypes.AUDIO_AC4); - configureCodec("exotest.audio.eac3", MimeTypes.AUDIO_E_AC3); - configureCodec("exotest.audio.eac3joc", MimeTypes.AUDIO_E_AC3_JOC); - configureCodec("exotest.audio.flac", MimeTypes.AUDIO_FLAC); - configureCodec("exotest.audio.mpeg", MimeTypes.AUDIO_MPEG); - configureCodec("exotest.audio.mpegl2", MimeTypes.AUDIO_MPEG_L2); - configureCodec("exotest.audio.opus", MimeTypes.AUDIO_OPUS); - configureCodec("exotest.audio.vorbis", MimeTypes.AUDIO_VORBIS); - - // Raw audio should use a bypass mode and never need this codec. However, to easily assert - // failures of the bypass mode we want to detect when the raw audio is decoded by this class and - // thus we need a codec to output samples. - configureCodec("exotest.audio.raw", MimeTypes.AUDIO_RAW); + configureCodecs(supportedMimeTypes); } @Override protected void after() { + supportedMimeTypes.clear(); MediaCodecUtil.clearDecoderInfoCache(); ShadowMediaCodecList.reset(); ShadowMediaCodec.clearCodecs(); } - private void configureCodec(String codecName, String mimeType) { - configureCodec( - codecName, - mimeType, - /* profileLevels= */ ImmutableList.of(), - /* colorFormats= */ ImmutableList.of()); + private void configureCodecs(Set mimeTypes) { + for (String mimeType : mimeTypes) { + checkStateNotNull(ALL_SUPPORTED_CODECS.get(mimeType)).configure(); + } } - private void configureCodec( - String codecName, - String mimeType, - List profileLevels, - List colorFormats) { - MediaFormat mediaFormat = new MediaFormat(); - mediaFormat.setString(MediaFormat.KEY_MIME, mimeType); - MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities = - MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder().setMediaFormat(mediaFormat); - if (!profileLevels.isEmpty()) { - capabilities.setProfileLevels(profileLevels.toArray(new MediaCodecInfo.CodecProfileLevel[0])); - } - if (!colorFormats.isEmpty()) { - capabilities.setColorFormats(Ints.toArray(colorFormats)); - } - ShadowMediaCodecList.addCodec( - MediaCodecInfoBuilder.newBuilder() - .setName(codecName) - .setCapabilities(capabilities.build()) - .build()); - // TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed - // to configure() so we don't have to specify large buffers here. - CodecImpl codec = new CodecImpl(mimeType); - ShadowMediaCodec.addDecoder( - codecName, - new ShadowMediaCodec.CodecConfig( - /* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, codec)); + private static ImmutableMap createAllSupportedCodecs() { + ImmutableMap.Builder codecs = new ImmutableMap.Builder<>(); + // Video codecs + codecs.put( + MimeTypes.VIDEO_H264, + new CodecImpl( + /* codecName= */ "exotest.video.avc", + /* mimeType= */ MimeTypes.VIDEO_H264, + /* profileLevels= */ ImmutableList.of( + createProfileLevel( + MediaCodecInfo.CodecProfileLevel.AVCProfileHigh, + MediaCodecInfo.CodecProfileLevel.AVCLevel62)), + /* colorFormats= */ ImmutableList.of( + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + codecs.put( + MimeTypes.VIDEO_MPEG2, + new CodecImpl( + /* codecName= */ "exotest.video.mpeg2", + /* mimeType= */ MimeTypes.VIDEO_MPEG2, + /* profileLevels= */ ImmutableList.of( + createProfileLevel( + MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain, + MediaCodecInfo.CodecProfileLevel.MPEG2LevelML)), + /* colorFormats= */ ImmutableList.of( + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + codecs.put( + MimeTypes.VIDEO_VP9, + new CodecImpl( + /* codecName= */ "exotest.video.vp9", + /* mimeType= */ MimeTypes.VIDEO_VP9, + /* profileLevels= */ ImmutableList.of(), + /* colorFormats= */ ImmutableList.of( + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + + // Audio codecs + codecs.put( + MimeTypes.AUDIO_AAC, + new CodecImpl(/* codecName= */ "exotest.audio.aac", /* mimeType= */ MimeTypes.AUDIO_AAC)); + codecs.put( + MimeTypes.AUDIO_AC3, + new CodecImpl(/* codecName= */ "exotest.audio.ac3", /* mimeType= */ MimeTypes.AUDIO_AC3)); + codecs.put( + MimeTypes.AUDIO_AC4, + new CodecImpl(/* codecName= */ "exotest.audio.ac4", /* mimeType= */ MimeTypes.AUDIO_AC4)); + codecs.put( + MimeTypes.AUDIO_E_AC3, + new CodecImpl( + /* codecName= */ "exotest.audio.eac3", /* mimeType= */ MimeTypes.AUDIO_E_AC3)); + codecs.put( + MimeTypes.AUDIO_E_AC3_JOC, + new CodecImpl( + /* codecName= */ "exotest.audio.eac3joc", /* mimeType= */ MimeTypes.AUDIO_E_AC3_JOC)); + codecs.put( + MimeTypes.AUDIO_FLAC, + new CodecImpl(/* codecName= */ "exotest.audio.flac", /* mimeType= */ MimeTypes.AUDIO_FLAC)); + codecs.put( + MimeTypes.AUDIO_MPEG, + new CodecImpl(/* codecName= */ "exotest.audio.mpeg", /* mimeType= */ MimeTypes.AUDIO_MPEG)); + codecs.put( + MimeTypes.AUDIO_MPEG_L2, + new CodecImpl( + /* codecName= */ "exotest.audio.mpegl2", /* mimeType= */ MimeTypes.AUDIO_MPEG_L2)); + codecs.put( + MimeTypes.AUDIO_OPUS, + new CodecImpl(/* codecName= */ "exotest.audio.opus", /* mimeType= */ MimeTypes.AUDIO_OPUS)); + codecs.put( + MimeTypes.AUDIO_VORBIS, + new CodecImpl( + /* codecName= */ "exotest.audio.vorbis", /* mimeType= */ MimeTypes.AUDIO_VORBIS)); + // Raw audio should use a bypass mode and never need this codec. However, to easily assert + // failures of the bypass mode we want to detect when the raw audio is decoded by this + codecs.put( + MimeTypes.AUDIO_RAW, + new CodecImpl(/* codecName= */ "exotest.audio.raw", /* mimeType= */ MimeTypes.AUDIO_RAW)); + + return codecs.buildOrThrow(); } private static MediaCodecInfo.CodecProfileLevel createProfileLevel(int profile, int level) { @@ -146,10 +186,55 @@ public final class ShadowMediaCodecConfig extends ExternalResource { */ private static final class CodecImpl implements ShadowMediaCodec.CodecConfig.Codec { + private final String codecName; private final String mimeType; + private final ImmutableList profileLevels; + private final ImmutableList colorFormats; + private final @C.TrackType int trackType; - public CodecImpl(String mimeType) { + public CodecImpl(String codecName, String mimeType) { + this( + codecName, + mimeType, + /* profileLevels= */ ImmutableList.of(), + /* colorFormats= */ ImmutableList.of()); + } + + public CodecImpl( + String codecName, + String mimeType, + ImmutableList profileLevels, + ImmutableList colorFormats) { + this.codecName = codecName; this.mimeType = mimeType; + this.profileLevels = profileLevels; + this.colorFormats = colorFormats; + trackType = MimeTypes.getTrackType(mimeType); + } + + public void configure() { + MediaFormat mediaFormat = new MediaFormat(); + mediaFormat.setString(MediaFormat.KEY_MIME, mimeType); + MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities = + MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder().setMediaFormat(mediaFormat); + if (!profileLevels.isEmpty()) { + capabilities.setProfileLevels( + profileLevels.toArray(new MediaCodecInfo.CodecProfileLevel[0])); + } + if (!colorFormats.isEmpty()) { + capabilities.setColorFormats(Ints.toArray(colorFormats)); + } + ShadowMediaCodecList.addCodec( + MediaCodecInfoBuilder.newBuilder() + .setName(codecName) + .setCapabilities(capabilities.build()) + .build()); + // TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed + // to configure() so we don't have to specify large buffers here. + ShadowMediaCodec.addDecoder( + codecName, + new ShadowMediaCodec.CodecConfig( + /* inputBufferSize= */ 100_000, /* outputBufferSize= */ 100_000, this)); } @Override @@ -158,7 +243,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource { in.get(bytes); // TODO(internal b/174737370): Output audio bytes as well. - if (!MimeTypes.isAudio(mimeType)) { + if (trackType != C.TRACK_TYPE_AUDIO) { out.put(bytes); } }