diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java index 5c440929190..664014984a9 100644 --- a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java +++ b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/ConfigurationActivity.java @@ -68,6 +68,7 @@ public final class ConfigurationActivity extends AppCompatActivity { public static final String ENABLE_FALLBACK = "enable_fallback"; public static final String ENABLE_DEBUG_PREVIEW = "enable_debug_preview"; public static final String ENABLE_REQUEST_SDR_TONE_MAPPING = "enable_request_sdr_tone_mapping"; + public static final String FORCE_INTERPRET_HDR_VIDEO_AS_SDR = "force_interpret_hdr_video_as_sdr"; public static final String ENABLE_HDR_EDITING = "enable_hdr_editing"; public static final String DEMO_EFFECTS_SELECTIONS = "demo_effects_selections"; public static final String PERIODIC_VIGNETTE_CENTER_X = "periodic_vignette_center_x"; @@ -153,6 +154,7 @@ public final class ConfigurationActivity extends AppCompatActivity { private @MonotonicNonNull CheckBox enableFallbackCheckBox; private @MonotonicNonNull CheckBox enableDebugPreviewCheckBox; private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox; + private @MonotonicNonNull CheckBox forceInterpretHdrVideoAsSdrCheckBox; private @MonotonicNonNull CheckBox enableHdrEditingCheckBox; private @MonotonicNonNull Button selectDemoEffectsButton; private boolean @MonotonicNonNull [] demoEffectsSelections; @@ -248,6 +250,8 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { enableRequestSdrToneMappingCheckBox = findViewById(R.id.request_sdr_tone_mapping_checkbox); enableRequestSdrToneMappingCheckBox.setEnabled(isRequestSdrToneMappingSupported()); findViewById(R.id.request_sdr_tone_mapping).setEnabled(isRequestSdrToneMappingSupported()); + forceInterpretHdrVideoAsSdrCheckBox = + findViewById(R.id.force_interpret_hdr_video_as_sdr_checkbox); enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox); demoEffectsSelections = new boolean[DEMO_EFFECTS.length]; @@ -306,6 +310,7 @@ protected void onNewIntent(Intent intent) { "enableFallbackCheckBox", "enableDebugPreviewCheckBox", "enableRequestSdrToneMappingCheckBox", + "forceInterpretHdrVideoAsSdrCheckBox", "enableHdrEditingCheckBox", "demoEffectsSelections" }) @@ -346,6 +351,8 @@ private void startTransformation(View view) { bundle.putBoolean(ENABLE_DEBUG_PREVIEW, enableDebugPreviewCheckBox.isChecked()); bundle.putBoolean( ENABLE_REQUEST_SDR_TONE_MAPPING, enableRequestSdrToneMappingCheckBox.isChecked()); + bundle.putBoolean( + FORCE_INTERPRET_HDR_VIDEO_AS_SDR, forceInterpretHdrVideoAsSdrCheckBox.isChecked()); bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked()); bundle.putBooleanArray(DEMO_EFFECTS_SELECTIONS, demoEffectsSelections); bundle.putInt(COLOR_FILTER_SELECTION, colorFilterSelection); @@ -585,6 +592,7 @@ private void controlPeriodicVignetteSettings() { "rotateSpinner", "enableDebugPreviewCheckBox", "enableRequestSdrToneMappingCheckBox", + "forceInterpretHdrVideoAsSdrCheckBox", "enableHdrEditingCheckBox", "selectDemoEffectsButton" }) @@ -606,6 +614,7 @@ private void onRemoveAudio(View view) { "rotateSpinner", "enableDebugPreviewCheckBox", "enableRequestSdrToneMappingCheckBox", + "forceInterpretHdrVideoAsSdrCheckBox", "enableHdrEditingCheckBox", "selectDemoEffectsButton" }) @@ -626,6 +635,7 @@ private void onRemoveVideo(View view) { "rotateSpinner", "enableDebugPreviewCheckBox", "enableRequestSdrToneMappingCheckBox", + "forceInterpretHdrVideoAsSdrCheckBox", "enableHdrEditingCheckBox", "selectDemoEffectsButton" }) @@ -638,6 +648,7 @@ private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoE enableDebugPreviewCheckBox.setEnabled(isVideoEnabled); enableRequestSdrToneMappingCheckBox.setEnabled( isRequestSdrToneMappingSupported() && isVideoEnabled); + forceInterpretHdrVideoAsSdrCheckBox.setEnabled(isVideoEnabled); enableHdrEditingCheckBox.setEnabled(isVideoEnabled); selectDemoEffectsButton.setEnabled(isVideoEnabled); @@ -648,6 +659,7 @@ private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoE findViewById(R.id.rotate).setEnabled(isVideoEnabled); findViewById(R.id.request_sdr_tone_mapping) .setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled); + findViewById(R.id.force_interpret_hdr_video_as_sdr).setEnabled(isVideoEnabled); findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled); } diff --git a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java index a3b3f46d126..a742c66aaaf 100644 --- a/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java +++ b/demos/transformer/src/main/java/com/google/android/exoplayer2/transformerdemo/TransformerActivity.java @@ -272,6 +272,8 @@ private Transformer createTransformer(@Nullable Bundle bundle, String filePath) requestBuilder.setEnableRequestSdrToneMapping( bundle.getBoolean(ConfigurationActivity.ENABLE_REQUEST_SDR_TONE_MAPPING)); + requestBuilder.experimental_setForceInterpretHdrVideoAsSdr( + bundle.getBoolean(ConfigurationActivity.FORCE_INTERPRET_HDR_VIDEO_AS_SDR)); requestBuilder.experimental_setEnableHdrEditing( bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING)); transformerBuilder diff --git a/demos/transformer/src/main/res/layout/configuration_activity.xml b/demos/transformer/src/main/res/layout/configuration_activity.xml index e090eb55f26..2a481bea698 100644 --- a/demos/transformer/src/main/res/layout/configuration_activity.xml +++ b/demos/transformer/src/main/res/layout/configuration_activity.xml @@ -217,6 +217,16 @@ android:id="@+id/hdr_editing_checkbox" android:layout_gravity="end" /> + + + + Enable debug preview Trim Request SDR tone-mapping (API 31+) - [Experimental] HDR editing + [Experimental] Force interpret HDR video as SDR (API 29+) + [Experimental] HDR editing (API 31+) Add demo effects Periodic vignette options Failed to load MediaPipe processor. Check the README for instructions. diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java index 400fa31ab7d..03bb02d8583 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/AndroidTestUtil.java @@ -23,12 +23,14 @@ import android.content.Context; import android.os.Build; import androidx.annotation.Nullable; +import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.mediacodec.MediaCodecInfo; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.ColorInfo; import com.google.common.collect.ImmutableList; import java.io.File; import java.io.FileWriter; @@ -86,6 +88,19 @@ public final class AndroidTestUtil { public static final String MP4_ASSET_1080P_4_SECOND_HDR10 = "https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4"; + public static final Format MP4_ASSET_1080P_4_SECOND_HDR10_FORMAT = + new Format.Builder() + .setSampleMimeType(VIDEO_H265) + .setWidth(1920) + .setHeight(1080) + .setFrameRate(23.517f) + .setColorInfo( + new ColorInfo( + C.COLOR_SPACE_BT2020, + C.COLOR_RANGE_LIMITED, + C.COLOR_TRANSFER_ST2084, + /* hdrStaticInfo= */ null)) + .build(); public static final String MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER = "asset:///media/mp4/hdr10-video-with-sdr-container.mp4"; diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetForceInterpretHdrVideoAsSdrTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetForceInterpretHdrVideoAsSdrTest.java new file mode 100644 index 00000000000..99f6395d397 --- /dev/null +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetForceInterpretHdrVideoAsSdrTest.java @@ -0,0 +1,80 @@ +/* + * 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 com.google.android.exoplayer2.transformer.mh; + +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10; +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10_FORMAT; +import static com.google.android.exoplayer2.transformer.mh.analysis.FileUtil.assertFileHasColorTransfer; + +import android.content.Context; +import android.net.Uri; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.transformer.AndroidTestUtil; +import com.google.android.exoplayer2.transformer.TransformationException; +import com.google.android.exoplayer2.transformer.TransformationRequest; +import com.google.android.exoplayer2.transformer.TransformationTestResult; +import com.google.android.exoplayer2.transformer.Transformer; +import com.google.android.exoplayer2.transformer.TransformerAndroidTestRunner; +import com.google.android.exoplayer2.util.Log; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * {@link Transformer} instrumentation test for {@linkplain + * TransformationRequest#forceInterpretHdrVideoAsSdr forcing HDR contents to be interpreted as SDR}. + */ +@RunWith(AndroidJUnit4.class) +public class SetForceInterpretHdrVideoAsSdrTest { + public static final String TAG = "SetForceInterpretHdrVideoAsSdrTest"; + + @Test + public void forceInterpretHdrVideoAsSdrTest_hdr10File_transformsOrThrows() throws Exception { + String testId = "forceInterpretHdrVideoAsSdrTest_hdr10File_transformsOrThrows"; + Context context = ApplicationProvider.getApplicationContext(); + + if (AndroidTestUtil.skipAndLogIfInsufficientCodecSupport( + context, + testId, + /* decodingFormat= */ MP4_ASSET_1080P_4_SECOND_HDR10_FORMAT, + /* encodingFormat= */ null)) { + return; + } + + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder() + .experimental_setForceInterpretHdrVideoAsSdr(true) + .build()) + .build(); + try { + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR); + Log.i(TAG, "Transformed."); + } catch (TransformationException exception) { + if (exception.errorCode != TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED + && exception.errorCode != TransformationException.ERROR_CODE_HDR_DECODING_UNSUPPORTED) { + throw exception; + } + } + } +} diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTest.java index 840db569146..29f410a7f40 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTest.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTest.java @@ -79,7 +79,7 @@ public void transform_noRequestedTranscode_hdr10File_transformsOrThrows() throws Log.i(TAG, checkNotNull(exception.getCause()).toString()); assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(exception.errorCode) - .isEqualTo(TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED); + .isEqualTo(TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED); } } @@ -156,7 +156,7 @@ public void onFallbackApplied( assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(exception.errorCode) .isAnyOf( - TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED, + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED, TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED); assertThat(isFallbackListenerInvoked.get()).isFalse(); return; diff --git a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrToSdrToneMapTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrToSdrToneMapTest.java index 936b6bed57a..010cf7f31d9 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrToSdrToneMapTest.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrToSdrToneMapTest.java @@ -80,7 +80,7 @@ public void onFallbackApplied( assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(exception.errorCode) .isAnyOf( - TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED, + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED, TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED); return; } @@ -126,7 +126,7 @@ public void onFallbackApplied( assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); assertThat(exception.errorCode) .isAnyOf( - TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED, + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED, TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED); return; } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java index 43e84c99671..df8a0a0154d 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationException.java @@ -69,7 +69,7 @@ public final class TransformationException extends Exception { ERROR_CODE_ENCODER_INIT_FAILED, ERROR_CODE_ENCODING_FAILED, ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED, - ERROR_CODE_HDR_EDITING_UNSUPPORTED, + ERROR_CODE_HDR_ENCODING_UNSUPPORTED, ERROR_CODE_FRAME_PROCESSING_FAILED, ERROR_CODE_MUXING_FAILED, }) @@ -137,6 +137,8 @@ public final class TransformationException extends Exception { public static final int ERROR_CODE_DECODING_FAILED = 3002; /** Caused by trying to decode content whose format is not supported. */ public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 3003; + /** Caused by the decoder not supporting HDR formats. */ + public static final int ERROR_CODE_HDR_DECODING_UNSUPPORTED = 3004; // Encoding errors (4xxx). @@ -152,7 +154,7 @@ public final class TransformationException extends Exception { */ public static final int ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED = 4003; /** Caused by the encoder not supporting HDR formats. */ - public static final int ERROR_CODE_HDR_EDITING_UNSUPPORTED = 4004; + public static final int ERROR_CODE_HDR_ENCODING_UNSUPPORTED = 4004; // Video editing errors (5xxx). @@ -178,10 +180,11 @@ public final class TransformationException extends Exception { .put("ERROR_CODE_DECODER_INIT_FAILED", ERROR_CODE_DECODER_INIT_FAILED) .put("ERROR_CODE_DECODING_FAILED", ERROR_CODE_DECODING_FAILED) .put("ERROR_CODE_DECODING_FORMAT_UNSUPPORTED", ERROR_CODE_DECODING_FORMAT_UNSUPPORTED) + .put("ERROR_CODE_HDR_DECODING_UNSUPPORTED", ERROR_CODE_HDR_DECODING_UNSUPPORTED) .put("ERROR_CODE_ENCODER_INIT_FAILED", ERROR_CODE_ENCODER_INIT_FAILED) .put("ERROR_CODE_ENCODING_FAILED", ERROR_CODE_ENCODING_FAILED) .put("ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED", ERROR_CODE_OUTPUT_FORMAT_UNSUPPORTED) - .put("ERROR_CODE_HDR_EDITING_UNSUPPORTED", ERROR_CODE_HDR_EDITING_UNSUPPORTED) + .put("ERROR_CODE_HDR_ENCODING_UNSUPPORTED", ERROR_CODE_HDR_ENCODING_UNSUPPORTED) .put("ERROR_CODE_FRAME_PROCESSING_FAILED", ERROR_CODE_FRAME_PROCESSING_FAILED) .put("ERROR_CODE_MUXING_FAILED", ERROR_CODE_MUXING_FAILED) .buildOrThrow(); diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java index 55535e7d02a..d6fd7968fa2 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformationRequest.java @@ -40,6 +40,7 @@ public static final class Builder { @Nullable private String audioMimeType; @Nullable private String videoMimeType; private boolean enableRequestSdrToneMapping; + private boolean forceInterpretHdrVideoAsSdr; private boolean enableHdrEditing; /** @@ -63,6 +64,7 @@ private Builder(TransformationRequest transformationRequest) { this.audioMimeType = transformationRequest.audioMimeType; this.videoMimeType = transformationRequest.videoMimeType; this.enableRequestSdrToneMapping = transformationRequest.enableRequestSdrToneMapping; + this.forceInterpretHdrVideoAsSdr = transformationRequest.forceInterpretHdrVideoAsSdr; this.enableHdrEditing = transformationRequest.enableHdrEditing; } @@ -216,31 +218,78 @@ public Builder setAudioMimeType(@Nullable String audioMimeType) { * supported, high dynamic range (HDR) input will be tone-mapped into an SDR opto-electrical * transfer function before processing. * + * The default value is {@code true}, which corresponds to tone-mapping output if possible. + * * The setting has no effect if the input is already in SDR, or if tone-mapping is not * supported. Currently tone-mapping is only guaranteed to be supported from Android T onwards. * + * Setting this as {@code true} will set {@linkplain #experimental_setEnableHdrEditing} and + * {@linkplain #forceInterpretHdrVideoAsSdr} to {@code false}. + * * @param enableRequestSdrToneMapping Whether to request tone-mapping down to SDR. * @return This builder. */ @CanIgnoreReturnValue public Builder setEnableRequestSdrToneMapping(boolean enableRequestSdrToneMapping) { this.enableRequestSdrToneMapping = enableRequestSdrToneMapping; + if (enableRequestSdrToneMapping) { + forceInterpretHdrVideoAsSdr = false; + enableHdrEditing = false; + } + return this; + } + + /** + * Sets whether to interpret HDR video as SDR, resulting in washed out video. + * + * The default value is {@code false}, with {@link #setEnableRequestSdrToneMapping} being + * applied. + * + * Use of this flag may result in {@code + * TransformationException.ERROR_CODE_HDR_DECODING_UNSUPPORTED} or {@code + * ERROR_CODE_DECODING_FORMAT_UNSUPPORTED}. + * + * This method is experimental, and will be renamed or removed in a future release. + * + * If enabled, HDR information will be interpreted as SDR, which is much more widely + * supported than tone mapping or HDR editing. However, as HDR transfer functions and metadata + * will be ignored, contents will be displayed incorrectly, likely with a washed out look. + * + * The setting has no effect if the input is already in SDR. + * + * Setting this as {@code true} will set {@linkplain #experimental_setEnableHdrEditing} and + * {@linkplain #forceInterpretHdrVideoAsSdr} to {@code false}. + * + * @param forceInterpretHdrVideoAsSdr Whether to interpret HDR contents as SDR. + * @return This builder. + */ + // TODO(http://b/258246130): Use IntDef to select between tone mapping, HDR editing, and this. + @CanIgnoreReturnValue + public Builder experimental_setForceInterpretHdrVideoAsSdr( + boolean forceInterpretHdrVideoAsSdr) { + this.forceInterpretHdrVideoAsSdr = forceInterpretHdrVideoAsSdr; + if (forceInterpretHdrVideoAsSdr) { + enableRequestSdrToneMapping = false; + enableHdrEditing = false; + } return this; } /** * Sets whether to allow processing high dynamic range (HDR) input video streams as HDR. * + * The default value is {@code false}, with {@link #setEnableRequestSdrToneMapping} being + * applied. + * * This method is experimental, and will be renamed or removed in a future release. The HDR - * editing feature is under development and is intended for developing/testing HDR support. HDR - * editing can't be enabled at the same time as {@linkplain - * #setEnableRequestSdrToneMapping(boolean) SDR tone-mapping}. + * editing feature is under development and is intended for developing/testing HDR support. + * + * Setting this as {@code true} will set {@linkplain #experimental_setEnableHdrEditing} and + * {@linkplain #forceInterpretHdrVideoAsSdr} to {@code false}. * * With this flag enabled, HDR streams will correctly edit in HDR, convert via tone-mapping - * to SDR, or throw an error, based on the device's HDR support. Without both this flag and - * {@linkplain #setEnableRequestSdrToneMapping(boolean) SDR tone-mapping} as false, HDR streams - * will be incorrectly interpreted as SDR streams, with no conversion. SDR streams will be - * interpreted the same way regardless of this flag's state. + * to SDR, or throw an error, based on the device's HDR support. SDR streams will be interpreted + * the same way regardless of this flag's state. * * @param enableHdrEditing Whether to attempt to process any input video stream as a high * dynamic range (HDR) signal. @@ -249,6 +298,10 @@ public Builder setEnableRequestSdrToneMapping(boolean enableRequestSdrToneMappin @CanIgnoreReturnValue public Builder experimental_setEnableHdrEditing(boolean enableHdrEditing) { this.enableHdrEditing = enableHdrEditing; + if (enableHdrEditing) { + enableRequestSdrToneMapping = false; + forceInterpretHdrVideoAsSdr = false; + } return this; } @@ -263,6 +316,7 @@ public TransformationRequest build() { audioMimeType, videoMimeType, enableRequestSdrToneMapping, + forceInterpretHdrVideoAsSdr, enableHdrEditing); } } @@ -316,6 +370,9 @@ public TransformationRequest build() { /** Whether to request tone-mapping to standard dynamic range (SDR). */ public final boolean enableRequestSdrToneMapping; + /** Whether to force interpreting HDR video as SDR. */ + public final boolean forceInterpretHdrVideoAsSdr; + /** * Whether to attempt to process any input video stream as a high dynamic range (HDR) signal. * @@ -332,8 +389,12 @@ private TransformationRequest( @Nullable String audioMimeType, @Nullable String videoMimeType, boolean enableRequestSdrToneMapping, + boolean forceInterpretHdrVideoAsSdr, boolean enableHdrEditing) { + checkArgument(!forceInterpretHdrVideoAsSdr || !enableRequestSdrToneMapping); + checkArgument(!enableHdrEditing || !forceInterpretHdrVideoAsSdr); checkArgument(!enableHdrEditing || !enableRequestSdrToneMapping); + this.flattenForSlowMotion = flattenForSlowMotion; this.scaleX = scaleX; this.scaleY = scaleY; @@ -342,6 +403,7 @@ private TransformationRequest( this.audioMimeType = audioMimeType; this.videoMimeType = videoMimeType; this.enableRequestSdrToneMapping = enableRequestSdrToneMapping; + this.forceInterpretHdrVideoAsSdr = forceInterpretHdrVideoAsSdr; this.enableHdrEditing = enableHdrEditing; } @@ -362,6 +424,7 @@ public boolean equals(@Nullable Object o) { && Util.areEqual(audioMimeType, that.audioMimeType) && Util.areEqual(videoMimeType, that.videoMimeType) && enableRequestSdrToneMapping == that.enableRequestSdrToneMapping + && forceInterpretHdrVideoAsSdr == that.forceInterpretHdrVideoAsSdr && enableHdrEditing == that.enableHdrEditing; } @@ -375,6 +438,7 @@ public int hashCode() { result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0); result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0); result = 31 * result + (enableRequestSdrToneMapping ? 1 : 0); + result = 31 * result + (forceInterpretHdrVideoAsSdr ? 1 : 0); result = 31 * result + (enableHdrEditing ? 1 : 0); return result; } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java index 397f8f751e9..868007eb567 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java @@ -131,6 +131,9 @@ private boolean shouldTranscode(Format inputFormat) { if (transformationRequest.enableRequestSdrToneMapping) { return true; } + if (transformationRequest.forceInterpretHdrVideoAsSdr) { + return true; + } if (transformationRequest.videoMimeType != null && !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) { return true; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java index d821bd4b121..8ae54d1b81c 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java @@ -85,15 +85,27 @@ public VideoTranscodingSamplePipeline( transformationRequest.flattenForSlowMotion, muxerWrapper); - if (ColorInfo.isTransferHdr(inputFormat.colorInfo) - && (SDK_INT < 31 || deviceNeedsNoToneMappingWorkaround())) { - throw TransformationException.createForCodec( - new IllegalArgumentException("HDR editing and tone mapping not supported."), - /* isVideo= */ true, - /* isDecoder= */ false, - inputFormat, - /* mediaCodecName= */ null, - TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED); + if (ColorInfo.isTransferHdr(inputFormat.colorInfo)) { + if (transformationRequest.forceInterpretHdrVideoAsSdr) { + if (SDK_INT < 29) { + throw TransformationException.createForCodec( + new IllegalArgumentException("Interpreting HDR video as SDR is not supported."), + /* isVideo= */ true, + /* isDecoder= */ true, + inputFormat, + /* mediaCodecName= */ null, + TransformationException.ERROR_CODE_HDR_DECODING_UNSUPPORTED); + } + inputFormat = inputFormat.buildUpon().setColorInfo(ColorInfo.SDR_BT709_LIMITED).build(); + } else if (SDK_INT < 31 || deviceNeedsNoToneMappingWorkaround()) { + throw TransformationException.createForCodec( + new IllegalArgumentException("HDR editing and tone mapping is not supported."), + /* isVideo= */ true, + /* isDecoder= */ false, + inputFormat, + /* mediaCodecName= */ null, + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED); + } } decoderInputBuffer =
The default value is {@code true}, which corresponds to tone-mapping output if possible. + * *
The setting has no effect if the input is already in SDR, or if tone-mapping is not * supported. Currently tone-mapping is only guaranteed to be supported from Android T onwards. * + *
Setting this as {@code true} will set {@linkplain #experimental_setEnableHdrEditing} and + * {@linkplain #forceInterpretHdrVideoAsSdr} to {@code false}. + * * @param enableRequestSdrToneMapping Whether to request tone-mapping down to SDR. * @return This builder. */ @CanIgnoreReturnValue public Builder setEnableRequestSdrToneMapping(boolean enableRequestSdrToneMapping) { this.enableRequestSdrToneMapping = enableRequestSdrToneMapping; + if (enableRequestSdrToneMapping) { + forceInterpretHdrVideoAsSdr = false; + enableHdrEditing = false; + } + return this; + } + + /** + * Sets whether to interpret HDR video as SDR, resulting in washed out video. + * + *
The default value is {@code false}, with {@link #setEnableRequestSdrToneMapping} being + * applied. + * + *
Use of this flag may result in {@code + * TransformationException.ERROR_CODE_HDR_DECODING_UNSUPPORTED} or {@code + * ERROR_CODE_DECODING_FORMAT_UNSUPPORTED}. + * + *
This method is experimental, and will be renamed or removed in a future release. + * + *
If enabled, HDR information will be interpreted as SDR, which is much more widely + * supported than tone mapping or HDR editing. However, as HDR transfer functions and metadata + * will be ignored, contents will be displayed incorrectly, likely with a washed out look. + * + *
The setting has no effect if the input is already in SDR. + * + *
Setting this as {@code true} will set {@linkplain #experimental_setEnableHdrEditing} and + * {@linkplain #forceInterpretHdrVideoAsSdr} to {@code false}. + * + * @param forceInterpretHdrVideoAsSdr Whether to interpret HDR contents as SDR. + * @return This builder. + */ + // TODO(http://b/258246130): Use IntDef to select between tone mapping, HDR editing, and this. + @CanIgnoreReturnValue + public Builder experimental_setForceInterpretHdrVideoAsSdr( + boolean forceInterpretHdrVideoAsSdr) { + this.forceInterpretHdrVideoAsSdr = forceInterpretHdrVideoAsSdr; + if (forceInterpretHdrVideoAsSdr) { + enableRequestSdrToneMapping = false; + enableHdrEditing = false; + } return this; } /** * Sets whether to allow processing high dynamic range (HDR) input video streams as HDR. * + *
The default value is {@code false}, with {@link #setEnableRequestSdrToneMapping} being + * applied. + * *
This method is experimental, and will be renamed or removed in a future release. The HDR - * editing feature is under development and is intended for developing/testing HDR support. HDR - * editing can't be enabled at the same time as {@linkplain - * #setEnableRequestSdrToneMapping(boolean) SDR tone-mapping}. + * editing feature is under development and is intended for developing/testing HDR support. + * + *
Setting this as {@code true} will set {@linkplain #experimental_setEnableHdrEditing} and + * {@linkplain #forceInterpretHdrVideoAsSdr} to {@code false}. * *
With this flag enabled, HDR streams will correctly edit in HDR, convert via tone-mapping - * to SDR, or throw an error, based on the device's HDR support. Without both this flag and - * {@linkplain #setEnableRequestSdrToneMapping(boolean) SDR tone-mapping} as false, HDR streams - * will be incorrectly interpreted as SDR streams, with no conversion. SDR streams will be - * interpreted the same way regardless of this flag's state. + * to SDR, or throw an error, based on the device's HDR support. SDR streams will be interpreted + * the same way regardless of this flag's state. * * @param enableHdrEditing Whether to attempt to process any input video stream as a high * dynamic range (HDR) signal. @@ -249,6 +298,10 @@ public Builder setEnableRequestSdrToneMapping(boolean enableRequestSdrToneMappin @CanIgnoreReturnValue public Builder experimental_setEnableHdrEditing(boolean enableHdrEditing) { this.enableHdrEditing = enableHdrEditing; + if (enableHdrEditing) { + enableRequestSdrToneMapping = false; + forceInterpretHdrVideoAsSdr = false; + } return this; } @@ -263,6 +316,7 @@ public TransformationRequest build() { audioMimeType, videoMimeType, enableRequestSdrToneMapping, + forceInterpretHdrVideoAsSdr, enableHdrEditing); } } @@ -316,6 +370,9 @@ public TransformationRequest build() { /** Whether to request tone-mapping to standard dynamic range (SDR). */ public final boolean enableRequestSdrToneMapping; + /** Whether to force interpreting HDR video as SDR. */ + public final boolean forceInterpretHdrVideoAsSdr; + /** * Whether to attempt to process any input video stream as a high dynamic range (HDR) signal. * @@ -332,8 +389,12 @@ private TransformationRequest( @Nullable String audioMimeType, @Nullable String videoMimeType, boolean enableRequestSdrToneMapping, + boolean forceInterpretHdrVideoAsSdr, boolean enableHdrEditing) { + checkArgument(!forceInterpretHdrVideoAsSdr || !enableRequestSdrToneMapping); + checkArgument(!enableHdrEditing || !forceInterpretHdrVideoAsSdr); checkArgument(!enableHdrEditing || !enableRequestSdrToneMapping); + this.flattenForSlowMotion = flattenForSlowMotion; this.scaleX = scaleX; this.scaleY = scaleY; @@ -342,6 +403,7 @@ private TransformationRequest( this.audioMimeType = audioMimeType; this.videoMimeType = videoMimeType; this.enableRequestSdrToneMapping = enableRequestSdrToneMapping; + this.forceInterpretHdrVideoAsSdr = forceInterpretHdrVideoAsSdr; this.enableHdrEditing = enableHdrEditing; } @@ -362,6 +424,7 @@ public boolean equals(@Nullable Object o) { && Util.areEqual(audioMimeType, that.audioMimeType) && Util.areEqual(videoMimeType, that.videoMimeType) && enableRequestSdrToneMapping == that.enableRequestSdrToneMapping + && forceInterpretHdrVideoAsSdr == that.forceInterpretHdrVideoAsSdr && enableHdrEditing == that.enableHdrEditing; } @@ -375,6 +438,7 @@ public int hashCode() { result = 31 * result + (audioMimeType != null ? audioMimeType.hashCode() : 0); result = 31 * result + (videoMimeType != null ? videoMimeType.hashCode() : 0); result = 31 * result + (enableRequestSdrToneMapping ? 1 : 0); + result = 31 * result + (forceInterpretHdrVideoAsSdr ? 1 : 0); result = 31 * result + (enableHdrEditing ? 1 : 0); return result; } diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java index 397f8f751e9..868007eb567 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/TransformerVideoRenderer.java @@ -131,6 +131,9 @@ private boolean shouldTranscode(Format inputFormat) { if (transformationRequest.enableRequestSdrToneMapping) { return true; } + if (transformationRequest.forceInterpretHdrVideoAsSdr) { + return true; + } if (transformationRequest.videoMimeType != null && !transformationRequest.videoMimeType.equals(inputFormat.sampleMimeType)) { return true; diff --git a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java index d821bd4b121..8ae54d1b81c 100644 --- a/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java +++ b/library/transformer/src/main/java/com/google/android/exoplayer2/transformer/VideoTranscodingSamplePipeline.java @@ -85,15 +85,27 @@ public VideoTranscodingSamplePipeline( transformationRequest.flattenForSlowMotion, muxerWrapper); - if (ColorInfo.isTransferHdr(inputFormat.colorInfo) - && (SDK_INT < 31 || deviceNeedsNoToneMappingWorkaround())) { - throw TransformationException.createForCodec( - new IllegalArgumentException("HDR editing and tone mapping not supported."), - /* isVideo= */ true, - /* isDecoder= */ false, - inputFormat, - /* mediaCodecName= */ null, - TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED); + if (ColorInfo.isTransferHdr(inputFormat.colorInfo)) { + if (transformationRequest.forceInterpretHdrVideoAsSdr) { + if (SDK_INT < 29) { + throw TransformationException.createForCodec( + new IllegalArgumentException("Interpreting HDR video as SDR is not supported."), + /* isVideo= */ true, + /* isDecoder= */ true, + inputFormat, + /* mediaCodecName= */ null, + TransformationException.ERROR_CODE_HDR_DECODING_UNSUPPORTED); + } + inputFormat = inputFormat.buildUpon().setColorInfo(ColorInfo.SDR_BT709_LIMITED).build(); + } else if (SDK_INT < 31 || deviceNeedsNoToneMappingWorkaround()) { + throw TransformationException.createForCodec( + new IllegalArgumentException("HDR editing and tone mapping is not supported."), + /* isVideo= */ true, + /* isDecoder= */ false, + inputFormat, + /* mediaCodecName= */ null, + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED); + } } decoderInputBuffer =