diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java index a977796149..eddd5a17cb 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java @@ -21,17 +21,20 @@ import static java.util.concurrent.TimeUnit.SECONDS; import android.content.Context; import android.net.Uri; import androidx.annotation.Nullable; +import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.util.Log; import androidx.media3.common.util.SystemClock; import androidx.test.platform.app.InstrumentationRegistry; import java.io.File; import java.io.IOException; +import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import org.checkerframework.checker.nullness.compatqual.NullableType; +import org.json.JSONException; import org.json.JSONObject; /** An android instrumentation test runner for {@link Transformer}. */ @@ -140,6 +143,7 @@ public class TransformerAndroidTestRunner { } private final Context context; + private final CodecNameForwardingEncoderFactory transformerEncoderFactory; private final Transformer transformer; private final int timeoutSeconds; private final boolean calculateSsim; @@ -154,7 +158,9 @@ public class TransformerAndroidTestRunner { boolean suppressAnalysisExceptions, @Nullable Map inputValues) { this.context = context; - this.transformer = transformer; + this.transformerEncoderFactory = + new CodecNameForwardingEncoderFactory(transformer.encoderFactory); + this.transformer = transformer.buildUpon().setEncoderFactory(transformerEncoderFactory).build(); this.timeoutSeconds = timeoutSeconds; this.calculateSsim = calculateSsim; this.suppressAnalysisExceptions = suppressAnalysisExceptions; @@ -186,6 +192,7 @@ public class TransformerAndroidTestRunner { resultJson.put("exception", AndroidTestUtil.exceptionAsJsonObject(e)); throw e; } finally { + resultJson.put("codecDetails", transformerEncoderFactory.getCodecNamesAsJsonObject()); AndroidTestUtil.writeTestSummaryToFile(context, testId, resultJson); } } @@ -307,4 +314,57 @@ public class TransformerAndroidTestRunner { } return resultBuilder.build(); } + + /** + * A {@link Codec.EncoderFactory} that forwards all methods to another encoder factory, whilst + * providing visibility into the names of last codecs created by it. + */ + private static class CodecNameForwardingEncoderFactory implements Codec.EncoderFactory { + + @Nullable public String audioEncoderName; + @Nullable public String videoEncoderName; + + private final Codec.EncoderFactory encoderFactory; + + public CodecNameForwardingEncoderFactory(Codec.EncoderFactory encoderFactory) { + this.encoderFactory = encoderFactory; + } + + @Override + public Codec createForAudioEncoding(Format format, List allowedMimeTypes) + throws TransformationException { + Codec audioEncoder = encoderFactory.createForAudioEncoding(format, allowedMimeTypes); + audioEncoderName = audioEncoder.getName(); + return audioEncoder; + } + + @Override + public Codec createForVideoEncoding(Format format, List allowedMimeTypes) + throws TransformationException { + Codec videoEncoder = encoderFactory.createForVideoEncoding(format, allowedMimeTypes); + videoEncoderName = videoEncoder.getName(); + return videoEncoder; + } + + @Override + public boolean audioNeedsEncoding() { + return encoderFactory.audioNeedsEncoding(); + } + + @Override + public boolean videoNeedsEncoding() { + return encoderFactory.videoNeedsEncoding(); + } + + public JSONObject getCodecNamesAsJsonObject() throws JSONException { + JSONObject detailsJson = new JSONObject(); + if (audioEncoderName != null) { + detailsJson.put("audioEncoderName", audioEncoderName); + } + if (videoEncoderName != null) { + detailsJson.put("videoEncoderName", videoEncoderName); + } + return detailsJson; + } + } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Codec.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Codec.java index 122470b111..d345aa63d6 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Codec.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Codec.java @@ -133,6 +133,9 @@ public interface Codec { */ Format getConfigurationFormat(); + /** Returns the name of the underlying codec. */ + String getName(); + /** * Returns the input {@link Surface} of an underlying video encoder. * diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultCodec.java b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultCodec.java index 85033f8efc..5c723705b8 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultCodec.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultCodec.java @@ -19,6 +19,7 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkStateNotNull; +import static androidx.media3.common.util.Util.SDK_INT; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; @@ -30,7 +31,6 @@ import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.TraceUtil; import androidx.media3.common.util.UnstableApi; -import androidx.media3.common.util.Util; import androidx.media3.decoder.DecoderInputBuffer; import com.google.common.base.Ascii; import com.google.common.collect.ImmutableList; @@ -124,7 +124,7 @@ public final class DefaultCodec implements Codec { @Override public int getMaxPendingFrameCount() { - if (Util.SDK_INT < 29) { + if (SDK_INT < 29) { // Prior to API 29, decoders may drop frames to keep their output surface from growing out of // bounds. From API 29, the {@link MediaFormat#KEY_ALLOW_FRAME_DROP} key prevents frame // dropping even when the surface is full. Frame dropping is never desired, so allow a maximum @@ -257,6 +257,23 @@ public final class DefaultCodec implements Codec { mediaCodec.release(); } + /** + * {@inheritDoc} + * + *

This name is of the actual codec, which may not be the same as the {@code mediaCodecName} + * passed to {@link #DefaultCodec(Format, MediaFormat, String, boolean, Surface)}. + * + * @see MediaCodec#getCanonicalName() + */ + @Override + public String getName() { + if (SDK_INT >= 29) { + return mediaCodec.getCanonicalName(); + } + + return mediaCodec.getName(); + } + /** * Attempts to dequeue an output buffer if there is no output buffer pending. Does nothing * otherwise. diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java index 3981edfe62..b786027fbc 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -557,10 +557,10 @@ public final class Transformer { private final ImmutableList videoFrameEffects; private final Looper looper; private final Clock clock; - private final Codec.EncoderFactory encoderFactory; - private final Codec.DecoderFactory decoderFactory; private final Transformer.DebugViewProvider debugViewProvider; private final ListenerSet listeners; + private final Codec.DecoderFactory decoderFactory; + @VisibleForTesting /* package */ final Codec.EncoderFactory encoderFactory; @Nullable private MuxerWrapper muxerWrapper; @Nullable private ExoPlayer player;