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 @@ ...@@ -15,31 +15,117 @@
*/ */
package androidx.media3.transformer; 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. */ /** A test only class for holding the details of a test transformation. */
public class TransformationTestResult { public class TransformationTestResult {
/** Represents an unset or unknown SSIM score. */ /** Represents an unset or unknown SSIM score. */
public static final double SSIM_UNSET = -1.0d; 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 TransformationResult transformationResult;
public final String filePath;
/** The amount of time taken to perform the transformation in milliseconds. */ @Nullable public final String filePath;
public final long transformationDurationMs; /**
* 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. */ /** The SSIM score of the transformation, {@link #SSIM_UNSET} if unavailable. */
public final double ssim; 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( private TransformationTestResult(
TransformationResult transformationResult, String filePath, long transformationDurationMs) {
this(transformationResult, filePath, transformationDurationMs, /* ssim= */ SSIM_UNSET);
}
public TransformationTestResult(
TransformationResult transformationResult, TransformationResult transformationResult,
String filePath, @Nullable String filePath,
long transformationDurationMs, long elapsedTimeMs,
double ssim) { double ssim,
@Nullable Exception analysisException) {
this.transformationResult = transformationResult; this.transformationResult = transformationResult;
this.filePath = filePath; this.filePath = filePath;
this.transformationDurationMs = transformationDurationMs; this.elapsedTimeMs = elapsedTimeMs;
this.ssim = ssim; this.ssim = ssim;
this.analysisException = analysisException;
} }
} }
...@@ -39,6 +39,7 @@ import org.json.JSONObject; ...@@ -39,6 +39,7 @@ import org.json.JSONObject;
/** An android instrumentation test runner for {@link Transformer}. */ /** An android instrumentation test runner for {@link Transformer}. */
public class TransformerAndroidTestRunner { public class TransformerAndroidTestRunner {
private static final String TAG_PREFIX = "TransformerAndroidTest_";
/** The default transformation timeout value. */ /** The default transformation timeout value. */
public static final int DEFAULT_TIMEOUT_SECONDS = 120; public static final int DEFAULT_TIMEOUT_SECONDS = 120;
...@@ -49,6 +50,7 @@ public class TransformerAndroidTestRunner { ...@@ -49,6 +50,7 @@ public class TransformerAndroidTestRunner {
private final Transformer transformer; private final Transformer transformer;
private boolean calculateSsim; private boolean calculateSsim;
private int timeoutSeconds; private int timeoutSeconds;
private boolean suppressAnalysisExceptions;
/** /**
* Creates a {@link Builder}. * Creates a {@link Builder}.
...@@ -93,9 +95,29 @@ public class TransformerAndroidTestRunner { ...@@ -93,9 +95,29 @@ public class TransformerAndroidTestRunner {
return this; 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}. */ /** Builds the {@link TransformerAndroidTestRunner}. */
public TransformerAndroidTestRunner build() { 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 { ...@@ -103,13 +125,19 @@ public class TransformerAndroidTestRunner {
private final Transformer transformer; private final Transformer transformer;
private final int timeoutSeconds; private final int timeoutSeconds;
private final boolean calculateSsim; private final boolean calculateSsim;
private final boolean suppressAnalysisExceptions;
private TransformerAndroidTestRunner( private TransformerAndroidTestRunner(
Context context, Transformer transformer, int timeoutSeconds, boolean calculateSsim) { Context context,
Transformer transformer,
int timeoutSeconds,
boolean calculateSsim,
boolean suppressAnalysisExceptions) {
this.context = context; this.context = context;
this.transformer = transformer; this.transformer = transformer;
this.timeoutSeconds = timeoutSeconds; this.timeoutSeconds = timeoutSeconds;
this.calculateSsim = calculateSsim; this.calculateSsim = calculateSsim;
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
} }
/** /**
...@@ -126,6 +154,9 @@ public class TransformerAndroidTestRunner { ...@@ -126,6 +154,9 @@ public class TransformerAndroidTestRunner {
try { try {
TransformationTestResult transformationTestResult = runInternal(testId, uriString); TransformationTestResult transformationTestResult = runInternal(testId, uriString);
resultJson.put("transformationResult", getTestResultJson(transformationTestResult)); resultJson.put("transformationResult", getTestResultJson(transformationTestResult));
if (!suppressAnalysisExceptions && transformationTestResult.analysisException != null) {
throw transformationTestResult.analysisException;
}
return transformationTestResult; return transformationTestResult;
} catch (Exception e) { } catch (Exception e) {
resultJson.put("exception", getExceptionJson(e)); resultJson.put("exception", getExceptionJson(e));
...@@ -147,11 +178,10 @@ public class TransformerAndroidTestRunner { ...@@ -147,11 +178,10 @@ public class TransformerAndroidTestRunner {
* complete. * complete.
* @throws TransformationException If an exception occurs as a result of the transformation. * @throws TransformationException If an exception occurs as a result of the transformation.
* @throws IllegalArgumentException If the path is invalid. * @throws IllegalArgumentException If the path is invalid.
* @throws IllegalStateException If this method is called from the wrong thread. * @throws IllegalStateException If an unexpected exception occurs when starting a transformation.
* @throws IllegalStateException If a transformation is already in progress.
* @throws Exception If the transformation did not complete.
*/ */
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 = AtomicReference<@NullableType TransformationException> transformationExceptionReference =
new AtomicReference<>(); new AtomicReference<>();
AtomicReference<@NullableType Exception> unexpectedExceptionReference = new AtomicReference<>(); AtomicReference<@NullableType Exception> unexpectedExceptionReference = new AtomicReference<>();
...@@ -201,11 +231,12 @@ public class TransformerAndroidTestRunner { ...@@ -201,11 +231,12 @@ public class TransformerAndroidTestRunner {
if (!countDownLatch.await(timeoutSeconds, SECONDS)) { if (!countDownLatch.await(timeoutSeconds, SECONDS)) {
throw new TimeoutException("Transformer timed out after " + 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(); @Nullable Exception unexpectedException = unexpectedExceptionReference.get();
if (unexpectedException != null) { if (unexpectedException != null) {
throw unexpectedException; throw new IllegalStateException(
"Unexpected exception starting the transformer.", unexpectedException);
} }
@Nullable @Nullable
...@@ -222,16 +253,31 @@ public class TransformerAndroidTestRunner { ...@@ -222,16 +253,31 @@ public class TransformerAndroidTestRunner {
.setFileSizeBytes(outputVideoFile.length()) .setFileSizeBytes(outputVideoFile.length())
.build(); .build();
if (!calculateSsim) { TransformationTestResult.Builder resultBuilder =
return new TransformationTestResult( new TransformationTestResult.Builder(transformationResult)
transformationResult, outputVideoFile.getPath(), transformationDurationMs); .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 = return resultBuilder.build();
SsimHelper.calculate(
context, /* expectedVideoPath= */ uriString, outputVideoFile.getPath());
return new TransformationTestResult(
transformationResult, outputVideoFile.getPath(), transformationDurationMs, ssim);
} }
private static void writeTestSummaryToFile(Context context, String testId, JSONObject resultJson) private static void writeTestSummaryToFile(Context context, String testId, JSONObject resultJson)
...@@ -241,7 +287,7 @@ public class TransformerAndroidTestRunner { ...@@ -241,7 +287,7 @@ public class TransformerAndroidTestRunner {
String analysisContents = resultJson.toString(/* indentSpaces= */ 2); String analysisContents = resultJson.toString(/* indentSpaces= */ 2);
// Log contents as well as writing to file, for easier visibility on individual device testing. // 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 = File analysisFile =
AndroidTestUtil.createExternalCacheFile(context, /* fileName= */ testId + "-result.txt"); AndroidTestUtil.createExternalCacheFile(context, /* fileName= */ testId + "-result.txt");
...@@ -272,10 +318,16 @@ public class TransformerAndroidTestRunner { ...@@ -272,10 +318,16 @@ public class TransformerAndroidTestRunner {
if (transformationResult.averageVideoBitrate != C.RATE_UNSET_INT) { if (transformationResult.averageVideoBitrate != C.RATE_UNSET_INT) {
transformationResultJson.put("averageVideoBitrate", transformationResult.averageVideoBitrate); transformationResultJson.put("averageVideoBitrate", transformationResult.averageVideoBitrate);
} }
if (testResult.elapsedTimeMs != C.TIME_UNSET) {
transformationResultJson.put("elapsedTimeMs", testResult.elapsedTimeMs);
}
if (testResult.ssim != TransformationTestResult.SSIM_UNSET) { if (testResult.ssim != TransformationTestResult.SSIM_UNSET) {
transformationResultJson.put("ssim", testResult.ssim); transformationResultJson.put("ssim", testResult.ssim);
} }
transformationResultJson.put("transformationDurationMs", testResult.transformationDurationMs); if (testResult.analysisException != null) {
transformationResultJson.put(
"analysisException", getExceptionJson(testResult.analysisException));
}
return transformationResultJson; return transformationResultJson;
} }
......
...@@ -47,8 +47,10 @@ public class TransformationTest { ...@@ -47,8 +47,10 @@ public class TransformationTest {
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build(); 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) new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build() .build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING); .run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
} }
...@@ -87,8 +89,10 @@ public class TransformationTest { ...@@ -87,8 +89,10 @@ public class TransformationTest {
} }
}) })
.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) new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build() .build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING); .run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
} }
...@@ -97,10 +101,12 @@ public class TransformationTest { ...@@ -97,10 +101,12 @@ public class TransformationTest {
public void transform4K60() throws Exception { public void transform4K60() throws Exception {
final String testId = TAG + "_transform4K60"; final String testId = TAG + "_transform4K60";
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build(); 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) new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build() .build()
.run(testId, MP4_REMOTE_4K60_PORTRAIT_URI_STRING); .run(testId, MP4_REMOTE_4K60_PORTRAIT_URI_STRING);
} }
...@@ -109,10 +115,12 @@ public class TransformationTest { ...@@ -109,10 +115,12 @@ public class TransformationTest {
public void transformNoAudio() throws Exception { public void transformNoAudio() throws Exception {
final String testId = TAG + "_transformNoAudio"; final String testId = TAG + "_transformNoAudio";
// TODO(b/223381524): Enable Ssim calculation after fixing queueInputBuffer exception.
Context context = ApplicationProvider.getApplicationContext(); Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).setRemoveAudio(true).build(); 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) new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build() .build()
.run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING); .run(testId, MP4_ASSET_WITH_INCREASING_TIMESTAMPS_URI_STRING);
} }
...@@ -144,8 +152,10 @@ public class TransformationTest { ...@@ -144,8 +152,10 @@ public class TransformationTest {
.setTransformationRequest( .setTransformationRequest(
new TransformationRequest.Builder().setFlattenForSlowMotion(true).build()) new TransformationRequest.Builder().setFlattenForSlowMotion(true).build())
.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) new TransformerAndroidTestRunner.Builder(context, transformer)
.setCalculateSsim(true)
.setSuppressAnalysisExceptions(true)
.build() .build()
.run(testId, MP4_ASSET_SEF_URI_STRING); .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