Commit e46b4c0f by samrobinson Committed by Ian Baker

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
parent c9e644b4
...@@ -311,13 +311,15 @@ public final class TransformerActivity extends AppCompatActivity { ...@@ -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);
} }
}) })
......
...@@ -65,12 +65,12 @@ app is notified of events via the listener passed to the `Transformer` builder. ...@@ -65,12 +65,12 @@ app is notified of events via the listener passed to the `Transformer` builder.
Transformer.Listener transformerListener = Transformer.Listener transformerListener =
new Transformer.Listener() { new Transformer.Listener() {
@Override @Override
public void onTransformationCompleted(MediaItem inputMediaItem, TransformationResult transformationResult) { public void onTransformationCompleted(MediaItem inputMediaItem, TransformationResult result) {
playOutput(); playOutput();
} }
@Override @Override
public void onTransformationError(MediaItem inputMediaItem, TransformationException e) { public void onTransformationError(MediaItem inputMediaItem, TransformationResult result, TransformationException exception) {
displayError(e); displayError(e);
} }
}; };
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.transformer; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.transformer;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.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,21 +29,34 @@ public class TransformationTestResult { ...@@ -28,21 +29,34 @@ 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.
* *
* <p>{@code null} represents an unset or unknown value. * <p>{@code null} represents an unset or unknown value.
...@@ -86,6 +100,20 @@ public class TransformationTestResult { ...@@ -86,6 +100,20 @@ public class TransformationTestResult {
} }
/** /**
* 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.
* *
* <p>{@code null} represents an unset or unknown value. * <p>{@code null} represents an unset or unknown value.
...@@ -102,7 +130,12 @@ public class TransformationTestResult { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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
......
...@@ -36,6 +36,7 @@ public final class TransformationResult { ...@@ -36,6 +36,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;
...@@ -51,7 +52,7 @@ public final class TransformationResult { ...@@ -51,7 +52,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;
} }
...@@ -132,6 +133,14 @@ public final class TransformationResult { ...@@ -132,6 +133,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,
...@@ -142,7 +151,8 @@ public final class TransformationResult { ...@@ -142,7 +151,8 @@ public final class TransformationResult {
audioDecoderName, audioDecoderName,
audioEncoderName, audioEncoderName,
videoDecoderName, videoDecoderName,
videoEncoderName); videoEncoderName,
transformationException);
} }
} }
...@@ -160,7 +170,6 @@ public final class TransformationResult { ...@@ -160,7 +170,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. */
...@@ -169,6 +178,11 @@ public final class TransformationResult { ...@@ -169,6 +178,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,
...@@ -179,7 +193,8 @@ public final class TransformationResult { ...@@ -179,7 +193,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;
...@@ -189,6 +204,7 @@ public final class TransformationResult { ...@@ -189,6 +204,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() {
...@@ -201,7 +217,8 @@ public final class TransformationResult { ...@@ -201,7 +217,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
...@@ -221,7 +238,8 @@ public final class TransformationResult { ...@@ -221,7 +238,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
...@@ -235,6 +253,7 @@ public final class TransformationResult { ...@@ -235,6 +253,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;
} }
} }
...@@ -533,7 +533,8 @@ public final class Transformer { ...@@ -533,7 +533,8 @@ 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) {
...@@ -541,13 +542,24 @@ public final class Transformer { ...@@ -541,13 +542,24 @@ public final class Transformer {
} }
/** /**
* @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
...@@ -878,13 +890,14 @@ public final class Transformer { ...@@ -878,13 +890,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();
}); });
} }
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.transformer; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.transformer;
import static com.google.android.exoplayer2.transformer.TransformationException.ERROR_CODE_MUXING_FAILED; import static com.google.android.exoplayer2.transformer.TransformationException.ERROR_CODE_MUXING_FAILED;
import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION; import static com.google.android.exoplayer2.transformer.Transformer.PROGRESS_STATE_NO_TRANSFORMATION;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context; import android.content.Context;
...@@ -57,9 +56,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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);
} }
}); });
......
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