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 2c9c2a7f08..ddc4f9eb2f 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/AndroidTestUtil.java @@ -84,6 +84,8 @@ public final class AndroidTestUtil { .setFrameRate(30.472f) .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 String MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER = "asset:///media/mp4/hdr10-video-with-sdr-container.mp4"; diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTransformationTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTransformationTest.java index e72544d6c1..bb9001e2ef 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTransformationTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/SetHdrEditingTransformationTest.java @@ -15,31 +15,168 @@ */ package androidx.media3.transformer.mh; +import static androidx.media3.common.MimeTypes.VIDEO_H265; 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.recordTestSkipped; +import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.net.Uri; +import androidx.media3.common.C; +import androidx.media3.common.ColorInfo; import androidx.media3.common.MediaItem; import androidx.media3.common.util.Util; +import androidx.media3.transformer.EncoderUtil; +import androidx.media3.transformer.TransformationException; import androidx.media3.transformer.TransformationRequest; import androidx.media3.transformer.Transformer; import androidx.media3.transformer.TransformerAndroidTestRunner; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; import org.junit.runner.RunWith; +// TODO(b/239172735): Add a SetToneMappingTransformationTest for when we request tone mapping. +// TODO(b/239172735): Add HLG tests after finding a shareable HLG file. /** {@link Transformer} instrumentation test for applying an HDR frame edit. */ @RunWith(AndroidJUnit4.class) public class SetHdrEditingTransformationTest { + private static final ColorInfo HDR10_DEFAULT_COLOR_INFO = + new ColorInfo( + C.COLOR_SPACE_BT2020, + C.COLOR_RANGE_LIMITED, + C.COLOR_TRANSFER_ST2084, + /* hdrStaticInfo= */ null); + + @Test + public void transform_noRequestedTranscode_hdr10File_transformsOrThrows() throws Exception { + String testId = "transform_noRequestedTranscode_hdr10File_transformsOrThrows"; + Context context = ApplicationProvider.getApplicationContext(); + + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder().experimental_setEnableHdrEditing(true).build()) + .build(); + + try { + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + return; + } catch (TransformationException exception) { + assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + assertThat(exception.errorCode) + .isEqualTo(TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("HDR editing and tone mapping not supported under API 31."); + return; + } + } + + @Test + public void transformAndTranscode_hdr10File_whenHdrEditingIsSupported() throws Exception { + String testId = "transformAndTranscode_hdr10File_whenHdrEditingIsSupported"; + Context context = ApplicationProvider.getApplicationContext(); + if (!deviceSupportsHdrEditing(VIDEO_H265, HDR10_DEFAULT_COLOR_INFO)) { + recordTestSkipped( + context, + testId, + /* reason= */ "Skipping on this device due to lack of HDR10 editing support."); + return; + } + + Transformer transformer = + new Transformer.Builder(context) + .setTransformationRequest( + new TransformationRequest.Builder() + .experimental_setEnableHdrEditing(true) + .setRotationDegrees(180) + .build()) + .build(); + + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + } + + @Test + public void transformAndTranscode_hdr10File_toneMapsOrThrows_whenHdrEditingUnsupported() + throws Exception { + String testId = "transformAndTranscode_hdr10File_toneMapsOrThrows_whenHdrEditingUnsupported"; + Context context = ApplicationProvider.getApplicationContext(); + if (deviceSupportsHdrEditing(VIDEO_H265, HDR10_DEFAULT_COLOR_INFO)) { + recordTestSkipped( + context, + testId, + /* reason= */ "Skipping on this device due to presence of HDR10 editing support"); + return; + } + + 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) { + assertThat(originalTransformationRequest.enableRequestSdrToneMapping).isFalse(); + if (fallbackTransformationRequest.enableRequestSdrToneMapping) { + isToneMappingFallbackApplied.set(true); + } + } + }) + .build(); + + try { + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_4_SECOND_HDR10))); + } catch (TransformationException exception) { + assertThat(exception).hasCauseThat().isInstanceOf(IllegalArgumentException.class); + // TODO(b/245364266): After fixing the bug, replace the API version check with a check that + // isToneMappingFallbackApplied.get() is true. + if (Util.SDK_INT < 31) { + assertThat(exception.errorCode) + .isEqualTo(TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("HDR editing and tone mapping not supported under API 31."); + } else { + assertThat(exception.errorCode) + .isEqualTo(TransformationException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED); + assertThat(exception) + .hasCauseThat() + .hasMessageThat() + .isEqualTo("Tone-mapping requested but not supported by the decoder"); + } + return; + } + + assertThat(isToneMappingFallbackApplied.get()).isTrue(); + } + @Test public void transformUnexpectedColorInfo() throws Exception { + String testId = "transformUnexpectedColorInfo"; Context context = ApplicationProvider.getApplicationContext(); if (Util.SDK_INT < 29) { recordTestSkipped( context, - "SetHdrEditingTransformationTest", + testId, /* reason= */ "Skipping on this API version due to lack of support for" + " MediaFormat#getInteger(String, int)."); return; @@ -53,7 +190,11 @@ public class SetHdrEditingTransformationTest { new TransformerAndroidTestRunner.Builder(context, transformer) .build() .run( - /* testId= */ "transformUnexpectedColorInfo", + testId, MediaItem.fromUri(Uri.parse(MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER))); } + + private static boolean deviceSupportsHdrEditing(String mimeType, ColorInfo colorInfo) { + return !EncoderUtil.getSupportedEncoderNamesForHdrEditing(mimeType, colorInfo).isEmpty(); + } } diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java index 19be11a6d4..79ce5848da 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/TransformerVideoRenderer.java @@ -17,12 +17,10 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.common.util.Util.SDK_INT; import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT; import android.content.Context; import androidx.media3.common.C; -import androidx.media3.common.ColorInfo; import androidx.media3.common.DebugViewProvider; import androidx.media3.common.Effect; import androidx.media3.common.Format; @@ -99,15 +97,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; return false; } Format inputFormat = checkNotNull(formatHolder.format); - if (SDK_INT < 31 && ColorInfo.isTransferHdr(inputFormat.colorInfo)) { - throw TransformationException.createForCodec( - new IllegalArgumentException("HDR editing not supported under API 31."), - /* isVideo= */ true, - /* isDecoder= */ false, - inputFormat, - /* mediaCodecName= */ null, - TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED); - } if (shouldTranscode(inputFormat)) { samplePipeline = new VideoTranscodingSamplePipeline( diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java index 09d6306883..0c10c8c9a5 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/VideoTranscodingSamplePipeline.java @@ -18,6 +18,7 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkState; +import static androidx.media3.common.util.Util.SDK_INT; import android.content.Context; import android.media.MediaCodec; @@ -75,6 +76,16 @@ import org.checkerframework.dataflow.qual.Pure; Transformer.AsyncErrorListener asyncErrorListener, DebugViewProvider debugViewProvider) throws TransformationException { + if (SDK_INT < 31 && ColorInfo.isTransferHdr(inputFormat.colorInfo)) { + throw TransformationException.createForCodec( + new IllegalArgumentException("HDR editing and tone mapping not supported under API 31."), + /* isVideo= */ true, + /* isDecoder= */ false, + inputFormat, + /* mediaCodecName= */ null, + TransformationException.ERROR_CODE_HDR_EDITING_UNSUPPORTED); + } + decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); encoderOutputBuffer =