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 sequences, Effects effects, boolean forceAudioTrack, boolean transmuxAudio, - boolean transmuxVideo) { + boolean transmuxVideo, + @HdrMode int hdrMode) { checkArgument( !transmuxAudio || !forceAudioTrack, "Audio transmuxing and audio track forcing are not allowed together."); @@ -214,5 +318,6 @@ public final class Composition { this.transmuxAudio = transmuxAudio; this.transmuxVideo = transmuxVideo; this.forceAudioTrack = forceAudioTrack; + this.hdrMode = hdrMode; } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java index a5784fc976..19fd1e5f66 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformationRequest.java @@ -35,15 +35,7 @@ import java.lang.annotation.Target; @UnstableApi public final class TransformationRequest { - /** - * 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. - */ + /** 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 audioProcessors; private ImmutableList videoEffects; private boolean removeAudio; @@ -102,7 +104,6 @@ public final class Transformer { */ public Builder(Context context) { this.context = context.getApplicationContext(); - transformationRequest = new TransformationRequest.Builder().build(); audioProcessors = ImmutableList.of(); videoEffects = ImmutableList.of(); videoFrameProcessorFactory = new DefaultVideoFrameProcessor.Factory.Builder().build(); @@ -117,6 +118,8 @@ public final class Transformer { /** Creates a builder with the values of the provided {@link Transformer}. */ private Builder(Transformer transformer) { this.context = transformer.context; + this.audioMimeType = transformer.transformationRequest.audioMimeType; + this.videoMimeType = transformer.transformationRequest.videoMimeType; this.transformationRequest = transformer.transformationRequest; this.audioProcessors = transformer.audioProcessors; this.videoEffects = transformer.videoEffects; @@ -133,16 +136,72 @@ public final class Transformer { } /** - * Sets the {@link TransformationRequest} which configures the editing and transcoding options. + * Sets the audio {@linkplain MimeTypes MIME type} of the output. * - *

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 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 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 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();