diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformationTestResult.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformationTestResult.java index 79d1148ac6..af8ab7f1c3 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformationTestResult.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformationTestResult.java @@ -144,6 +144,18 @@ public class TransformationTestResult { if (transformationResult.videoFrameCount > 0) { jsonObject.put("videoFrameCount", transformationResult.videoFrameCount); } + if (transformationResult.audioDecoderName != null) { + jsonObject.put("audioDecoderName", transformationResult.audioDecoderName); + } + if (transformationResult.videoDecoderName != null) { + jsonObject.put("videoDecoderName", transformationResult.videoDecoderName); + } + if (transformationResult.audioEncoderName != null) { + jsonObject.put("audioEncoderName", transformationResult.audioEncoderName); + } + if (transformationResult.videoEncoderName != null) { + jsonObject.put("videoEncoderName", transformationResult.videoEncoderName); + } if (throughputFps != C.RATE_UNSET) { jsonObject.put("throughputFps", throughputFps); } 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 e79123a3c3..b14179f4cf 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java @@ -23,9 +23,7 @@ import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.Uri; -import android.view.Surface; 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; @@ -155,7 +153,8 @@ public class TransformerAndroidTestRunner { } private final Context context; - private final CodecNameForwardingCodecFactory transformerCodecFactory; + private final CapturingDecoderFactory decoderFactory; + private final CapturingEncoderFactory encoderFactory; private final Transformer transformer; private final int timeoutSeconds; private final boolean requestCalculateSsim; @@ -170,13 +169,13 @@ public class TransformerAndroidTestRunner { boolean suppressAnalysisExceptions, @Nullable Map inputValues) { this.context = context; - this.transformerCodecFactory = - new CodecNameForwardingCodecFactory(transformer.decoderFactory, transformer.encoderFactory); + this.decoderFactory = new CapturingDecoderFactory(transformer.decoderFactory); + this.encoderFactory = new CapturingEncoderFactory(transformer.encoderFactory); this.transformer = transformer .buildUpon() - .setDecoderFactory(transformerCodecFactory) - .setEncoderFactory(transformerCodecFactory) + .setDecoderFactory(decoderFactory) + .setEncoderFactory(encoderFactory) .build(); this.timeoutSeconds = timeoutSeconds; this.requestCalculateSsim = requestCalculateSsim; @@ -209,7 +208,7 @@ public class TransformerAndroidTestRunner { resultJson.put("exception", AndroidTestUtil.exceptionAsJsonObject(e)); throw e; } finally { - resultJson.put("codecDetails", transformerCodecFactory.getCodecNamesAsJsonObject()); + resultJson.put("codecDetails", getCodecNamesAsJsonObject()); AndroidTestUtil.writeTestSummaryToFile(context, testId, resultJson); } } @@ -402,87 +401,20 @@ public class TransformerAndroidTestRunner { return false; } - /** - * 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 CodecNameForwardingCodecFactory - implements Codec.DecoderFactory, Codec.EncoderFactory { - - /** The name of the last audio {@link Codec decoder} created. */ - @Nullable public String audioDecoderName; - /** The name of the last video {@link Codec decoder} created. */ - @Nullable public String videoDecoderName; - /** The name of the last audio {@link Codec encoder} created. */ - @Nullable public String audioEncoderName; - /** The name of the last video {@link Codec encoder} created. */ - @Nullable public String videoEncoderName; - - private final Codec.DecoderFactory decoderFactory; - private final Codec.EncoderFactory encoderFactory; - - public CodecNameForwardingCodecFactory( - Codec.DecoderFactory decoderFactory, Codec.EncoderFactory encoderFactory) { - this.decoderFactory = decoderFactory; - this.encoderFactory = encoderFactory; + private JSONObject getCodecNamesAsJsonObject() throws JSONException { + JSONObject detailsJson = new JSONObject(); + if (decoderFactory.getAudioDecoderName() != null) { + detailsJson.put("audioDecoderName", decoderFactory.getAudioDecoderName()); } - - @Override - public Codec createForAudioDecoding(Format format) throws TransformationException { - Codec audioDecoder = decoderFactory.createForAudioDecoding(format); - audioDecoderName = audioDecoder.getName(); - return audioDecoder; + if (decoderFactory.getVideoDecoderName() != null) { + detailsJson.put("videoDecoderName", decoderFactory.getVideoDecoderName()); } - - @Override - public Codec createForVideoDecoding( - Format format, Surface outputSurface, boolean requestSdrToneMapping) - throws TransformationException { - Codec videoDecoder = - decoderFactory.createForVideoDecoding(format, outputSurface, requestSdrToneMapping); - videoDecoderName = videoDecoder.getName(); - return videoDecoder; + if (encoderFactory.getAudioEncoderName() != null) { + detailsJson.put("audioEncoderName", encoderFactory.getAudioEncoderName()); } - - @Override - public Codec createForAudioEncoding(Format format) throws TransformationException { - Codec audioEncoder = encoderFactory.createForAudioEncoding(format); - audioEncoderName = audioEncoder.getName(); - return audioEncoder; - } - - @Override - public Codec createForVideoEncoding(Format format) throws TransformationException { - Codec videoEncoder = encoderFactory.createForVideoEncoding(format); - 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 (audioDecoderName != null) { - detailsJson.put("audioDecoderName", audioDecoderName); - } - if (videoDecoderName != null) { - detailsJson.put("videoDecoderName", videoDecoderName); - } - if (audioEncoderName != null) { - detailsJson.put("audioEncoderName", audioEncoderName); - } - if (videoEncoderName != null) { - detailsJson.put("videoEncoderName", videoEncoderName); - } - return detailsJson; + if (encoderFactory.getVideoEncoderName() != null) { + detailsJson.put("videoEncoderName", encoderFactory.getVideoEncoderName()); } + return detailsJson; } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/CapturingDecoderFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/CapturingDecoderFactory.java new file mode 100644 index 0000000000..4f54902268 --- /dev/null +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CapturingDecoderFactory.java @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.transformer; + +import android.view.Surface; +import androidx.annotation.Nullable; +import androidx.media3.common.Format; + +/** A forwarding {@link Codec.DecoderFactory} that captures details about the codecs created. */ +/* package */ final class CapturingDecoderFactory implements Codec.DecoderFactory { + private final Codec.DecoderFactory decoderFactory; + + @Nullable private String audioDecoderName; + @Nullable private String videoDecoderName; + + public CapturingDecoderFactory(Codec.DecoderFactory decoderFactory) { + this.decoderFactory = decoderFactory; + } + + @Override + public Codec createForAudioDecoding(Format format) throws TransformationException { + Codec audioDecoder = decoderFactory.createForAudioDecoding(format); + audioDecoderName = audioDecoder.getName(); + return audioDecoder; + } + + @Override + public Codec createForVideoDecoding( + Format format, Surface outputSurface, boolean requestSdrToneMapping) + throws TransformationException { + Codec videoDecoder = + decoderFactory.createForVideoDecoding(format, outputSurface, requestSdrToneMapping); + videoDecoderName = videoDecoder.getName(); + return videoDecoder; + } + + /** + * Returns the name of the last audio {@linkplain Codec decoder} created, or {@code null} if none + * were created. + */ + @Nullable + public String getAudioDecoderName() { + return audioDecoderName; + } + + /** + * Returns the name of the last video {@linkplain Codec decoder} created, or {@code null} if none + * were created. + */ + @Nullable + public String getVideoDecoderName() { + return videoDecoderName; + } +} diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/CapturingEncoderFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/CapturingEncoderFactory.java new file mode 100644 index 0000000000..e20ad36dee --- /dev/null +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/CapturingEncoderFactory.java @@ -0,0 +1,73 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.transformer; + +import androidx.annotation.Nullable; +import androidx.media3.common.Format; + +/** A forwarding {@link Codec.EncoderFactory} that captures details about the codecs created. */ +/* package */ final class CapturingEncoderFactory implements Codec.EncoderFactory { + private final Codec.EncoderFactory encoderFactory; + + @Nullable private String audioEncoderName; + @Nullable private String videoEncoderName; + + public CapturingEncoderFactory(Codec.EncoderFactory encoderFactory) { + this.encoderFactory = encoderFactory; + } + + @Override + public Codec createForAudioEncoding(Format format) throws TransformationException { + Codec audioEncoder = encoderFactory.createForAudioEncoding(format); + audioEncoderName = audioEncoder.getName(); + return audioEncoder; + } + + @Override + public Codec createForVideoEncoding(Format format) throws TransformationException { + Codec videoEncoder = encoderFactory.createForVideoEncoding(format); + videoEncoderName = videoEncoder.getName(); + return videoEncoder; + } + + @Override + public boolean audioNeedsEncoding() { + return encoderFactory.audioNeedsEncoding(); + } + + @Override + public boolean videoNeedsEncoding() { + return encoderFactory.videoNeedsEncoding(); + } + + /** + * Returns the name of the last audio {@linkplain Codec encoder} created, or {@code null} if none + * were created. + */ + @Nullable + public String getAudioEncoderName() { + return audioEncoderName; + } + + /** + * Returns the name of the last video {@linkplain Codec encoder} created, or {@code null} if none + * were created. + */ + @Nullable + public String getVideoEncoderName() { + return videoEncoderName; + } +} diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationResult.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationResult.java index c13b63edf6..89fd480de4 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationResult.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationResult.java @@ -21,8 +21,9 @@ import androidx.annotation.Nullable; import androidx.media3.common.C; import androidx.media3.common.util.UnstableApi; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.util.Objects; -/** Information about the result of a successful transformation. */ +/** Information about the result of a transformation. */ @UnstableApi public final class TransformationResult { @@ -33,6 +34,10 @@ public final class TransformationResult { private int averageAudioBitrate; private int averageVideoBitrate; private int videoFrameCount; + @Nullable private String audioDecoderName; + @Nullable private String audioEncoderName; + @Nullable private String videoDecoderName; + @Nullable private String videoEncoderName; public Builder() { durationMs = C.TIME_UNSET; @@ -101,9 +106,45 @@ public final class TransformationResult { return this; } + /** Sets the name of the audio decoder used. */ + @CanIgnoreReturnValue + public Builder setAudioDecoderName(@Nullable String audioDecoderName) { + this.audioDecoderName = audioDecoderName; + return this; + } + + /** Sets the name of the audio encoder used. */ + @CanIgnoreReturnValue + public Builder setAudioEncoderName(@Nullable String audioEncoderName) { + this.audioEncoderName = audioEncoderName; + return this; + } + + /** Sets the name of the video decoder used. */ + @CanIgnoreReturnValue + public Builder setVideoDecoderName(@Nullable String videoDecoderName) { + this.videoDecoderName = videoDecoderName; + return this; + } + + /** Sets the name of the video encoder used. */ + @CanIgnoreReturnValue + public Builder setVideoEncoderName(@Nullable String videoEncoderName) { + this.videoEncoderName = videoEncoderName; + return this; + } + public TransformationResult build() { return new TransformationResult( - durationMs, fileSizeBytes, averageAudioBitrate, averageVideoBitrate, videoFrameCount); + durationMs, + fileSizeBytes, + averageAudioBitrate, + averageVideoBitrate, + videoFrameCount, + audioDecoderName, + audioEncoderName, + videoDecoderName, + videoEncoderName); } } @@ -122,17 +163,34 @@ public final class TransformationResult { /** The number of video frames. */ public final int videoFrameCount; + /** The name of the audio decoder used, or {@code null} if none were used. */ + @Nullable public final String audioDecoderName; + /** The name of the audio encoder used, or {@code null} if none were used. */ + @Nullable public final String audioEncoderName; + /** The name of the video decoder used, or {@code null} if none were used. */ + @Nullable public final String videoDecoderName; + /** The name of the video encoder used, or {@code null} if none were used. */ + @Nullable public final String videoEncoderName; + private TransformationResult( long durationMs, long fileSizeBytes, int averageAudioBitrate, int averageVideoBitrate, - int videoFrameCount) { + int videoFrameCount, + @Nullable String audioDecoderName, + @Nullable String audioEncoderName, + @Nullable String videoDecoderName, + @Nullable String videoEncoderName) { this.durationMs = durationMs; this.fileSizeBytes = fileSizeBytes; this.averageAudioBitrate = averageAudioBitrate; this.averageVideoBitrate = averageVideoBitrate; this.videoFrameCount = videoFrameCount; + this.audioDecoderName = audioDecoderName; + this.audioEncoderName = audioEncoderName; + this.videoDecoderName = videoDecoderName; + this.videoEncoderName = videoEncoderName; } public Builder buildUpon() { @@ -141,7 +199,11 @@ public final class TransformationResult { .setFileSizeBytes(fileSizeBytes) .setAverageAudioBitrate(averageAudioBitrate) .setAverageVideoBitrate(averageVideoBitrate) - .setVideoFrameCount(videoFrameCount); + .setVideoFrameCount(videoFrameCount) + .setAudioDecoderName(audioDecoderName) + .setAudioEncoderName(audioEncoderName) + .setVideoDecoderName(videoDecoderName) + .setVideoEncoderName(videoEncoderName); } @Override @@ -157,7 +219,11 @@ public final class TransformationResult { && fileSizeBytes == result.fileSizeBytes && averageAudioBitrate == result.averageAudioBitrate && averageVideoBitrate == result.averageVideoBitrate - && videoFrameCount == result.videoFrameCount; + && videoFrameCount == result.videoFrameCount + && Objects.equals(audioDecoderName, result.audioDecoderName) + && Objects.equals(audioEncoderName, result.audioEncoderName) + && Objects.equals(videoDecoderName, result.videoDecoderName) + && Objects.equals(videoEncoderName, result.videoEncoderName); } @Override @@ -167,6 +233,10 @@ public final class TransformationResult { result = 31 * result + averageAudioBitrate; result = 31 * result + averageVideoBitrate; result = 31 * result + videoFrameCount; + result = 31 * result + Objects.hashCode(audioDecoderName); + result = 31 * result + Objects.hashCode(audioEncoderName); + result = 31 * result + Objects.hashCode(videoDecoderName); + result = 31 * result + Objects.hashCode(videoEncoderName); return result; } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java index a909d95b24..b4fd6feb6e 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerInternal.java @@ -94,8 +94,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final ImmutableList audioProcessors; private final ImmutableList videoEffects; private final boolean forceSilentAudio; - private final Codec.DecoderFactory decoderFactory; - private final Codec.EncoderFactory encoderFactory; + private final CapturingDecoderFactory decoderFactory; + private final CapturingEncoderFactory encoderFactory; private final FrameProcessor.Factory frameProcessorFactory; private final Listener listener; private final DebugViewProvider debugViewProvider; @@ -141,8 +141,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; this.audioProcessors = audioProcessors; this.videoEffects = videoEffects; this.forceSilentAudio = forceSilentAudio; - this.decoderFactory = decoderFactory; - this.encoderFactory = encoderFactory; + this.decoderFactory = new CapturingDecoderFactory(decoderFactory); + this.encoderFactory = new CapturingEncoderFactory(encoderFactory); this.frameProcessorFactory = frameProcessorFactory; this.listener = listener; this.debugViewProvider = debugViewProvider; @@ -316,6 +316,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; for (int i = 0; i < samplePipelines.size(); i++) { samplePipelines.get(i).release(); } + + // TODO(b/250564186): Create TransformationResult on END_REASON_ERROR as well. if (endReason == END_REASON_COMPLETED) { transformationResult = new TransformationResult.Builder() @@ -326,6 +328,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_VIDEO)) .setVideoFrameCount(muxerWrapper.getTrackSampleCount(C.TRACK_TYPE_VIDEO)) .setFileSizeBytes(muxerWrapper.getCurrentOutputSizeBytes()) + .setAudioDecoderName(decoderFactory.getAudioDecoderName()) + .setAudioEncoderName(encoderFactory.getAudioEncoderName()) + .setVideoDecoderName(decoderFactory.getVideoDecoderName()) + .setVideoEncoderName(encoderFactory.getVideoEncoderName()) .build(); } } finally {