diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ca28769495..fc3bddb03e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -39,6 +39,7 @@ * Relax trim optimization H.264 level checks. * Add support for changing between SDR and HDR input media in a sequence. * Add support for composition-level audio effects. + * Add support for transcoding Ultra HDR images into HDR videos. * Track Selection: * `DefaultTrackSelector`: Prefer video tracks with a 'reasonable' frame rate (>=10fps) over those with a lower or unset frame rate. This ensures diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportHdrVideoThenUltraHdrImage_exportsHdr_0.png b/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportHdrVideoThenUltraHdrImage_exportsHdr_0.png new file mode 100644 index 0000000000..2f66f5f4b5 Binary files /dev/null and b/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportHdrVideoThenUltraHdrImage_exportsHdr_0.png differ diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportHdrVideoThenUltraHdrImage_exportsHdr_1.png b/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportHdrVideoThenUltraHdrImage_exportsHdr_1.png new file mode 100644 index 0000000000..9810761d3e Binary files /dev/null and b/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportHdrVideoThenUltraHdrImage_exportsHdr_1.png differ diff --git a/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportUltraHdrImage_withUltraHdrEnabledOnSupportedDevice_succeeds_0.png b/libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportUltraHdrImage_withUltraHdrEnabledOnSupportedDevice_exportsHdr_0.png similarity index 100% rename from libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportUltraHdrImage_withUltraHdrEnabledOnSupportedDevice_succeeds_0.png rename to libraries/test_data/src/test/assets/test-generated-goldens/TransformerUltraHdrPixelTest/exportUltraHdrImage_withUltraHdrEnabledOnSupportedDevice_exportsHdr_0.png diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerUltraHdrTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerUltraHdrTest.java index 42a481167a..1bd84b6e10 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerUltraHdrTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerUltraHdrTest.java @@ -141,6 +141,26 @@ public final class TransformerUltraHdrTest { assertThat(colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_SDR); } + @Test + public void exportSdrImageThenUltraHdrImage_exportsSdr() throws Exception { + Composition composition = + createUltraHdrComposition( + /* tonemap= */ false, + oneFrameFromImage(JPG_ASSET_URI_STRING, NO_EFFECT), + oneFrameFromImage(ULTRA_HDR_URI_STRING, NO_EFFECT)); + + ExportTestResult result = + new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build()) + .build() + .run(testId, composition); + + assertThat(result.filePath).isNotNull(); + ColorInfo colorInfo = + retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO).colorInfo; + assertThat(colorInfo.colorSpace).isEqualTo(C.COLOR_SPACE_BT709); + assertThat(colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_SDR); + } + private static Composition createUltraHdrComposition( boolean tonemap, EditedMediaItem editedMediaItem, EditedMediaItem... editedMediaItems) { Composition.Builder builder = diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerMhUltraHdrPixelTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerMhUltraHdrPixelTest.java index 664b21de06..08d4debd12 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerMhUltraHdrPixelTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/TransformerMhUltraHdrPixelTest.java @@ -22,11 +22,15 @@ import static androidx.media3.test.utils.BitmapPixelTestUtil.getBitmapAveragePix import static androidx.media3.test.utils.BitmapPixelTestUtil.maybeSaveTestBitmap; import static androidx.media3.test.utils.BitmapPixelTestUtil.readBitmap; import static androidx.media3.test.utils.TestUtil.retrieveTrackFormat; +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.AndroidTestUtil.ULTRA_HDR_URI_STRING; import static androidx.media3.transformer.AndroidTestUtil.extractBitmapsFromVideo; import static androidx.media3.transformer.AndroidTestUtil.recordTestSkipped; +import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL; import static androidx.media3.transformer.SequenceEffectTestUtil.NO_EFFECT; +import static androidx.media3.transformer.SequenceEffectTestUtil.clippedVideo; +import static androidx.media3.transformer.SequenceEffectTestUtil.createComposition; import static androidx.media3.transformer.SequenceEffectTestUtil.oneFrameFromImage; import static androidx.test.core.app.ApplicationProvider.getApplicationContext; import static com.google.common.truth.Truth.assertThat; @@ -63,6 +67,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class TransformerMhUltraHdrPixelTest { + private static final int ONE_FRAME_END_POSITION_MS = 30; private static final String PNG_ASSET_BASE_PATH = "test-generated-goldens/TransformerUltraHdrPixelTest"; @@ -77,10 +82,12 @@ public final class TransformerMhUltraHdrPixelTest { } @Test - public void exportUltraHdrImage_withUltraHdrEnabledOnSupportedDevice_succeeds() throws Exception { + public void exportUltraHdrImage_withUltraHdrEnabledOnSupportedDevice_exportsHdr() + throws Exception { assumeDeviceSupportsUltraHdrEditing(); Composition composition = - createUltraHdrComposition(oneFrameFromImage(ULTRA_HDR_URI_STRING, NO_EFFECT)); + createUltraHdrComposition( + /* tonemap= */ false, oneFrameFromImage(ULTRA_HDR_URI_STRING, NO_EFFECT)); ExportTestResult result = new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build()) @@ -96,6 +103,70 @@ public final class TransformerMhUltraHdrPixelTest { extractBitmapsFromVideo(context, result.filePath, Config.RGBA_F16), testId); } + @Test + public void exportHdrVideoThenUltraHdrImage_exportsHdr() throws Exception { + assumeDeviceSupportsUltraHdrEditing(); + Composition composition = + createComposition( + /* presentation= */ null, + clippedVideo(MP4_ASSET_1080P_5_SECOND_HLG10, NO_EFFECT, ONE_FRAME_END_POSITION_MS), + oneFrameFromImage(ULTRA_HDR_URI_STRING, NO_EFFECT)); + + ExportTestResult result = + new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build()) + .build() + .run(testId, composition); + + assertThat(result.filePath).isNotNull(); + ColorInfo colorInfo = + retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO).colorInfo; + assertThat(colorInfo.colorSpace).isEqualTo(C.COLOR_SPACE_BT2020); + assertThat(colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); + assertFp16BitmapsMatchExpectedAndSave( + extractBitmapsFromVideo(context, result.filePath, Config.RGBA_F16), testId); + } + + @Test + public void exportUltraHdrImageThenHdrVideo_exportsHdr() throws Exception { + assumeDeviceSupportsUltraHdrEditing(); + Composition composition = + createComposition( + /* presentation= */ null, + oneFrameFromImage(ULTRA_HDR_URI_STRING, NO_EFFECT), + clippedVideo(MP4_ASSET_1080P_5_SECOND_HLG10, NO_EFFECT, ONE_FRAME_END_POSITION_MS)); + + ExportTestResult result = + new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build()) + .build() + .run(testId, composition); + + assertThat(result.filePath).isNotNull(); + ColorInfo colorInfo = + retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO).colorInfo; + assertThat(colorInfo.colorSpace).isEqualTo(C.COLOR_SPACE_BT2020); + assertThat(colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); + } + + @Test + public void exportTonemappedHdrVideoThenUltraHdrImage_exportsSdr() throws Exception { + Composition composition = + createUltraHdrComposition( + /* tonemap= */ true, + clippedVideo(MP4_ASSET_1080P_5_SECOND_HLG10, NO_EFFECT, ONE_FRAME_END_POSITION_MS), + oneFrameFromImage(ULTRA_HDR_URI_STRING, NO_EFFECT)); + + ExportTestResult result = + new TransformerAndroidTestRunner.Builder(context, new Transformer.Builder(context).build()) + .build() + .run(testId, composition); + + assertThat(result.filePath).isNotNull(); + ColorInfo colorInfo = + retrieveTrackFormat(context, result.filePath, C.TRACK_TYPE_VIDEO).colorInfo; + assertThat(colorInfo.colorSpace).isEqualTo(C.COLOR_SPACE_BT709); + assertThat(colorInfo.colorTransfer).isEqualTo(C.COLOR_TRANSFER_SDR); + } + @RequiresApi(29) // getBitmapAveragePixelAbsoluteDifferenceFp16() public static void assertFp16BitmapsMatchExpectedAndSave( List actualBitmaps, String testId) throws IOException { @@ -117,6 +188,17 @@ public final class TransformerMhUltraHdrPixelTest { } } + private static Composition createUltraHdrComposition( + boolean tonemap, EditedMediaItem editedMediaItem, EditedMediaItem... editedMediaItems) { + Composition.Builder builder = + new Composition.Builder(new EditedMediaItemSequence(editedMediaItem, editedMediaItems)) + .experimentalSetRetainHdrFromUltraHdrImage(true); + if (tonemap) { + builder.setHdrMode(HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL); + } + return builder.build(); + } + private void assumeDeviceSupportsUltraHdrEditing() throws JSONException, IOException, DecoderQueryException { if (Util.SDK_INT < 34) { @@ -130,12 +212,4 @@ public final class TransformerMhUltraHdrPixelTest { /* inputFormat= */ MP4_ASSET_1080P_5_SECOND_HLG10_FORMAT, /* outputFormat= */ null); } - - private static Composition createUltraHdrComposition( - EditedMediaItem editedMediaItem, EditedMediaItem... editedMediaItems) { - Composition.Builder builder = - new Composition.Builder(new EditedMediaItemSequence(editedMediaItem, editedMediaItems)) - .experimentalSetRetainHdrFromUltraHdrImage(true); - return builder.build(); - } } 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 601ca952fe..6e3e2f0900 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/Composition.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/Composition.java @@ -237,6 +237,7 @@ public final class Composition { * *

If the {@link HdrMode} is {@link #HDR_MODE_KEEP_HDR}, then setting this to {@code true} * applies the recovery map (i.e. the gainmap) to the base image to produce HDR video frames. + * This is automatically overridden to true, if the first asset is a HDR video. * *

The output video will have the same color encoding as the first {@link EditedMediaItem} * the sequence. If the Ultra HDR image is first in the sequence, output video will default to