1 /* 2 * Copyright (C) 2024 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.decoder.cts; 18 19 import static android.media.MediaCodecInfo.CodecProfileLevel.*; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertTrue; 24 import static org.junit.Assert.fail; 25 import static org.junit.Assume.assumeFalse; 26 import static org.junit.Assume.assumeTrue; 27 28 import android.content.Context; 29 import android.content.res.AssetFileDescriptor; 30 import android.hardware.camera2.CameraAccessException; 31 import android.hardware.camera2.CameraCharacteristics; 32 import android.hardware.camera2.CameraManager; 33 import android.hardware.camera2.CameraMetadata; 34 import android.hardware.camera2.params.DynamicRangeProfiles; 35 import android.media.MediaCodec; 36 import android.media.MediaCodec.BufferInfo; 37 import android.media.MediaCodecInfo; 38 import android.media.MediaCodecList; 39 import android.media.MediaExtractor; 40 import android.media.MediaFormat; 41 import android.media.cts.MediaHeavyPresubmitTest; 42 import android.media.cts.TestUtils; 43 import android.os.Bundle; 44 import android.platform.test.annotations.AppModeFull; 45 import android.util.Log; 46 import android.view.Surface; 47 48 import androidx.test.platform.app.InstrumentationRegistry; 49 50 import com.android.compatibility.common.util.ApiTest; 51 import com.android.compatibility.common.util.CddTest; 52 import com.android.compatibility.common.util.MediaUtils; 53 import com.android.compatibility.common.util.Preconditions; 54 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 import org.junit.runners.Parameterized; 58 59 import java.nio.ByteBuffer; 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.Collection; 63 import java.util.HashMap; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Set; 67 import java.util.concurrent.CountDownLatch; 68 import java.util.concurrent.TimeUnit; 69 import java.util.stream.IntStream; 70 71 @MediaHeavyPresubmitTest 72 @AppModeFull(reason = "There should be no instant apps specific behavior related to decoders") 73 @RunWith(Parameterized.class) 74 public class HdrToSdrDecoderTest extends HDRDecoderTestBase{ 75 private static final String TAG = "HdrToSdrDecoderTest"; 76 77 private static boolean DEBUG_HDR_TO_SDR_PLAY_VIDEO = false; 78 private static final String INVALID_HDR_STATIC_INFO = 79 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" + 80 "00 00 00 00 00 00 00 00 00 " ; 81 82 public static final Map<String, Map<Integer, Long>> PROFILE_HDR_MAP = new HashMap<>(); 83 84 static { 85 Map<Integer, Long> AV1_PROFILE_MAP = new HashMap<>(); AV1_PROFILE_MAP.put(AV1ProfileMain10, DynamicRangeProfiles.HLG10)86 AV1_PROFILE_MAP.put(AV1ProfileMain10, DynamicRangeProfiles.HLG10); AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10, DynamicRangeProfiles.HDR10)87 AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10, DynamicRangeProfiles.HDR10); AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS)88 AV1_PROFILE_MAP.put(AV1ProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS); 89 90 Map<Integer, Long> HEVC_PROFILE_MAP = new HashMap<>(); HEVC_PROFILE_MAP.put(HEVCProfileMain10, DynamicRangeProfiles.HLG10)91 HEVC_PROFILE_MAP.put(HEVCProfileMain10, DynamicRangeProfiles.HLG10); HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10, DynamicRangeProfiles.HDR10)92 HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10, DynamicRangeProfiles.HDR10); HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS)93 HEVC_PROFILE_MAP.put(HEVCProfileMain10HDR10Plus, DynamicRangeProfiles.HDR10_PLUS); 94 95 Map<Integer, Long> VP9_PROFILE_MAP = new HashMap<>(); VP9_PROFILE_MAP.put(VP9Profile2, DynamicRangeProfiles.HLG10)96 VP9_PROFILE_MAP.put(VP9Profile2, DynamicRangeProfiles.HLG10); VP9_PROFILE_MAP.put(VP9Profile2HDR, DynamicRangeProfiles.HDR10)97 VP9_PROFILE_MAP.put(VP9Profile2HDR, DynamicRangeProfiles.HDR10); VP9_PROFILE_MAP.put(VP9Profile2HDR10Plus, DynamicRangeProfiles.HDR10_PLUS)98 VP9_PROFILE_MAP.put(VP9Profile2HDR10Plus, DynamicRangeProfiles.HDR10_PLUS); 99 PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_PROFILE_MAP)100 PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_PROFILE_MAP); PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_PROFILE_MAP)101 PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_PROFILE_MAP); PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_PROFILE_MAP)102 PROFILE_HDR_MAP.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_PROFILE_MAP); 103 } 104 105 @Parameterized.Parameter(0) 106 public String mCodecName; 107 108 @Parameterized.Parameter(1) 109 public String mMediaType; 110 111 @Parameterized.Parameter(2) 112 public String mInputFile; 113 114 @Parameterized.Parameter(3) 115 public String mHdrStaticInfo; 116 117 @Parameterized.Parameter(4) 118 public String[] mHdrDynamicInfo; 119 120 @Parameterized.Parameter(5) 121 public boolean mMetaDataInContainer; 122 123 @Parameterized.Parameter(6) 124 public int mProfile; 125 prepareParamList(List<Object[]> exhaustiveArgsList)126 private static List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) { 127 final List<Object[]> argsList = new ArrayList<>(); 128 int argLength = exhaustiveArgsList.get(0).length; 129 for (Object[] arg : exhaustiveArgsList) { 130 String mediaType = (String) arg[0]; 131 String[] decoderNames = MediaUtils.getDecoderNamesForMime(mediaType); 132 133 for (String decoder : decoderNames) { 134 if (TestUtils.isMainlineCodec(decoder)) { 135 if (!TestUtils.isTestingModules()) { 136 Log.i(TAG, "not testing modules, skip module codec " + decoder); 137 continue; 138 } 139 } else { 140 if (TestUtils.isTestingModules()) { 141 Log.i(TAG, "testing modules, skip non-module codec " + decoder); 142 continue; 143 } 144 } 145 Object[] testArgs = new Object[argLength + 1]; 146 testArgs[0] = decoder; 147 System.arraycopy(arg, 0, testArgs, 1, argLength); 148 argsList.add(testArgs); 149 } 150 } 151 return argsList; 152 } 153 154 @Parameterized.Parameters(name = "{index}_{0}_{1}_{2}") input()155 public static Collection<Object[]> input() { 156 final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{ 157 {MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR_RES, AV1_HDR_STATIC_INFO, null, false, 158 AV1ProfileMain10HDR10}, 159 {MediaFormat.MIMETYPE_VIDEO_HEVC, H265_HDR10_RES, H265_HDR10_STATIC_INFO, null, 160 false, HEVCProfileMain10HDR10}, 161 {MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR_RES, VP9_HDR_STATIC_INFO, null, true, 162 VP9Profile2HDR}, 163 {MediaFormat.MIMETYPE_VIDEO_HEVC, H265_HDR10PLUS_RES, H265_HDR10PLUS_STATIC_INFO, 164 H265_HDR10PLUS_DYNAMIC_INFO, false, HEVCProfileMain10HDR10}, 165 {MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR10PLUS_RES, VP9_HDR10PLUS_STATIC_INFO, 166 VP9_HDR10PLUS_DYNAMIC_INFO, true, VP9Profile2HDR}, 167 {MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HLG_RES, null, null, false, 168 AV1ProfileMain10}, 169 {MediaFormat.MIMETYPE_VIDEO_HEVC, H265_HLG_RES, null, null, false, 170 HEVCProfileMain10}, 171 {MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HLG_RES, null, null, false, 172 VP9Profile2}, 173 }); 174 175 return prepareParamList(exhaustiveArgsList); 176 } 177 getAvailableHDRCaptureProfiles()178 private static Set<Long> getAvailableHDRCaptureProfiles() throws CameraAccessException { 179 final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 180 CameraManager cm = context.getSystemService(CameraManager.class); 181 String[] cameraIdList = cm.getCameraIdList(); 182 for (String cameraId : cameraIdList) { 183 CameraCharacteristics ch = cm.getCameraCharacteristics(cameraId); 184 int[] caps = ch.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); 185 if (IntStream.of(caps).anyMatch(x -> x 186 == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) { 187 Set<Long> profiles = 188 ch.get(CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES) 189 .getSupportedProfiles(); 190 return profiles; 191 } 192 } 193 return null; 194 } 195 isHardwareAcceleratedCodec(String codecName)196 private static boolean isHardwareAcceleratedCodec(String codecName) { 197 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 198 for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) { 199 if (codecName.equals(codecInfo.getName())) { 200 return codecInfo.isHardwareAccelerated(); 201 } 202 } 203 return false; 204 } 205 206 @CddTest(requirements = {"5.12/C-6-6"}) 207 @Test 208 @ApiTest(apis = {"android.media.MediaFormat#KEY_COLOR_TRANSFER_REQUEST"}) testHdrToSdr()209 public void testHdrToSdr() throws Exception { 210 AssetFileDescriptor infd = null; 211 final boolean dynamic = mHdrDynamicInfo != null; 212 213 Preconditions.assertTestFileExists(MEDIA_DIR + mInputFile); 214 mExtractor = new MediaExtractor(); 215 mExtractor.setDataSource(MEDIA_DIR + mInputFile); 216 217 MediaFormat format = null; 218 int trackIndex = -1; 219 for (int i = 0; i < mExtractor.getTrackCount(); i++) { 220 format = mExtractor.getTrackFormat(i); 221 if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) { 222 trackIndex = i; 223 break; 224 } 225 } 226 assumeTrue("Media format of input file is not supported.", 227 MediaUtils.supports(mCodecName, format)); 228 229 long requiredHdrProfile = PROFILE_HDR_MAP.get(mMediaType).get(mProfile); 230 Set<Long> availableProfiles = getAvailableHDRCaptureProfiles(); 231 boolean isProfileSupported = 232 availableProfiles != null && availableProfiles.contains(requiredHdrProfile); 233 assumeTrue("HDR capture is not supported for input file profile.", isProfileSupported); 234 235 // If extractor returns profile, ensure it is as expected. 236 int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1); 237 if (profile != -1) { 238 assertEquals("profile returned by the extractor is invalid.", 239 mProfile, profile); 240 } 241 242 mExtractor.selectTrack(trackIndex); 243 Log.v(TAG, "format " + format); 244 245 String mime = format.getString(MediaFormat.KEY_MIME); 246 format.setInteger( 247 MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO); 248 249 final Surface surface = getActivity().getSurfaceHolder().getSurface(); 250 251 Log.d(TAG, "Testing candicate decoder " + mCodecName); 252 CountDownLatch latch = new CountDownLatch(1); 253 mExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); 254 255 mDecoder = MediaCodec.createByCodecName(mCodecName); 256 mDecoder.setCallback(new MediaCodec.Callback() { 257 boolean mInputEOS; 258 boolean mOutputReceived; 259 int mInputCount; 260 int mOutputCount; 261 262 @Override 263 public void onOutputBufferAvailable( 264 MediaCodec codec, int index, BufferInfo info) { 265 if (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO) { 266 return; 267 } 268 269 MediaFormat bufferFormat = codec.getOutputFormat(index); 270 Log.i(TAG, "got output buffer: format " + bufferFormat); 271 272 assertEquals("unexpected color transfer for the buffer", 273 MediaFormat.COLOR_TRANSFER_SDR_VIDEO, 274 bufferFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, 0)); 275 ByteBuffer staticInfo = bufferFormat.getByteBuffer( 276 MediaFormat.KEY_HDR_STATIC_INFO, null); 277 if (staticInfo != null) { 278 assertTrue( 279 "Buffer should not have a valid static HDR metadata present", 280 Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO), 281 staticInfo.array())); 282 } 283 ByteBuffer hdr10PlusInfo = bufferFormat.getByteBuffer( 284 MediaFormat.KEY_HDR10_PLUS_INFO, null); 285 if (hdr10PlusInfo != null) { 286 assertEquals( 287 "Buffer should not have a valid dynamic HDR metadata present", 288 0, hdr10PlusInfo.remaining()); 289 } 290 291 if (!dynamic) { 292 codec.releaseOutputBuffer(index, true); 293 mOutputReceived = true; 294 latch.countDown(); 295 } else { 296 codec.releaseOutputBuffer(index, true); 297 mOutputCount++; 298 if (mOutputCount >= mHdrDynamicInfo.length) { 299 mOutputReceived = true; 300 latch.countDown(); 301 } 302 } 303 } 304 305 @Override 306 public void onInputBufferAvailable(MediaCodec codec, int index) { 307 // keep queuing until input EOS, or first output buffer received. 308 if (mInputEOS || (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO)) { 309 return; 310 } 311 312 ByteBuffer inputBuffer = codec.getInputBuffer(index); 313 314 if (mExtractor.getSampleTrackIndex() == -1) { 315 codec.queueInputBuffer( 316 index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); 317 mInputEOS = true; 318 } else { 319 int size = mExtractor.readSampleData(inputBuffer, 0); 320 long timestamp = mExtractor.getSampleTime(); 321 mExtractor.advance(); 322 323 if (dynamic && mMetaDataInContainer) { 324 final Bundle params = new Bundle(); 325 // TODO: extractor currently doesn't extract the dynamic metadata. 326 // Send in the test pattern for now to test the metadata propagation. 327 byte[] info = loadByteArrayFromString(mHdrDynamicInfo[mInputCount]); 328 params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info); 329 codec.setParameters(params); 330 mInputCount++; 331 if (mInputCount >= mHdrDynamicInfo.length) { 332 mInputEOS = true; 333 } 334 } 335 codec.queueInputBuffer(index, 0, size, timestamp, 0); 336 } 337 } 338 339 @Override 340 public void onError(MediaCodec codec, MediaCodec.CodecException e) { 341 Log.e(TAG, "got codec exception", e); 342 } 343 344 @Override 345 public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { 346 Log.i(TAG, "got output format: " + format); 347 ByteBuffer staticInfo = format.getByteBuffer( 348 MediaFormat.KEY_HDR_STATIC_INFO, null); 349 if (staticInfo != null) { 350 assertTrue( 351 "output format should not have a valid " + 352 "static HDR metadata present", 353 Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO), 354 staticInfo.array())); 355 } 356 } 357 }); 358 mDecoder.configure(format, surface, null/*crypto*/, 0/*flags*/); 359 int transferRequest = mDecoder.getInputFormat().getInteger( 360 MediaFormat.KEY_COLOR_TRANSFER_REQUEST, 0); 361 if (DecoderTest.isDefaultCodec(mCodecName, mMediaType) && isHardwareAcceleratedCodec( 362 mCodecName)) { 363 assertFalse(mCodecName + " does not support HDR to SDR tone mapping", 364 transferRequest == 0); 365 } else { 366 assumeFalse(mCodecName + " does not support HDR to SDR tone mapping", 367 transferRequest == 0); 368 } 369 assertEquals("unexpected color transfer request value from input format", 370 MediaFormat.COLOR_TRANSFER_SDR_VIDEO, transferRequest); 371 mDecoder.start(); 372 try { 373 assertTrue(latch.await(2000, TimeUnit.MILLISECONDS)); 374 } catch (InterruptedException e) { 375 fail("playback interrupted"); 376 } 377 if (DEBUG_HDR_TO_SDR_PLAY_VIDEO) { 378 Thread.sleep(5000); 379 } 380 mDecoder.stop(); 381 } 382 } 383