diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 82a9e41dbd..7950c9e542 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -11,6 +11,9 @@
key names by modifying it to only check for hyphen
([#1028](https://github.com/androidx/media/issues/1028)).
* Transformer:
+ * Add `audioConversionProcess` and `videoConversionProcess` to
+ `ExportResult` indicating how the respective track in the output file
+ was made.
* Track Selection:
* Extractors:
* Audio:
diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java
index fd67bb1aac..e6063e66b0 100644
--- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java
+++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java
@@ -30,6 +30,10 @@ import static androidx.media3.transformer.AndroidTestUtil.PNG_ASSET_URI_STRING;
import static androidx.media3.transformer.AndroidTestUtil.createOpenGlObjects;
import static androidx.media3.transformer.AndroidTestUtil.generateTextureFromBitmap;
import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped;
+import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA;
+import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_TRANSCODED;
+import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_TRANSMUXED;
+import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED;
import static androidx.media3.transformer.ExportResult.OPTIMIZATION_ABANDONED_KEYFRAME_PLACEMENT_OPTIMAL_FOR_TRIM;
import static androidx.media3.transformer.ExportResult.OPTIMIZATION_ABANDONED_TRIM_AND_TRANSCODING_TRANSFORMATION_REQUESTED;
import static androidx.media3.transformer.ExportResult.OPTIMIZATION_FAILED_FORMAT_MISMATCH;
@@ -530,6 +534,8 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.optimizationResult)
.isEqualTo(OPTIMIZATION_FAILED_FORMAT_MISMATCH);
assertThat(result.exportResult.durationMs).isAtMost(clippingEndMs - clippingStartMs);
+ assertThat(result.exportResult.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSCODED);
+ assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
@@ -617,6 +623,8 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.optimizationResult)
.isEqualTo(OPTIMIZATION_ABANDONED_KEYFRAME_PLACEMENT_OPTIMAL_FOR_TRIM);
assertThat(result.exportResult.durationMs).isAtMost(clippingEndMs - clippingStartMs);
+ assertThat(result.exportResult.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSCODED);
+ assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
@@ -653,6 +661,8 @@ public class TransformerEndToEndTest {
.isEqualTo(OPTIMIZATION_ABANDONED_KEYFRAME_PLACEMENT_OPTIMAL_FOR_TRIM);
// The asset is 15 s 537 ms long.
assertThat(result.exportResult.durationMs).isAtMost(1_017);
+ assertThat(result.exportResult.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSCODED);
+ assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
@@ -685,6 +695,9 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.optimizationResult).isEqualTo(OPTIMIZATION_SUCCEEDED);
assertThat(result.exportResult.durationMs).isAtMost(2000);
+ assertThat(result.exportResult.videoConversionProcess)
+ .isEqualTo(CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED);
+ assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
@@ -726,6 +739,9 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.optimizationResult).isEqualTo(OPTIMIZATION_SUCCEEDED);
assertThat(result.exportResult.durationMs).isAtMost(2000);
+ assertThat(result.exportResult.videoConversionProcess)
+ .isEqualTo(CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED);
+ assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_NA);
Format format = FileUtil.retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO);
// The video is trim-optimized, so the rotation is performed in MuxerWrapper.
@@ -768,6 +784,8 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.optimizationResult)
.isEqualTo(OPTIMIZATION_ABANDONED_TRIM_AND_TRANSCODING_TRANSFORMATION_REQUESTED);
+ assertThat(result.exportResult.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSCODED);
+ assertThat(result.exportResult.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExportResult.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExportResult.java
index a4e159a7df..4602e5858f 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExportResult.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExportResult.java
@@ -378,6 +378,39 @@ public final class ExportResult {
*/
public static final int OPTIMIZATION_FAILED_FORMAT_MISMATCH = 6;
+ /**
+ * Specifies what conversion process was used to make a track in the output file. One of:
+ *
+ *
+ * - {@link #CONVERSION_PROCESS_NA}
+ *
- {@link #CONVERSION_PROCESS_TRANSCODED}
+ *
- {@link #CONVERSION_PROCESS_TRANSMUXED}
+ *
- {@link #CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED}
+ *
+ */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(TYPE_USE)
+ @IntDef({
+ CONVERSION_PROCESS_NA,
+ CONVERSION_PROCESS_TRANSCODED,
+ CONVERSION_PROCESS_TRANSMUXED,
+ CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED
+ })
+ @interface ConversionProcess {}
+
+ /** The output file doesn't contain this track type. */
+ public static final int CONVERSION_PROCESS_NA = 0;
+
+ /** The track was transcoded. */
+ public static final int CONVERSION_PROCESS_TRANSCODED = 1;
+
+ /** The track was transmuxed. */
+ public static final int CONVERSION_PROCESS_TRANSMUXED = 2;
+
+ /** The track was both transcoded and transmuxed. */
+ public static final int CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED = 3;
+
/** The list of {@linkplain ProcessedInput processed inputs}. */
public final ImmutableList processedInputs;
@@ -436,6 +469,12 @@ public final class ExportResult {
*/
@Nullable public final ExportException exportException;
+ /** Returns the {@link ConversionProcess} taken to create the video track in the output file. */
+ public final @ConversionProcess int videoConversionProcess;
+
+ /** Returns the {@link ConversionProcess} taken to create the audio track in the output file. */
+ public final @ConversionProcess int audioConversionProcess;
+
private ExportResult(
ImmutableList processedInputs,
long durationMs,
@@ -471,6 +510,12 @@ public final class ExportResult {
this.videoMimeType = videoMimeType;
this.optimizationResult = optimizationResult;
this.exportException = exportException;
+ audioConversionProcess =
+ getConversionProcess(
+ audioMimeType, optimizationResult, processedInputs, C.TRACK_TYPE_AUDIO);
+ videoConversionProcess =
+ getConversionProcess(
+ videoMimeType, optimizationResult, processedInputs, C.TRACK_TYPE_VIDEO);
}
public Builder buildUpon() {
@@ -543,4 +588,41 @@ public final class ExportResult {
result = 31 * result + Objects.hashCode(exportException);
return result;
}
+
+ // Nullness test incorrectly throws monotonic type error when assigning the @Nullable decoderName.
+ @SuppressWarnings("monotonic.type.incompatible")
+ private static @ConversionProcess int getConversionProcess(
+ @Nullable String mimeType,
+ @OptimizationResult int optimizationResult,
+ List processedInputs,
+ @C.TrackType int trackType) {
+ if (mimeType == null) {
+ return CONVERSION_PROCESS_NA;
+ }
+ if (optimizationResult == OPTIMIZATION_SUCCEEDED) { // Trim optimization occurred.
+ return trackType == C.TRACK_TYPE_AUDIO
+ ? CONVERSION_PROCESS_TRANSMUXED
+ : CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED;
+ }
+ @ConversionProcess int conversionProcess = CONVERSION_PROCESS_NA;
+ for (ProcessedInput processedInput : processedInputs) {
+ @Nullable
+ String decoderName =
+ trackType == C.TRACK_TYPE_AUDIO
+ ? processedInput.audioDecoderName
+ : processedInput.videoDecoderName;
+ if (decoderName == null) {
+ if (conversionProcess == CONVERSION_PROCESS_TRANSCODED) {
+ return CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED;
+ }
+ conversionProcess = CONVERSION_PROCESS_TRANSMUXED;
+ } else {
+ if (conversionProcess == CONVERSION_PROCESS_TRANSMUXED) {
+ return CONVERSION_PROCESS_TRANSMUXED_AND_TRANSCODED;
+ }
+ conversionProcess = CONVERSION_PROCESS_TRANSCODED;
+ }
+ }
+ return conversionProcess;
+ }
}
diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
index 178e4ced3c..7d9c8b9cc9 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
@@ -1475,6 +1475,7 @@ public final class Transformer {
if (!doesFormatsMatch(mediaItemInfo, firstEditedMediaItem)) {
remuxingMuxerWrapper = null;
transformerInternal = null;
+ exportResultBuilder.reset();
exportResultBuilder.setOptimizationResult(OPTIMIZATION_FAILED_FORMAT_MISMATCH);
processFullInput();
return;
diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java
index c578b09405..1af86c9dc7 100644
--- a/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java
+++ b/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java
@@ -19,6 +19,8 @@ package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil;
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED;
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED;
+import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_NA;
+import static androidx.media3.transformer.ExportResult.CONVERSION_PROCESS_TRANSMUXED;
import static androidx.media3.transformer.ExportResult.OPTIMIZATION_ABANDONED_KEYFRAME_PLACEMENT_OPTIMAL_FOR_TRIM;
import static androidx.media3.transformer.ExportResult.OPTIMIZATION_FAILED_EXTRACTION_FAILED;
import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX;
@@ -168,15 +170,17 @@ public final class MediaItemExportTest {
.build();
transformer.start(mediaItem, outputDir.newFile().getPath());
- ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
+ ExportResult result = TransformerTestRunner.runLooper(transformer);
- assertThat(exportResult.optimizationResult)
+ assertThat(result.optimizationResult)
.isEqualTo(OPTIMIZATION_ABANDONED_KEYFRAME_PLACEMENT_OPTIMAL_FOR_TRIM);
// Asserts against file generated when experimentalSetTrimOptimizationEnabled is set to false.
DumpFileAsserts.assertOutput(
context,
muxerFactory.getCreatedMuxer(),
getDumpFileName(/* originalFileName= */ FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S));
+ assertThat(result.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
+ assertThat(result.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
@@ -230,9 +234,9 @@ public final class MediaItemExportTest {
.build();
transformer.start(mediaItem, outputDir.newFile().getPath());
- ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
+ ExportResult result = TransformerTestRunner.runLooper(transformer);
- assertThat(exportResult.optimizationResult)
+ assertThat(result.optimizationResult)
.isEqualTo(OPTIMIZATION_ABANDONED_KEYFRAME_PLACEMENT_OPTIMAL_FOR_TRIM);
DumpFileAsserts.assertOutput(
context,
@@ -240,6 +244,8 @@ public final class MediaItemExportTest {
getDumpFileName(
/* originalFileName= */ FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S,
/* modifications...= */ "clipped"));
+ assertThat(result.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
+ assertThat(result.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
@@ -259,13 +265,15 @@ public final class MediaItemExportTest {
.build();
transformer.start(mediaItem, outputDir.newFile().getPath());
- ExportResult exportResult = TransformerTestRunner.runLooper(transformer);
+ ExportResult result = TransformerTestRunner.runLooper(transformer);
- assertThat(exportResult.optimizationResult).isEqualTo(OPTIMIZATION_FAILED_EXTRACTION_FAILED);
+ assertThat(result.optimizationResult).isEqualTo(OPTIMIZATION_FAILED_EXTRACTION_FAILED);
DumpFileAsserts.assertOutput(
context,
muxerFactory.getCreatedMuxer(),
getDumpFileName(/* originalFileName= */ FILE_AUDIO_RAW, /* modifications...= */ "clipped"));
+ assertThat(result.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_NA);
+ assertThat(result.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test
@@ -1069,11 +1077,13 @@ public final class MediaItemExportTest {
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
transformer.start(editedMediaItem, outputDir.newFile().getPath());
- TransformerTestRunner.runLooper(transformer);
+ ExportResult result = TransformerTestRunner.runLooper(transformer);
// Video transcoding in unit tests is not supported.
DumpFileAsserts.assertOutput(
context, muxerFactory.getCreatedMuxer(), getDumpFileName(FILE_VIDEO_ONLY));
+ assertThat(result.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
+ assertThat(result.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_NA);
}
@Test
@@ -1089,7 +1099,7 @@ public final class MediaItemExportTest {
new EditedMediaItem.Builder(mediaItem).setEffects(effects).build();
transformer.start(editedMediaItem, outputDir.newFile().getPath());
- TransformerTestRunner.runLooper(transformer);
+ ExportResult result = TransformerTestRunner.runLooper(transformer);
// Video transcoding in unit tests is not supported.
DumpFileAsserts.assertOutput(
@@ -1097,6 +1107,8 @@ public final class MediaItemExportTest {
muxerFactory.getCreatedMuxer(),
getDumpFileName(
/* originalFileName= */ FILE_AUDIO_VIDEO, /* modifications...= */ "rotated"));
+ assertThat(result.videoConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
+ assertThat(result.audioConversionProcess).isEqualTo(CONVERSION_PROCESS_TRANSMUXED);
}
@Test