Home | History | Annotate | Download | only in stress
      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