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
17package com.android.mediaframeworktest.stress;
18
19import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
20import com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
21
22import android.hardware.camera2.CameraCharacteristics;
23import android.hardware.camera2.CameraDevice;
24import android.hardware.camera2.CaptureRequest;
25import android.hardware.camera2.CaptureResult;
26import android.util.Log;
27import android.util.Size;
28
29import java.util.Arrays;
30
31import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_OFF;
32import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON;
33import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
34import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH;
35import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
36import 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 */
54public 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