From 8eab7391c21b05f2f04d8f1d2c97967b80d7eb75 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 (cherry picked from commit 8ee2121627c2702419402b514fa93dbe8ac19b8b) --- .../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/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 c2e9f3584f..400fa31ab7 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 @@ -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/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java index bac9491473..794620283c 100644 --- a/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java +++ b/library/transformer/src/androidTest/java/com/google/android/exoplayer2/transformer/mh/SetHdrEditingTransformationTest.java @@ -16,30 +16,167 @@ package com.google.android.exoplayer2.transformer.mh; import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_1080P_1_SECOND_HDR10_VIDEO_SDR_CONTAINER; +import static com.google.android.exoplayer2.transformer.AndroidTestUtil.MP4_ASSET_1080P_4_SECOND_HDR10; import static com.google.android.exoplayer2.transformer.AndroidTestUtil.recordTestSkipped; +import static com.google.android.exoplayer2.util.MimeTypes.VIDEO_H265; +import static com.google.common.truth.Truth.assertThat; 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.EncoderUtil; +import com.google.android.exoplayer2.transformer.TransformationException; import com.google.android.exoplayer2.transformer.TransformationRequest; import com.google.android.exoplayer2.transformer.Transformer; import com.google.android.exoplayer2.transformer.TransformerAndroidTestRunner; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.ColorInfo; +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/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 ec501f9d56..f1f4a4c1d4 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 @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.transformer; import static com.google.android.exoplayer2.source.SampleStream.FLAG_REQUIRE_FORMAT; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import static com.google.android.exoplayer2.util.Util.SDK_INT; import android.content.Context; import androidx.media3.common.DebugViewProvider; @@ -29,7 +28,6 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.source.SampleStream.ReadDataResult; -import com.google.android.exoplayer2.video.ColorInfo; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -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/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 51fd2379f2..476a848e3c 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 @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.transformer; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; +import static com.google.android.exoplayer2.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 =