From 0d8fd3d4fc1da31c05fa82bb5e815332a6c7fa12 Mon Sep 17 00:00:00 2001 From: huangdarwin Date: Thu, 8 Sep 2022 12:03:40 +0000 Subject: [PATCH] HDR: Add fallback MH tests. Test that HDR editing succeeds on devices supporting HDR editing, tone maps on devices supporting tone mapping, and throws exceptions on all other devices. Also, only restrict HDR editing and tone mapping support to API 31+ only when transcoding, not for all transformations. PiperOrigin-RevId: 472958965 --- .../media3/transformer/AndroidTestUtil.java | 2 + .../mh/SetHdrEditingTransformationTest.java | 145 +++++++++++++++++- .../transformer/TransformerVideoRenderer.java | 11 -- .../VideoTranscodingSamplePipeline.java | 11 ++ 4 files changed, 156 insertions(+), 13 deletions(-) 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 =