1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.mediaediting.cts; 18 19 import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10; 20 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10; 21 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10; 22 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus; 23 import static android.media.mediaediting.cts.FileUtil.assertFileHasColorTransfer; 24 import static com.google.common.truth.Truth.assertThat; 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNotNull; 27 28 import android.content.Context; 29 import android.media.MediaFormat; 30 import android.net.Uri; 31 import android.platform.test.annotations.AppModeFull; 32 33 import androidx.annotation.NonNull; 34 import androidx.media3.common.C; 35 import androidx.media3.common.ColorInfo; 36 import androidx.media3.common.MediaItem; 37 import androidx.media3.common.MimeTypes; 38 import androidx.media3.transformer.ExportException; 39 import androidx.media3.transformer.TransformationRequest; 40 import androidx.media3.transformer.Composition; 41 import androidx.media3.transformer.Transformer; 42 import androidx.test.core.app.ApplicationProvider; 43 44 import com.android.compatibility.common.util.ApiTest; 45 import com.android.compatibility.common.util.Preconditions; 46 47 import org.junit.Assume; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.junit.runners.Parameterized; 51 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.Collection; 55 import java.util.HashMap; 56 import java.util.List; 57 import java.util.Objects; 58 import java.util.stream.IntStream; 59 60 /** 61 * Instrumentation tests for transform HDR to SDR ToneMapping for given inputs. 62 */ 63 @AppModeFull(reason = "Instant apps cannot access the SD card") 64 @RunWith(Parameterized.class) 65 public final class TransformHdrToSdrToneMapTest { 66 private static final String MEDIA_DIR = WorkDir.getMediaDirString(); 67 private static final HashMap<String, int[]> PROFILE_HDR_MAP = new HashMap<>(); 68 private static final int[] HEVC_HDR_PROFILES = new int[] {HEVCProfileMain10, 69 HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus}; 70 private static final int[] AVC_HDR_PROFILES = new int[] {AVCProfileHigh10}; 71 static { PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_HDR_PROFILES)72 PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_HDR_PROFILES); PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR_PROFILES)73 PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR_PROFILES); 74 } 75 76 private final String mediaType; 77 private final String testFile; 78 private final String testId; 79 TransformHdrToSdrToneMapTest(String mediaType, String testFile, String testId)80 public TransformHdrToSdrToneMapTest(String mediaType, String testFile, String testId) { 81 this.mediaType = mediaType; 82 this.testFile = testFile; 83 this.testId = testId; 84 } 85 86 @Parameterized.Parameters(name = "{index}_{2}") input()87 public static Collection<Object[]> input() { 88 // mediaType, clip, width, height, frameRate 89 final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(new Object[][] { 90 // H264 91 {MimeTypes.VIDEO_H264, MediaEditingUtil.MKV_ASSET_H264_340W_280H_10BIT, "340x280_24fps"}, 92 {MimeTypes.VIDEO_H264, MediaEditingUtil.MKV_ASSET_H264_520W_390H_10BIT, "520x390_24fps"}, 93 {MimeTypes.VIDEO_H264, MediaEditingUtil.MKV_ASSET_H264_640W_360H_10BIT, "640x360_24fps"}, 94 {MimeTypes.VIDEO_H264, MediaEditingUtil.MKV_ASSET_H264_800W_640H_10BIT, "800x640_24fps"}, 95 {MimeTypes.VIDEO_H264, MediaEditingUtil.MKV_ASSET_H264_1280W_720H_10BIT, "1280x720_24fps"}, 96 // Hevc 97 {MimeTypes.VIDEO_H265, MediaEditingUtil.MKV_ASSET_HEVC_340W_280H_5S_10BIT, "340x280_24fps"}, 98 {MimeTypes.VIDEO_H265, MediaEditingUtil.MKV_ASSET_HEVC_520W_390H_5S_10BIT, "520x390_24fps"}, 99 {MimeTypes.VIDEO_H265, MediaEditingUtil.MKV_ASSET_HEVC_640W_360H_5S_10BIT, "640x360_24fps"}, 100 {MimeTypes.VIDEO_H265, MediaEditingUtil.MKV_ASSET_HEVC_800W_640H_5S_10BIT, "800x640_24fps"}, 101 {MimeTypes.VIDEO_H265, MediaEditingUtil.MKV_ASSET_HEVC_1280W_720H_5S_10BIT, 102 "1280x720_24fps"}, 103 })); 104 return prepareParamList(exhaustiveArgsList); 105 } 106 prepareParamList(List<Object[]> exhaustiveArgsList)107 public static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) { 108 List<Object[]> argsList = new ArrayList<>(); 109 110 for (Object[] arg : exhaustiveArgsList) { 111 String codec = arg[0].toString(); 112 // Trim the mime baseType with slash. 113 int lastIndex = codec.lastIndexOf('/'); 114 if (lastIndex != -1) { 115 codec = codec.substring(lastIndex + 1); 116 } 117 String testID = String.format("transformHdrToSdr_%s_%s_10bit", codec, arg[2]); 118 arg[2] = testID; 119 argsList.add(arg); 120 } 121 return argsList; 122 } 123 createTransformer(Context context)124 private static Transformer createTransformer(Context context) { 125 return new Transformer.Builder(context) 126 .setTransformationRequest( 127 new TransformationRequest.Builder() 128 .setHdrMode(Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC) 129 .build()) 130 .addListener( 131 new Transformer.Listener() { 132 @Override 133 public void onFallbackApplied( 134 @NonNull MediaItem inputMediaItem, 135 @NonNull TransformationRequest originalTransformationRequest, 136 @NonNull TransformationRequest fallbackTransformationRequest) { 137 // Tone mapping flag shouldn't change in fallback when tone mapping is requested. 138 assertThat(originalTransformationRequest.hdrMode) 139 .isEqualTo(fallbackTransformationRequest.hdrMode); 140 } 141 }) 142 .build(); 143 } 144 145 @ApiTest(apis = {"android.media.MediaCodec#configure", 146 "android.media.MediaCodec#createByCodecName", 147 "android.media.MediaCodecList#findDecoderForFormat", 148 "android.media.MediaFormat#createVideoFormat", 149 "android.media.MediaFormat#KEY_COLOR_TRANSFER_REQUEST"}) 150 @Test 151 public void transformHdrToSdrTest() throws Exception { 152 Preconditions.assertTestFileExists(MEDIA_DIR + testFile); 153 Context context = ApplicationProvider.getApplicationContext(); 154 155 Assume.assumeTrue("Skipping transformTest for " + testId, 156 !AndroidTestUtil.skipAndLogIfFormatsUnsupported(context, testId, 157 /* inputFormat= */ MediaEditingUtil.getFormatForTestFile(testFile), 158 /* outputFormat= */ MediaEditingUtil.getFormatForTestFile(testFile) 159 .buildUpon() 160 .setColorInfo(ColorInfo.SDR_BT709_LIMITED) 161 .build())); 162 163 Transformer transformer = createTransformer(context); 164 ExportTestResult transformationTestResult; 165 try { 166 transformationTestResult = new TransformerAndroidTestRunner.Builder(context, transformer) 167 .build() 168 .run(testId, MediaItem.fromUri(Uri.parse(MEDIA_DIR + testFile))); 169 } catch (ExportException exception) { 170 if (exception.getCause() != null 171 && (Objects.equals( 172 exception.getCause().getMessage(), 173 "Tone-mapping HDR is not supported on this device.") 174 || Objects.equals( 175 exception.getCause().getMessage(), 176 "Tone-mapping requested but not supported by the decoder."))) { 177 // Expected on devices without a tone-mapping plugin for this codec. 178 return; 179 } 180 throw exception; 181 } 182 assertFileHasColorTransfer(context, transformationTestResult.filePath, C.COLOR_TRANSFER_SDR); 183 int profile = MediaEditingUtil.getMuxedOutputProfile(transformationTestResult.filePath); 184 int[] profileArray = PROFILE_HDR_MAP.get(mediaType); 185 assertNotNull("Expected value to be not null", profileArray); 186 assertFalse(testId + " must not contain HDR profile after tone mapping", 187 IntStream.of(profileArray).anyMatch(x -> x == profile)); 188 } 189 } 190