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);
}
/**
* 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) {
for (String mimeType : mimeTypes) {
checkState(!supportedMimeTypes.contains(mimeType), "MIME type already added: " + mimeType);
@ -231,29 +274,19 @@ public final class ShadowMediaCodecConfig extends ExternalResource {
}
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.
int bufferSize = mimeType.equals(MimeTypes.VIDEO_H265) ? 250_000 : 150_000;
ShadowMediaCodec.addDecoder(
configureShadowMediaCodec(
codecName,
mimeType,
/* isEncoder= */ false,
profileLevels,
colorFormats,
new ShadowMediaCodec.CodecConfig(
/* inputBufferSize= */ bufferSize, /* outputBufferSize= */ bufferSize, this));
/* inputBufferSize= */ bufferSize,
/* outputBufferSize= */ bufferSize,
/* codec= */ this));
}
@Override

View File

@ -15,7 +15,8 @@
*/
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.audio.AudioProcessor;
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.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import java.util.List;
import java.util.StringJoiner;
import org.robolectric.shadows.MediaCodecInfoBuilder;
import org.robolectric.shadows.ShadowMediaCodec;
import org.robolectric.shadows.ShadowMediaCodecList;
@ -209,33 +208,17 @@ public final class TestUtil {
private static void addCodec(
String mimeType,
ShadowMediaCodec.CodecConfig codecConfig,
List<Integer> colorFormats,
ImmutableList<Integer> colorFormats,
boolean isDecoder) {
String codecName =
Util.formatInvariant(
isDecoder ? "exo.%s.decoder" : "exo.%s.encoder", mimeType.replace('/', '-'));
if (isDecoder) {
ShadowMediaCodec.addDecoder(codecName, codecConfig);
} else {
ShadowMediaCodec.addEncoder(codecName, codecConfig);
}
MediaFormat mediaFormat = new MediaFormat();
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());
configureShadowMediaCodec(
codecName,
mimeType,
!isDecoder,
/* profileLevels= */ ImmutableList.of(),
colorFormats,
codecConfig);
}
}