diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java index eac602c80e..33569265b8 100644 --- a/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java @@ -101,6 +101,7 @@ public final class ConfigurationActivity extends AppCompatActivity { "https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4", "https://storage.googleapis.com/exoplayer-test-media-1/gen/screens/dash-vod-single-segment/manifest-baseline.mpd", "https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4", + "https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4", }; private static final String[] PRESET_FILE_URI_DESCRIPTIONS = { // same order as PRESET_FILE_URIS "720p H264 video and AAC audio", @@ -116,6 +117,7 @@ public final class ConfigurationActivity extends AppCompatActivity { "SEF slow motion with 240 fps", "480p DASH (non-square pixels)", "HDR (HDR10) H265 limited range video (encoding may fail)", + "HDR (HLG) H265 limited range video (encoding may fail)", }; private static final String[] DEMO_EFFECTS = { "Dizzy crop", diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java index aff2575c07..13ceb626c6 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java @@ -86,6 +86,21 @@ public final class AndroidTestUtil { .setFrameRate(30.472f) .build(); + public static final String MP4_ASSET_1080P_5_SECOND_HLG10 = + "https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4"; + public static final Format MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT = + new Format.Builder() + .setSampleMimeType(VIDEO_H265) + .setWidth(1920) + .setHeight(1080) + .setFrameRate(30.000f) + .setColorInfo( + new ColorInfo( + C.COLOR_SPACE_BT2020, + C.COLOR_RANGE_LIMITED, + C.COLOR_TRANSFER_HLG, + /* hdrStaticInfo= */ null)) + .build(); 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 = diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetForceInterpretHdrVideoAsSdrTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetForceInterpretHdrVideoAsSdrTest.java index 93e07afba7..212e601a2c 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetForceInterpretHdrVideoAsSdrTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetForceInterpretHdrVideoAsSdrTest.java @@ -17,6 +17,8 @@ package androidx.media3.transformer.mh; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10_FORMAT; +import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECOND_HLG10; +import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT; import static androidx.media3.transformer.mh.analysis.FileUtil.assertFileHasColorTransfer; import android.content.Context; @@ -77,4 +79,39 @@ public class SetForceInterpretHdrVideoAsSdrTest { } } } + + @Test + public void forceInterpretHdrVideoAsSdrTest_hlg10File_transformsOrThrows() throws Exception { + String testId = "forceInterpretHdrVideoAsSdrTest_hlg10File_transformsOrThrows"; + Context context = ApplicationProvider.getApplicationContext(); + + if (AndroidTestUtil.skipAndLogIfInsufficientCodecSupport( + context, + testId, + /* decodingFormat= */ MP4_ASSET_1080P_5_SECOND_HLG10_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_5_SECOND_HLG10))); + 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/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTest.java index f8b20bb0b3..d09c6af1ee 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTest.java @@ -19,6 +19,7 @@ import static androidx.media3.common.MimeTypes.VIDEO_H265; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10; +import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECOND_HLG10; import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped; import static androidx.media3.transformer.mh.analysis.FileUtil.assertFileHasColorTransfer; import static com.google.common.truth.Truth.assertThat; @@ -55,6 +56,12 @@ public class SetHdrEditingTest { C.COLOR_RANGE_LIMITED, C.COLOR_TRANSFER_ST2084, /* hdrStaticInfo= */ null); + private static final ColorInfo HLG10_DEFAULT_COLOR_INFO = + new ColorInfo( + C.COLOR_SPACE_BT2020, + C.COLOR_RANGE_LIMITED, + C.COLOR_TRANSFER_HLG, + /* hdrStaticInfo= */ null); @Test public void transform_noRequestedTranscode_hdr10File_transformsOrThrows() throws Exception { @@ -83,6 +90,33 @@ public class SetHdrEditingTest { } } + @Test + public void transform_noRequestedTranscode_hlg10File_transformsOrThrows() throws Exception { + String testId = "transform_noRequestedTranscode_hlg10File_transformsOrThrows"; + Context context = ApplicationProvider.getApplicationContext(); + + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder().experimental_setEnableHdrEditing(true).build()) + .build(); + + try { + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10))); + Log.i(TAG, "Transformed."); + assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_HLG); + return; + } catch (TransformationException exception) { + Log.i(TAG, checkNotNull(exception.getCause()).toString()); + assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(exception.errorCode) + .isEqualTo(TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED); + } + } + @Test public void transformAndTranscode_hdr10File_whenHdrEditingIsSupported_transforms() throws Exception { @@ -109,6 +143,32 @@ public class SetHdrEditingTest { assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_ST2084); } + @Test + public void transformAndTranscode_hlg10File_whenHdrEditingIsSupported_transforms() + throws Exception { + String testId = "transformAndTranscode_hlg10File_whenHdrEditingIsSupported_transforms"; + Context context = ApplicationProvider.getApplicationContext(); + if (!deviceSupportsHdrEditing(VIDEO_H265, HLG10_DEFAULT_COLOR_INFO)) { + recordTestSkipped(context, testId, /* reason= */ "Device lacks HLG10 editing support."); + return; + } + + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder() + .experimental_setEnableHdrEditing(true) + .setRotationDegrees(180) + .build()) + .build(); + + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10))); + assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_HLG); + } + @Test public void transformAndTranscode_hdr10File_whenHdrEditingUnsupported_toneMapsOrThrows() throws Exception { @@ -163,6 +223,60 @@ public class SetHdrEditingTest { } } + @Test + public void transformAndTranscode_hlg10File_whenHdrEditingUnsupported_toneMapsOrThrows() + throws Exception { + String testId = "transformAndTranscode_hlg10File_whenHdrEditingUnsupported_toneMapsOrThrows"; + Context context = ApplicationProvider.getApplicationContext(); + if (deviceSupportsHdrEditing(VIDEO_H265, HLG10_DEFAULT_COLOR_INFO)) { + recordTestSkipped(context, testId, /* reason= */ "Device supports HLG10 editing."); + return; + } + + AtomicBoolean isFallbackListenerInvoked = new AtomicBoolean(); + AtomicBoolean isToneMappingFallbackApplied = new AtomicBoolean(); + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder() + .experimental_setEnableHdrEditing(true) + .setRotationDegrees(180) + .build()) + .addListener( + new Transformer.Listener() { + @Override + public void onFallbackApplied( + MediaItem inputMediaItem, + TransformationRequest originalTransformationRequest, + TransformationRequest fallbackTransformationRequest) { + isFallbackListenerInvoked.set(true); + assertThat(originalTransformationRequest.enableRequestSdrToneMapping).isFalse(); + isToneMappingFallbackApplied.set( + fallbackTransformationRequest.enableRequestSdrToneMapping); + } + }) + .build(); + + try { + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10))); + Log.i(TAG, "Tone mapped."); + assertThat(isToneMappingFallbackApplied.get()).isTrue(); + assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR); + } catch (TransformationException exception) { + Log.i(TAG, checkNotNull(exception.getCause()).toString()); + assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(exception.errorCode) + .isAnyOf( + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED, + TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED); + assertThat(isFallbackListenerInvoked.get()).isFalse(); + return; + } + } + @Test public void transformUnexpectedColorInfo() throws Exception { String testId = "transformUnexpectedColorInfo"; diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrToSdrToneMapTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrToSdrToneMapTest.java index d3818eecde..8633a691ea 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrToSdrToneMapTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrToSdrToneMapTest.java @@ -17,6 +17,7 @@ package androidx.media3.transformer.mh; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10; +import static androidx.media3.transformer.AndroidTestUtil.MP4_ASSET_1080P_5_SECOND_HLG10; import static androidx.media3.transformer.mh.analysis.FileUtil.assertFileHasColorTransfer; import static com.google.common.truth.Truth.assertThat; @@ -86,6 +87,49 @@ public class SetHdrToSdrToneMapTest { } } + @Test + public void transform_toneMapNoRequestedTranscode_hlg10File_toneMapsOrThrows() throws Exception { + String testId = "transform_toneMapNoRequestedTranscode_hlg10File_toneMapsOrThrows"; + Context context = ApplicationProvider.getApplicationContext(); + + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder().setEnableRequestSdrToneMapping(true).build()) + .addListener( + new Transformer.Listener() { + @Override + public void onFallbackApplied( + MediaItem inputMediaItem, + TransformationRequest originalTransformationRequest, + TransformationRequest fallbackTransformationRequest) { + // Tone mapping flag shouldn't change in fallback when tone mapping is + // requested. + assertThat(originalTransformationRequest.enableRequestSdrToneMapping) + .isEqualTo(fallbackTransformationRequest.enableRequestSdrToneMapping); + } + }) + .build(); + + try { + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10))); + Log.i(TAG, "Tone mapped."); + assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR); + return; + } catch (TransformationException exception) { + Log.i(TAG, checkNotNull(exception.getCause()).toString()); + assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(exception.errorCode) + .isAnyOf( + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED, + TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED); + return; + } + } + @Test public void transform_toneMapAndTranscode_hdr10File_toneMapsOrThrows() throws Exception { String testId = "transform_toneMapAndTranscode_hdr10File_toneMapsOrThrows"; @@ -131,4 +175,50 @@ public class SetHdrToSdrToneMapTest { return; } } + + @Test + public void transform_toneMapAndTranscode_hlg10File_toneMapsOrThrows() throws Exception { + String testId = "transform_toneMapAndTranscode_hlg10File_toneMapsOrThrows"; + Context context = ApplicationProvider.getApplicationContext(); + + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder() + .setEnableRequestSdrToneMapping(true) + .setRotationDegrees(180) + .build()) + .addListener( + new Transformer.Listener() { + @Override + public void onFallbackApplied( + MediaItem inputMediaItem, + TransformationRequest originalTransformationRequest, + TransformationRequest fallbackTransformationRequest) { + // Tone mapping flag shouldn't change in fallback when tone mapping is + // requested. + assertThat(originalTransformationRequest.enableRequestSdrToneMapping) + .isEqualTo(fallbackTransformationRequest.enableRequestSdrToneMapping); + } + }) + .build(); + + try { + TransformationTestResult transformationTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_5_SECOND_HLG10))); + Log.i(TAG, "Tone mapped."); + assertFileHasColorTransfer(transformationTestResult.filePath, C.COLOR_TRANSFER_SDR); + return; + } catch (TransformationException exception) { + Log.i(TAG, checkNotNull(exception.getCause()).toString()); + assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(exception.errorCode) + .isAnyOf( + TransformationException.ERROR_CODE_HDR_ENCODING_UNSUPPORTED, + TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED); + return; + } + } }