From 5eb064d2d11a7de0db842f7aaaa1fe98ca32be01 Mon Sep 17 00:00:00 2001 From: ybai001 Date: Wed, 3 Jul 2024 16:22:43 +0800 Subject: [PATCH] Add test cases for Dolby Vision transformer support --- .../transformer/TransformerEndToEndTest.java | 33 ++++++ .../media3/transformer/mh/HdrEditingTest.java | 109 +++++++++++++++++- .../media3/transformer/FrameworkMuxer.java | 6 +- 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java index f9b2f8e03e..541437dbf0 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/TransformerEndToEndTest.java @@ -1280,6 +1280,39 @@ public class TransformerEndToEndTest { assertThat(exportResult.videoMimeType).isEqualTo(MimeTypes.VIDEO_AV1); } + @Test + public void transcode_withOutputVideoMimeTypeDolbyVision_completesSuccessfully() throws Exception { + if (AndroidTestUtil.skipAndLogIfFormatsUnsupported( + context, + testId, + /* inputFormat= */ MP4_ASSET_FORMAT, + /* outputFormat= */ MP4_ASSET_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.VIDEO_DOLBY_VISION) + .setCodecs(null) + .build())) { + return; + } + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_URI_STRING)); + EditedMediaItem editedMediaItem = new EditedMediaItem.Builder(mediaItem).build(); + Transformer transformer = + new Transformer.Builder(context).setVideoMimeType(MimeTypes.VIDEO_DOLBY_VISION).build(); + + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, editedMediaItem); + ExportResult exportResult = exportTestResult.exportResult; + + assert exportTestResult.filePath != null; + String actualMimeType = + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO).sampleMimeType; + assertThat(actualMimeType).isEqualTo(MimeTypes.VIDEO_DOLBY_VISION); + assertThat(exportResult.exportException).isNull(); + assertThat(exportResult.durationMs).isGreaterThan(0); + assertThat(exportResult.videoMimeType).isEqualTo(MimeTypes.VIDEO_DOLBY_VISION); + } + @Test public void transcode_withOutputAudioMimeTypeAac_completesSuccessfully() throws Exception { MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP3_ASSET_URI_STRING)); diff --git a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java index 908cf0ec7e..0b896da096 100644 --- a/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java +++ b/libraries/transformer/src/androidTest/java/androidx/media3/transformer/mh/HdrEditingTest.java @@ -139,6 +139,41 @@ public final class HdrEditingTest { assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); } + @Test + public void export_transmuxDolbyVisionFile() throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + + if (Util.SDK_INT < 24) { + // TODO: b/285543404 - Remove suppression once we can transmux H.265/HEVC before API 24. + recordTestSkipped(context, testId, /* reason= */ "Can't transmux H.265/HEVC before API 24"); + return; + } + + if (AndroidTestUtil.skipAndLogIfFormatsUnsupported( + context, + testId, + /* inputFormat= */ MP4_ASSET_DOLBY_VISION_HDR_FORMAT, + /* outputFormat= */ null)) { + return; + } + + Transformer transformer = new Transformer.Builder(context).build(); + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_DOLBY_VISION_HDR)); + + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, mediaItem); + assert exportTestResult.filePath != null; + @C.ColorTransfer + int actualColorTransfer = + Objects.requireNonNull( + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO) + .colorInfo) + .colorTransfer; + assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); + } + @Test public void exportAndTranscode_hdr10File_whenHdrEditingIsSupported() throws Exception { Context context = ApplicationProvider.getApplicationContext(); @@ -224,10 +259,12 @@ public final class HdrEditingTest { new TransformerAndroidTestRunner.Builder(context, transformer) .build() .run(testId, editedMediaItem); + assert exportTestResult.filePath != null; @C.ColorTransfer int actualColorTransfer = - retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO) - .colorInfo + Objects.requireNonNull( + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO) + .colorInfo) .colorTransfer; assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_HLG); } @@ -360,6 +397,74 @@ public final class HdrEditingTest { } } + @Test + public void exportAndTranscode_dolbyVisionFile_whenHdrEditingUnsupported_toneMapsOrThrows() + throws Exception { + Context context = ApplicationProvider.getApplicationContext(); + Format format = MP4_ASSET_DOLBY_VISION_HDR_FORMAT; + assert format.colorInfo != null; + if (deviceSupportsHdrEditing(VIDEO_H265, format.colorInfo)) { + recordTestSkipped(context, testId, /* reason= */ "Device supports Dolby Vision editing."); + return; + } + + if (AndroidTestUtil.skipAndLogIfFormatsUnsupported( + context, testId, /* inputFormat= */ format, /* outputFormat= */ null)) { + return; + } + + AtomicBoolean isFallbackListenerInvoked = new AtomicBoolean(); + AtomicBoolean isToneMappingFallbackApplied = new AtomicBoolean(); + Transformer transformer = + new Transformer.Builder(context) + .addListener( + new Transformer.Listener() { + @Override + public void onFallbackApplied( + MediaItem inputMediaItem, + TransformationRequest originalTransformationRequest, + TransformationRequest fallbackTransformationRequest) { + isFallbackListenerInvoked.set(true); + assertThat(originalTransformationRequest.hdrMode).isEqualTo(HDR_MODE_KEEP_HDR); + isToneMappingFallbackApplied.set( + fallbackTransformationRequest.hdrMode + == HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL); + } + }) + .build(); + MediaItem mediaItem = MediaItem.fromUri(Uri.parse(MP4_ASSET_DOLBY_VISION_HDR)); + EditedMediaItem editedMediaItem = + new EditedMediaItem.Builder(mediaItem).setEffects(FORCE_TRANSCODE_VIDEO_EFFECTS).build(); + + try { + ExportTestResult exportTestResult = + new TransformerAndroidTestRunner.Builder(context, transformer) + .build() + .run(testId, editedMediaItem); + assertThat(isToneMappingFallbackApplied.get()).isTrue(); + assert exportTestResult.filePath != null; + @C.ColorTransfer + int actualColorTransfer = + Objects.requireNonNull( + retrieveTrackFormat(context, exportTestResult.filePath, C.TRACK_TYPE_VIDEO) + .colorInfo) + .colorTransfer; + assertThat(actualColorTransfer).isEqualTo(C.COLOR_TRANSFER_SDR); + } catch (ExportException exception) { + if (exception.getCause() != null) { + @Nullable String message = exception.getCause().getMessage(); + if (message != null + && (Objects.equals(message, "Decoding HDR is not supported on this device.") + || message.contains( + "OpenGL ES 3.0 context support is required for HDR input or output.") + || Objects.equals(message, "Device lacks YUV extension support."))) { + return; + } + } + throw exception; + } + } + private static boolean deviceSupportsHdrEditing(String mimeType, ColorInfo colorInfo) { checkState(ColorInfo.isTransferHdr(colorInfo)); return !EncoderUtil.getSupportedEncodersForHdrEditing(mimeType, colorInfo).isEmpty(); diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java index b309fe2448..d54d7f7b45 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/FrameworkMuxer.java @@ -313,7 +313,7 @@ import java.nio.ByteBuffer; if (maxWidthHeight <= 1280) { if (pps <= 22118400) { level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd24; // Level 01 - } else if (pps <= 27648000) { + } else { // pps <= 27648000 level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd30; // Level 02 } } else if (maxWidthHeight <= 1920 && pps <= 49766400) { @@ -331,13 +331,13 @@ import java.nio.ByteBuffer; level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd48; // Level 08 } else if (pps <= 497664000) { level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60; // Level 09 - } else if (pps <= 995328000) { + } else { // pps <= 995328000 level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd120; // Level 10 } } else if (maxWidthHeight <= 7680) { if (pps <= 995328000) { level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k30; // Level 11 - } else if (pps <= 1990656000) { + } else { // pps <= 1990656000 level = MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k60; // Level 12 } }