Rename TransformationResult to ExportResult

Also replace some usages of deprecated Transformer listeners with the new
ones.

PiperOrigin-RevId: 507743860
This commit is contained in:
kimvde 2023-02-07 12:50:12 +00:00 committed by microkatz
parent 01d7bc7279
commit c434cc0c9f
20 changed files with 671 additions and 242 deletions

View File

@ -74,14 +74,15 @@ import androidx.media3.effect.TextureOverlay;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor;
import androidx.media3.exoplayer.util.DebugTextViewHelper;
import androidx.media3.transformer.Composition;
import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.DefaultMuxer;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.Effects;
import androidx.media3.transformer.ExportResult;
import androidx.media3.transformer.ProgressHolder;
import androidx.media3.transformer.TransformationException;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationResult;
import androidx.media3.transformer.Transformer;
import androidx.media3.ui.AspectRatioFrameLayout;
import androidx.media3.ui.PlayerView;
@ -307,15 +308,16 @@ public final class TransformerActivity extends AppCompatActivity {
.addListener(
new Transformer.Listener() {
@Override
public void onTransformationCompleted(
MediaItem mediaItem, TransformationResult result) {
public void onCompleted(Composition composition, ExportResult exportResult) {
MediaItem mediaItem =
composition.sequences.get(0).editedMediaItems.get(0).mediaItem;
TransformerActivity.this.onTransformationCompleted(filePath, mediaItem);
}
@Override
public void onTransformationError(
MediaItem mediaItem,
TransformationResult result,
public void onError(
Composition composition,
ExportResult exportResult,
TransformationException exception) {
TransformerActivity.this.onTransformationError(exception);
}

View File

@ -504,17 +504,17 @@ public final class AndroidTestUtil {
}
/**
* Creates a {@link JSONArray} from {@link TransformationResult.ProcessedInput processed inputs}.
* Creates a {@link JSONArray} from {@link ExportResult.ProcessedInput processed inputs}.
*
* @param processedInputs The list of {@link TransformationResult.ProcessedInput} instances.
* @param processedInputs The list of {@link ExportResult.ProcessedInput} instances.
* @return A {@link JSONArray} containing {@link JSONObject} instances representing the {@link
* TransformationResult.ProcessedInput} instances.
* ExportResult.ProcessedInput} instances.
*/
public static JSONArray processedInputsAsJsonArray(
ImmutableList<TransformationResult.ProcessedInput> processedInputs) throws JSONException {
ImmutableList<ExportResult.ProcessedInput> processedInputs) throws JSONException {
JSONArray jsonArray = new JSONArray();
for (int i = 0; i < processedInputs.size(); i++) {
TransformationResult.ProcessedInput processedInput = processedInputs.get(i);
ExportResult.ProcessedInput processedInput = processedInputs.get(i);
JSONObject jsonObject = new JSONObject();
@Nullable
MediaItem.LocalConfiguration localConfiguration = processedInput.mediaItem.localConfiguration;

View File

@ -25,14 +25,14 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.json.JSONException;
import org.json.JSONObject;
/** A test only class for holding the details of a test transformation. */
public class TransformationTestResult {
/** A test only class for holding the details of a test export. */
public class ExportTestResult {
/** Represents an unset or unknown SSIM score. */
public static final double SSIM_UNSET = -1.0d;
/** A builder for {@link TransformationTestResult}. */
/** A builder for {@link ExportTestResult}. */
public static class Builder {
private final TransformationResult transformationResult;
private final ExportResult exportResult;
@Nullable private String filePath;
private long elapsedTimeMs;
@ -41,8 +41,8 @@ public class TransformationTestResult {
@Nullable private Exception analysisException;
/** Creates a new {@link Builder}. */
public Builder(TransformationResult transformationResult) {
this.transformationResult = transformationResult;
public Builder(ExportResult exportResult) {
this.exportResult = exportResult;
this.elapsedTimeMs = C.TIME_UNSET;
this.ssim = SSIM_UNSET;
}
@ -62,8 +62,8 @@ public class TransformationTestResult {
}
/**
* Sets the amount of time taken to perform the transformation in milliseconds. {@link
* C#TIME_UNSET} if unset.
* Sets the amount of time taken to perform the export in milliseconds. {@link C#TIME_UNSET} if
* unset.
*
* <p>{@link C#TIME_UNSET} represents an unset or unknown value.
*
@ -92,7 +92,7 @@ public class TransformationTestResult {
/**
* Sets an {@link FallbackDetails} object that describes the fallbacks that occurred during
* post-transformation analysis.
* post-export analysis.
*
* <p>{@code null} represents no fallback was applied.
*
@ -106,7 +106,7 @@ public class TransformationTestResult {
}
/**
* Sets an {@link Exception} that occurred during post-transformation analysis.
* Sets an {@link Exception} that occurred during post-export analysis.
*
* <p>{@code null} represents an unset or unknown value.
*
@ -119,20 +119,20 @@ public class TransformationTestResult {
return this;
}
/** Builds the {@link TransformationTestResult} instance. */
public TransformationTestResult build() {
return new TransformationTestResult(
transformationResult, filePath, elapsedTimeMs, ssim, fallbackDetails, analysisException);
/** Builds the {@link ExportTestResult} instance. */
public ExportTestResult build() {
return new ExportTestResult(
exportResult, filePath, elapsedTimeMs, ssim, fallbackDetails, analysisException);
}
}
/** The {@link TransformationResult} of the transformation. */
public final TransformationResult transformationResult;
/** The path to the file created in the transformation, or {@code null} if unset. */
/** The {@link ExportResult} of the export. */
public final ExportResult exportResult;
/** The path to the file created in the export, or {@code null} if unset. */
@Nullable public final String filePath;
/**
* The amount of time taken to perform the transformation in milliseconds, or {@link C#TIME_UNSET}
* if unset.
* The amount of time taken to perform the export in milliseconds, or {@link C#TIME_UNSET} if
* unset.
*/
public final long elapsedTimeMs;
/**
@ -140,16 +140,16 @@ public class TransformationTestResult {
* C#RATE_UNSET} if unset.
*/
public final float throughputFps;
/** The SSIM score of the transformation, or {@link #SSIM_UNSET} if unset. */
/** The SSIM score of the export, or {@link #SSIM_UNSET} if unset. */
public final double ssim;
/**
* The {@link FallbackDetails} describing the fallbacks that occurred doing transformation, or
* {@code null} if no fallback occurred.
* The {@link FallbackDetails} describing the fallbacks that occurred doing export, or {@code
* null} if no fallback occurred.
*/
@Nullable public final FallbackDetails fallbackDetails;
/**
* The {@link Exception} thrown during post-transformation analysis, or {@code null} if nothing
* was thrown.
* The {@link Exception} thrown during post-export analysis, or {@code null} if nothing was
* thrown.
*/
@Nullable public final Exception analysisException;
@ -157,80 +157,77 @@ public class TransformationTestResult {
public JSONObject asJsonObject() throws JSONException {
JSONObject jsonObject =
new JSONObject()
.putOpt("audioEncoderName", transformationResult.audioEncoderName)
.putOpt("audioEncoderName", exportResult.audioEncoderName)
.putOpt(
"fallbackDetails", fallbackDetails != null ? fallbackDetails.asJsonObject() : null)
.putOpt("filePath", filePath)
.putOpt("colorInfo", transformationResult.colorInfo)
.putOpt("videoEncoderName", transformationResult.videoEncoderName)
.putOpt(
"testException",
exceptionAsJsonObject(transformationResult.transformationException))
.putOpt("colorInfo", exportResult.colorInfo)
.putOpt("videoEncoderName", exportResult.videoEncoderName)
.putOpt("testException", exceptionAsJsonObject(exportResult.transformationException))
.putOpt("analysisException", exceptionAsJsonObject(analysisException));
if (!transformationResult.processedInputs.isEmpty()) {
jsonObject.put(
"processedInputs", processedInputsAsJsonArray(transformationResult.processedInputs));
if (!exportResult.processedInputs.isEmpty()) {
jsonObject.put("processedInputs", processedInputsAsJsonArray(exportResult.processedInputs));
}
if (transformationResult.averageAudioBitrate != C.RATE_UNSET_INT) {
jsonObject.put("averageAudioBitrate", transformationResult.averageAudioBitrate);
if (exportResult.averageAudioBitrate != C.RATE_UNSET_INT) {
jsonObject.put("averageAudioBitrate", exportResult.averageAudioBitrate);
}
if (transformationResult.averageVideoBitrate != C.RATE_UNSET_INT) {
jsonObject.put("averageVideoBitrate", transformationResult.averageVideoBitrate);
if (exportResult.averageVideoBitrate != C.RATE_UNSET_INT) {
jsonObject.put("averageVideoBitrate", exportResult.averageVideoBitrate);
}
if (transformationResult.channelCount != C.LENGTH_UNSET) {
jsonObject.put("channelCount", transformationResult.channelCount);
if (exportResult.channelCount != C.LENGTH_UNSET) {
jsonObject.put("channelCount", exportResult.channelCount);
}
if (transformationResult.durationMs != C.TIME_UNSET) {
jsonObject.put("durationMs", transformationResult.durationMs);
if (exportResult.durationMs != C.TIME_UNSET) {
jsonObject.put("durationMs", exportResult.durationMs);
}
if (elapsedTimeMs != C.TIME_UNSET) {
jsonObject.put("elapsedTimeMs", elapsedTimeMs);
}
if (transformationResult.fileSizeBytes != C.LENGTH_UNSET) {
jsonObject.put("fileSizeBytes", transformationResult.fileSizeBytes);
if (exportResult.fileSizeBytes != C.LENGTH_UNSET) {
jsonObject.put("fileSizeBytes", exportResult.fileSizeBytes);
}
if (transformationResult.height != C.LENGTH_UNSET) {
jsonObject.put("height", transformationResult.height);
if (exportResult.height != C.LENGTH_UNSET) {
jsonObject.put("height", exportResult.height);
}
if (transformationResult.pcmEncoding != Format.NO_VALUE) {
jsonObject.put("pcmEncoding", transformationResult.pcmEncoding);
if (exportResult.pcmEncoding != Format.NO_VALUE) {
jsonObject.put("pcmEncoding", exportResult.pcmEncoding);
}
if (transformationResult.sampleRate != C.RATE_UNSET_INT) {
jsonObject.put("sampleRate", transformationResult.sampleRate);
if (exportResult.sampleRate != C.RATE_UNSET_INT) {
jsonObject.put("sampleRate", exportResult.sampleRate);
}
if (ssim != TransformationTestResult.SSIM_UNSET) {
if (ssim != ExportTestResult.SSIM_UNSET) {
jsonObject.put("ssim", ssim);
}
if (throughputFps != C.RATE_UNSET) {
jsonObject.put("throughputFps", throughputFps);
}
if (transformationResult.videoFrameCount > 0) {
jsonObject.put("videoFrameCount", transformationResult.videoFrameCount);
if (exportResult.videoFrameCount > 0) {
jsonObject.put("videoFrameCount", exportResult.videoFrameCount);
}
if (transformationResult.width != C.LENGTH_UNSET) {
jsonObject.put("width", transformationResult.width);
if (exportResult.width != C.LENGTH_UNSET) {
jsonObject.put("width", exportResult.width);
}
return jsonObject;
}
private TransformationTestResult(
TransformationResult transformationResult,
private ExportTestResult(
ExportResult exportResult,
@Nullable String filePath,
long elapsedTimeMs,
double ssim,
@Nullable FallbackDetails fallbackDetails,
@Nullable Exception analysisException) {
this.transformationResult = transformationResult;
this.exportResult = exportResult;
this.filePath = filePath;
this.elapsedTimeMs = elapsedTimeMs;
this.ssim = ssim;
this.fallbackDetails = fallbackDetails;
this.analysisException = analysisException;
this.throughputFps =
elapsedTimeMs != C.TIME_UNSET && transformationResult.videoFrameCount > 0
? 1000f * transformationResult.videoFrameCount / elapsedTimeMs
elapsedTimeMs != C.TIME_UNSET && exportResult.videoFrameCount > 0
? 1000f * exportResult.videoFrameCount / elapsedTimeMs
: C.RATE_UNSET;
}
}

View File

@ -174,36 +174,34 @@ public class TransformerAndroidTestRunner {
}
/**
* Transforms the {@link EditedMediaItem}, saving a summary of the transformation to the
* application cache.
* Exports the {@link EditedMediaItem}, saving a summary of the export to the application cache.
*
* @param testId A unique identifier for the transformer test run.
* @param editedMediaItem The {@link EditedMediaItem} to transform.
* @return The {@link TransformationTestResult}.
* @throws Exception The cause of the transformation not completing.
* @param editedMediaItem The {@link EditedMediaItem} to export.
* @return The {@link ExportTestResult}.
* @throws Exception The cause of the export not completing.
*/
public TransformationTestResult run(String testId, EditedMediaItem editedMediaItem)
throws Exception {
public ExportTestResult run(String testId, EditedMediaItem editedMediaItem) throws Exception {
JSONObject resultJson = new JSONObject();
if (inputValues != null) {
resultJson.put("inputValues", JSONObject.wrap(inputValues));
}
try {
TransformationTestResult transformationTestResult = runInternal(testId, editedMediaItem);
resultJson.put("transformationResult", transformationTestResult.asJsonObject());
if (transformationTestResult.transformationResult.transformationException != null) {
throw transformationTestResult.transformationResult.transformationException;
ExportTestResult exportTestResult = runInternal(testId, editedMediaItem);
resultJson.put("exportResult", exportTestResult.asJsonObject());
if (exportTestResult.exportResult.transformationException != null) {
throw exportTestResult.exportResult.transformationException;
}
if (!suppressAnalysisExceptions && transformationTestResult.analysisException != null) {
throw transformationTestResult.analysisException;
if (!suppressAnalysisExceptions && exportTestResult.analysisException != null) {
throw exportTestResult.analysisException;
}
return transformationTestResult;
return exportTestResult;
} catch (InterruptedException
| IOException
| TimeoutException
| UnsupportedOperationException e) {
resultJson.put(
"transformationResult",
"exportResult",
new JSONObject().put("testException", AndroidTestUtil.exceptionAsJsonObject(e)));
throw e;
} finally {
@ -212,33 +210,32 @@ public class TransformerAndroidTestRunner {
}
/**
* Transforms the {@link MediaItem}, saving a summary of the transformation to the application
* cache.
* Exports the {@link MediaItem}, saving a summary of the export to the application cache.
*
* @param testId A unique identifier for the transformer test run.
* @param mediaItem The {@link MediaItem} to transform.
* @return The {@link TransformationTestResult}.
* @throws Exception The cause of the transformation not completing.
* @param mediaItem The {@link MediaItem} to export.
* @return The {@link ExportTestResult}.
* @throws Exception The cause of the export not completing.
*/
public TransformationTestResult run(String testId, MediaItem mediaItem) throws Exception {
public ExportTestResult run(String testId, MediaItem mediaItem) throws Exception {
EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build();
return run(testId, editedMediaItem);
}
/**
* Transforms the {@link EditedMediaItem}.
* Exports the {@link EditedMediaItem}.
*
* @param testId An identifier for the test.
* @param editedMediaItem The {@link EditedMediaItem} to transform.
* @return The {@link TransformationTestResult}.
* @param editedMediaItem The {@link EditedMediaItem} to export.
* @return The {@link ExportTestResult}.
* @throws IllegalStateException See {@link Transformer#start(EditedMediaItem, String)}.
* @throws InterruptedException If the thread is interrupted whilst waiting for transformer to
* complete.
* @throws IOException If an error occurs opening the output file for writing.
* @throws TimeoutException If the transformation has not completed after {@linkplain
* @throws TimeoutException If the export has not completed after {@linkplain
* Builder#setTimeoutSeconds(int) the given timeout}.
*/
private TransformationTestResult runInternal(String testId, EditedMediaItem editedMediaItem)
private ExportTestResult runInternal(String testId, EditedMediaItem editedMediaItem)
throws InterruptedException, IOException, TimeoutException {
MediaItem mediaItem = editedMediaItem.mediaItem;
if (!mediaItem.clippingConfiguration.equals(MediaItem.ClippingConfiguration.UNSET)
@ -258,8 +255,7 @@ public class TransformerAndroidTestRunner {
AtomicReference<@NullableType FallbackDetails> fallbackDetailsReference =
new AtomicReference<>();
AtomicReference<@NullableType Exception> unexpectedExceptionReference = new AtomicReference<>();
AtomicReference<@NullableType TransformationResult> transformationResultReference =
new AtomicReference<>();
AtomicReference<@NullableType ExportResult> exportResultReference = new AtomicReference<>();
CountDownLatch countDownLatch = new CountDownLatch(1);
long startTimeMs = SystemClock.DEFAULT.elapsedRealtime();
@ -269,18 +265,17 @@ public class TransformerAndroidTestRunner {
.addListener(
new Transformer.Listener() {
@Override
public void onTransformationCompleted(
MediaItem inputMediaItem, TransformationResult result) {
transformationResultReference.set(result);
public void onCompleted(Composition composition, ExportResult exportResult) {
exportResultReference.set(exportResult);
countDownLatch.countDown();
}
@Override
public void onTransformationError(
MediaItem inputMediaItem,
TransformationResult result,
public void onError(
Composition composition,
ExportResult exportResult,
TransformationException exception) {
transformationResultReference.set(result);
exportResultReference.set(exportResult);
countDownLatch.countDown();
}
@ -333,19 +328,19 @@ public class TransformerAndroidTestRunner {
long elapsedTimeMs = SystemClock.DEFAULT.elapsedRealtime() - startTimeMs;
@Nullable FallbackDetails fallbackDetails = fallbackDetailsReference.get();
TransformationResult transformationResult = checkNotNull(transformationResultReference.get());
ExportResult exportResult = checkNotNull(exportResultReference.get());
if (transformationResult.transformationException != null) {
return new TransformationTestResult.Builder(transformationResult)
if (exportResult.transformationException != null) {
return new ExportTestResult.Builder(exportResult)
.setElapsedTimeMs(elapsedTimeMs)
.setFallbackDetails(fallbackDetails)
.build();
}
// No exceptions raised, transformation has succeeded.
TransformationTestResult.Builder testResultBuilder =
new TransformationTestResult.Builder(
checkNotNull(transformationResultReference.get())
ExportTestResult.Builder testResultBuilder =
new ExportTestResult.Builder(
checkNotNull(exportResultReference.get())
.buildUpon()
.setFileSizeBytes(outputVideoFile.length())
.build())
@ -373,7 +368,7 @@ public class TransformerAndroidTestRunner {
} 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.
// ExportTestResult.
throw interruptedException;
} catch (Throwable analysisFailure) {
if (Util.SDK_INT == 21 && Ascii.toLowerCase(Util.MODEL).contains("nexus")) {
@ -381,7 +376,7 @@ public class TransformerAndroidTestRunner {
Log.i(TAG, testId + ": Skipping SSIM calculation due to known device-specific issue");
} else {
// Catch all (checked and unchecked) failures thrown by the SsimHelper and process them as
// part of the TransformationTestResult.
// part of the ExportTestResult.
Exception analysisException =
analysisFailure instanceof Exception
? (Exception) analysisFailure

View File

@ -53,12 +53,12 @@ public class TransformerAudioEndToEndTest {
.setEffects(effects)
.build();
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build())
.build()
.run(testId, editedMediaItem);
assertThat(result.transformationResult.channelCount).isEqualTo(2);
assertThat(result.exportResult.channelCount).isEqualTo(2);
}
@Test
@ -76,12 +76,12 @@ public class TransformerAudioEndToEndTest {
.setEffects(effects)
.build();
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build())
.build()
.run(testId, editedMediaItem);
assertThat(result.transformationResult.pcmEncoding).isEqualTo(C.ENCODING_PCM_FLOAT);
assertThat(result.exportResult.pcmEncoding).isEqualTo(C.ENCODING_PCM_FLOAT);
}
@Test
@ -100,12 +100,12 @@ public class TransformerAudioEndToEndTest {
.setEffects(effects)
.build();
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build())
.build()
.run(testId, editedMediaItem);
assertThat(result.transformationResult.pcmEncoding).isEqualTo(C.ENCODING_PCM_16BIT);
assertThat(result.exportResult.pcmEncoding).isEqualTo(C.ENCODING_PCM_16BIT);
}
private static Effects createForAudioProcessors(AudioProcessor... audioProcessors) {

View File

@ -57,12 +57,12 @@ public class TransformerEndToEndTest {
// ffprobe -count_frames -select_streams v:0 -show_entries stream=nb_read_frames sample.mp4
int expectedFrameCount = 30;
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(/* testId= */ "videoEditing_completesWithConsistentFrameCount", editedMediaItem);
assertThat(result.transformationResult.videoFrameCount).isEqualTo(expectedFrameCount);
assertThat(result.exportResult.videoFrameCount).isEqualTo(expectedFrameCount);
}
@Test
@ -79,12 +79,12 @@ public class TransformerEndToEndTest {
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).setEffects(effects).build();
long expectedDurationMs = 967;
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(/* testId= */ "videoOnly_completesWithConsistentDuration", editedMediaItem);
assertThat(result.transformationResult.durationMs).isEqualTo(expectedDurationMs);
assertThat(result.exportResult.durationMs).isEqualTo(expectedDurationMs);
}
@Test
@ -102,12 +102,12 @@ public class TransformerEndToEndTest {
.build())
.build();
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(/* testId= */ "clippedMedia_completesWithClippedDuration", mediaItem);
assertThat(result.transformationResult.durationMs).isAtMost(clippingEndMs - clippingStartMs);
assertThat(result.exportResult.durationMs).isAtMost(clippingEndMs - clippingStartMs);
}
@Test

View File

@ -27,9 +27,9 @@ import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Log;
import androidx.media3.transformer.AndroidTestUtil;
import androidx.media3.transformer.ExportTestResult;
import androidx.media3.transformer.TransformationException;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationTestResult;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.test.core.app.ApplicationProvider;
@ -69,11 +69,11 @@ public class ForceInterpretHdrVideoAsSdrTest {
.build();
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
Log.i(TAG, "Transformed.");
} catch (TransformationException exception) {
if (exception.errorCode != TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED
@ -106,11 +106,11 @@ public class ForceInterpretHdrVideoAsSdrTest {
.build();
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
Log.i(TAG, "Transformed.");
} catch (TransformationException exception) {
if (exception.errorCode != TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED

View File

@ -36,9 +36,9 @@ import androidx.media3.effect.ScaleToFitTransformation;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.Effects;
import androidx.media3.transformer.EncoderUtil;
import androidx.media3.transformer.ExportTestResult;
import androidx.media3.transformer.TransformationException;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationTestResult;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.test.core.app.ApplicationProvider;
@ -77,12 +77,12 @@ public class HdrEditingTest {
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Log.i(TAG, "Transformed.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_ST2084);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_ST2084);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
@ -102,12 +102,12 @@ public class HdrEditingTest {
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Log.i(TAG, "Transformed.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_HLG);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_HLG);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
@ -136,11 +136,11 @@ public class HdrEditingTest {
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_ST2084);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_ST2084);
}
@Test
@ -161,11 +161,11 @@ public class HdrEditingTest {
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_HLG);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_HLG);
}
@Test
@ -206,13 +206,13 @@ public class HdrEditingTest {
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
Log.i(TAG, "Tone mapped.");
assertThat(isToneMappingFallbackApplied.get()).isTrue();
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
@ -260,13 +260,13 @@ public class HdrEditingTest {
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
Log.i(TAG, "Tone mapped.");
assertThat(isToneMappingFallbackApplied.get()).isTrue();
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);

View File

@ -26,8 +26,8 @@ import androidx.media3.effect.ScaleToFitTransformation;
import androidx.media3.transformer.AndroidTestUtil;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.Effects;
import androidx.media3.transformer.ExportTestResult;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationTestResult;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.test.core.app.ApplicationProvider;
@ -65,10 +65,10 @@ public final class RepeatedTranscodeTest {
Set<Long> differentOutputSizesBytes = new HashSet<>();
for (int i = 0; i < TRANSCODE_COUNT; i++) {
// Use a long video in case an error occurs a while after the start of the video.
TransformationTestResult testResult =
ExportTestResult testResult =
transformerRunner.run(
/* testId= */ "repeatedTranscode_givesConsistentLengthOutput_" + i, editedMediaItem);
differentOutputSizesBytes.add(checkNotNull(testResult.transformationResult.fileSizeBytes));
differentOutputSizesBytes.add(checkNotNull(testResult.exportResult.fileSizeBytes));
}
assertWithMessage(
@ -98,11 +98,11 @@ public final class RepeatedTranscodeTest {
Set<Long> differentOutputSizesBytes = new HashSet<>();
for (int i = 0; i < TRANSCODE_COUNT; i++) {
// Use a long video in case an error occurs a while after the start of the video.
TransformationTestResult testResult =
ExportTestResult testResult =
transformerRunner.run(
/* testId= */ "repeatedTranscodeNoAudio_givesConsistentLengthOutput_" + i,
editedMediaItem);
differentOutputSizesBytes.add(checkNotNull(testResult.transformationResult.fileSizeBytes));
differentOutputSizesBytes.add(checkNotNull(testResult.exportResult.fileSizeBytes));
}
assertWithMessage(
@ -130,11 +130,11 @@ public final class RepeatedTranscodeTest {
Set<Long> differentOutputSizesBytes = new HashSet<>();
for (int i = 0; i < TRANSCODE_COUNT; i++) {
// Use a long video in case an error occurs a while after the start of the video.
TransformationTestResult testResult =
ExportTestResult testResult =
transformerRunner.run(
/* testId= */ "repeatedTranscodeNoVideo_givesConsistentLengthOutput_" + i,
editedMediaItem);
differentOutputSizesBytes.add(checkNotNull(testResult.transformationResult.fileSizeBytes));
differentOutputSizesBytes.add(checkNotNull(testResult.exportResult.fileSizeBytes));
}
assertWithMessage(

View File

@ -30,9 +30,9 @@ import androidx.media3.common.util.Log;
import androidx.media3.effect.ScaleToFitTransformation;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.Effects;
import androidx.media3.transformer.ExportTestResult;
import androidx.media3.transformer.TransformationException;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationTestResult;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.test.core.app.ApplicationProvider;
@ -78,12 +78,12 @@ public class ToneMapHdrToSdrUsingMediaCodecTest {
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Log.i(TAG, "Tone mapped.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
@ -120,12 +120,12 @@ public class ToneMapHdrToSdrUsingMediaCodecTest {
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Log.i(TAG, "Tone mapped.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
@ -167,12 +167,12 @@ public class ToneMapHdrToSdrUsingMediaCodecTest {
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
Log.i(TAG, "Tone mapped.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
@ -214,12 +214,12 @@ public class ToneMapHdrToSdrUsingMediaCodecTest {
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, editedMediaItem);
Log.i(TAG, "Tone mapped.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class);

View File

@ -32,9 +32,9 @@ import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.transformer.AndroidTestUtil;
import androidx.media3.transformer.ExportTestResult;
import androidx.media3.transformer.TransformationException;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationTestResult;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.test.core.app.ApplicationProvider;
@ -87,12 +87,12 @@ public class ToneMapHdrToSdrUsingOpenGlTest {
.build();
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Log.i(TAG, "Tone mapped.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
if (exception.errorCode != TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED) {
@ -138,12 +138,12 @@ public class ToneMapHdrToSdrUsingOpenGlTest {
.build();
MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10));
try {
TransformationTestResult transformationTestResult =
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Log.i(TAG, "Tone mapped.");
assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR);
assertFileHasColorTransfer(exportTestResult.filePath, C.COLOR_TRANSFER_SDR);
} catch (TransformationException exception) {
Log.i(TAG, checkNotNull(exception.getCause()).toString());
if (exception.errorCode != TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED) {

View File

@ -25,8 +25,8 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.transformer.AndroidTestUtil;
import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.ExportTestResult;
import androidx.media3.transformer.TransformationRequest;
import androidx.media3.transformer.TransformationTestResult;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.TransformerAndroidTestRunner;
import androidx.media3.transformer.VideoEncoderSettings;
@ -70,13 +70,13 @@ public final class TranscodeQualityTest {
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.setRequestCalculateSsim(true)
.build()
.run(testId, editedMediaItem);
if (result.ssim != TransformationTestResult.SSIM_UNSET) {
if (result.ssim != ExportTestResult.SSIM_UNSET) {
assertThat(result.ssim).isGreaterThan(0.90);
}
}
@ -108,13 +108,13 @@ public final class TranscodeQualityTest {
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.setRequestCalculateSsim(true)
.build()
.run(testId, editedMediaItem);
if (result.ssim != TransformationTestResult.SSIM_UNSET) {
if (result.ssim != ExportTestResult.SSIM_UNSET) {
assertThat(result.ssim).isGreaterThan(0.90);
}
}
@ -140,13 +140,13 @@ public final class TranscodeQualityTest {
EditedMediaItem editedMediaItem =
new EditedMediaItem.Builder(mediaItem).setRemoveAudio(true).build();
TransformationTestResult result =
ExportTestResult result =
new TransformerAndroidTestRunner.Builder(context, transformer)
.setRequestCalculateSsim(true)
.build()
.run(testId, editedMediaItem);
if (result.ssim != TransformationTestResult.SSIM_UNSET) {
if (result.ssim != ExportTestResult.SSIM_UNSET) {
assertThat(result.ssim).isGreaterThan(0.90);
}
}

View File

@ -43,7 +43,7 @@ import static androidx.media3.transformer.AndroidTestUtil.MP4_REMOTE_854W_480H_3
import static androidx.media3.transformer.AndroidTestUtil.MP4_REMOTE_854W_480H_30_SECOND_ROOF_REDMINOTE9_DOWNSAMPLED;
import static androidx.media3.transformer.AndroidTestUtil.getFormatForTestFile;
import static androidx.media3.transformer.AndroidTestUtil.skipAndLogIfInsufficientCodecSupport;
import static androidx.media3.transformer.TransformationTestResult.SSIM_UNSET;
import static androidx.media3.transformer.ExportTestResult.SSIM_UNSET;
import static com.google.common.collect.Iterables.getLast;
import android.content.Context;

View File

@ -54,7 +54,7 @@ import java.util.concurrent.atomic.AtomicLong;
private final Listener compositeAssetLoaderListener;
private final Map<Integer, SampleConsumer> sampleConsumersByTrackType;
private final Map<Integer, OnMediaItemChangedListener> mediaItemChangedListenersByTrackType;
private final ImmutableList.Builder<TransformationResult.ProcessedInput> processedInputsBuilder;
private final ImmutableList.Builder<ExportResult.ProcessedInput> processedInputsBuilder;
private final AtomicLong totalDurationUs;
private final AtomicInteger nonEndedTracks;
@ -114,10 +114,9 @@ import java.util.concurrent.atomic.AtomicLong;
}
/**
* Returns the partially or entirely {@linkplain TransformationResult.ProcessedInput processed
* inputs}.
* Returns the partially or entirely {@linkplain ExportResult.ProcessedInput processed inputs}.
*/
public ImmutableList<TransformationResult.ProcessedInput> getProcessedInputs() {
public ImmutableList<ExportResult.ProcessedInput> getProcessedInputs() {
addCurrentProcessedInput();
return processedInputsBuilder.build();
}
@ -212,7 +211,7 @@ import java.util.concurrent.atomic.AtomicLong;
MediaItem mediaItem = editedMediaItems.get(currentMediaItemIndex).mediaItem;
ImmutableMap<Integer, String> decoders = currentAssetLoader.getDecoderNames();
processedInputsBuilder.add(
new TransformationResult.ProcessedInput(
new ExportResult.ProcessedInput(
mediaItem, decoders.get(C.TRACK_TYPE_AUDIO), decoders.get(C.TRACK_TYPE_VIDEO)));
processedInputsSize++;
}

View File

@ -0,0 +1,401 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Information about the result of an export. */
@UnstableApi
public final class ExportResult {
/** A builder for {@link ExportResult} instances. */
public static final class Builder {
private ImmutableList<ProcessedInput> processedInputs;
private long durationMs;
private long fileSizeBytes;
private int averageAudioBitrate;
private int channelCount;
private @C.PcmEncoding int pcmEncoding;
private int sampleRate;
@Nullable private String audioEncoderName;
private int averageVideoBitrate;
@Nullable ColorInfo colorInfo;
private int height;
private int width;
private int videoFrameCount;
@Nullable private String videoEncoderName;
@Nullable private TransformationException transformationException;
/** Creates a builder. */
public Builder() {
processedInputs = ImmutableList.of();
durationMs = C.TIME_UNSET;
fileSizeBytes = C.LENGTH_UNSET;
averageAudioBitrate = C.RATE_UNSET_INT;
channelCount = C.LENGTH_UNSET;
pcmEncoding = Format.NO_VALUE;
sampleRate = C.RATE_UNSET_INT;
averageVideoBitrate = C.RATE_UNSET_INT;
height = C.LENGTH_UNSET;
width = C.LENGTH_UNSET;
}
/** Sets the {@linkplain ProcessedInput processed inputs}. */
@CanIgnoreReturnValue
public Builder setProcessedInputs(ImmutableList<ProcessedInput> processedInputs) {
this.processedInputs = processedInputs;
return this;
}
/**
* Sets the duration of the output in milliseconds.
*
* <p>Must be positive or {@link C#TIME_UNSET}.
*/
@CanIgnoreReturnValue
public Builder setDurationMs(long durationMs) {
checkArgument(durationMs >= 0 || durationMs == C.TIME_UNSET);
this.durationMs = durationMs;
return this;
}
/**
* Sets the file size in bytes.
*
* <p>Must be positive or {@link C#LENGTH_UNSET}.
*/
@CanIgnoreReturnValue
public Builder setFileSizeBytes(long fileSizeBytes) {
checkArgument(fileSizeBytes > 0 || fileSizeBytes == C.LENGTH_UNSET);
this.fileSizeBytes = fileSizeBytes;
return this;
}
/**
* Sets the average audio bitrate.
*
* <p>Must be positive or {@link C#RATE_UNSET_INT}.
*/
@CanIgnoreReturnValue
public Builder setAverageAudioBitrate(int averageAudioBitrate) {
checkArgument(averageAudioBitrate > 0 || averageAudioBitrate == C.RATE_UNSET_INT);
this.averageAudioBitrate = averageAudioBitrate;
return this;
}
/**
* Sets the channel count.
*
* <p>Must be positive or {@link C#LENGTH_UNSET}.
*/
@CanIgnoreReturnValue
public Builder setChannelCount(int channelCount) {
checkArgument(channelCount > 0 || channelCount == C.LENGTH_UNSET);
this.channelCount = channelCount;
return this;
}
/** Sets the {@link C.PcmEncoding}. */
@CanIgnoreReturnValue
public Builder setPcmEncoding(@C.PcmEncoding int pcmEncoding) {
this.pcmEncoding = pcmEncoding;
return this;
}
/**
* Sets the sample rate.
*
* <p>Must be positive or {@link C#RATE_UNSET_INT}.
*/
@CanIgnoreReturnValue
public Builder setSampleRate(int sampleRate) {
checkArgument(sampleRate > 0 || sampleRate == C.RATE_UNSET_INT);
this.sampleRate = sampleRate;
return this;
}
/** Sets the name of the audio encoder used. */
@CanIgnoreReturnValue
public Builder setAudioEncoderName(@Nullable String audioEncoderName) {
this.audioEncoderName = audioEncoderName;
return this;
}
/**
* Sets the average video bitrate.
*
* <p>Must be positive or {@link C#RATE_UNSET_INT}.
*/
@CanIgnoreReturnValue
public Builder setAverageVideoBitrate(int averageVideoBitrate) {
checkArgument(averageVideoBitrate > 0 || averageVideoBitrate == C.RATE_UNSET_INT);
this.averageVideoBitrate = averageVideoBitrate;
return this;
}
/** Sets the {@link ColorInfo}. */
@CanIgnoreReturnValue
public Builder setColorInfo(@Nullable ColorInfo colorInfo) {
this.colorInfo = colorInfo;
return this;
}
/**
* Sets the height.
*
* <p>Must be positive or {@link C#LENGTH_UNSET}.
*/
@CanIgnoreReturnValue
public Builder setHeight(int height) {
checkArgument(height > 0 || height == C.LENGTH_UNSET);
this.height = height;
return this;
}
/**
* Sets the width.
*
* <p>Must be positive or {@link C#LENGTH_UNSET}.
*/
@CanIgnoreReturnValue
public Builder setWidth(int width) {
checkArgument(width > 0 || width == C.LENGTH_UNSET);
this.width = width;
return this;
}
/**
* Sets the number of video frames.
*
* <p>Must be positive or {@code 0}.
*/
@CanIgnoreReturnValue
public Builder setVideoFrameCount(int videoFrameCount) {
checkArgument(videoFrameCount >= 0);
this.videoFrameCount = videoFrameCount;
return this;
}
/** Sets the name of the video encoder used. */
@CanIgnoreReturnValue
public Builder setVideoEncoderName(@Nullable String videoEncoderName) {
this.videoEncoderName = videoEncoderName;
return this;
}
/** Sets the {@link TransformationException} that caused the export to fail. */
@CanIgnoreReturnValue
public Builder setTransformationException(
@Nullable TransformationException transformationException) {
this.transformationException = transformationException;
return this;
}
/** Builds an {@link ExportResult} instance. */
public ExportResult build() {
return new ExportResult(
processedInputs,
durationMs,
fileSizeBytes,
averageAudioBitrate,
channelCount,
pcmEncoding,
sampleRate,
audioEncoderName,
averageVideoBitrate,
colorInfo,
height,
width,
videoFrameCount,
videoEncoderName,
transformationException);
}
}
/** An input entirely or partially processed. */
public static final class ProcessedInput {
/** The processed {@link MediaItem}. */
public final MediaItem mediaItem;
/**
* The name of the audio decoder used to process {@code mediaItem}. This field is {@code null}
* if no audio decoder was used.
*/
public final @MonotonicNonNull String audioDecoderName;
/**
* The name of the video decoder used to process {@code mediaItem}. This field is {@code null}
* if no video decoder was used.
*/
public final @MonotonicNonNull String videoDecoderName;
/** Creates an instance. */
public ProcessedInput(
MediaItem mediaItem, @Nullable String audioDecoderName, @Nullable String videoDecoderName) {
this.mediaItem = mediaItem;
this.audioDecoderName = audioDecoderName;
this.videoDecoderName = videoDecoderName;
}
}
/** The list of {@linkplain ProcessedInput processed inputs}. */
public final ImmutableList<ProcessedInput> processedInputs;
/** The duration of the file in milliseconds, or {@link C#TIME_UNSET} if unset or unknown. */
public final long durationMs;
/** The size of the file in bytes, or {@link C#LENGTH_UNSET} if unset or unknown. */
public final long fileSizeBytes;
/**
* The average bitrate of the audio track data, or {@link C#RATE_UNSET_INT} if unset or unknown.
*/
public final int averageAudioBitrate;
/** The channel count of the audio, or {@link C#LENGTH_UNSET} if unset or unknown. */
public final int channelCount;
/** The {@link C.PcmEncoding} of the audio, or {@link Format#NO_VALUE} if unset or unknown. */
public final @C.PcmEncoding int pcmEncoding;
/** The sample rate of the audio, or {@link C#RATE_UNSET_INT} if unset or unknown. */
public final int sampleRate;
/** The name of the audio encoder used, or {@code null} if none were used. */
@Nullable public final String audioEncoderName;
/**
* The average bitrate of the video track data, or {@link C#RATE_UNSET_INT} if unset or unknown.
*/
public final int averageVideoBitrate;
/** The {@link ColorInfo} of the video, or {@code null} if unset or unknown. */
@Nullable public final ColorInfo colorInfo;
/** The height of the video, or {@link C#LENGTH_UNSET} if unset or unknown. */
public final int height;
/** The width of the video, or {@link C#LENGTH_UNSET} if unset or unknown. */
public final int width;
/** The number of video frames. */
public final int videoFrameCount;
/** The name of the video encoder used, or {@code null} if none were used. */
@Nullable public final String videoEncoderName;
/**
* The {@link TransformationException} that caused the export to fail, or {@code null} if the
* export was a success.
*/
@Nullable public final TransformationException transformationException;
private ExportResult(
ImmutableList<ProcessedInput> processedInputs,
long durationMs,
long fileSizeBytes,
int averageAudioBitrate,
int channelCount,
@C.PcmEncoding int pcmEncoding,
int sampleRate,
@Nullable String audioEncoderName,
int averageVideoBitrate,
@Nullable ColorInfo colorInfo,
int height,
int width,
int videoFrameCount,
@Nullable String videoEncoderName,
@Nullable TransformationException transformationException) {
this.processedInputs = processedInputs;
this.durationMs = durationMs;
this.fileSizeBytes = fileSizeBytes;
this.averageAudioBitrate = averageAudioBitrate;
this.channelCount = channelCount;
this.pcmEncoding = pcmEncoding;
this.sampleRate = sampleRate;
this.audioEncoderName = audioEncoderName;
this.averageVideoBitrate = averageVideoBitrate;
this.colorInfo = colorInfo;
this.height = height;
this.width = width;
this.videoFrameCount = videoFrameCount;
this.videoEncoderName = videoEncoderName;
this.transformationException = transformationException;
}
public Builder buildUpon() {
return new Builder()
.setProcessedInputs(processedInputs)
.setDurationMs(durationMs)
.setFileSizeBytes(fileSizeBytes)
.setAverageAudioBitrate(averageAudioBitrate)
.setChannelCount(channelCount)
.setPcmEncoding(pcmEncoding)
.setSampleRate(sampleRate)
.setAudioEncoderName(audioEncoderName)
.setAverageVideoBitrate(averageVideoBitrate)
.setColorInfo(colorInfo)
.setHeight(height)
.setWidth(width)
.setVideoFrameCount(videoFrameCount)
.setVideoEncoderName(videoEncoderName)
.setTransformationException(transformationException);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ExportResult)) {
return false;
}
ExportResult result = (ExportResult) o;
return Objects.equals(processedInputs, result.processedInputs)
&& durationMs == result.durationMs
&& fileSizeBytes == result.fileSizeBytes
&& averageAudioBitrate == result.averageAudioBitrate
&& channelCount == result.channelCount
&& pcmEncoding == result.pcmEncoding
&& sampleRate == result.sampleRate
&& Objects.equals(audioEncoderName, result.audioEncoderName)
&& averageVideoBitrate == result.averageVideoBitrate
&& Objects.equals(colorInfo, result.colorInfo)
&& height == result.height
&& width == result.width
&& videoFrameCount == result.videoFrameCount
&& Objects.equals(videoEncoderName, result.videoEncoderName)
&& Objects.equals(transformationException, result.transformationException);
}
@Override
public int hashCode() {
int result = Objects.hashCode(processedInputs);
result = 31 * result + (int) durationMs;
result = 31 * result + (int) fileSizeBytes;
result = 31 * result + averageAudioBitrate;
result = 31 * result + channelCount;
result = 31 * result + pcmEncoding;
result = 31 * result + sampleRate;
result = 31 * result + Objects.hashCode(audioEncoderName);
result = 31 * result + averageVideoBitrate;
result = 31 * result + Objects.hashCode(colorInfo);
result = 31 * result + height;
result = 31 * result + width;
result = 31 * result + videoFrameCount;
result = 31 * result + Objects.hashCode(videoEncoderName);
result = 31 * result + Objects.hashCode(transformationException);
return result;
}
}

View File

@ -28,10 +28,16 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Information about the result of a transformation. */
/**
* @deprecated Use {@link ExportResult} instead.
*/
@Deprecated
@UnstableApi
public final class TransformationResult {
/** A builder for {@link TransformationResult} instances. */
/**
* @deprecated Use {@link ExportResult.Builder} instead.
*/
@Deprecated
public static final class Builder {
private ImmutableList<ProcessedInput> processedInputs;
private long durationMs;
@ -63,6 +69,34 @@ public final class TransformationResult {
width = C.LENGTH_UNSET;
}
/** Creates a builder from an {@link ExportResult}. */
/* package */ Builder(ExportResult exportResult) {
ImmutableList.Builder<ProcessedInput> processedInputsBuilder = new ImmutableList.Builder<>();
for (int i = 0; i < exportResult.processedInputs.size(); i++) {
ExportResult.ProcessedInput processedInput = exportResult.processedInputs.get(i);
processedInputsBuilder.add(
new ProcessedInput(
processedInput.mediaItem,
processedInput.audioDecoderName,
processedInput.videoDecoderName));
}
processedInputs = processedInputsBuilder.build();
durationMs = exportResult.durationMs;
fileSizeBytes = exportResult.fileSizeBytes;
averageAudioBitrate = exportResult.averageAudioBitrate;
channelCount = exportResult.channelCount;
pcmEncoding = exportResult.pcmEncoding;
sampleRate = exportResult.sampleRate;
audioEncoderName = exportResult.audioEncoderName;
averageVideoBitrate = exportResult.averageVideoBitrate;
colorInfo = exportResult.colorInfo;
height = exportResult.height;
width = exportResult.width;
videoFrameCount = exportResult.videoFrameCount;
videoEncoderName = exportResult.videoEncoderName;
transformationException = exportResult.transformationException;
}
/** Sets the {@linkplain ProcessedInput processed inputs}. */
@CanIgnoreReturnValue
public Builder setProcessedInputs(ImmutableList<ProcessedInput> processedInputs) {
@ -235,7 +269,10 @@ public final class TransformationResult {
}
}
/** An input entirely or partially processed. */
/**
* @deprecated Use {@link ExportResult.ProcessedInput} instead.
*/
@Deprecated
public static final class ProcessedInput {
/** The processed {@link MediaItem}. */
public final MediaItem mediaItem;

View File

@ -446,13 +446,13 @@ public final class Transformer {
public interface Listener {
/**
* @deprecated Use {@link #onCompleted(Composition, TransformationResult)} instead.
* @deprecated Use {@link #onCompleted(Composition, ExportResult)} instead.
*/
@Deprecated
default void onTransformationCompleted(MediaItem inputMediaItem) {}
/**
* @deprecated Use {@link #onCompleted(Composition, TransformationResult)} instead.
* @deprecated Use {@link #onCompleted(Composition, ExportResult)} instead.
*/
@Deprecated
default void onTransformationCompleted(MediaItem inputMediaItem, TransformationResult result) {
@ -463,23 +463,22 @@ public final class Transformer {
* Called when the export is completed successfully.
*
* @param composition The {@link Composition} for which the export is completed.
* @param result The {@link TransformationResult} of the export.
* @param exportResult The {@link ExportResult} of the export.
*/
default void onCompleted(Composition composition, TransformationResult result) {
@SuppressWarnings("deprecation") // Calling deprecated listener method.
default void onCompleted(Composition composition, ExportResult exportResult) {
MediaItem mediaItem = composition.sequences.get(0).editedMediaItems.get(0).mediaItem;
onTransformationCompleted(mediaItem, result);
onTransformationCompleted(mediaItem, new TransformationResult.Builder(exportResult).build());
}
/**
* @deprecated Use {@link #onError(Composition, TransformationResult, TransformationException)}
* instead.
* @deprecated Use {@link #onError(Composition, ExportResult, TransformationException)} instead.
*/
@Deprecated
default void onTransformationError(MediaItem inputMediaItem, Exception exception) {}
/**
* @deprecated Use {@link #onError(Composition, TransformationResult, TransformationException)}
* instead.
* @deprecated Use {@link #onError(Composition, ExportResult, TransformationException)} instead.
*/
@Deprecated
default void onTransformationError(
@ -488,8 +487,7 @@ public final class Transformer {
}
/**
* @deprecated Use {@link #onError(Composition, TransformationResult, TransformationException)}
* instead.
* @deprecated Use {@link #onError(Composition, ExportResult, TransformationException)} instead.
*/
@Deprecated
default void onTransformationError(
@ -501,15 +499,17 @@ public final class Transformer {
* Called if an exception occurs during the export.
*
* @param composition The {@link Composition} for which the exception occurs.
* @param result The {@link TransformationResult} of the export.
* @param exportResult The {@link ExportResult} of the export.
* @param exception The {@link TransformationException} describing the exception. This is the
* same instance as the {@linkplain TransformationResult#transformationException exception}
* in {@code result}.
* same instance as the {@linkplain ExportResult#transformationException exception} in
* {@code result}.
*/
@SuppressWarnings("deprecation") // Calling deprecated listener method.
default void onError(
Composition composition, TransformationResult result, TransformationException exception) {
Composition composition, ExportResult exportResult, TransformationException exception) {
MediaItem mediaItem = composition.sequences.get(0).editedMediaItems.get(0).mediaItem;
onTransformationError(mediaItem, result, exception);
onTransformationError(
mediaItem, new TransformationResult.Builder(exportResult).build(), exception);
}
/**
@ -534,6 +534,7 @@ public final class Transformer {
* TransformationRequest#videoMimeType}, {@link TransformationRequest#outputHeight}, and
* {@link TransformationRequest#hdrMode} values set.
*/
@SuppressWarnings("deprecation") // Calling deprecated listener method.
default void onFallbackApplied(
Composition composition,
TransformationRequest originalTransformationRequest,
@ -792,8 +793,8 @@ public final class Transformer {
* Returns the current {@link ProgressState} and updates {@code progressHolder} with the current
* progress if it is {@link #PROGRESS_STATE_AVAILABLE available}.
*
* <p>After an export {@linkplain Listener#onCompleted(Composition,TransformationResult)
* completes}, this method returns {@link #PROGRESS_STATE_NOT_STARTED}.
* <p>After an export {@linkplain Listener#onCompleted(Composition, ExportResult) completes}, this
* method returns {@link #PROGRESS_STATE_NOT_STARTED}.
*
* @param progressHolder A {@link ProgressHolder}, updated to hold the percentage progress if
* {@link #PROGRESS_STATE_AVAILABLE available}.
@ -839,21 +840,21 @@ public final class Transformer {
}
@Override
public void onCompleted(TransformationResult transformationResult) {
public void onCompleted(ExportResult exportResult) {
// TODO(b/213341814): Add event flags for Transformer events.
transformerInternal = null;
listeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET,
listener -> listener.onCompleted(composition, transformationResult));
listener -> listener.onCompleted(composition, exportResult));
listeners.flushEvents();
}
@Override
public void onError(TransformationResult result, TransformationException exception) {
public void onError(ExportResult exportResult, TransformationException exception) {
transformerInternal = null;
listeners.queueEvent(
/* eventFlag= */ C.INDEX_UNSET,
listener -> listener.onError(composition, result, exception));
listener -> listener.onError(composition, exportResult, exception));
listeners.flushEvents();
}
}

View File

@ -57,9 +57,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public interface Listener {
void onCompleted(TransformationResult result);
void onCompleted(ExportResult exportResult);
void onError(TransformationResult result, TransformationException exception);
void onError(ExportResult exportResult, TransformationException exception);
}
/**
@ -100,7 +100,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private final List<SamplePipeline> samplePipelines;
private final MuxerWrapper muxerWrapper;
private final ConditionVariable transformerConditionVariable;
private final TransformationResult.Builder transformationResultBuilder;
private final ExportResult.Builder exportResultBuilder;
private boolean generateSilentAudio;
private boolean isDrainingPipelines;
@ -142,7 +142,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
samplePipelines = new ArrayList<>();
muxerWrapper = new MuxerWrapper(outputPath, muxerFactory, componentListener);
transformerConditionVariable = new ConditionVariable();
transformationResultBuilder = new TransformationResult.Builder();
exportResultBuilder = new ExportResult.Builder();
// It's safe to use "this" because we don't send a message before exiting the constructor.
@SuppressWarnings("nullness:methodref.receiver.bound")
HandlerWrapper internalHandler =
@ -243,9 +243,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private void endInternal(
@EndReason int endReason, @Nullable TransformationException transformationException) {
ImmutableList<TransformationResult.ProcessedInput> processedInputs =
ImmutableList<ExportResult.ProcessedInput> processedInputs =
compositeAssetLoader.getProcessedInputs();
transformationResultBuilder
exportResultBuilder
.setProcessedInputs(processedInputs)
.setAudioEncoderName(encoderFactory.getAudioEncoderName())
.setVideoEncoderName(encoderFactory.getVideoEncoderName());
@ -300,10 +300,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
applicationHandler.post(
() ->
listener.onError(
transformationResultBuilder.setTransformationException(finalException).build(),
exportResultBuilder.setTransformationException(finalException).build(),
finalException));
} else {
applicationHandler.post(() -> listener.onCompleted(transformationResultBuilder.build()));
applicationHandler.post(() -> listener.onCompleted(exportResultBuilder.build()));
}
}
@ -416,32 +416,32 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public void onTrackEnded(
@C.TrackType int trackType, Format format, int averageBitrate, int sampleCount) {
if (trackType == C.TRACK_TYPE_AUDIO) {
transformationResultBuilder
exportResultBuilder
.setAverageAudioBitrate(averageBitrate)
.setPcmEncoding(format.pcmEncoding);
if (format.channelCount != Format.NO_VALUE) {
transformationResultBuilder.setChannelCount(format.channelCount);
exportResultBuilder.setChannelCount(format.channelCount);
}
if (format.sampleRate != Format.NO_VALUE) {
transformationResultBuilder.setSampleRate(format.sampleRate);
exportResultBuilder.setSampleRate(format.sampleRate);
}
} else if (trackType == C.TRACK_TYPE_VIDEO) {
transformationResultBuilder
exportResultBuilder
.setAverageVideoBitrate(averageBitrate)
.setColorInfo(format.colorInfo)
.setVideoFrameCount(sampleCount);
if (format.height != Format.NO_VALUE) {
transformationResultBuilder.setHeight(format.height);
exportResultBuilder.setHeight(format.height);
}
if (format.width != Format.NO_VALUE) {
transformationResultBuilder.setWidth(format.width);
exportResultBuilder.setWidth(format.width);
}
}
}
@Override
public void onEnded(long durationMs, long fileSizeBytes) {
transformationResultBuilder.setDurationMs(durationMs).setFileSizeBytes(fileSizeBytes);
exportResultBuilder.setDurationMs(durationMs).setFileSizeBytes(fileSizeBytes);
internalHandler
.obtainMessage(

View File

@ -602,10 +602,10 @@ public final class TransformerEndToEndTest {
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_VIDEO);
transformer.start(mediaItem, outputPath);
TransformationResult result = TransformerTestRunner.runLooper(transformer);
ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
assertThat(result.averageAudioBitrate).isGreaterThan(0);
assertThat(result.averageVideoBitrate).isGreaterThan(0);
assertThat(exportResult.averageAudioBitrate).isGreaterThan(0);
assertThat(exportResult.averageVideoBitrate).isGreaterThan(0);
}
@Test
@ -760,10 +760,10 @@ public final class TransformerEndToEndTest {
// This would throw if the previous transformation had not been cancelled.
transformer.start(mediaItem, outputPath);
TransformationResult transformationResult = TransformerTestRunner.runLooper(transformer);
ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
// TODO(b/264974805): Make transformation output deterministic and check it against dump file.
assertThat(transformationResult.transformationException).isNull();
assertThat(exportResult.transformationException).isNull();
}
@Test

View File

@ -19,7 +19,6 @@ package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil;
import androidx.media3.common.MediaItem;
import androidx.media3.test.utils.robolectric.RobolectricUtil;
import java.util.Objects;
import java.util.concurrent.TimeoutException;
@ -33,46 +32,44 @@ public final class TransformerTestRunner {
/**
* Runs tasks of the {@linkplain Transformer#getApplicationLooper() transformer Looper} until the
* {@linkplain Transformer transformation} ends.
* {@linkplain Transformer export} ends.
*
* @param transformer The {@link Transformer}.
* @return The {@link TransformationResult}.
* @return The {@link ExportResult}.
* @throws TransformationException If the transformation threw an exception.
* @throws TimeoutException If the {@link RobolectricUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
* @throws IllegalStateException If the method is not called from the main thread.
*/
public static TransformationResult runLooper(Transformer transformer)
public static ExportResult runLooper(Transformer transformer)
throws TransformationException, TimeoutException {
AtomicReference<@NullableType TransformationResult> transformationResultRef =
new AtomicReference<>();
AtomicReference<@NullableType ExportResult> exportResultRef = new AtomicReference<>();
transformer.addListener(
new Transformer.Listener() {
@Override
public void onTransformationCompleted(
MediaItem inputMediaItem, TransformationResult result) {
transformationResultRef.set(result);
public void onCompleted(Composition composition, ExportResult exportResult) {
exportResultRef.set(exportResult);
}
@Override
public void onTransformationError(
MediaItem inputMediaItem,
TransformationResult result,
public void onError(
Composition composition,
ExportResult exportResult,
TransformationException exception) {
if (!Objects.equals(result.transformationException, exception)) {
result = result.buildUpon().setTransformationException(exception).build();
if (!Objects.equals(exportResult.transformationException, exception)) {
exportResult = exportResult.buildUpon().setTransformationException(exception).build();
}
transformationResultRef.set(result);
exportResultRef.set(exportResult);
}
});
runLooperUntil(transformer.getApplicationLooper(), () -> transformationResultRef.get() != null);
runLooperUntil(transformer.getApplicationLooper(), () -> exportResultRef.get() != null);
TransformationResult result = checkNotNull(transformationResultRef.get());
if (result.transformationException != null) {
throw result.transformationException;
ExportResult exportResult = checkNotNull(exportResultRef.get());
if (exportResult.transformationException != null) {
throw exportResult.transformationException;
}
return result;
return exportResult;
}
}