From 3be1d5646fdd36b198410955541e2a4baa5390f7 Mon Sep 17 00:00:00 2001 From: shahddaghash Date: Fri, 21 Mar 2025 02:59:16 -0700 Subject: [PATCH] Add support for explicit passthrough and encoder shadow codec configs This is a step towards unifying ShadowMediaCodecConfig structure to accomodate both ExoPlayer and Transcoding codecs configuration. ExoPlayer's `CodecConfig.Codec` implementation, due to b/174737370, discards all audio samples. This issue is only applicable for ExoPlayer tests and doesn't impact transcoding tests. To accomodate for having transcoding codecs, 2 fields were added to CodecImpl: `isPassThrough` that specifies whether we want to drop frames/samples or not, and `isEncoder` that specifies whether we are configuring a decoder or an encoder. This is a non-functional refactor. PiperOrigin-RevId: 739112659 --- .../robolectric/ShadowMediaCodecConfig.java | 76 ++++++++++++++----- 1 file changed, 55 insertions(+), 21 deletions(-) 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 4280d63dab..28b7933639 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 @@ -23,7 +23,6 @@ import static androidx.media3.exoplayer.mediacodec.MediaCodecUtil.createCodecPro 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; @@ -150,7 +149,9 @@ public final class ShadowMediaCodecConfig extends ExternalResource { MediaCodecInfo.CodecProfileLevel.AVCProfileHigh, MediaCodecInfo.CodecProfileLevel.AVCLevel62)), /* colorFormats= */ ImmutableList.of( - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible), + /* isPassthrough= */ true, + /* isEncoder= */ false)); codecs.put( MimeTypes.VIDEO_H265, new CodecImpl( @@ -160,7 +161,9 @@ public final class ShadowMediaCodecConfig extends ExternalResource { createCodecProfileLevel( CodecProfileLevel.HEVCProfileMain, CodecProfileLevel.HEVCMainTierLevel61)), /* colorFormats= */ ImmutableList.of( - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible), + /* isPassthrough= */ true, + /* isEncoder= */ false)); codecs.put( MimeTypes.VIDEO_MPEG2, new CodecImpl( @@ -171,7 +174,9 @@ public final class ShadowMediaCodecConfig extends ExternalResource { MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain, MediaCodecInfo.CodecProfileLevel.MPEG2LevelML)), /* colorFormats= */ ImmutableList.of( - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible), + /* isPassthrough= */ true, + /* isEncoder= */ false)); codecs.put( MimeTypes.VIDEO_VP9, new CodecImpl( @@ -179,7 +184,9 @@ public final class ShadowMediaCodecConfig extends ExternalResource { /* mimeType= */ MimeTypes.VIDEO_VP9, /* profileLevels= */ ImmutableList.of(), /* colorFormats= */ ImmutableList.of( - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible), + /* isPassthrough= */ true, + /* isEncoder= */ false)); codecs.put( MimeTypes.VIDEO_AV1, new CodecImpl( @@ -187,9 +194,11 @@ public final class ShadowMediaCodecConfig extends ExternalResource { /* mimeType= */ MimeTypes.VIDEO_AV1, /* profileLevels= */ ImmutableList.of(), /* colorFormats= */ ImmutableList.of( - MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible))); + MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible), + /* isPassthrough= */ true, + /* isEncoder= */ false)); - // Audio codecs + // Frame-dropping audio decoders. codecs.put( MimeTypes.AUDIO_AAC, new CodecImpl(/* codecName= */ "exotest.audio.aac", /* mimeType= */ MimeTypes.AUDIO_AAC)); @@ -234,10 +243,8 @@ public final class ShadowMediaCodecConfig extends ExternalResource { } /** - * A {@link ShadowMediaCodec.CodecConfig.Codec} that passes data through without modifying it. - * - *

Note: This currently drops all audio data - removing this restriction is tracked in - * [internal b/174737370]. + * A {@link ShadowMediaCodec.CodecConfig.Codec} that provides pass-through or frame dropping + * encoders and decoders. */ private static final class CodecImpl implements ShadowMediaCodec.CodecConfig.Codec { @@ -245,26 +252,53 @@ public final class ShadowMediaCodecConfig extends ExternalResource { private final String mimeType; private final ImmutableList profileLevels; private final ImmutableList colorFormats; - private final @C.TrackType int trackType; + private final boolean isPassthrough; + private final boolean isEncoder; + /** + * Creates a frame dropping decoder with the specified {@code codecName} and {@code mimeType}. + * + *

This method is equivalent to {@code new CodecImpl(codecName, mimeType, ImmutableList.of(), + * ImmutableList.of(), false, false)}. + * + * @param codecName The name of the codec. + * @param mimeType The MIME type of the codec. + */ public CodecImpl(String codecName, String mimeType) { this( codecName, mimeType, /* profileLevels= */ ImmutableList.of(), - /* colorFormats= */ ImmutableList.of()); + /* colorFormats= */ ImmutableList.of(), + /* isPassthrough= */ false, + /* isEncoder= */ false); } + /** + * Creates an instance. + * + * @param codecName The name of the codec. + * @param mimeType The MIME type of the codec. + * @param profileLevels A list of profiles and levels supported by the codec. + * @param colorFormats A list of color formats supported by the codec. + * @param isPassthrough If {@code true}, the codec acts as a pass-through codec, directly + * copying input data to the output. If {@code false}, the codec drops frames. + * @param isEncoder If {@code true}, the codec is an encoder. If {@code false}, the codec is a + * decoder. + */ public CodecImpl( String codecName, String mimeType, ImmutableList profileLevels, - ImmutableList colorFormats) { + ImmutableList colorFormats, + boolean isPassthrough, + boolean isEncoder) { this.codecName = codecName; this.mimeType = mimeType; this.profileLevels = profileLevels; this.colorFormats = colorFormats; - trackType = MimeTypes.getTrackType(mimeType); + this.isPassthrough = isPassthrough; + this.isEncoder = isEncoder; } public void configure() { @@ -274,7 +308,7 @@ public final class ShadowMediaCodecConfig extends ExternalResource { configureShadowMediaCodec( codecName, mimeType, - /* isEncoder= */ false, + isEncoder, profileLevels, colorFormats, new ShadowMediaCodec.CodecConfig( @@ -285,12 +319,12 @@ public final class ShadowMediaCodecConfig extends ExternalResource { @Override public void process(ByteBuffer in, ByteBuffer out) { - byte[] bytes = new byte[in.remaining()]; - in.get(bytes); - // TODO(internal b/174737370): Output audio bytes as well. - if (trackType != C.TRACK_TYPE_AUDIO) { - out.put(bytes); + if (isPassthrough) { + out.put(in); + } else { + byte[] bytes = new byte[in.remaining()]; + in.get(bytes); } } }