diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/Composition.java b/libraries/transformer/src/main/java/androidx/media3/transformer/Composition.java index 61cec33e18..1d4211548e 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Composition.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Composition.java @@ -16,12 +16,18 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkArgument; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.SOURCE; +import androidx.annotation.IntDef; import androidx.media3.common.MediaItem; import androidx.media3.common.audio.AudioProcessor; import androidx.media3.common.util.UnstableApi; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; import java.util.List; /** @@ -42,6 +48,7 @@ public final class Composition { private boolean forceAudioTrack; private boolean transmuxAudio; private boolean transmuxVideo; + private @HdrMode int hdrMode; /** * Creates an instance. @@ -96,8 +103,8 @@ public final class Composition { * the {@link Composition} export doesn't produce any audio. * *
The MIME type of the output's audio track can be set using {@link - * TransformationRequest.Builder#setAudioMimeType(String)}. The sample rate and channel count - * can be set by passing relevant {@link AudioProcessor} instances to the {@link Composition}. + * Transformer.Builder#setAudioMimeType(String)}. The sample rate and channel count can be set + * by passing relevant {@link AudioProcessor} instances to the {@link Composition}. * *
Forcing an audio track and {@linkplain #setTransmuxAudio(boolean) requesting audio * transmuxing} are not allowed together because generating silence requires transcoding. @@ -163,12 +170,101 @@ public final class Composition { return this; } + /** + * Sets the {@link HdrMode} for HDR video input. + * + *
The default value is {@link #HDR_MODE_KEEP_HDR}. + * + * @param hdrMode The {@link HdrMode} used. + * @return This builder. + */ + @CanIgnoreReturnValue + public Builder setHdrMode(@HdrMode int hdrMode) { + this.hdrMode = hdrMode; + return this; + } + /** Builds a {@link Composition} instance. */ public Composition build() { - return new Composition(sequences, effects, forceAudioTrack, transmuxAudio, transmuxVideo); + return new Composition( + sequences, effects, forceAudioTrack, transmuxAudio, transmuxVideo, hdrMode); } } + /** + * The strategy to use to transcode or edit High Dynamic Range (HDR) input video. + * + *
One of {@link #HDR_MODE_KEEP_HDR}, {@link #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}, + * {@link #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL}, or {@link + * #HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}. + * + *
Standard Dynamic Range (SDR) input video is unaffected by these settings. + */ + @Documented + @Retention(SOURCE) + @Target(TYPE_USE) + @IntDef({ + HDR_MODE_KEEP_HDR, + HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC, + HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL, + HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR, + }) + public @interface HdrMode {} + + /** + * Processes HDR input as HDR, to generate HDR output. + * + *
The HDR output format (ex. color transfer) will be the same as the HDR input format. + * + *
Supported on API 31+, by some device and HDR format combinations. + * + *
If not supported, {@link Transformer} will attempt to use {@link + * #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}. + */ + public static final int HDR_MODE_KEEP_HDR = 0; + + /** + * Tone map HDR input to SDR before processing, to generate SDR output, using the {@link + * android.media.MediaCodec} decoder tone-mapper. + * + *
Supported on API 31+, by some device and HDR format combinations. Tone-mapping is only + * guaranteed to be supported on API 33+, on devices with HDR capture support. + * + *
If not supported, {@link Transformer} throws an {@link ExportException}. + */ + public static final int HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC = 1; + + /** + * Tone map HDR input to SDR before processing, to generate SDR output, using an OpenGL + * tone-mapper. + * + *
Supported on API 29+. + * + *
This may exhibit mild differences from {@link + * #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}, depending on the device's tone-mapping + * implementation, but should have much wider support and have more consistent results across + * devices. + * + *
If not supported, {@link Transformer} throws an {@link ExportException}. + */ + public static final int HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL = 2; + + /** + * Interpret HDR input as SDR, likely with a washed out look. + * + *
This is much more widely supported than {@link #HDR_MODE_KEEP_HDR} and {@link + * #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}. However, as HDR transfer functions and + * metadata will be ignored, contents will be displayed incorrectly, likely with a washed out + * look. + * + *
Using this API may lead to codec errors before API 29. + * + *
Use of this flag may result in {@code ERROR_CODE_DECODING_FORMAT_UNSUPPORTED}. + * + *
This field is experimental, and will be renamed or removed in a future release. + */ + public static final int HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR = 3; + /** * The {@link EditedMediaItemSequence} instances to compose. * @@ -200,12 +296,20 @@ public final class Composition { */ public final boolean transmuxVideo; + /** + * The {@link HdrMode} specifying how to handle HDR input video. + * + *
For more information, see {@link Builder#setHdrMode(int)}.
+ */
+ public final @HdrMode int hdrMode;
+
private Composition(
List One of {@link #HDR_MODE_KEEP_HDR}, {@link #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC},
- * {@link #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL}, or {@link
- * #HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}.
- *
- * Standard Dynamic Range (SDR) input video is unaffected by these settings.
- */
+ /** See {@link Composition.HdrMode}. */
@Documented
@Retention(SOURCE)
@Target(TYPE_USE)
@@ -55,59 +47,20 @@ public final class TransformationRequest {
})
public @interface HdrMode {}
- /**
- * Processes HDR input as HDR, to generate HDR output.
- *
- * The HDR output format (ex. color transfer) will be the same as the HDR input format.
- *
- * Supported on API 31+, by some device and HDR format combinations.
- *
- * If not supported, {@link Transformer} will attempt to use {@link
- * #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}.
- */
- public static final int HDR_MODE_KEEP_HDR = 0;
+ /** See {@link Composition#HDR_MODE_KEEP_HDR}. */
+ public static final int HDR_MODE_KEEP_HDR = Composition.HDR_MODE_KEEP_HDR;
- /**
- * Tone map HDR input to SDR before processing, to generate SDR output, using the {@link
- * android.media.MediaCodec} decoder tone-mapper.
- *
- * Supported on API 31+, by some device and HDR format combinations. Tone-mapping is only
- * guaranteed to be supported on API 33+, on devices with HDR capture support.
- *
- * If not supported, {@link Transformer} throws an {@link ExportException}.
- */
- public static final int HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC = 1;
+ /** See {@link Composition#HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}. */
+ public static final int HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC =
+ Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC;
- /**
- * Tone map HDR input to SDR before processing, to generate SDR output, using an OpenGL
- * tone-mapper.
- *
- * Supported on API 29+.
- *
- * This may exhibit mild differences from {@link
- * #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}, depending on the device's tone-mapping
- * implementation, but should have much wider support and have more consistent results across
- * devices.
- *
- * If not supported, {@link Transformer} throws an {@link ExportException}.
- */
- public static final int HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL = 2;
+ /** See {@link Composition#HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL}. */
+ public static final int HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL =
+ Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
- /**
- * Interpret HDR input as SDR, likely with a washed out look.
- *
- * This is much more widely supported than {@link #HDR_MODE_KEEP_HDR} and {@link
- * #HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC}. However, as HDR transfer functions and
- * metadata will be ignored, contents will be displayed incorrectly, likely with a washed out
- * look.
- *
- * Using this API may lead to codec errors before API 29.
- *
- * Use of this flag may result in {@code ERROR_CODE_DECODING_FORMAT_UNSUPPORTED}.
- *
- * This field is experimental, and will be renamed or removed in a future release.
- */
- public static final int HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR = 3;
+ /** See {@link Composition#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}. */
+ public static final int HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR =
+ Composition.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR;
/** A builder for {@link TransformationRequest} instances. */
public static final class Builder {
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 9f58c4572c..17144be4d4 100644
--- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
+++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java
@@ -80,7 +80,9 @@ public final class Transformer {
private final Context context;
// Optional fields.
- private TransformationRequest transformationRequest;
+ private @MonotonicNonNull String audioMimeType;
+ private @MonotonicNonNull String videoMimeType;
+ private @MonotonicNonNull TransformationRequest transformationRequest;
private ImmutableList Actual applied values may differ, per device capabilities. {@link
- * Listener#onFallbackApplied(Composition, TransformationRequest, TransformationRequest)} will
- * be invoked with the actual applied values.
+ * If no audio MIME type is passed, the output audio MIME type is the same as the first
+ * {@link MediaItem} in the {@link Composition}.
*
- * @param transformationRequest The {@link TransformationRequest}.
+ * Supported MIME types are:
+ *
+ * If no video MIME type is passed, the output video MIME type is the same as the first
+ * {@link MediaItem} in the {@link Composition}.
+ *
+ * Supported MIME types are:
+ *
+ *
+ *
+ *
+ * If the MIME type is not supported, {@link Transformer} will fallback to a supported MIME type
+ * and {@link Listener#onFallbackApplied(Composition, TransformationRequest,
+ * TransformationRequest)} will be invoked with the fallback value.
+ *
+ * @param audioMimeType The MIME type of the audio samples in the output.
* @return This builder.
+ * @throws IllegalArgumentException If the audio MIME type passed is not an audio {@linkplain
+ * MimeTypes MIME type}.
*/
@CanIgnoreReturnValue
+ public Builder setAudioMimeType(String audioMimeType) {
+ checkArgument(MimeTypes.isAudio(audioMimeType), "Not an audio MIME type: " + audioMimeType);
+ this.audioMimeType = audioMimeType;
+ return this;
+ }
+
+ /**
+ * Sets the video {@linkplain MimeTypes MIME type} of the output.
+ *
+ *
+ *
+ *
+ * If the MIME type is not supported, {@link Transformer} will fallback to a supported MIME type
+ * and {@link Listener#onFallbackApplied(Composition, TransformationRequest,
+ * TransformationRequest)} will be invoked with the fallback value.
+ *
+ * @param videoMimeType The MIME type of the video samples in the output.
+ * @return This builder.
+ * @throws IllegalArgumentException If the video MIME type passed is not a video {@linkplain
+ * MimeTypes MIME type}.
+ */
+ @CanIgnoreReturnValue
+ public Builder setVideoMimeType(String videoMimeType) {
+ checkArgument(MimeTypes.isVideo(videoMimeType), "Not a video MIME type: " + videoMimeType);
+ this.videoMimeType = videoMimeType;
+ return this;
+ }
+
+ /**
+ * @deprecated Use {@link #setAudioMimeType(String)}, {@link #setVideoMimeType(String)} and
+ * {@link Composition.Builder#setHdrMode(int)} instead.
+ */
+ @Deprecated
+ @CanIgnoreReturnValue
public Builder setTransformationRequest(TransformationRequest transformationRequest) {
this.transformationRequest = transformationRequest;
return this;
@@ -376,6 +435,17 @@ public final class Transformer {
* type.
*/
public Transformer build() {
+ TransformationRequest.Builder transformationRequestBuilder =
+ transformationRequest == null
+ ? new TransformationRequest.Builder()
+ : transformationRequest.buildUpon();
+ if (audioMimeType != null) {
+ transformationRequestBuilder.setAudioMimeType(audioMimeType);
+ }
+ if (videoMimeType != null) {
+ transformationRequestBuilder.setVideoMimeType(videoMimeType);
+ }
+ transformationRequest = transformationRequestBuilder.build();
if (transformationRequest.audioMimeType != null) {
checkSampleMimeType(transformationRequest.audioMimeType);
}
@@ -720,6 +790,11 @@ public final class Transformer {
TransformerInternalListener transformerInternalListener =
new TransformerInternalListener(composition);
HandlerWrapper applicationHandler = clock.createHandler(looper, /* callback= */ null);
+ TransformationRequest transformationRequest = this.transformationRequest;
+ if (composition.hdrMode != Composition.HDR_MODE_KEEP_HDR) {
+ transformationRequest =
+ transformationRequest.buildUpon().setHdrMode(composition.hdrMode).build();
+ }
FallbackListener fallbackListener =
new FallbackListener(composition, listeners, applicationHandler, transformationRequest);
DebugTraceUtil.reset();