Create and return a TransformationResult regardless of success.
The TransformationResult has some useful values that are set in error cases, such as the codecs used. PiperOrigin-RevId: 495568259
This commit is contained in:
parent
a09bdfe995
commit
63cc0338e1
@ -311,13 +311,15 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
new Transformer.Listener() {
|
new Transformer.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationCompleted(
|
public void onTransformationCompleted(
|
||||||
MediaItem mediaItem, TransformationResult transformationResult) {
|
MediaItem mediaItem, TransformationResult result) {
|
||||||
TransformerActivity.this.onTransformationCompleted(filePath, mediaItem);
|
TransformerActivity.this.onTransformationCompleted(filePath, mediaItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationError(
|
public void onTransformationError(
|
||||||
MediaItem mediaItem, TransformationException exception) {
|
MediaItem mediaItem,
|
||||||
|
TransformationResult result,
|
||||||
|
TransformationException exception) {
|
||||||
TransformerActivity.this.onTransformationError(exception);
|
TransformerActivity.this.onTransformationError(exception);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -17,6 +17,7 @@ package androidx.media3.transformer;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.util.Assertions;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@ -28,20 +29,33 @@ public class TransformationTestResult {
|
|||||||
|
|
||||||
/** A builder for {@link TransformationTestResult}. */
|
/** A builder for {@link TransformationTestResult}. */
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private final TransformationResult transformationResult;
|
@Nullable private TransformationResult transformationResult;
|
||||||
|
|
||||||
@Nullable private String filePath;
|
@Nullable private String filePath;
|
||||||
@Nullable private Exception analysisException;
|
|
||||||
private long elapsedTimeMs;
|
private long elapsedTimeMs;
|
||||||
private double ssim;
|
private double ssim;
|
||||||
|
@Nullable private Exception testException;
|
||||||
|
@Nullable private Exception analysisException;
|
||||||
|
|
||||||
/** Creates a new {@link Builder}. */
|
/** Creates a new {@link Builder}. */
|
||||||
public Builder(TransformationResult transformationResult) {
|
public Builder() {
|
||||||
this.transformationResult = transformationResult;
|
|
||||||
this.elapsedTimeMs = C.TIME_UNSET;
|
this.elapsedTimeMs = C.TIME_UNSET;
|
||||||
this.ssim = SSIM_UNSET;
|
this.ssim = SSIM_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link TransformationResult} of the transformation.
|
||||||
|
*
|
||||||
|
* <p>This field must be set.
|
||||||
|
*
|
||||||
|
* @param transformationResult The {@link TransformationResult}.
|
||||||
|
* @return This {@link Builder}
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setTransformationResult(TransformationResult transformationResult) {
|
||||||
|
this.transformationResult = transformationResult;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the file path of the output file.
|
* Sets the file path of the output file.
|
||||||
*
|
*
|
||||||
@ -85,6 +99,20 @@ public class TransformationTestResult {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets an {@link Exception} that occurred during the test.
|
||||||
|
*
|
||||||
|
* <p>{@code null} represents an unset or unknown value.
|
||||||
|
*
|
||||||
|
* @param testException The {@link Exception} thrown during the test.
|
||||||
|
* @return This {@link Builder}.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setTestException(@Nullable Exception testException) {
|
||||||
|
this.testException = testException;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an {@link Exception} that occurred during post-transformation analysis.
|
* Sets an {@link Exception} that occurred during post-transformation analysis.
|
||||||
*
|
*
|
||||||
@ -102,7 +130,12 @@ public class TransformationTestResult {
|
|||||||
/** Builds the {@link TransformationTestResult} instance. */
|
/** Builds the {@link TransformationTestResult} instance. */
|
||||||
public TransformationTestResult build() {
|
public TransformationTestResult build() {
|
||||||
return new TransformationTestResult(
|
return new TransformationTestResult(
|
||||||
transformationResult, filePath, elapsedTimeMs, ssim, analysisException);
|
Assertions.checkNotNull(transformationResult),
|
||||||
|
filePath,
|
||||||
|
elapsedTimeMs,
|
||||||
|
ssim,
|
||||||
|
testException,
|
||||||
|
analysisException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +153,12 @@ public class TransformationTestResult {
|
|||||||
public final long elapsedTimeMs;
|
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 the test, or {@code null} if nothing was thrown.
|
||||||
|
*/
|
||||||
|
@Nullable public final Exception testException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link Exception} that was thrown during post-transformation analysis, or {@code null} if
|
* The {@link Exception} that was thrown during post-transformation analysis, or {@code null} if
|
||||||
* nothing was thrown.
|
* nothing was thrown.
|
||||||
@ -165,6 +204,9 @@ public class TransformationTestResult {
|
|||||||
if (ssim != TransformationTestResult.SSIM_UNSET) {
|
if (ssim != TransformationTestResult.SSIM_UNSET) {
|
||||||
jsonObject.put("ssim", ssim);
|
jsonObject.put("ssim", ssim);
|
||||||
}
|
}
|
||||||
|
if (testException != null) {
|
||||||
|
jsonObject.put("testException", AndroidTestUtil.exceptionAsJsonObject(testException));
|
||||||
|
}
|
||||||
if (analysisException != null) {
|
if (analysisException != null) {
|
||||||
jsonObject.put("analysisException", AndroidTestUtil.exceptionAsJsonObject(analysisException));
|
jsonObject.put("analysisException", AndroidTestUtil.exceptionAsJsonObject(analysisException));
|
||||||
}
|
}
|
||||||
@ -176,11 +218,13 @@ public class TransformationTestResult {
|
|||||||
@Nullable String filePath,
|
@Nullable String filePath,
|
||||||
long elapsedTimeMs,
|
long elapsedTimeMs,
|
||||||
double ssim,
|
double ssim,
|
||||||
|
@Nullable Exception testException,
|
||||||
@Nullable Exception analysisException) {
|
@Nullable Exception analysisException) {
|
||||||
this.transformationResult = transformationResult;
|
this.transformationResult = transformationResult;
|
||||||
this.filePath = filePath;
|
this.filePath = filePath;
|
||||||
this.elapsedTimeMs = elapsedTimeMs;
|
this.elapsedTimeMs = elapsedTimeMs;
|
||||||
this.ssim = ssim;
|
this.ssim = ssim;
|
||||||
|
this.testException = testException;
|
||||||
this.analysisException = analysisException;
|
this.analysisException = analysisException;
|
||||||
this.throughputFps =
|
this.throughputFps =
|
||||||
elapsedTimeMs != C.TIME_UNSET && transformationResult.videoFrameCount > 0
|
elapsedTimeMs != C.TIME_UNSET && transformationResult.videoFrameCount > 0
|
||||||
|
@ -39,7 +39,6 @@ import java.util.concurrent.TimeoutException;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
/** An android instrumentation test runner for {@link Transformer}. */
|
/** An android instrumentation test runner for {@link Transformer}. */
|
||||||
@ -153,8 +152,6 @@ public class TransformerAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final CapturingDecoderFactory decoderFactory;
|
|
||||||
private final CapturingEncoderFactory encoderFactory;
|
|
||||||
private final Transformer transformer;
|
private final Transformer transformer;
|
||||||
private final int timeoutSeconds;
|
private final int timeoutSeconds;
|
||||||
private final boolean requestCalculateSsim;
|
private final boolean requestCalculateSsim;
|
||||||
@ -169,14 +166,7 @@ public class TransformerAndroidTestRunner {
|
|||||||
boolean suppressAnalysisExceptions,
|
boolean suppressAnalysisExceptions,
|
||||||
@Nullable Map<String, Object> inputValues) {
|
@Nullable Map<String, Object> inputValues) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.decoderFactory = new CapturingDecoderFactory(transformer.decoderFactory);
|
this.transformer = transformer;
|
||||||
this.encoderFactory = new CapturingEncoderFactory(transformer.encoderFactory);
|
|
||||||
this.transformer =
|
|
||||||
transformer
|
|
||||||
.buildUpon()
|
|
||||||
.setDecoderFactory(decoderFactory)
|
|
||||||
.setEncoderFactory(encoderFactory)
|
|
||||||
.build();
|
|
||||||
this.timeoutSeconds = timeoutSeconds;
|
this.timeoutSeconds = timeoutSeconds;
|
||||||
this.requestCalculateSsim = requestCalculateSsim;
|
this.requestCalculateSsim = requestCalculateSsim;
|
||||||
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
|
this.suppressAnalysisExceptions = suppressAnalysisExceptions;
|
||||||
@ -184,7 +174,7 @@ public class TransformerAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the {@code uriString}, saving a summary of the transformation to the application
|
* Transforms the {@link MediaItem}, saving a summary of the transformation to the application
|
||||||
* cache.
|
* cache.
|
||||||
*
|
*
|
||||||
* @param testId A unique identifier for the transformer test run.
|
* @param testId A unique identifier for the transformer test run.
|
||||||
@ -200,35 +190,35 @@ public class TransformerAndroidTestRunner {
|
|||||||
try {
|
try {
|
||||||
TransformationTestResult transformationTestResult = runInternal(testId, mediaItem);
|
TransformationTestResult transformationTestResult = runInternal(testId, mediaItem);
|
||||||
resultJson.put("transformationResult", transformationTestResult.asJsonObject());
|
resultJson.put("transformationResult", transformationTestResult.asJsonObject());
|
||||||
|
if (transformationTestResult.testException != null) {
|
||||||
|
throw transformationTestResult.testException;
|
||||||
|
}
|
||||||
if (!suppressAnalysisExceptions && transformationTestResult.analysisException != null) {
|
if (!suppressAnalysisExceptions && transformationTestResult.analysisException != null) {
|
||||||
throw transformationTestResult.analysisException;
|
throw transformationTestResult.analysisException;
|
||||||
}
|
}
|
||||||
return transformationTestResult;
|
return transformationTestResult;
|
||||||
} catch (Exception e) {
|
} catch (UnsupportedOperationException | InterruptedException | IOException e) {
|
||||||
resultJson.put("exception", AndroidTestUtil.exceptionAsJsonObject(e));
|
resultJson.put(
|
||||||
|
"transformationResult",
|
||||||
|
new JSONObject().put("testException", AndroidTestUtil.exceptionAsJsonObject(e)));
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
resultJson.put("codecDetails", getCodecNamesAsJsonObject());
|
|
||||||
AndroidTestUtil.writeTestSummaryToFile(context, testId, resultJson);
|
AndroidTestUtil.writeTestSummaryToFile(context, testId, resultJson);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the {@code uriString}.
|
* Transforms the {@link MediaItem}.
|
||||||
*
|
*
|
||||||
* @param testId An identifier for the test.
|
* @param testId An identifier for the test.
|
||||||
* @param mediaItem The {@link MediaItem} to transform.
|
* @param mediaItem The {@link MediaItem} to transform.
|
||||||
* @return The {@link TransformationTestResult}.
|
* @return The {@link TransformationTestResult}.
|
||||||
* @throws IOException If an error occurs opening the output file for writing
|
|
||||||
* @throws TimeoutException If the transformation takes longer than the {@link #timeoutSeconds}.
|
|
||||||
* @throws InterruptedException If the thread is interrupted whilst waiting for transformer to
|
* @throws InterruptedException If the thread is interrupted whilst waiting for transformer to
|
||||||
* complete.
|
* complete.
|
||||||
* @throws TransformationException If an exception occurs as a result of the transformation.
|
* @throws IOException If an error occurs opening the output file for writing.
|
||||||
* @throws IllegalArgumentException If the path is invalid.
|
|
||||||
* @throws IllegalStateException If an unexpected exception occurs when starting a transformation.
|
|
||||||
*/
|
*/
|
||||||
private TransformationTestResult runInternal(String testId, MediaItem mediaItem)
|
private TransformationTestResult runInternal(String testId, MediaItem mediaItem)
|
||||||
throws InterruptedException, IOException, TimeoutException, TransformationException {
|
throws InterruptedException, IOException {
|
||||||
if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
|
if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
|
||||||
&& requestCalculateSsim) {
|
&& requestCalculateSsim) {
|
||||||
throw new UnsupportedOperationException(
|
throw new UnsupportedOperationException(
|
||||||
@ -266,8 +256,11 @@ public class TransformerAndroidTestRunner {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationError(
|
public void onTransformationError(
|
||||||
MediaItem inputMediaItem, TransformationException exception) {
|
MediaItem inputMediaItem,
|
||||||
|
TransformationResult result,
|
||||||
|
TransformationException exception) {
|
||||||
transformationExceptionReference.set(exception);
|
transformationExceptionReference.set(exception);
|
||||||
|
transformationResultReference.set(result);
|
||||||
countDownLatch.countDown();
|
countDownLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,45 +295,54 @@ public class TransformerAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!countDownLatch.await(timeoutSeconds, SECONDS)) {
|
// Block here until timeout reached or latch is counted down.
|
||||||
throw new TimeoutException("Transformer timed out after " + timeoutSeconds + " seconds.");
|
boolean timeoutReached = !countDownLatch.await(timeoutSeconds, SECONDS);
|
||||||
}
|
|
||||||
long elapsedTimeMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
|
TransformationTestResult.Builder testResultBuilder =
|
||||||
|
new TransformationTestResult.Builder()
|
||||||
|
.setElapsedTimeMs(SystemClock.DEFAULT.elapsedRealtime() - startTimeMs);
|
||||||
|
|
||||||
@Nullable Exception unexpectedException = unexpectedExceptionReference.get();
|
@Nullable Exception unexpectedException = unexpectedExceptionReference.get();
|
||||||
if (unexpectedException != null) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Unexpected exception starting the transformer.", unexpectedException);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
TransformationException transformationException = transformationExceptionReference.get();
|
TransformationException transformationException = transformationExceptionReference.get();
|
||||||
if (transformationException != null) {
|
|
||||||
throw transformationException;
|
@Nullable Exception testException = null;
|
||||||
|
if (timeoutReached) {
|
||||||
|
testException =
|
||||||
|
new TimeoutException("Transformer timed out after " + timeoutSeconds + " seconds.");
|
||||||
|
} else if (unexpectedException != null) {
|
||||||
|
testException =
|
||||||
|
new IllegalStateException(
|
||||||
|
"Unexpected exception starting the transformer.", unexpectedException);
|
||||||
|
} else if (transformationException != null) {
|
||||||
|
testException = transformationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both exceptions are null, the Transformation must have succeeded, and a
|
if (testException != null) {
|
||||||
// transformationResult will be available.
|
return testResultBuilder
|
||||||
TransformationResult transformationResult =
|
.setTransformationResult(checkNotNull(transformationResultReference.get()))
|
||||||
checkNotNull(transformationResultReference.get())
|
.setTestException(testException)
|
||||||
.buildUpon()
|
.build();
|
||||||
.setFileSizeBytes(outputVideoFile.length())
|
}
|
||||||
.build();
|
|
||||||
|
|
||||||
TransformationTestResult.Builder resultBuilder =
|
// No exceptions raised, transformation has succeeded.
|
||||||
new TransformationTestResult.Builder(transformationResult)
|
testResultBuilder
|
||||||
.setFilePath(outputVideoFile.getPath())
|
.setTransformationResult(
|
||||||
.setElapsedTimeMs(elapsedTimeMs);
|
checkNotNull(transformationResultReference.get())
|
||||||
|
.buildUpon()
|
||||||
|
.setFileSizeBytes(outputVideoFile.length())
|
||||||
|
.build())
|
||||||
|
.setFilePath(outputVideoFile.getPath());
|
||||||
|
|
||||||
if (!requestCalculateSsim) {
|
if (!requestCalculateSsim) {
|
||||||
return resultBuilder.build();
|
return testResultBuilder.build();
|
||||||
}
|
}
|
||||||
if (fallbackResolutionApplied.get()) {
|
if (fallbackResolutionApplied.get()) {
|
||||||
Log.i(
|
Log.i(
|
||||||
TAG,
|
TAG,
|
||||||
testId
|
testId
|
||||||
+ ": Skipping SSIM calculation because an encoder resolution fallback was applied.");
|
+ ": Skipping SSIM calculation because an encoder resolution fallback was applied.");
|
||||||
return resultBuilder.build();
|
return testResultBuilder.build();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
double ssim =
|
double ssim =
|
||||||
@ -348,7 +350,7 @@ public class TransformerAndroidTestRunner {
|
|||||||
context,
|
context,
|
||||||
/* referenceVideoPath= */ checkNotNull(mediaItem.localConfiguration).uri.toString(),
|
/* referenceVideoPath= */ checkNotNull(mediaItem.localConfiguration).uri.toString(),
|
||||||
/* distortedVideoPath= */ outputVideoFile.getPath());
|
/* distortedVideoPath= */ outputVideoFile.getPath());
|
||||||
resultBuilder.setSsim(ssim);
|
testResultBuilder.setSsim(ssim);
|
||||||
} catch (InterruptedException interruptedException) {
|
} catch (InterruptedException interruptedException) {
|
||||||
// InterruptedException is a special unexpected case because it is not related to Ssim
|
// 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
|
// calculation, so it should be thrown, rather than processed as part of the
|
||||||
@ -366,11 +368,11 @@ public class TransformerAndroidTestRunner {
|
|||||||
? (Exception) analysisFailure
|
? (Exception) analysisFailure
|
||||||
: new IllegalStateException(analysisFailure);
|
: new IllegalStateException(analysisFailure);
|
||||||
|
|
||||||
resultBuilder.setAnalysisException(analysisException);
|
testResultBuilder.setAnalysisException(analysisException);
|
||||||
Log.e(TAG, testId + ": SSIM calculation failed.", analysisException);
|
Log.e(TAG, testId + ": SSIM calculation failed.", analysisException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultBuilder.build();
|
return testResultBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the context is connected to the network. */
|
/** Returns whether the context is connected to the network. */
|
||||||
@ -400,21 +402,4 @@ public class TransformerAndroidTestRunner {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JSONObject getCodecNamesAsJsonObject() throws JSONException {
|
|
||||||
JSONObject detailsJson = new JSONObject();
|
|
||||||
if (decoderFactory.getAudioDecoderName() != null) {
|
|
||||||
detailsJson.put("audioDecoderName", decoderFactory.getAudioDecoderName());
|
|
||||||
}
|
|
||||||
if (decoderFactory.getVideoDecoderName() != null) {
|
|
||||||
detailsJson.put("videoDecoderName", decoderFactory.getVideoDecoderName());
|
|
||||||
}
|
|
||||||
if (encoderFactory.getAudioEncoderName() != null) {
|
|
||||||
detailsJson.put("audioEncoderName", encoderFactory.getAudioEncoderName());
|
|
||||||
}
|
|
||||||
if (encoderFactory.getVideoEncoderName() != null) {
|
|
||||||
detailsJson.put("videoEncoderName", encoderFactory.getVideoEncoderName());
|
|
||||||
}
|
|
||||||
return detailsJson;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ public final class TransformationResult {
|
|||||||
@Nullable private String audioEncoderName;
|
@Nullable private String audioEncoderName;
|
||||||
@Nullable private String videoDecoderName;
|
@Nullable private String videoDecoderName;
|
||||||
@Nullable private String videoEncoderName;
|
@Nullable private String videoEncoderName;
|
||||||
|
@Nullable private TransformationException transformationException;
|
||||||
|
|
||||||
public Builder() {
|
public Builder() {
|
||||||
durationMs = C.TIME_UNSET;
|
durationMs = C.TIME_UNSET;
|
||||||
@ -53,7 +54,7 @@ public final class TransformationResult {
|
|||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setDurationMs(long durationMs) {
|
public Builder setDurationMs(long durationMs) {
|
||||||
checkArgument(durationMs > 0 || durationMs == C.TIME_UNSET);
|
checkArgument(durationMs >= 0 || durationMs == C.TIME_UNSET);
|
||||||
this.durationMs = durationMs;
|
this.durationMs = durationMs;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -134,6 +135,14 @@ public final class TransformationResult {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the {@link TransformationException} that caused the transformation to fail. */
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setTransformationException(
|
||||||
|
@Nullable TransformationException transformationException) {
|
||||||
|
this.transformationException = transformationException;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public TransformationResult build() {
|
public TransformationResult build() {
|
||||||
return new TransformationResult(
|
return new TransformationResult(
|
||||||
durationMs,
|
durationMs,
|
||||||
@ -144,7 +153,8 @@ public final class TransformationResult {
|
|||||||
audioDecoderName,
|
audioDecoderName,
|
||||||
audioEncoderName,
|
audioEncoderName,
|
||||||
videoDecoderName,
|
videoDecoderName,
|
||||||
videoEncoderName);
|
videoEncoderName,
|
||||||
|
transformationException);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +172,6 @@ public final class TransformationResult {
|
|||||||
public final int averageVideoBitrate;
|
public final int averageVideoBitrate;
|
||||||
/** The number of video frames. */
|
/** The number of video frames. */
|
||||||
public final int videoFrameCount;
|
public final int videoFrameCount;
|
||||||
|
|
||||||
/** The name of the audio decoder used, or {@code null} if none were used. */
|
/** The name of the audio decoder used, or {@code null} if none were used. */
|
||||||
@Nullable public final String audioDecoderName;
|
@Nullable public final String audioDecoderName;
|
||||||
/** The name of the audio encoder used, or {@code null} if none were used. */
|
/** The name of the audio encoder used, or {@code null} if none were used. */
|
||||||
@ -171,6 +180,11 @@ public final class TransformationResult {
|
|||||||
@Nullable public final String videoDecoderName;
|
@Nullable public final String videoDecoderName;
|
||||||
/** The name of the video encoder used, or {@code null} if none were used. */
|
/** The name of the video encoder used, or {@code null} if none were used. */
|
||||||
@Nullable public final String videoEncoderName;
|
@Nullable public final String videoEncoderName;
|
||||||
|
/**
|
||||||
|
* The {@link TransformationException} that caused the transformation to fail, or {@code null} if
|
||||||
|
* the transformation was a success.
|
||||||
|
*/
|
||||||
|
@Nullable public final TransformationException transformationException;
|
||||||
|
|
||||||
private TransformationResult(
|
private TransformationResult(
|
||||||
long durationMs,
|
long durationMs,
|
||||||
@ -181,7 +195,8 @@ public final class TransformationResult {
|
|||||||
@Nullable String audioDecoderName,
|
@Nullable String audioDecoderName,
|
||||||
@Nullable String audioEncoderName,
|
@Nullable String audioEncoderName,
|
||||||
@Nullable String videoDecoderName,
|
@Nullable String videoDecoderName,
|
||||||
@Nullable String videoEncoderName) {
|
@Nullable String videoEncoderName,
|
||||||
|
@Nullable TransformationException transformationException) {
|
||||||
this.durationMs = durationMs;
|
this.durationMs = durationMs;
|
||||||
this.fileSizeBytes = fileSizeBytes;
|
this.fileSizeBytes = fileSizeBytes;
|
||||||
this.averageAudioBitrate = averageAudioBitrate;
|
this.averageAudioBitrate = averageAudioBitrate;
|
||||||
@ -191,6 +206,7 @@ public final class TransformationResult {
|
|||||||
this.audioEncoderName = audioEncoderName;
|
this.audioEncoderName = audioEncoderName;
|
||||||
this.videoDecoderName = videoDecoderName;
|
this.videoDecoderName = videoDecoderName;
|
||||||
this.videoEncoderName = videoEncoderName;
|
this.videoEncoderName = videoEncoderName;
|
||||||
|
this.transformationException = transformationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder buildUpon() {
|
public Builder buildUpon() {
|
||||||
@ -203,7 +219,8 @@ public final class TransformationResult {
|
|||||||
.setAudioDecoderName(audioDecoderName)
|
.setAudioDecoderName(audioDecoderName)
|
||||||
.setAudioEncoderName(audioEncoderName)
|
.setAudioEncoderName(audioEncoderName)
|
||||||
.setVideoDecoderName(videoDecoderName)
|
.setVideoDecoderName(videoDecoderName)
|
||||||
.setVideoEncoderName(videoEncoderName);
|
.setVideoEncoderName(videoEncoderName)
|
||||||
|
.setTransformationException(transformationException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -223,7 +240,8 @@ public final class TransformationResult {
|
|||||||
&& Objects.equals(audioDecoderName, result.audioDecoderName)
|
&& Objects.equals(audioDecoderName, result.audioDecoderName)
|
||||||
&& Objects.equals(audioEncoderName, result.audioEncoderName)
|
&& Objects.equals(audioEncoderName, result.audioEncoderName)
|
||||||
&& Objects.equals(videoDecoderName, result.videoDecoderName)
|
&& Objects.equals(videoDecoderName, result.videoDecoderName)
|
||||||
&& Objects.equals(videoEncoderName, result.videoEncoderName);
|
&& Objects.equals(videoEncoderName, result.videoEncoderName)
|
||||||
|
&& Objects.equals(transformationException, result.transformationException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -237,6 +255,7 @@ public final class TransformationResult {
|
|||||||
result = 31 * result + Objects.hashCode(audioEncoderName);
|
result = 31 * result + Objects.hashCode(audioEncoderName);
|
||||||
result = 31 * result + Objects.hashCode(videoDecoderName);
|
result = 31 * result + Objects.hashCode(videoDecoderName);
|
||||||
result = 31 * result + Objects.hashCode(videoEncoderName);
|
result = 31 * result + Objects.hashCode(videoEncoderName);
|
||||||
|
result = 31 * result + Objects.hashCode(transformationException);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -535,21 +535,33 @@ public final class Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #onTransformationError(MediaItem, TransformationException)}.
|
* @deprecated Use {@link #onTransformationError(MediaItem, TransformationResult,
|
||||||
|
* TransformationException)}.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
default void onTransformationError(MediaItem inputMediaItem, Exception exception) {
|
default void onTransformationError(MediaItem inputMediaItem, Exception exception) {
|
||||||
onTransformationError(inputMediaItem, (TransformationException) exception);
|
onTransformationError(inputMediaItem, (TransformationException) exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #onTransformationError(MediaItem, TransformationResult,
|
||||||
|
* TransformationException)}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
default void onTransformationError(
|
||||||
|
MediaItem inputMediaItem, TransformationException exception) {
|
||||||
|
onTransformationError(inputMediaItem, new TransformationResult.Builder().build(), exception);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called if an exception occurs during the transformation.
|
* Called if an exception occurs during the transformation.
|
||||||
*
|
*
|
||||||
* @param inputMediaItem The {@link MediaItem} for which the exception occurs.
|
* @param inputMediaItem The {@link MediaItem} for which the exception occurs.
|
||||||
|
* @param result The {@link TransformationResult} of the transformation.
|
||||||
* @param exception The {@link TransformationException} describing the exception.
|
* @param exception The {@link TransformationException} describing the exception.
|
||||||
*/
|
*/
|
||||||
default void onTransformationError(
|
default void onTransformationError(
|
||||||
MediaItem inputMediaItem, TransformationException exception) {}
|
MediaItem inputMediaItem, TransformationResult result, TransformationException exception) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when fallback to an alternative {@link TransformationRequest} is necessary to comply
|
* Called when fallback to an alternative {@link TransformationRequest} is necessary to comply
|
||||||
@ -880,13 +892,14 @@ public final class Transformer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationError(TransformationException exception) {
|
public void onTransformationError(
|
||||||
|
TransformationResult result, TransformationException exception) {
|
||||||
handler.post(
|
handler.post(
|
||||||
() -> {
|
() -> {
|
||||||
transformerInternal = null;
|
transformerInternal = null;
|
||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
/* eventFlag= */ C.INDEX_UNSET,
|
/* eventFlag= */ C.INDEX_UNSET,
|
||||||
listener -> listener.onTransformationError(mediaItem, exception));
|
listener -> listener.onTransformationError(mediaItem, result, exception));
|
||||||
listeners.flushEvents();
|
listeners.flushEvents();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
package androidx.media3.transformer;
|
package androidx.media3.transformer;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
|
||||||
import static androidx.media3.transformer.TransformationException.ERROR_CODE_MUXING_FAILED;
|
import static androidx.media3.transformer.TransformationException.ERROR_CODE_MUXING_FAILED;
|
||||||
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION;
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
@ -57,9 +56,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
|
|
||||||
void onTransformationCompleted(TransformationResult transformationResult);
|
void onTransformationCompleted(TransformationResult result);
|
||||||
|
|
||||||
void onTransformationError(TransformationException exception);
|
void onTransformationError(TransformationResult result, TransformationException exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,7 +299,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
private void endInternal(
|
private void endInternal(
|
||||||
@EndReason int endReason, @Nullable TransformationException transformationException) {
|
@EndReason int endReason, @Nullable TransformationException transformationException) {
|
||||||
@Nullable TransformationResult transformationResult = null;
|
TransformationResult.Builder transformationResultBuilder =
|
||||||
|
new TransformationResult.Builder()
|
||||||
|
.setAudioDecoderName(decoderFactory.getAudioDecoderName())
|
||||||
|
.setVideoDecoderName(decoderFactory.getVideoDecoderName())
|
||||||
|
.setAudioEncoderName(encoderFactory.getAudioEncoderName())
|
||||||
|
.setVideoEncoderName(encoderFactory.getVideoEncoderName());
|
||||||
|
|
||||||
boolean forCancellation = endReason == END_REASON_CANCELLED;
|
boolean forCancellation = endReason == END_REASON_CANCELLED;
|
||||||
@Nullable TransformationException releaseTransformationException = null;
|
@Nullable TransformationException releaseTransformationException = null;
|
||||||
if (!released) {
|
if (!released) {
|
||||||
@ -315,26 +320,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
assetLoader.release();
|
assetLoader.release();
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < samplePipelines.size(); i++) {
|
if (endReason == END_REASON_COMPLETED) {
|
||||||
samplePipelines.get(i).release();
|
transformationResultBuilder
|
||||||
|
.setDurationMs(muxerWrapper.getDurationMs())
|
||||||
|
.setFileSizeBytes(muxerWrapper.getCurrentOutputSizeBytes())
|
||||||
|
.setAverageAudioBitrate(muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_AUDIO))
|
||||||
|
.setAverageVideoBitrate(muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_VIDEO))
|
||||||
|
.setVideoFrameCount(muxerWrapper.getTrackSampleCount(C.TRACK_TYPE_VIDEO));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(b/250564186): Create TransformationResult on END_REASON_ERROR as well.
|
for (int i = 0; i < samplePipelines.size(); i++) {
|
||||||
if (endReason == END_REASON_COMPLETED) {
|
samplePipelines.get(i).release();
|
||||||
transformationResult =
|
|
||||||
new TransformationResult.Builder()
|
|
||||||
.setDurationMs(checkNotNull(muxerWrapper).getDurationMs())
|
|
||||||
.setAverageAudioBitrate(
|
|
||||||
muxerWrapper.getTrackAverageBitrate(C.TRACK_TYPE_AUDIO))
|
|
||||||
.setAverageVideoBitrate(
|
|
||||||
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 {
|
} finally {
|
||||||
muxerWrapper.release(forCancellation);
|
muxerWrapper.release(forCancellation);
|
||||||
@ -369,9 +365,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
listener.onTransformationError(exception);
|
listener.onTransformationError(
|
||||||
|
transformationResultBuilder.setTransformationException(exception).build(), exception);
|
||||||
} else {
|
} else {
|
||||||
listener.onTransformationCompleted(checkNotNull(transformationResult));
|
listener.onTransformationCompleted(transformationResultBuilder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -337,9 +337,9 @@ public final class TransformerEndToEndTest {
|
|||||||
transformer.startTransformation(mediaItem, outputPath);
|
transformer.startTransformation(mediaItem, outputPath);
|
||||||
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
TransformationException exception = TransformerTestRunner.runUntilError(transformer);
|
||||||
|
|
||||||
verify(mockListener1).onTransformationError(mediaItem, exception);
|
verify(mockListener1).onTransformationError(eq(mediaItem), any(), eq(exception));
|
||||||
verify(mockListener2).onTransformationError(mediaItem, exception);
|
verify(mockListener2).onTransformationError(eq(mediaItem), any(), eq(exception));
|
||||||
verify(mockListener3).onTransformationError(mediaItem, exception);
|
verify(mockListener3).onTransformationError(eq(mediaItem), any(), eq(exception));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -85,7 +85,9 @@ public final class TransformerTestRunner {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationError(
|
public void onTransformationError(
|
||||||
MediaItem inputMediaItem, TransformationException exception) {
|
MediaItem inputMediaItem,
|
||||||
|
TransformationResult result,
|
||||||
|
TransformationException exception) {
|
||||||
transformationException.set(exception);
|
transformationException.set(exception);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user