Refactor ShadowMediaCodec to be used for transcoding cases

This is a step towards unifying ShadowMediaCodecConfig for ExoPlayer and Transcoding tests.

This change includes extracting encoder/decoder configurtion logic to a static method that can be called by `ShadowMediaCodecConfig.CodecImpl#configure()` and `TestUtil#addCodec()`.

This is a non-functional refactor.

PiperOrigin-RevId: 734137675
This commit is contained in:
shahddaghash 2025-03-06 07:36:49 -08:00 committed by Copybara-Service
parent c95516f0ab
commit c030e49dd6
2 changed files with 61 additions and 45 deletions

View File

@ -63,6 +63,49 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
supportedMimeTypes = new HashSet<>(mimeTypes); supportedMimeTypes = new HashSet<>(mimeTypes);
} }
/**
* Configures a shadow MediaCodec.
*
* @param codecName The name of the codec.
* @param mimeType The MIME type of the codec.
* @param isEncoder Whether the codec is an encoder or a decoder.
* @param profileLevels A list of profiles and levels supported by the codec.
* @param colorFormats A list of color formats supported by the codec.
* @param codecConfig The {@link ShadowMediaCodec.CodecConfig} for the codec, specifying its
* behavior.
*/
public static void configureShadowMediaCodec(
String codecName,
String mimeType,
boolean isEncoder,
ImmutableList<CodecProfileLevel> profileLevels,
ImmutableList<Integer> colorFormats,
ShadowMediaCodec.CodecConfig codecConfig) {
MediaFormat mediaFormat = new MediaFormat();
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities =
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
.setMediaFormat(mediaFormat)
.setIsEncoder(isEncoder);
if (!profileLevels.isEmpty()) {
capabilities.setProfileLevels(profileLevels.toArray(new CodecProfileLevel[0]));
}
if (!colorFormats.isEmpty()) {
capabilities.setColorFormats(Ints.toArray(colorFormats));
}
ShadowMediaCodecList.addCodec(
MediaCodecInfoBuilder.newBuilder()
.setName(codecName)
.setIsEncoder(isEncoder)
.setCapabilities(capabilities.build())
.build());
if (isEncoder) {
ShadowMediaCodec.addEncoder(codecName, codecConfig);
} else {
ShadowMediaCodec.addDecoder(codecName, codecConfig);
}
}
public void addSupportedMimeTypes(String... mimeTypes) { public void addSupportedMimeTypes(String... mimeTypes) {
for (String mimeType : mimeTypes) { for (String mimeType : mimeTypes) {
checkState(!supportedMimeTypes.contains(mimeType), "MIME type already added: " + mimeType); checkState(!supportedMimeTypes.contains(mimeType), "MIME type already added: " + mimeType);
@ -231,29 +274,19 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
} }
public void configure() { 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 // 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. // to configure() so we don't have to specify large buffers here.
int bufferSize = mimeType.equals(MimeTypes.VIDEO_H265) ? 250_000 : 150_000; int bufferSize = mimeType.equals(MimeTypes.VIDEO_H265) ? 250_000 : 150_000;
ShadowMediaCodec.addDecoder( configureShadowMediaCodec(
codecName, codecName,
mimeType,
/* isEncoder= */ false,
profileLevels,
colorFormats,
new ShadowMediaCodec.CodecConfig( new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ bufferSize, /* outputBufferSize= */ bufferSize, this)); /* inputBufferSize= */ bufferSize,
/* outputBufferSize= */ bufferSize,
/* codec= */ this));
} }
@Override @Override

View File

@ -15,7 +15,8 @@
*/ */
package androidx.media3.transformer; package androidx.media3.transformer;
import android.media.MediaFormat; import static androidx.media3.test.utils.robolectric.ShadowMediaCodecConfig.configureShadowMediaCodec;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.audio.AudioProcessor;
import androidx.media3.common.audio.ChannelMixingAudioProcessor; import androidx.media3.common.audio.ChannelMixingAudioProcessor;
@ -24,10 +25,8 @@ import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import java.util.List; import java.util.List;
import java.util.StringJoiner; import java.util.StringJoiner;
import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodec; import org.robolectric.shadows.ShadowMediaCodec;
import org.robolectric.shadows.ShadowMediaCodecList; import org.robolectric.shadows.ShadowMediaCodecList;
@ -209,33 +208,17 @@ public final class TestUtil {
private static void addCodec( private static void addCodec(
String mimeType, String mimeType,
ShadowMediaCodec.CodecConfig codecConfig, ShadowMediaCodec.CodecConfig codecConfig,
List<Integer> colorFormats, ImmutableList<Integer> colorFormats,
boolean isDecoder) { boolean isDecoder) {
String codecName = String codecName =
Util.formatInvariant( Util.formatInvariant(
isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-')); isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-'));
if (isDecoder) { configureShadowMediaCodec(
ShadowMediaCodec.addDecoder(codecName, codecConfig); codecName,
} else { mimeType,
ShadowMediaCodec.addEncoder(codecName, codecConfig); !isDecoder,
} /* profileLevels= */ ImmutableList.of(),
colorFormats,
MediaFormat mediaFormat = new MediaFormat(); codecConfig);
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
MediaCodecInfoBuilder.CodecCapabilitiesBuilder codecCapabilities =
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder()
.setMediaFormat(mediaFormat)
.setIsEncoder(!isDecoder);
if (!colorFormats.isEmpty()) {
codecCapabilities.setColorFormats(Ints.toArray(colorFormats));
}
ShadowMediaCodecList.addCodec(
MediaCodecInfoBuilder.newBuilder()
.setName(codecName)
.setIsEncoder(!isDecoder)
.setCapabilities(codecCapabilities.build())
.build());
} }
} }