1 /* 2 * Copyright 2016 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 com.android.mediaframeworktest.stress; 18 19 import com.android.mediaframeworktest.Camera2SurfaceViewTestCase; 20 import com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback; 21 22 import android.hardware.camera2.CameraCharacteristics; 23 import android.hardware.camera2.CameraDevice; 24 import android.hardware.camera2.CaptureRequest; 25 import android.hardware.camera2.CaptureResult; 26 import android.util.Log; 27 import android.util.Size; 28 29 import java.util.Arrays; 30 31 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_OFF; 32 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON; 33 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH; 34 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH; 35 import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE; 36 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull; 37 38 /** 39 * <p> 40 * Basic test for camera CaptureRequest key controls. 41 * </p> 42 * <p> 43 * Several test categories are covered: manual sensor control, 3A control, 44 * manual ISP control and other per-frame control and synchronization. 45 * </p> 46 * 47 * adb shell am instrument \ 48 * -e class com.android.mediaframeworktest.stress.Camera2CaptureRequestTest#testAeModeAndLock \ 49 * -e iterations 10 \ 50 * -e waitIntervalMs 1000 \ 51 * -e resultToFile false \ 52 * -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner 53 */ 54 public class Camera2CaptureRequestTest extends Camera2SurfaceViewTestCase { 55 private static final String TAG = "CaptureRequestTest"; 56 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 57 /** 30ms exposure time must be supported by full capability devices. */ 58 private static final long DEFAULT_EXP_TIME_NS = 30000000L; 59 private static final int DEFAULT_SENSITIVITY = 100; 60 private static final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000L; // 100us, Approximation. 61 private static final float EXPOSURE_TIME_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation. 62 private static final float SENSITIVITY_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation. 63 private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 3; 64 private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 16; 65 private static final int DEFAULT_SENSITIVITY_STEP_SIZE = 100; 66 67 @Override 68 protected void setUp() throws Exception { 69 super.setUp(); 70 } 71 72 @Override 73 protected void tearDown() throws Exception { 74 super.tearDown(); 75 } 76 77 /** 78 * Test AE mode and lock. 79 * 80 * <p> 81 * For AE lock, when it is locked, exposure parameters shouldn't be changed. 82 * For AE modes, each mode should satisfy the per frame controls defined in 83 * API specifications. 84 * </p> 85 */ 86 public void testAeModeAndLock() throws Exception { 87 for (int i = 0; i < mCameraIds.length; i++) { 88 try { 89 openDevice(mCameraIds[i]); 90 if (!mStaticInfo.isColorOutputSupported()) { 91 Log.i(TAG, "Camera " + mCameraIds[i] + 92 " does not support color outputs, skipping"); 93 continue; 94 } 95 96 Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size. 97 98 // Update preview surface with given size for all sub-tests. 99 updatePreviewSurface(maxPreviewSz); 100 101 // Test iteration starts... 102 for (int iteration = 0; iteration < getIterationCount(); ++iteration) { 103 Log.v(TAG, String.format("AE mode and lock: %d/%d", iteration + 1, 104 getIterationCount())); 105 106 // Test aeMode and lock 107 int[] aeModes = mStaticInfo.getAeAvailableModesChecked(); 108 for (int mode : aeModes) { 109 aeModeAndLockTestByMode(mode); 110 } 111 getResultPrinter().printStatus(getIterationCount(), iteration + 1, mCameraIds[i]); 112 Thread.sleep(getTestWaitIntervalMs()); 113 } 114 } finally { 115 closeDevice(); 116 } 117 } 118 } 119 120 /** 121 * Test the all available AE modes and AE lock. 122 * <p> 123 * For manual AE mode, test iterates through different sensitivities and 124 * exposure times, validate the result exposure time correctness. For 125 * CONTROL_AE_MODE_ON_ALWAYS_FLASH mode, the AE lock and flash are tested. 126 * For the rest of the AUTO mode, AE lock is tested. 127 * </p> 128 * 129 * @param mode 130 */ 131 private void aeModeAndLockTestByMode(int mode) 132 throws Exception { 133 switch (mode) { 134 case CONTROL_AE_MODE_OFF: 135 if (mStaticInfo.isCapabilitySupported( 136 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { 137 // Test manual exposure control. 138 aeManualControlTest(); 139 } else { 140 Log.w(TAG, 141 "aeModeAndLockTestByMode - can't test AE mode OFF without " + 142 "manual sensor control"); 143 } 144 break; 145 case CONTROL_AE_MODE_ON: 146 case CONTROL_AE_MODE_ON_AUTO_FLASH: 147 case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE: 148 case CONTROL_AE_MODE_ON_ALWAYS_FLASH: 149 // Test AE lock for above AUTO modes. 150 aeAutoModeTestLock(mode); 151 break; 152 default: 153 throw new UnsupportedOperationException("Unhandled AE mode " + mode); 154 } 155 } 156 157 /** 158 * Test AE auto modes. 159 * <p> 160 * Use single request rather than repeating request to test AE lock per frame control. 161 * </p> 162 */ 163 private void aeAutoModeTestLock(int mode) throws Exception { 164 CaptureRequest.Builder requestBuilder = 165 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 166 if (mStaticInfo.isAeLockSupported()) { 167 requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); 168 } 169 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode); 170 configurePreviewOutput(requestBuilder); 171 172 final int MAX_NUM_CAPTURES_DURING_LOCK = 5; 173 for (int i = 1; i <= MAX_NUM_CAPTURES_DURING_LOCK; i++) { 174 autoAeMultipleCapturesThenTestLock(requestBuilder, mode, i); 175 } 176 } 177 178 /** 179 * Issue multiple auto AE captures, then lock AE, validate the AE lock vs. 180 * the first capture result after the AE lock. The right AE lock behavior is: 181 * When it is locked, it locks to the current exposure value, and all subsequent 182 * request with lock ON will have the same exposure value locked. 183 */ 184 private void autoAeMultipleCapturesThenTestLock( 185 CaptureRequest.Builder requestBuilder, int aeMode, int numCapturesDuringLock) 186 throws Exception { 187 if (numCapturesDuringLock < 1) { 188 throw new IllegalArgumentException("numCapturesBeforeLock must be no less than 1"); 189 } 190 if (VERBOSE) { 191 Log.v(TAG, "Camera " + mCamera.getId() + ": Testing auto AE mode and lock for mode " 192 + aeMode + " with " + numCapturesDuringLock + " captures before lock"); 193 } 194 195 final int NUM_CAPTURES_BEFORE_LOCK = 2; 196 SimpleCaptureCallback listener = new SimpleCaptureCallback(); 197 198 CaptureResult[] resultsDuringLock = new CaptureResult[numCapturesDuringLock]; 199 boolean canSetAeLock = mStaticInfo.isAeLockSupported(); 200 201 // Reset the AE lock to OFF, since we are reusing this builder many times 202 if (canSetAeLock) { 203 requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); 204 } 205 206 // Just send several captures with auto AE, lock off. 207 CaptureRequest request = requestBuilder.build(); 208 for (int i = 0; i < NUM_CAPTURES_BEFORE_LOCK; i++) { 209 mSession.capture(request, listener, mHandler); 210 } 211 waitForNumResults(listener, NUM_CAPTURES_BEFORE_LOCK); 212 213 if (!canSetAeLock) { 214 // Without AE lock, the remaining tests items won't work 215 return; 216 } 217 218 // Then fire several capture to lock the AE. 219 requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); 220 221 int requestCount = captureRequestsSynchronized( 222 requestBuilder.build(), numCapturesDuringLock, listener, mHandler); 223 224 int[] sensitivities = new int[numCapturesDuringLock]; 225 long[] expTimes = new long[numCapturesDuringLock]; 226 Arrays.fill(sensitivities, -1); 227 Arrays.fill(expTimes, -1L); 228 229 // Get the AE lock on result and validate the exposure values. 230 waitForNumResults(listener, requestCount - numCapturesDuringLock); 231 for (int i = 0; i < resultsDuringLock.length; i++) { 232 resultsDuringLock[i] = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 233 } 234 235 for (int i = 0; i < numCapturesDuringLock; i++) { 236 mCollector.expectKeyValueEquals( 237 resultsDuringLock[i], CaptureResult.CONTROL_AE_LOCK, true); 238 } 239 240 // Can't read manual sensor/exposure settings without manual sensor 241 if (mStaticInfo.isCapabilitySupported( 242 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS)) { 243 int sensitivityLocked = 244 getValueNotNull(resultsDuringLock[0], CaptureResult.SENSOR_SENSITIVITY); 245 long expTimeLocked = 246 getValueNotNull(resultsDuringLock[0], CaptureResult.SENSOR_EXPOSURE_TIME); 247 for (int i = 1; i < resultsDuringLock.length; i++) { 248 mCollector.expectKeyValueEquals( 249 resultsDuringLock[i], CaptureResult.SENSOR_EXPOSURE_TIME, expTimeLocked); 250 mCollector.expectKeyValueEquals( 251 resultsDuringLock[i], CaptureResult.SENSOR_SENSITIVITY, sensitivityLocked); 252 } 253 } 254 } 255 256 /** 257 * Iterate through exposure times and sensitivities for manual AE control. 258 * <p> 259 * Use single request rather than repeating request to test manual exposure 260 * value change per frame control. 261 * </p> 262 */ 263 private void aeManualControlTest() 264 throws Exception { 265 CaptureRequest.Builder requestBuilder = 266 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 267 268 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF); 269 configurePreviewOutput(requestBuilder); 270 SimpleCaptureCallback listener = new SimpleCaptureCallback(); 271 272 long[] expTimes = getExposureTimeTestValues(); 273 int[] sensitivities = getSensitivityTestValues(); 274 // Submit single request at a time, then verify the result. 275 for (int i = 0; i < expTimes.length; i++) { 276 for (int j = 0; j < sensitivities.length; j++) { 277 if (VERBOSE) { 278 Log.v(TAG, "Camera " + mCamera.getId() + ": Testing sensitivity " 279 + sensitivities[j] + ", exposure time " + expTimes[i] + "ns"); 280 } 281 282 changeExposure(requestBuilder, expTimes[i], sensitivities[j]); 283 mSession.capture(requestBuilder.build(), listener, mHandler); 284 285 // make sure timeout is long enough for long exposure time 286 long timeout = WAIT_FOR_RESULT_TIMEOUT_MS + expTimes[i]; 287 CaptureResult result = listener.getCaptureResult(timeout); 288 long resultExpTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME); 289 int resultSensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY); 290 validateExposureTime(expTimes[i], resultExpTime); 291 validateSensitivity(sensitivities[j], resultSensitivity); 292 validateFrameDurationForCapture(result); 293 } 294 } 295 // TODO: Add another case to test where we can submit all requests, then wait for 296 // results, which will hide the pipeline latency. this is not only faster, but also 297 // test high speed per frame control and synchronization. 298 } 299 300 //---------------------------------------------------------------- 301 //---------Below are common functions for all tests.-------------- 302 //---------------------------------------------------------------- 303 304 /** 305 * Enable exposure manual control and change exposure and sensitivity and 306 * clamp the value into the supported range. 307 */ 308 private void changeExposure(CaptureRequest.Builder requestBuilder, 309 long expTime, int sensitivity) { 310 // Check if the max analog sensitivity is available and no larger than max sensitivity. 311 // The max analog sensitivity is not actually used here. This is only an extra sanity check. 312 mStaticInfo.getMaxAnalogSensitivityChecked(); 313 314 expTime = mStaticInfo.getExposureClampToRange(expTime); 315 sensitivity = mStaticInfo.getSensitivityClampToRange(sensitivity); 316 317 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF); 318 requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTime); 319 requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity); 320 } 321 322 /** 323 * Get the exposure time array that contains multiple exposure time steps in 324 * the exposure time range. 325 */ 326 private long[] getExposureTimeTestValues() { 327 long[] testValues = new long[DEFAULT_NUM_EXPOSURE_TIME_STEPS + 1]; 328 long maxExpTime = mStaticInfo.getExposureMaximumOrDefault(DEFAULT_EXP_TIME_NS); 329 long minExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS); 330 331 long range = maxExpTime - minExpTime; 332 double stepSize = range / (double)DEFAULT_NUM_EXPOSURE_TIME_STEPS; 333 for (int i = 0; i < testValues.length; i++) { 334 testValues[i] = maxExpTime - (long)(stepSize * i); 335 testValues[i] = mStaticInfo.getExposureClampToRange(testValues[i]); 336 } 337 338 return testValues; 339 } 340 341 /** 342 * Get the sensitivity array that contains multiple sensitivity steps in the 343 * sensitivity range. 344 * <p> 345 * Sensitivity number of test values is determined by 346 * {@value #DEFAULT_SENSITIVITY_STEP_SIZE} and sensitivity range, and 347 * bounded by {@value #DEFAULT_NUM_SENSITIVITY_STEPS}. 348 * </p> 349 */ 350 private int[] getSensitivityTestValues() { 351 int maxSensitivity = mStaticInfo.getSensitivityMaximumOrDefault( 352 DEFAULT_SENSITIVITY); 353 int minSensitivity = mStaticInfo.getSensitivityMinimumOrDefault( 354 DEFAULT_SENSITIVITY); 355 356 int range = maxSensitivity - minSensitivity; 357 int stepSize = DEFAULT_SENSITIVITY_STEP_SIZE; 358 int numSteps = range / stepSize; 359 // Bound the test steps to avoid supper long test. 360 if (numSteps > DEFAULT_NUM_SENSITIVITY_STEPS) { 361 numSteps = DEFAULT_NUM_SENSITIVITY_STEPS; 362 stepSize = range / numSteps; 363 } 364 int[] testValues = new int[numSteps + 1]; 365 for (int i = 0; i < testValues.length; i++) { 366 testValues[i] = maxSensitivity - stepSize * i; 367 testValues[i] = mStaticInfo.getSensitivityClampToRange(testValues[i]); 368 } 369 370 return testValues; 371 } 372 373 /** 374 * Validate the AE manual control exposure time. 375 * 376 * <p>Exposure should be close enough, and only round down if they are not equal.</p> 377 * 378 * @param request Request exposure time 379 * @param result Result exposure time 380 */ 381 private void validateExposureTime(long request, long result) { 382 long expTimeDelta = request - result; 383 long expTimeErrorMargin = (long)(Math.max(EXPOSURE_TIME_ERROR_MARGIN_NS, request 384 * EXPOSURE_TIME_ERROR_MARGIN_RATE)); 385 // First, round down not up, second, need close enough. 386 mCollector.expectTrue("Exposture time is invalid for AE manaul control test, request: " 387 + request + " result: " + result, 388 expTimeDelta < expTimeErrorMargin && expTimeDelta >= 0); 389 } 390 391 /** 392 * Validate AE manual control sensitivity. 393 * 394 * @param request Request sensitivity 395 * @param result Result sensitivity 396 */ 397 private void validateSensitivity(int request, int result) { 398 float sensitivityDelta = request - result; 399 float sensitivityErrorMargin = request * SENSITIVITY_ERROR_MARGIN_RATE; 400 // First, round down not up, second, need close enough. 401 mCollector.expectTrue("Sensitivity is invalid for AE manaul control test, request: " 402 + request + " result: " + result, 403 sensitivityDelta < sensitivityErrorMargin && sensitivityDelta >= 0); 404 } 405 406 /** 407 * Validate frame duration for a given capture. 408 * 409 * <p>Frame duration should be longer than exposure time.</p> 410 * 411 * @param result The capture result for a given capture 412 */ 413 private void validateFrameDurationForCapture(CaptureResult result) { 414 long expTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME); 415 long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION); 416 if (VERBOSE) { 417 Log.v(TAG, "frame duration: " + frameDuration + " Exposure time: " + expTime); 418 } 419 420 mCollector.expectTrue(String.format("Frame duration (%d) should be longer than exposure" 421 + " time (%d) for a given capture", frameDuration, expTime), 422 frameDuration >= expTime); 423 424 validatePipelineDepth(result); 425 } 426 427 /** 428 * Validate the pipeline depth result. 429 * 430 * @param result The capture result to get pipeline depth data 431 */ 432 private void validatePipelineDepth(CaptureResult result) { 433 final byte MIN_PIPELINE_DEPTH = 1; 434 byte maxPipelineDepth = mStaticInfo.getPipelineMaxDepthChecked(); 435 Byte pipelineDepth = getValueNotNull(result, CaptureResult.REQUEST_PIPELINE_DEPTH); 436 mCollector.expectInRange(String.format("Pipeline depth must be in the range of [%d, %d]", 437 MIN_PIPELINE_DEPTH, maxPipelineDepth), pipelineDepth, MIN_PIPELINE_DEPTH, 438 maxPipelineDepth); 439 } 440 } 441