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.hardware.camera2.cts; 18 19 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes; 20 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.JPEG; 21 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.JPEG_R; 22 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.PRIV; 23 import static android.hardware.camera2.cts.CameraTestUtils.MaxStreamSizes.YUV; 24 import static android.hardware.camera2.cts.CameraTestUtils.assertNull; 25 26 import static junit.framework.Assert.assertNotNull; 27 import static junit.framework.Assert.assertTrue; 28 import static junit.framework.Assert.fail; 29 30 import android.graphics.ImageFormat; 31 import android.graphics.SurfaceTexture; 32 import android.hardware.HardwareBuffer; 33 import android.hardware.camera2.CameraCaptureSession; 34 import android.hardware.camera2.CameraCharacteristics; 35 import android.hardware.camera2.CameraDevice; 36 import android.hardware.camera2.CaptureRequest; 37 import android.hardware.camera2.cts.CameraTestUtils.MockStateCallback; 38 import android.hardware.camera2.cts.helpers.StaticMetadata; 39 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase; 40 import android.hardware.camera2.params.DynamicRangeProfiles; 41 import android.hardware.camera2.params.OutputConfiguration; 42 import android.hardware.camera2.params.SessionConfiguration; 43 import android.media.ImageReader; 44 import android.os.Build; 45 import android.platform.test.annotations.RequiresFlagsEnabled; 46 import android.platform.test.flag.junit.CheckFlagsRule; 47 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 48 import android.util.Log; 49 import android.util.Range; 50 import android.util.Size; 51 52 import com.android.ex.camera2.blocking.BlockingSessionCallback; 53 import com.android.ex.camera2.blocking.BlockingStateCallback; 54 import com.android.internal.camera.flags.Flags; 55 56 import org.junit.Assert; 57 import org.junit.Rule; 58 import org.junit.Test; 59 import org.junit.runner.RunWith; 60 import org.junit.runners.Parameterized; 61 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.HashMap; 65 import java.util.HashSet; 66 import java.util.List; 67 import java.util.Map; 68 import java.util.Random; 69 import java.util.Set; 70 import java.util.concurrent.Executor; 71 import java.util.concurrent.ExecutorService; 72 import java.util.concurrent.Executors; 73 74 /** 75 * Tests the functionality of {@link CameraDevice.CameraDeviceSetup} APIs. 76 * <p> 77 * NOTE: Functionality of {@link CameraDevice.CameraDeviceSetup#createCaptureRequest} and 78 * {@link CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported} is tested by 79 * {@link FeatureCombinationTest} 80 */ 81 @RunWith(Parameterized.class) 82 public class CameraDeviceSetupTest extends Camera2AndroidTestCase { 83 private static final String TAG = CameraDeviceSetupTest.class.getSimpleName(); 84 private static final int CAMERA_STATE_TIMEOUT_MS = 3000; 85 86 @Rule 87 public final CheckFlagsRule mFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); 88 89 @Test 90 @RequiresFlagsEnabled({Flags.FLAG_CAMERA_DEVICE_SETUP, Flags.FLAG_FEATURE_COMBINATION_QUERY}) testCameraDeviceSetupSupport()91 public void testCameraDeviceSetupSupport() throws Exception { 92 for (String cameraId : getCameraIdsUnderTest()) { 93 CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId); 94 Integer queryVersion = chars.get( 95 CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION); 96 mCollector.expectNotNull(CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION 97 + " must not be null", queryVersion); 98 if (queryVersion == null) { 99 continue; 100 } 101 boolean cameraDeviceSetupSupported = mCameraManager.isCameraDeviceSetupSupported( 102 cameraId); 103 104 // CameraDeviceSetup must be supported for all CameraDevices that report 105 // INFO_SESSION_CONFIGURATION_QUERY_VERSION > U, and false for those that don't. 106 mCollector.expectEquals(CameraCharacteristics.INFO_SESSION_CONFIGURATION_QUERY_VERSION 107 + " and CameraManager.isCameraDeviceSetupSupported give differing " 108 + "answers for camera id" + cameraId, 109 queryVersion > Build.VERSION_CODES.UPSIDE_DOWN_CAKE, 110 cameraDeviceSetupSupported); 111 } 112 } 113 114 @Test 115 @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP) testCameraDeviceSetupCreationSuccessful()116 public void testCameraDeviceSetupCreationSuccessful() throws Exception { 117 for (String cameraId : getCameraIdsUnderTest()) { 118 if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) { 119 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId); 120 continue; 121 } 122 123 CameraDevice.CameraDeviceSetup cameraDeviceSetup = 124 mCameraManager.getCameraDeviceSetup(cameraId); 125 mCollector.expectEquals("Camera ID of created object is not the same as that " 126 + "passed to CameraManager.", 127 cameraId, cameraDeviceSetup.getId()); 128 } 129 } 130 131 @Test 132 @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP) testCameraDeviceSetupCreationFailure()133 public void testCameraDeviceSetupCreationFailure() throws Exception { 134 try { 135 mCameraManager.getCameraDeviceSetup(/*cameraId=*/ null); 136 Assert.fail("Calling getCameraDeviceSetup with null should have raised an " 137 + "IllegalArgumentException."); 138 } catch (IllegalArgumentException e) { 139 // Expected. Don't do anything. 140 } 141 142 // Try to get an invalid camera ID by randomly generating 100 integers. 143 // NOTE: We don't actually expect to generate more than an int or two. 144 // Not being able to find an invalid camera within 100 random ints 145 // is astronomical. 146 HashSet<String> cameraIds = new HashSet<>(List.of(getCameraIdsUnderTest())); 147 Random rng = new Random(); 148 int invalidId = rng.ints(/*streamSize=*/ 100, /*randomNumberOrigin=*/ 0, 149 /*randomNumberBound=*/ Integer.MAX_VALUE) 150 .filter(i -> !cameraIds.contains(String.valueOf(i))) 151 .findAny() 152 .orElseThrow(() -> new AssertionError( 153 "Could not find an invalid cameraID within 100 randomly generated " 154 + "numbers.")); 155 String invalidCameraId = String.valueOf(invalidId); 156 Log.i(TAG, "Using invalid Camera ID: " + invalidCameraId); 157 try { 158 mCameraManager.getCameraDeviceSetup(invalidCameraId); 159 Assert.fail("Calling getCameraDeviceSetup with a cameraId not in getCameraIdList()" 160 + "should have raised an IllegalArgumentException."); 161 } catch (IllegalArgumentException e) { 162 // Expected. Don't do anything. 163 } 164 } 165 166 @Test 167 @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP) testOpenSuccessful()168 public void testOpenSuccessful() throws Exception { 169 ExecutorService callbackExecutor = Executors.newCachedThreadPool(); 170 for (String cameraId : getCameraIdsUnderTest()) { 171 if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) { 172 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId); 173 continue; 174 } 175 176 // mock listener to capture the CameraDevice from callbacks 177 MockStateCallback mockListener = MockStateCallback.mock(); 178 BlockingStateCallback callback = new BlockingStateCallback(mockListener); 179 180 CameraDevice.CameraDeviceSetup cameraDeviceSetup = 181 mCameraManager.getCameraDeviceSetup(cameraId); 182 cameraDeviceSetup.openCamera(callbackExecutor, callback); 183 184 callback.waitForState(BlockingStateCallback.STATE_OPENED, CAMERA_STATE_TIMEOUT_MS); 185 CameraDevice cameraDevice = CameraTestUtils.verifyCameraStateOpened( 186 cameraId, mockListener); 187 188 mCollector.expectEquals("CameraDeviceSetup and created CameraDevice must have " 189 + "the same ID", 190 cameraDeviceSetup.getId(), cameraDevice.getId()); 191 192 cameraDevice.close(); 193 callback.waitForState(BlockingStateCallback.STATE_CLOSED, CAMERA_STATE_TIMEOUT_MS); 194 } 195 } 196 197 /** 198 * Verify if valid session characteristics can be fetched for a particular camera. 199 */ 200 @Test 201 @RequiresFlagsEnabled({Flags.FLAG_FEATURE_COMBINATION_QUERY, Flags.FLAG_CAMERA_DEVICE_SETUP}) testSessionCharacteristics()202 public void testSessionCharacteristics() throws Exception { 203 String[] cameraIdsUnderTest = getCameraIdsUnderTest(); 204 for (String cameraId : cameraIdsUnderTest) { 205 // Without the following check, mOrderedPreviewSizes will be null. 206 StaticMetadata staticChars = new StaticMetadata( 207 mCameraManager.getCameraCharacteristics(cameraId)); 208 209 if (!staticChars.isColorOutputSupported()) { 210 Log.i(TAG, "Camera " + cameraId + " does not support color outputs, skipping."); 211 continue; 212 } 213 214 if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) { 215 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId); 216 continue; 217 } 218 219 CameraDevice.CameraDeviceSetup cameraDeviceSetup = mCameraManager.getCameraDeviceSetup( 220 cameraId); 221 222 int outputFormat = ImageFormat.YUV_420_888; 223 List<Size> orderedPreviewSizes = CameraTestUtils.getSupportedPreviewSizes(cameraId, 224 mCameraManager, CameraTestUtils.PREVIEW_SIZE_BOUND); 225 Size outputSize = orderedPreviewSizes.get(0); 226 try (ImageReader imageReader = ImageReader.newInstance(outputSize.getWidth(), 227 outputSize.getHeight(), outputFormat, /*maxImages*/3)) { 228 CameraCaptureSession.StateCallback sessionListener = new BlockingSessionCallback(); 229 230 List<OutputConfiguration> outputs = new ArrayList<>(); 231 outputs.add(new OutputConfiguration(imageReader.getSurface())); 232 233 SessionConfiguration sessionConfig = new SessionConfiguration( 234 SessionConfiguration.SESSION_REGULAR, outputs, 235 new CameraTestUtils.HandlerExecutor(mHandler), sessionListener); 236 237 CaptureRequest.Builder builder = cameraDeviceSetup.createCaptureRequest( 238 CameraDevice.TEMPLATE_STILL_CAPTURE); 239 builder.addTarget(imageReader.getSurface()); 240 241 CaptureRequest request = builder.build(); 242 sessionConfig.setSessionParameters(request); 243 244 CameraCharacteristics sessionCharacteristics = 245 cameraDeviceSetup.getSessionCharacteristics(sessionConfig); 246 StaticMetadata sessionMetadata = new StaticMetadata(sessionCharacteristics); 247 248 List<CameraCharacteristics.Key<?>> availableSessionCharKeys = 249 staticChars.getCharacteristics().getAvailableSessionCharacteristicsKeys(); 250 251 mCollector.expectNotNull("Session Characteristics keys must not be null", 252 availableSessionCharKeys); 253 254 // Ensure every key in availableSessionCharKeys is present in 255 // sessionCharacteristics. 256 for (CameraCharacteristics.Key<?> key : availableSessionCharKeys) { 257 mCollector.expectNotNull(key.toString() 258 + " is null in Session Characteristics", 259 sessionCharacteristics.get(key)); 260 } 261 262 List<CameraCharacteristics.Key<?>> staticKeys = 263 staticChars.getCharacteristics().getKeys(); 264 List<CameraCharacteristics.Key<?>> sessionCharKeys = 265 sessionCharacteristics.getKeys(); 266 267 // Ensure that there are no duplicate keys in session chars 268 HashSet<CameraCharacteristics.Key<?>> uniqueSessionCharKeys = 269 new HashSet<>(sessionCharKeys); 270 mCollector.expectEquals( 271 "Session Characteristics should not contain duplicate keys.", 272 uniqueSessionCharKeys.size(), sessionCharKeys.size()); 273 274 // Ensure all keys in static chars are present in session chars. 275 mCollector.expectEquals( 276 "Session Characteristics and Static Characteristics must have the same " 277 + "number of keys.", 278 staticKeys.size(), uniqueSessionCharKeys.size()); 279 mCollector.expectContainsAll( 280 "Session Characteristics must have all the keys in Static " 281 + "Characteristics", 282 uniqueSessionCharKeys, staticKeys); 283 284 285 // TODO: Do more thorough testing to make sure the max_digital_zoom and 286 // zoom_ratio_range have valid values. 287 sessionMetadata.getAvailableMaxDigitalZoomChecked(); 288 sessionMetadata.getZoomRatioRangeChecked(); 289 290 checkSessionCharacteristicsForNoCallbackConfig(outputs, builder, 291 cameraDeviceSetup, sessionCharacteristics); 292 } 293 } 294 } 295 296 /** 297 * Check the session characteristics consistency when queried via a SessionConfiguration 298 * without callbacks. 299 */ checkSessionCharacteristicsForNoCallbackConfig(List<OutputConfiguration> outputs, CaptureRequest.Builder requestBuilder, CameraDevice.CameraDeviceSetup deviceSetup, CameraCharacteristics sessionCharacteristics)300 private void checkSessionCharacteristicsForNoCallbackConfig(List<OutputConfiguration> outputs, 301 CaptureRequest.Builder requestBuilder, 302 CameraDevice.CameraDeviceSetup deviceSetup, 303 CameraCharacteristics sessionCharacteristics) throws Exception { 304 // Session configuration with no callbacks 305 SessionConfiguration sessionConfigNoCallback = new SessionConfiguration( 306 SessionConfiguration.SESSION_REGULAR, outputs); 307 sessionConfigNoCallback.setSessionParameters(requestBuilder.build()); 308 309 CameraCharacteristics sessionCharsWithoutCallbacks = deviceSetup.getSessionCharacteristics( 310 sessionConfigNoCallback); 311 List<CameraCharacteristics.Key<?>> sessionCharKeysWithoutCallback = 312 sessionCharsWithoutCallbacks.getKeys(); 313 314 // Ensure that there are no duplicate keys in session chars without callbacks. 315 HashSet<CameraCharacteristics.Key<?>> uniqueSessionCharNoCallbackKeys = 316 new HashSet<>(sessionCharKeysWithoutCallback); 317 mCollector.expectEquals( 318 "Session Characteristics without callback should not contain duplicate keys.", 319 uniqueSessionCharNoCallbackKeys.size(), sessionCharKeysWithoutCallback.size()); 320 321 // Ensure all keys in session chars with callback are present in session chars 322 // without callback. 323 List<CameraCharacteristics.Key<?>> sessionCharKeys = sessionCharacteristics.getKeys(); 324 mCollector.expectEquals( 325 "Session Characteristics with and without callback must have the same " 326 + "number of keys.", 327 sessionCharKeys.size(), uniqueSessionCharNoCallbackKeys.size()); 328 mCollector.expectContainsAll( 329 "Session Characteristics without callback must have the all the keys in Session " 330 + "Characteristics with callback.", 331 uniqueSessionCharNoCallbackKeys, sessionCharKeys); 332 333 // setStateCallback works as expected 334 CameraCaptureSession.StateCallback sessionListener = new BlockingSessionCallback(); 335 Executor executor = new CameraTestUtils.HandlerExecutor(mHandler); 336 sessionConfigNoCallback.setStateCallback(executor, sessionListener); 337 mCollector.expectEquals( 338 "CameraCaptureSession.StateCallback set by setStateCallback not reflected in " 339 + "getStateCallback.", 340 sessionListener, sessionConfigNoCallback.getStateCallback()); 341 mCollector.expectEquals("Executor set by setStateCallback not reflect in getExecutor.", 342 executor, sessionConfigNoCallback.getExecutor()); 343 } 344 345 @Test 346 @RequiresFlagsEnabled(Flags.FLAG_CAMERA_DEVICE_SETUP) testOutputConfigurationForCameraSetup()347 public void testOutputConfigurationForCameraSetup() throws Exception { 348 Size size = new Size(640, 480); 349 int fmt = ImageFormat.YUV_420_888; 350 int jpegFmt = ImageFormat.JPEG; 351 int maxImages = 2; 352 int surfaceGroupId = 1; 353 ImageReader reader = ImageReader.newInstance( 354 size.getWidth(), size.getHeight(), fmt, maxImages); 355 356 // Test new constructors for OutputConfiguration and add Surface later. 357 OutputConfiguration config = new OutputConfiguration(fmt, size); 358 config.addSurface(reader.getSurface()); 359 360 // Adding a mismatched format surface throws an IllegalArgumentException. 361 OutputConfiguration configWithGroupId = new OutputConfiguration( 362 surfaceGroupId, jpegFmt, size); 363 try { 364 configWithGroupId.addSurface(reader.getSurface()); 365 fail("Adding surface with mismatching format must throw an exception!"); 366 } catch (IllegalArgumentException e) { 367 } 368 369 // Adding a format with different dataspace throws an IllegalArgumentException. 370 final long usageFlag = HardwareBuffer.USAGE_GPU_COLOR_OUTPUT 371 | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE; 372 OutputConfiguration configWithUsageFlag = new OutputConfiguration( 373 ImageFormat.JPEG_R, size, usageFlag); 374 try { 375 configWithUsageFlag.addSurface(reader.getSurface()); 376 fail("Adding surface with mismatching dataspace must throw an exception!"); 377 } catch (IllegalArgumentException e) { 378 } 379 380 // Create OutputConfiguration with groupdId, format, size, and usage flag 381 OutputConfiguration configWithGroupIdAndUsage = new OutputConfiguration( 382 surfaceGroupId, fmt, size, usageFlag); 383 assertNull(configWithGroupIdAndUsage.getSurface()); 384 } 385 386 @Test 387 @RequiresFlagsEnabled({Flags.FLAG_CAMERA_DEVICE_SETUP, Flags.FLAG_FEATURE_COMBINATION_QUERY}) testCameraDeviceSetupTemplates()388 public void testCameraDeviceSetupTemplates() throws Exception { 389 for (String cameraId : getCameraIdsUnderTest()) { 390 if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) { 391 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId); 392 continue; 393 } 394 395 testCameraDeviceSetupTemplatesByCamera(cameraId); 396 } 397 } 398 testCameraDeviceSetupTemplatesByCamera(String cameraId)399 private void testCameraDeviceSetupTemplatesByCamera(String cameraId) throws Exception { 400 int[] templates = { 401 CameraDevice.TEMPLATE_PREVIEW, 402 CameraDevice.TEMPLATE_STILL_CAPTURE, 403 CameraDevice.TEMPLATE_RECORD, 404 CameraDevice.TEMPLATE_VIDEO_SNAPSHOT, 405 CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG, 406 CameraDevice.TEMPLATE_MANUAL, 407 }; 408 409 try { 410 CameraDevice.CameraDeviceSetup cameraDeviceSetup = 411 mCameraManager.getCameraDeviceSetup(cameraId); 412 assertNotNull("Failed to create camera device setup for id " + cameraId, 413 cameraDeviceSetup); 414 415 openDevice(cameraId); 416 mCollector.setCameraId(cameraId); 417 418 for (int template : templates) { 419 try { 420 CaptureRequest.Builder requestFromSetup = 421 cameraDeviceSetup.createCaptureRequest(template); 422 assertNotNull("CameraDeviceSetup failed to create capture request for camera " 423 + cameraId + " template " + template, requestFromSetup); 424 425 CaptureRequest.Builder request = mCamera.createCaptureRequest(template); 426 assertNotNull("CameraDevice failed to create capture request for template " 427 + template, request); 428 429 mCollector.expectEquals("The CaptureRequest created by CameraDeviceSetup " 430 + "and CameraDevice must have the same set of keys", 431 request.build().getKeys(), requestFromSetup.build().getKeys()); 432 } catch (IllegalArgumentException e) { 433 if (template == CameraDevice.TEMPLATE_MANUAL 434 && !mStaticInfo.isCapabilitySupported(CameraCharacteristics 435 .REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { 436 // OK 437 } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG 438 && !mStaticInfo.isCapabilitySupported(CameraCharacteristics 439 .REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) { 440 // OK. 441 } else if (sLegacySkipTemplates.contains(template) 442 && mStaticInfo.isHardwareLevelLegacy()) { 443 // OK 444 } else if (template != CameraDevice.TEMPLATE_PREVIEW 445 && mStaticInfo.isDepthOutputSupported() 446 && !mStaticInfo.isColorOutputSupported()) { 447 // OK, depth-only devices need only support PREVIEW template 448 } else { 449 throw e; // rethrow 450 } 451 } 452 } 453 } finally { 454 closeDevice(cameraId); 455 } 456 } 457 458 /** 459 * Verify if valid session characteristics can be fetched for a particular camera. 460 */ 461 @Test 462 @RequiresFlagsEnabled({Flags.FLAG_FEATURE_COMBINATION_QUERY, Flags.FLAG_CAMERA_DEVICE_SETUP}) testFeatureCombinationQueryConsistency()463 public void testFeatureCombinationQueryConsistency() throws Exception { 464 for (String cameraId : getCameraIdsUnderTest()) { 465 if (!mCameraManager.isCameraDeviceSetupSupported(cameraId)) { 466 Log.i(TAG, "CameraDeviceSetup not supported for camera id " + cameraId); 467 continue; 468 } 469 470 testFeatureCombinationQueryConsistencyByCamera(cameraId); 471 } 472 } 473 474 /** 475 * Check the feature combination query consistency between different feature combinations. 476 */ testFeatureCombinationQueryConsistencyByCamera(String cameraId)477 private void testFeatureCombinationQueryConsistencyByCamera(String cameraId) throws Exception { 478 CameraDevice.CameraDeviceSetup cameraDeviceSetup = 479 mCameraManager.getCameraDeviceSetup(cameraId); 480 assertNotNull("Failed to create camera device setup for id " + cameraId, 481 cameraDeviceSetup); 482 483 StaticMetadata staticInfo = mAllStaticInfo.get(cameraId); 484 if (!staticInfo.isColorOutputSupported()) { 485 Log.i(TAG, "Camera " + cameraId + " does not support color outputs, skipping"); 486 return; 487 } 488 489 MaxStreamSizes maxStreamSizes = new MaxStreamSizes(staticInfo, 490 cameraDeviceSetup.getId(), mContext, /*matchSize*/true); 491 Set<Long> dynamicRangeProfiles = staticInfo.getAvailableDynamicRangeProfilesChecked(); 492 int[] videoStabilizationModes = staticInfo.getAvailableVideoStabilizationModesChecked(); 493 Range<Integer>[] fpsRanges = staticInfo.getAeAvailableTargetFpsRangesChecked(); 494 final int kPreviewStabilization = 495 CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION; 496 boolean supportPreviewStab = CameraTestUtils.contains(videoStabilizationModes, 497 kPreviewStabilization); 498 Map<CameraFeatureWrapper, Boolean> featureCombinationSupport = new HashMap<>(); 499 for (int[] c : maxStreamSizes.getQueryableCombinations()) { 500 for (Long dynamicProfile : dynamicRangeProfiles) { 501 if (dynamicProfile != DynamicRangeProfiles.STANDARD 502 && dynamicProfile != DynamicRangeProfiles.HLG10) { 503 // Only verify HLG10 and STANDARD. Skip for others. 504 continue; 505 } 506 // Setup outputs 507 List<OutputConfiguration> outputConfigs = new ArrayList<>(); 508 long minFrameDuration = setupConfigurations(staticInfo, c, maxStreamSizes, 509 outputConfigs, dynamicProfile); 510 if (minFrameDuration == -1) { 511 // Stream combination is not valid. For example, if the stream sizes are not 512 // supported, or if the device doesn't support JPEG_R, minFrameDuration will 513 // be -1. 514 continue; 515 } 516 517 for (Range<Integer> fpsRange : fpsRanges) { 518 if ((fpsRange.getUpper() != 60) && (fpsRange.getUpper() != 30)) { 519 // Skip fps ranges that are not 30fps or 60fps. 520 continue; 521 } 522 if (minFrameDuration > 1e9 * 1.01 / fpsRange.getUpper()) { 523 // Skip the fps range because the minFrameDuration cannot meet the 524 // required range 525 continue; 526 } 527 528 String combinationStr = MaxStreamSizes.combinationToString(c) 529 + ", dynamicRangeProfile " + dynamicProfile 530 + ", fpsRange " + fpsRange.toString(); 531 try { 532 CaptureRequest.Builder builder = cameraDeviceSetup.createCaptureRequest( 533 CameraDevice.TEMPLATE_PREVIEW); 534 builder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 535 CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_OFF); 536 CaptureRequest request = builder.build(); 537 SessionConfiguration sessionConfig = new SessionConfiguration( 538 SessionConfiguration.SESSION_REGULAR, outputConfigs); 539 sessionConfig.setSessionParameters(request); 540 boolean isSupported = cameraDeviceSetup.isSessionConfigurationSupported( 541 sessionConfig); 542 543 // Make sure isSessionConfigurationSupported and getSessionCharacteristics 544 // behaviors are consistent. 545 try { 546 cameraDeviceSetup.getSessionCharacteristics(sessionConfig); 547 mCollector.expectTrue("getSessionCharacteristics succeeds, but " 548 + "isSessionConfigurationSupported return false!", 549 isSupported); 550 } catch (IllegalArgumentException e) { 551 mCollector.expectTrue( 552 "getSessionCharacteristics throws IllegalArgumentException," 553 + "but isSessionConfigurationSupported returns true!", 554 !isSupported); 555 } 556 557 // If preview stabilization is supported, the return value of 558 // isSessionConfigurationSupported when stabilization is off should match 559 // the result value when preview stabilization is on. 560 if (supportPreviewStab) { 561 builder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 562 kPreviewStabilization); 563 CaptureRequest requestWithStab = builder.build(); 564 SessionConfiguration sessionConfigWithStab = new SessionConfiguration( 565 SessionConfiguration.SESSION_REGULAR, outputConfigs); 566 sessionConfigWithStab.setSessionParameters(requestWithStab); 567 568 boolean isSupportedWithStab = 569 cameraDeviceSetup.isSessionConfigurationSupported( 570 sessionConfigWithStab); 571 572 mCollector.expectEquals( 573 "isSessionCharacteristicsSupported returns " + isSupported 574 + " with stabilization off, but returns " + isSupportedWithStab 575 + " with preview stabilization on.", isSupported, 576 isSupportedWithStab); 577 578 try { 579 cameraDeviceSetup.getSessionCharacteristics(sessionConfigWithStab); 580 mCollector.expectTrue( 581 "With stabilization on, getSessionCharacteristics " 582 + "succeeds, but isSessionConfigurationSupported return " 583 + "false!", isSupportedWithStab); 584 } catch (IllegalArgumentException e) { 585 mCollector.expectTrue("With stabilization on, " 586 + "getSessionCharacteristics throws " 587 + "IllegalArgumentException, but " 588 + "isSessionConfigurationSupported returns true!", 589 !isSupportedWithStab); 590 } 591 } 592 593 featureCombinationSupport.put( 594 new CameraFeatureWrapper(c, fpsRange, dynamicProfile), 595 isSupported); 596 } catch (Throwable e) { 597 mCollector.addMessage(String.format( 598 "Output combination %s failed due to: %s", 599 combinationStr, e.getMessage())); 600 } 601 } 602 } 603 } 604 605 // Verify that: 606 // - If a combination with JPEG_R is supported, replacing JPEG_R with JPEG is 607 // still supported. 608 // - If a combination with 10-bit HDR is supported, replacing HDR with SDR is 609 // still supported. 610 for (Map.Entry<CameraFeatureWrapper, Boolean> entry 611 : featureCombinationSupport.entrySet()) { 612 CameraFeatureWrapper features = entry.getKey(); 613 boolean isSupported = entry.getValue(); 614 615 Log.v(TAG, "Features: " 616 + Arrays.toString(features.mStreamCombination) + ", isJpegR " 617 + features.mIsJpegR + ", fpsRange: " 618 + features.mFpsRange + ", dynamicProfile: " 619 + features.mDynamicProfile); 620 if (features.mIsJpegR) { 621 CameraFeatureWrapper featuresNoJpegR = new CameraFeatureWrapper(features, 622 /*isJpegR*/false); 623 assertTrue(featureCombinationSupport.containsKey(featuresNoJpegR)); 624 boolean isSupportedNoJpegR = featureCombinationSupport.get(featuresNoJpegR); 625 mCollector.expectEquals( 626 features.toString() + " (support: " + isSupported 627 + ") doesn't match " + featuresNoJpegR.toString() + " (support: " 628 + isSupportedNoJpegR, isSupported, isSupportedNoJpegR); 629 } 630 631 if (features.mDynamicProfile != DynamicRangeProfiles.STANDARD) { 632 CameraFeatureWrapper featuresNoHdr = new CameraFeatureWrapper( 633 features, DynamicRangeProfiles.STANDARD); 634 assertTrue(featureCombinationSupport.containsKey(featuresNoHdr)); 635 boolean isSupportedNoHdr = featureCombinationSupport.get(featuresNoHdr); 636 mCollector.expectEquals( 637 features.toString() + " (support: " + isSupported 638 + ") doesn't match " + featuresNoHdr.toString() + " (support: " 639 + isSupportedNoHdr, isSupported, isSupportedNoHdr); 640 } 641 } 642 } 643 644 /** 645 * A helper class to wrap camera features. 646 */ 647 static class CameraFeatureWrapper { CameraFeatureWrapper(int[] streamCombination, Range<Integer> fpsRange, long dynamicProfile)648 CameraFeatureWrapper(int[] streamCombination, 649 Range<Integer> fpsRange, long dynamicProfile) { 650 mFpsRange = fpsRange; 651 mDynamicProfile = dynamicProfile; 652 mStreamCombination = Arrays.copyOf(streamCombination, streamCombination.length); 653 654 // Replace JPEG_R format with JPEG, and use a boolean to indicate whether 655 // it's JPEG_R or JPEG. This is for easier feature look-up. 656 boolean hasJpegR = false; 657 for (int i = 0; i < mStreamCombination.length; i += 2) { 658 if (mStreamCombination[i] == JPEG_R) { 659 mStreamCombination[i] = JPEG; 660 hasJpegR = true; 661 break; 662 } 663 } 664 mIsJpegR = hasJpegR; 665 } 666 CameraFeatureWrapper(CameraFeatureWrapper other, boolean isJpegR)667 CameraFeatureWrapper(CameraFeatureWrapper other, boolean isJpegR) { 668 this.mStreamCombination = other.mStreamCombination; 669 this.mIsJpegR = isJpegR; 670 this.mFpsRange = other.mFpsRange; 671 this.mDynamicProfile = other.mDynamicProfile; 672 } 673 CameraFeatureWrapper(CameraFeatureWrapper other, long dynamicRangeProfile)674 CameraFeatureWrapper(CameraFeatureWrapper other, long dynamicRangeProfile) { 675 this.mStreamCombination = other.mStreamCombination; 676 this.mIsJpegR = other.mIsJpegR; 677 this.mFpsRange = other.mFpsRange; 678 this.mDynamicProfile = dynamicRangeProfile; 679 } 680 681 @Override hashCode()682 public int hashCode() { 683 int result = 17; 684 for (int i : mStreamCombination) { 685 result = 31 * result + i; 686 } 687 result = 31 * result + (mIsJpegR ? 1 : 0); 688 result = 31 * result + mFpsRange.hashCode(); 689 result = 31 * result + (int) mDynamicProfile; 690 return result; 691 } 692 693 @Override equals(Object obj)694 public boolean equals(Object obj) { 695 if (obj == null) { 696 return false; 697 } else if (obj == this) { 698 return true; 699 } else if (obj instanceof CameraFeatureWrapper) { 700 final CameraFeatureWrapper other = (CameraFeatureWrapper) obj; 701 return (Arrays.equals(mStreamCombination, other.mStreamCombination) 702 && mIsJpegR == other.mIsJpegR 703 && mFpsRange.equals(other.mFpsRange) 704 && mDynamicProfile == other.mDynamicProfile); 705 } 706 707 return false; 708 } 709 710 @Override toString()711 public String toString() { 712 return "Stream combination: " + Arrays.toString(mStreamCombination) 713 + ", isJpegR: " + mIsJpegR 714 + ", fpsRange: " + mFpsRange 715 + ", dynamicRangeProfile: " + mDynamicProfile; 716 } 717 718 public final int[] mStreamCombination; 719 public final boolean mIsJpegR; 720 public final Range<Integer> mFpsRange; 721 public final long mDynamicProfile; 722 } 723 setupConfigurations(StaticMetadata staticInfo, int[] configs, MaxStreamSizes maxSizes, List<OutputConfiguration> outputConfigs, Long dynamicProfile)724 private long setupConfigurations(StaticMetadata staticInfo, int[] configs, 725 MaxStreamSizes maxSizes, List<OutputConfiguration> outputConfigs, Long dynamicProfile) { 726 long frameDuration = -1; 727 for (int i = 0; i < configs.length; i += 2) { 728 int format = configs[i]; 729 int sizeLimit = configs[i + 1]; 730 731 Size targetSize = null; 732 switch (format) { 733 case PRIV: { 734 targetSize = maxSizes.getOutputSizeForFormat(PRIV, sizeLimit); 735 OutputConfiguration config = new OutputConfiguration( 736 targetSize, SurfaceTexture.class); 737 config.setDynamicRangeProfile(dynamicProfile); 738 outputConfigs.add(config); 739 break; 740 } 741 case JPEG: 742 case JPEG_R: { 743 targetSize = maxSizes.getOutputSizeForFormat(format, sizeLimit); 744 OutputConfiguration config = new OutputConfiguration(format, targetSize); 745 outputConfigs.add(config); 746 break; 747 } 748 case YUV: { 749 if (dynamicProfile == DynamicRangeProfiles.HLG10) { 750 format = ImageFormat.YCBCR_P010; 751 } 752 targetSize = maxSizes.getOutputSizeForFormat(YUV, sizeLimit); 753 OutputConfiguration config = new OutputConfiguration(format, targetSize); 754 config.setDynamicRangeProfile(dynamicProfile); 755 outputConfigs.add(config); 756 break; 757 } 758 default: 759 fail("Unknown output format " + format); 760 } 761 762 Map<Size, Long> minFrameDurations = 763 staticInfo.getAvailableMinFrameDurationsForFormatChecked(format); 764 if (minFrameDurations.containsKey(targetSize) 765 && minFrameDurations.get(targetSize) > frameDuration) { 766 frameDuration = minFrameDurations.get(targetSize); 767 } 768 } 769 770 return frameDuration; 771 } 772 } 773