Commit fb03db81 by samrobinson Committed by Ian Baker

Add analysisException field to TransformationTestResult new Builder.

Having this in place means that analysis exceptions can be swallowed
or thrown as needed.

PiperOrigin-RevId: 434788802
parent 72aca582
......@@ -15,31 +15,117 @@
*/
package androidx.media3.transformer;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
/** A test only class for holding the details of a test transformation. */
public class TransformationTestResult {
/** Represents an unset or unknown SSIM score. */
public static final double SSIM_UNSET = -1.0d;
/** A builder for {@link TransformationTestResult}. */
public static class Builder {
private final TransformationResult transformationResult;
@Nullable private String filePath;
@Nullable private Exception analysisException;
private long elapsedTimeMs;
private double ssim;
/** Creates a new {@link Builder}. */
public Builder(TransformationResult transformationResult) {
this.transformationResult = transformationResult;
this.elapsedTimeMs = C.TIME_UNSET;
this.ssim = SSIM_UNSET;
}
/**
* Sets the file path of the output file.
*
* <p>{@code null} represents an unset or unknown value.
*
* @param filePath The path.
* @return This {@link Builder}.
*/
public Builder setFilePath(@Nullable String filePath) {
this.filePath = filePath;
return this;
}
/**
* Sets the amount of time taken to perform the transformation in milliseconds. {@link
* C#TIME_UNSET} if unset.
*
* <p>{@link C#TIME_UNSET} represents an unset or unknown value.
*
* @param elapsedTimeMs The time, in ms.
* @return This {@link Builder}.
*/
public Builder setElapsedTimeMs(long elapsedTimeMs) {
this.elapsedTimeMs = elapsedTimeMs;
return this;
}
/**
* Sets the SSIM of the output file, compared to input file.
*
* <p>{@link #SSIM_UNSET} represents an unset or unknown value.
*
* @param ssim The structural similarity index.
* @return This {@link Builder}.
*/
public Builder setSsim(double ssim) {
this.ssim = ssim;
return this;
}
/**
* Sets an {@link Exception} that occurred during post-transformation analysis.
*
* <p>{@code null} represents an unset or unknown value.
*
* @param analysisException The {@link Exception} thrown during analysis.
* @return This {@link Builder}.
*/
public Builder setAnalysisException(@Nullable Exception analysisException) {
this.analysisException = analysisException;
return this;
}
/** Builds the {@link TransformationTestResult} instance. */
public TransformationTestResult build() {
return new TransformationTestResult(
transformationResult, filePath, elapsedTimeMs, ssim, analysisException);
}
}
public final TransformationResult transformationResult;
public final String filePath;
/** The amount of time taken to perform the transformation in milliseconds. */
public final long transformationDurationMs;
@Nullable public final String filePath;
/**
* The amount of time taken to perform the transformation in milliseconds. {@link C#TIME_UNSET} if
* unset.
*/
public final long elapsedTimeMs;
/** The SSIM score of the transformation, {@link #SSIM_UNSET} if unavailable. */
public final double ssim;
/**
* The {@link Exception} that was thrown during post-tranformation analysis, or {@code null} if
* nothing was thrown.
*/
@Nullable public final Exception analysisException;
public TransformationTestResult(
TransformationResult transformationResult, String filePath, long transformationDurationMs) {
this(transformationResult, filePath, transformationDurationMs, /* ssim= */ SSIM_UNSET);
}
public TransformationTestResult(
private TransformationTestResult(
TransformationResult transformationResult,
String filePath,
long transformationDurationMs,
double ssim) {
@Nullable String filePath,
long elapsedTimeMs,
double ssim,
@Nullable Exception analysisException) {
this.transformationResult = transformationResult;
this.filePath = filePath;
this.transformationDurationMs = transformationDurationMs;
this.elapsedTimeMs = elapsedTimeMs;
this.ssim = ssim;
this.analysisException = analysisException;
}
}
......@@ -39,6 +39,7 @@ import org.json.JSONObject;
/** An android instrumentation test runner for {@link Transformer}. */
public class TransformerAndroidTestRunner {
private static final String TAG_PREFIX = "TransformerAndroidTest_";
/** The default transformation timeout value. */
public static final int DEFAULT_TIMEOUT_SECONDS = 120;
......@@ -49,6 +50,7 @@ public class TransformerAndroidTestRunner {
private final Transformer transformer;
private boolean calculateSsim;
private int timeoutSeconds;
private boolean suppressAnalysisExceptions;
/**
* Creates a {@link Builder}.
......@@ -93,9 +95,29 @@ public class TransformerAndroidTestRunner {
return this;
}
/**
* Sets whether the runner should suppress any {@link Exception} that occurs as a result of
* post-transformation analysis, such as SSIM calculation.
*
* <p>Regardless of this value, analysis exceptions are attached to the analysis file.
*
* <p>It's recommended to add a comment explaining why this suppression is needed, ideally with
* a bug number.
*
* <p>The default value is {@code false}.
*
* @param suppressAnalysisExceptions Whether to suppress analysis exceptions.
* @return This {@link Builder}.
*/
public Builder setSuppressAnalysisExceptions(boolean suppressAnalysisExceptions) {
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
return this;
}
/** Builds the {@link TransformerAndroidTestRunner}. */
public TransformerAndroidTestRunner build() {
return new TransformerAndroidTestRunner(context, transformer, timeoutSeconds, calculateSsim);
return new TransformerAndroidTestRunner(
context, transformer, timeoutSeconds, calculateSsim, suppressAnalysisExceptions);
}
}
......@@ -103,13 +125,19 @@ public class TransformerAndroidTestRunner {
private final Transformer transformer;
private final int timeoutSeconds;
private final boolean calculateSsim;
private final boolean suppressAnalysisExceptions;
private TransformerAndroidTestRunner(
Context context, Transformer transformer, int timeoutSeconds, boolean calculateSsim) {
Context context,
Transformer transformer,
int timeoutSeconds,
boolean calculateSsim,
boolean suppressAnalysisExceptions) {
this.context = context;
this.transformer = transformer;
this.timeoutSeconds = timeoutSeconds;
this.calculateSsim = calculateSsim;
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
}
/**
......@@ -126,6 +154,9 @@ public class TransformerAndroidTestRunner {
try {
TransformationTestResult transformationTestResult = runInternal(testId, uriString);
resultJson.put("transformationResult", getTestResultJson(transformationTestResult));
if (!suppressAnalysisExceptions && transformationTestResult.analysisException != null) {
throw transformationTestResult.analysisException;
}
return transformationTestResult;
} catch (Exception e) {
resultJson.put("exception", getExceptionJson(e));
......@@ -147,11 +178,10 @@ public class TransformerAndroidTestRunner {
* complete.
* @throws TransformationException If an exception occurs as a result of the transformation.
* @throws IllegalArgumentException If the path is invalid.
* @throws IllegalStateException If this method is called from the wrong thread.
* @throws IllegalStateException If a transformation is already in progress.
* @throws Exception If the transformation did not complete.
* @throws IllegalStateException If an unexpected exception occurs when starting a transformation.
*/
private TransformationTestResult runInternal(String testId, String uriString) throws Exception {
private TransformationTestResult runInternal(String testId, String uriString)
throws InterruptedException, IOException, TimeoutException, TransformationException {
AtomicReference<@NullableType TransformationException> transformationExceptionReference =
new AtomicReference<>();
AtomicReference<@NullableType Exception> unexpectedExceptionReference = new AtomicReference<>();
......@@ -201,11 +231,12 @@ public class TransformerAndroidTestRunner {
if (!countDownLatch.await(timeoutSeconds, SECONDS)) {
throw new TimeoutException("Transformer timed out after " + timeoutSeconds + " seconds.");
}
long transformationDurationMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
long elapsedTimeMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
@Nullable Exception unexpectedException = unexpectedExceptionReference.get();
if (unexpectedException != null) {
throw unexpectedException;
throw new IllegalStateException(
"Unexpected exception starting the transformer.", unexpectedException);
}
@Nullable
......@@ -222,16 +253,31 @@ public class TransformerAndroidTestRunner {
.setFileSizeBytes(outputVideoFile.length())
.build();
if (!calculateSsim) {
return new TransformationTestResult(
transformationResult, outputVideoFile.getPath(), transformationDurationMs);
TransformationTestResult.Builder resultBuilder =
new TransformationTestResult.Builder(transformationResult)
.setFilePath(outputVideoFile.getPath())
.setElapsedTimeMs(elapsedTimeMs);
try {
if (calculateSsim) {
double ssim =
SsimHelper.calculate(
context, /* expectedVideoPath= */ uriString, 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
// TransformationTestResult.
throw interruptedException;
} catch (Exception analysisException) {
// Catch all (checked and unchecked) exceptions throw by the SsimHelper and process them as
// part of the TransformationTestResult.
resultBuilder.setAnalysisException(analysisException);
Log.e(TAG_PREFIX + testId, "SSIM calculation failed.", analysisException);
}
double ssim =
SsimHelper.calculate(
context, /* expectedVideoPath= */ uriString, outputVideoFile.getPath());
return new TransformationTestResult(
transformationResult, outputVideoFile.getPath(), transformationDurationMs, ssim);
return resultBuilder.build();
}
private static void writeTestSummaryToFile(Context context, String testId, JSONObject resultJson)
......@@ -241,7 +287,7 @@ public class TransformerAndroidTestRunner {
String analysisContents = resultJson.toString(/* indentSpaces= */ 2);
// Log contents as well as writing to file, for easier visibility on individual device testing.
Log.i("TransformerAndroidTest_" + testId, analysisContents);
Log.i(TAG_PREFIX + testId, analysisContents);
File analysisFile =
AndroidTestUtil.createExternalCacheFile(context, /* fileName= */ testId + "-result.txt");
......@@ -272,10 +318,16 @@ public class TransformerAndroidTestRunner {
if (transformationResult.averageVideoBitrate != C.RATE_UNSET_INT) {
transformationResultJson.put("averageVideoBitrate", transformationResult.averageVideoBitrate);
}
if (testResult.elapsedTimeMs != C.TIME_UNSET) {
transformationResultJson.put("elapsedTimeMs", testResult.elapsedTimeMs);
}
if (testResult.ssim != TransformationTestResult.SSIM_UNSET) {
transformationResultJson.put("ssim", testResult.ssim);
}
transformationResultJson.put("transformationDurationMs", testResult.transformationDurationMs);
if (testResult.analysisException != null) {
transformationResultJson.put(
"analysisException", getExceptionJson(testResult.analysisException));
}
return transformationResultJson;
}
......
......@@ -47,8 +47,10 @@ public class TransformationTest {
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build();
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
}
......@@ -87,8 +89,10 @@ public class TransformationTest {
}
})
.build();
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
}
......@@ -97,10 +101,12 @@ public class TransformationTest {
public void transform4K60() throws Exception {
final String testId = TAG + "_transform4K60";
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build();
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_REMOTE_4K60_PORTRAIT_URI_STRING);
}
......@@ -109,10 +115,12 @@ public class TransformationTest {
public void transformNoAudio() throws Exception {
final String testId = TAG + "_transformNoAudio";
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).setRemoveAudio(true).build();
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
}
......@@ -144,8 +152,10 @@ public class TransformationTest {
.setTransformationRequest(
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
.build();
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
// TODO(b/223381524): Remove analysis failure suppression after ssim calculation doesn't fail.
new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build()
.run(testId, MP4_ASSET_SEF_URI_STRING);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment