diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java index d0e6630385..8fa9ebe654 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/DefaultAssetLoaderFactory.java @@ -34,6 +34,7 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory { private final Context context; private final Codec.DecoderFactory decoderFactory; + private final boolean forceInterpretHdrAsSdr; private final Clock clock; private final MediaSource.@MonotonicNonNull Factory mediaSourceFactory; @@ -45,13 +46,19 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory { * @param context The {@link Context}. * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if * necessary). + * @param forceInterpretHdrAsSdr Whether to apply {@link + * TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}. * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for * testing. */ public DefaultAssetLoaderFactory( - Context context, Codec.DecoderFactory decoderFactory, Clock clock) { + Context context, + Codec.DecoderFactory decoderFactory, + boolean forceInterpretHdrAsSdr, + Clock clock) { this.context = context; this.decoderFactory = decoderFactory; + this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr; this.clock = clock; this.mediaSourceFactory = null; } @@ -62,6 +69,8 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory { * @param context The {@link Context}. * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if * necessary). + * @param forceInterpretHdrAsSdr Whether to apply {@link + * TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}. * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for * testing. * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to @@ -70,10 +79,12 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory { public DefaultAssetLoaderFactory( Context context, Codec.DecoderFactory decoderFactory, + boolean forceInterpretHdrAsSdr, Clock clock, MediaSource.Factory mediaSourceFactory) { this.context = context; this.decoderFactory = decoderFactory; + this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr; this.clock = clock; this.mediaSourceFactory = mediaSourceFactory; } @@ -91,8 +102,10 @@ public final class DefaultAssetLoaderFactory implements AssetLoader.Factory { if (exoPlayerAssetLoaderFactory == null) { exoPlayerAssetLoaderFactory = mediaSourceFactory != null - ? new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory) - : new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock); + ? new ExoPlayerAssetLoader.Factory( + context, decoderFactory, forceInterpretHdrAsSdr, clock, mediaSourceFactory) + : new ExoPlayerAssetLoader.Factory( + context, decoderFactory, forceInterpretHdrAsSdr, clock); } return exoPlayerAssetLoaderFactory.createAssetLoader(editedMediaItem, looper, listener); } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java index bbbbec31e3..da00c98a12 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderBaseRenderer.java @@ -135,6 +135,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; } } + /** Overrides the {@code inputFormat}. */ + protected Format overrideFormat(Format inputFormat) throws ExportException { + return inputFormat; + } + /** Called when the {@link Format} of the samples fed to the renderer is known. */ protected void onInputFormatRead(Format inputFormat) {} @@ -176,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; if (result != C.RESULT_FORMAT_READ) { return false; } - Format inputFormat = checkNotNull(formatHolder.format); + Format inputFormat = overrideFormat(checkNotNull(formatHolder.format)); @AssetLoader.SupportedOutputTypes int supportedOutputTypes = SUPPORTED_OUTPUT_TYPE_ENCODED | SUPPORTED_OUTPUT_TYPE_DECODED; sampleConsumer = diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java index 99b34b84b7..a9f7db53f6 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoAssetLoaderVideoRenderer.java @@ -16,6 +16,7 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.common.util.Util.SDK_INT; import android.media.MediaCodec; import androidx.annotation.Nullable; @@ -35,6 +36,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private final boolean flattenForSlowMotion; private final Codec.DecoderFactory decoderFactory; + private final boolean forceInterpretHdrAsSdr; private final List decodeOnlyPresentationTimestamps; private @MonotonicNonNull SefSlowMotionFlattener sefVideoSlowMotionFlattener; @@ -43,11 +45,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; public ExoAssetLoaderVideoRenderer( boolean flattenForSlowMotion, Codec.DecoderFactory decoderFactory, + boolean forceInterpretHdrAsSdr, TransformerMediaClock mediaClock, AssetLoader.Listener assetLoaderListener) { super(C.TRACK_TYPE_VIDEO, mediaClock, assetLoaderListener); this.flattenForSlowMotion = flattenForSlowMotion; this.decoderFactory = decoderFactory; + this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr; decodeOnlyPresentationTimestamps = new ArrayList<>(); } @@ -56,6 +60,23 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return TAG; } + @Override + protected Format overrideFormat(Format inputFormat) throws ExportException { + if (forceInterpretHdrAsSdr && ColorInfo.isTransferHdr(inputFormat.colorInfo)) { + if (SDK_INT < 29) { + throw ExportException.createForCodec( + new IllegalArgumentException( + "Interpreting HDR video as SDR is not supported on this device."), + ExportException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, + /* isVideo= */ true, + /* isDecoder= */ true, + inputFormat); + } + return inputFormat.buildUpon().setColorInfo(ColorInfo.SDR_BT709_LIMITED).build(); + } + return inputFormat; + } + @Override protected void onInputFormatRead(Format inputFormat) { if (flattenForSlowMotion) { diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java index ec998ec513..9a4d75e450 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/ExoPlayerAssetLoader.java @@ -64,6 +64,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader { private final Context context; private final Codec.DecoderFactory decoderFactory; + private final boolean forceInterpretHdrAsSdr; private final Clock clock; @Nullable private final MediaSource.Factory mediaSourceFactory; @@ -73,12 +74,19 @@ public final class ExoPlayerAssetLoader implements AssetLoader { * @param context The {@link Context}. * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if * necessary). + * @param forceInterpretHdrAsSdr Whether to apply {@link + * TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}. * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for * testing. */ - public Factory(Context context, Codec.DecoderFactory decoderFactory, Clock clock) { + public Factory( + Context context, + Codec.DecoderFactory decoderFactory, + boolean forceInterpretHdrAsSdr, + Clock clock) { this.context = context; this.decoderFactory = decoderFactory; + this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr; this.clock = clock; this.mediaSourceFactory = null; } @@ -89,6 +97,8 @@ public final class ExoPlayerAssetLoader implements AssetLoader { * @param context The {@link Context}. * @param decoderFactory The {@link Codec.DecoderFactory} to use to decode the samples (if * necessary). + * @param forceInterpretHdrAsSdr Whether to apply {@link + * TransformationRequest#HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR}. * @param clock The {@link Clock} to use. It should always be {@link Clock#DEFAULT}, except for * testing. * @param mediaSourceFactory The {@link MediaSource.Factory} to use to retrieve the samples to @@ -97,10 +107,12 @@ public final class ExoPlayerAssetLoader implements AssetLoader { public Factory( Context context, Codec.DecoderFactory decoderFactory, + boolean forceInterpretHdrAsSdr, Clock clock, MediaSource.Factory mediaSourceFactory) { this.context = context; this.decoderFactory = decoderFactory; + this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr; this.clock = clock; this.mediaSourceFactory = mediaSourceFactory; } @@ -117,7 +129,14 @@ public final class ExoPlayerAssetLoader implements AssetLoader { mediaSourceFactory = new DefaultMediaSourceFactory(context, defaultExtractorsFactory); } return new ExoPlayerAssetLoader( - context, editedMediaItem, mediaSourceFactory, decoderFactory, looper, listener, clock); + context, + editedMediaItem, + mediaSourceFactory, + decoderFactory, + forceInterpretHdrAsSdr, + looper, + listener, + clock); } } @@ -132,6 +151,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader { EditedMediaItem editedMediaItem, MediaSource.Factory mediaSourceFactory, Codec.DecoderFactory decoderFactory, + boolean forceInterpretHdrAsSdr, Looper looper, Listener listener, Clock clock) { @@ -161,6 +181,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader { editedMediaItem.removeVideo, editedMediaItem.flattenForSlowMotion, this.decoderFactory, + forceInterpretHdrAsSdr, listener)) .setMediaSourceFactory(mediaSourceFactory) .setTrackSelector(trackSelector) @@ -223,6 +244,7 @@ public final class ExoPlayerAssetLoader implements AssetLoader { private final boolean removeVideo; private final boolean flattenForSlowMotion; private final Codec.DecoderFactory decoderFactory; + private final boolean forceInterpretHdrAsSdr; private final Listener assetLoaderListener; public RenderersFactoryImpl( @@ -230,11 +252,13 @@ public final class ExoPlayerAssetLoader implements AssetLoader { boolean removeVideo, boolean flattenForSlowMotion, Codec.DecoderFactory decoderFactory, + boolean forceInterpretHdrAsSdr, Listener assetLoaderListener) { this.removeAudio = removeAudio; this.removeVideo = removeVideo; this.flattenForSlowMotion = flattenForSlowMotion; this.decoderFactory = decoderFactory; + this.forceInterpretHdrAsSdr = forceInterpretHdrAsSdr; this.assetLoaderListener = assetLoaderListener; mediaClock = new TransformerMediaClock(); } @@ -257,7 +281,11 @@ public final class ExoPlayerAssetLoader implements AssetLoader { if (!removeVideo) { renderers[index] = new ExoAssetLoaderVideoRenderer( - flattenForSlowMotion, decoderFactory, mediaClock, assetLoaderListener); + flattenForSlowMotion, + decoderFactory, + forceInterpretHdrAsSdr, + mediaClock, + assetLoaderListener); index++; } return renderers; 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 0eb147e938..f48f01b9b6 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Transformer.java @@ -437,7 +437,12 @@ public final class Transformer { } if (assetLoaderFactory == null) { assetLoaderFactory = - new DefaultAssetLoaderFactory(context, new DefaultDecoderFactory(context), clock); + new DefaultAssetLoaderFactory( + context, + new DefaultDecoderFactory(context), + /* forceInterpretHdrAsSdr= */ transformationRequest.hdrMode + == TransformationRequest.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR, + clock); } return new Transformer( context, diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java index 8ff7e043cc..45d52e7992 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoSamplePipeline.java @@ -18,9 +18,7 @@ package androidx.media3.transformer; import static androidx.media3.common.ColorInfo.isTransferHdr; import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.transformer.EncoderUtil.getSupportedEncodersForHdrEditing; -import static androidx.media3.transformer.TransformationRequest.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR; import static androidx.media3.transformer.TransformationRequest.HDR_MODE_KEEP_HDR; import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC; import static androidx.media3.transformer.TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; @@ -87,25 +85,6 @@ import org.checkerframework.dataflow.qual.Pure; throws ExportException { super(firstInputFormat, streamStartPositionUs, muxerWrapper); - boolean isGlToneMapping = false; - if (isTransferHdr(firstInputFormat.colorInfo)) { - if (transformationRequest.hdrMode == HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR) { - if (SDK_INT < 29) { - throw ExportException.createForCodec( - new IllegalArgumentException( - "Interpreting HDR video as SDR is not supported on this device."), - ExportException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, - /* isVideo= */ true, - /* isDecoder= */ true, - firstInputFormat); - } - firstInputFormat = - firstInputFormat.buildUpon().setColorInfo(ColorInfo.SDR_BT709_LIMITED).build(); - } else if (transformationRequest.hdrMode == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL) { - isGlToneMapping = true; - } - } - finalFramePresentationTimeUs = C.TIME_UNSET; encoderOutputBuffer = @@ -122,6 +101,9 @@ import org.checkerframework.dataflow.qual.Pure; ColorInfo encoderInputColor = encoderWrapper.getSupportedInputColor(); // If not tone mapping using OpenGL, the decoder will output the encoderInputColor, // possibly by tone mapping. + boolean isGlToneMapping = + ColorInfo.isTransferHdr(firstInputFormat.colorInfo) + && transformationRequest.hdrMode == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; videoFrameProcessorInputColor = isGlToneMapping ? checkNotNull(firstInputFormat.colorInfo) : encoderInputColor; // For consistency with the Android platform, OpenGL tone mapping outputs colors with diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java index 78bb896fab..f0d64ac2d5 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/ExoPlayerAssetLoaderTest.java @@ -119,7 +119,8 @@ public class ExoPlayerAssetLoaderTest { Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context); EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(MediaItem.fromUri("asset:///media/mp4/sample.mp4")).build(); - return new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock) + return new ExoPlayerAssetLoader.Factory( + context, decoderFactory, /* forceInterpretHdrAsSdr= */ false, clock) .createAssetLoader(editedMediaItem, looper, listener); } diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java index 1a5caf7669..5805c2f7da 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerEndToEndTest.java @@ -802,7 +802,12 @@ public final class TransformerEndToEndTest { context, new SlowExtractorsFactory(/* delayBetweenReadsMs= */ 10)); Codec.DecoderFactory decoderFactory = new DefaultDecoderFactory(context); AssetLoader.Factory assetLoaderFactory = - new ExoPlayerAssetLoader.Factory(context, decoderFactory, clock, mediaSourceFactory); + new ExoPlayerAssetLoader.Factory( + context, + decoderFactory, + /* forceInterpretHdrAsSdr= */ false, + clock, + mediaSourceFactory); Muxer.Factory muxerFactory = new TestMuxerFactory(/* maxDelayBetweenSamplesMs= */ 1); Transformer transformer = createTransformerBuilder(/* enableFallback= */ false)