diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java
index 8fb3ae288c..51c20b6525 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java
@@ -229,7 +229,9 @@ public final class AndroidTestUtil {
/**
* Checks whether the test should be skipped because the device is incapable of decoding and
- * encoding the given formats. If the test should be skipped, logs the reason for skipping.
+ * encoding the given formats.
+ *
+ *
If the test should be skipped, logs the reason for skipping.
*
* @param context The {@link Context context}.
* @param testId The test ID.
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/SsimHelper.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/SsimHelper.java
index 8d9bb05924..b504e28ac5 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/SsimHelper.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/SsimHelper.java
@@ -68,7 +68,7 @@ public final class SsimHelper {
*
The method compares every {@link #DEFAULT_COMPARISON_INTERVAL n-th} frame from both videos.
*
* @param context The {@link Context}.
- * @param referenceVideoPath The path to the reference video file, must be in {@link
+ * @param referenceVideoPath The path to the reference video file, which must be in {@linkplain
* Context#getAssets() Assets}.
* @param actualVideoPath The path to the actual video file.
* @throws IOException When unable to open the provided video paths.
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 286c858e5b..1c162647ca 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerAndroidTestRunner.java
@@ -33,6 +33,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.json.JSONException;
@@ -49,7 +50,7 @@ public class TransformerAndroidTestRunner {
public static class Builder {
private final Context context;
private final Transformer transformer;
- private boolean calculateSsim;
+ private boolean maybeCalculateSsim;
private int timeoutSeconds;
private boolean suppressAnalysisExceptions;
@Nullable private Map inputValues;
@@ -81,19 +82,21 @@ public class TransformerAndroidTestRunner {
}
/**
- * Sets whether to calculate the SSIM of the transformation output.
+ * Sets whether to try to calculate the SSIM of the transformation output.
+ *
+ * SSIM requires the input and output video dimensions to match. Therefore, if encoder
+ * resolution fallback occurs, this calculation is skipped.
*
*
The calculation involves decoding and comparing both the input and the output video.
- * Consequently this calculation is not cost-free. Requires the input and output video to be the
- * same size.
+ * Consequently this calculation is not cost-free.
*
*
The default value is {@code false}.
*
- * @param calculateSsim Whether to calculate SSIM.
+ * @param maybeCalculateSsim Whether to try to calculate SSIM.
* @return This {@link Builder}.
*/
- public Builder setCalculateSsim(boolean calculateSsim) {
- this.calculateSsim = calculateSsim;
+ public Builder setMaybeCalculateSsim(boolean maybeCalculateSsim) {
+ this.maybeCalculateSsim = maybeCalculateSsim;
return this;
}
@@ -137,7 +140,7 @@ public class TransformerAndroidTestRunner {
context,
transformer,
timeoutSeconds,
- calculateSsim,
+ maybeCalculateSsim,
suppressAnalysisExceptions,
inputValues);
}
@@ -147,7 +150,7 @@ public class TransformerAndroidTestRunner {
private final CodecNameForwardingCodecFactory transformerCodecFactory;
private final Transformer transformer;
private final int timeoutSeconds;
- private final boolean calculateSsim;
+ private final boolean maybeCalculateSsim;
private final boolean suppressAnalysisExceptions;
@Nullable private final Map inputValues;
@@ -155,7 +158,7 @@ public class TransformerAndroidTestRunner {
Context context,
Transformer transformer,
int timeoutSeconds,
- boolean calculateSsim,
+ boolean maybeCalculateSsim,
boolean suppressAnalysisExceptions,
@Nullable Map inputValues) {
this.context = context;
@@ -168,7 +171,7 @@ public class TransformerAndroidTestRunner {
.setEncoderFactory(transformerCodecFactory)
.build();
this.timeoutSeconds = timeoutSeconds;
- this.calculateSsim = calculateSsim;
+ this.maybeCalculateSsim = maybeCalculateSsim;
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
this.inputValues = inputValues;
}
@@ -220,7 +223,7 @@ public class TransformerAndroidTestRunner {
private TransformationTestResult runInternal(String testId, MediaItem mediaItem)
throws InterruptedException, IOException, TimeoutException, TransformationException {
if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
- && calculateSsim) {
+ && maybeCalculateSsim) {
throw new UnsupportedOperationException(
"SSIM calculation is not supported for clipped inputs.");
}
@@ -231,6 +234,7 @@ public class TransformerAndroidTestRunner {
AtomicReference<@NullableType TransformationResult> transformationResultReference =
new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
+ AtomicBoolean fallbackResolutionApplied = new AtomicBoolean(false);
long startTimeMs = SystemClock.DEFAULT.elapsedRealtime();
Transformer testTransformer =
@@ -251,6 +255,20 @@ public class TransformerAndroidTestRunner {
transformationExceptionReference.set(exception);
countDownLatch.countDown();
}
+
+ @Override
+ public void onFallbackApplied(
+ MediaItem inputMediaItem,
+ TransformationRequest originalTransformationRequest,
+ TransformationRequest fallbackTransformationRequest) {
+ // Note: As TransformationRequest only reports the output height but not the
+ // output width, it's not possible to check whether the encoder has changed
+ // the output aspect ratio.
+ if (originalTransformationRequest.outputHeight
+ != fallbackTransformationRequest.outputHeight) {
+ fallbackResolutionApplied.set(true);
+ }
+ }
})
.build();
@@ -299,15 +317,23 @@ public class TransformerAndroidTestRunner {
.setFilePath(outputVideoFile.getPath())
.setElapsedTimeMs(elapsedTimeMs);
+ if (!maybeCalculateSsim) {
+ return resultBuilder.build();
+ }
+ if (fallbackResolutionApplied.get()) {
+ Log.i(
+ TAG,
+ testId
+ + ": Skipping SSIM calculation because an encoder resolution fallback was applied.");
+ return resultBuilder.build();
+ }
try {
- if (calculateSsim) {
- double ssim =
- SsimHelper.calculate(
- context,
- /* referenceVideoPath= */ checkNotNull(mediaItem.localConfiguration).uri.toString(),
- outputVideoFile.getPath());
- resultBuilder.setSsim(ssim);
- }
+ double ssim =
+ SsimHelper.calculate(
+ context,
+ /* referenceVideoPath= */ checkNotNull(mediaItem.localConfiguration).uri.toString(),
+ outputVideoFile.getPath());
+ resultBuilder.setSsim(ssim);
} catch (InterruptedException interruptedException) {
// InterruptedException is a special unexpected case because it is not related to Ssim
// calculation, so it should be thrown, rather than processed as part of the
@@ -317,7 +343,7 @@ public class TransformerAndroidTestRunner {
if (Util.SDK_INT == 21 && "Nexus 5".equals(Util.MODEL)) { // b/233584640
Log.i(TAG, testId + ": Skipping SSIM calculation due to known device-specific issue");
} else {
- // Catch all (checked and unchecked) failures throw by the SsimHelper and process them as
+ // Catch all (checked and unchecked) failures thrown by the SsimHelper and process them as
// part of the TransformationTestResult.
Exception analysisException =
analysisFailure instanceof Exception
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TranscodeQualityTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TranscodeQualityTest.java
index d0e62e3301..3947435c16 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TranscodeQualityTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TranscodeQualityTest.java
@@ -58,7 +58,7 @@ public final class TranscodeQualityTest {
TransformationTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.build()
.run(
testId,
@@ -95,7 +95,7 @@ public final class TranscodeQualityTest {
TransformationTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.build()
.run(
testId,
@@ -125,7 +125,7 @@ public final class TranscodeQualityTest {
TransformationTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.build()
.run(
testId,
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java
index bbc9aa29a1..0452abb65c 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformationTest.java
@@ -54,7 +54,7 @@ public class TransformationTest {
.setEncoderFactory(AndroidTestUtil.FORCE_ENCODE_ENCODER_FACTORY)
.build();
new TransformerAndroidTestRunner.Builder(context, transformer)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.build()
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING)));
}
@@ -84,7 +84,7 @@ public class TransformationTest {
/* enableFallback= */ true))
.build();
new TransformerAndroidTestRunner.Builder(context, transformer)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.build()
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING)));
}
@@ -105,7 +105,7 @@ public class TransformationTest {
Transformer transformer =
new Transformer.Builder(context).setEncoderFactory(FORCE_ENCODE_ENCODER_FACTORY).build();
new TransformerAndroidTestRunner.Builder(context, transformer)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.setTimeoutSeconds(180)
.build()
.run(testId, MediaItem.fromUri(Uri.parse(MP4_REMOTE_4K60_PORTRAIT_URI_STRING)));
@@ -121,7 +121,7 @@ public class TransformationTest {
.setRemoveAudio(true)
.build();
new TransformerAndroidTestRunner.Builder(context, transformer)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.build()
.run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING)));
}
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java
index 3d31d43718..cd9ccbfecd 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/analysis/BitrateAnalysisTest.java
@@ -116,7 +116,7 @@ public class BitrateAnalysisTest {
new TransformerAndroidTestRunner.Builder(context, transformer)
.setInputValues(inputValues)
- .setCalculateSsim(true)
+ .setMaybeCalculateSsim(true)
.build()
.run(testId, MediaItem.fromUri(Uri.parse(fileUri)));
}
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 41acc4b6a0..0fc6473e64 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
@@ -513,7 +513,9 @@ public final class Transformer {
* @param inputMediaItem The {@link MediaItem} for which the transformation is requested.
* @param originalTransformationRequest The unsupported {@link TransformationRequest} used when
* building {@link Transformer}.
- * @param fallbackTransformationRequest The alternative {@link TransformationRequest}.
+ * @param fallbackTransformationRequest The alternative {@link TransformationRequest}, with
+ * supported {@link TransformationRequest#outputHeight} and {@link
+ * TransformationRequest#videoMimeType} values set.
*/
default void onFallbackApplied(
MediaItem inputMediaItem,