blob: 92eef806258319c92853af073a82f019d928f726 [file] [log] [blame]
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.camera2.cts;
import static android.hardware.camera2.cts.CameraTestUtils.*;
import static android.hardware.camera2.CameraCharacteristics.*;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
import android.hardware.camera2.params.ColorSpaceTransform;
import android.hardware.camera2.params.Face;
import android.hardware.camera2.params.LensShadingMap;
import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.params.RggbChannelVector;
import android.hardware.camera2.params.TonemapCurve;
import android.util.Log;
import android.util.Range;
import android.util.Rational;
import android.util.Size;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* <p>
* Basic test for camera CaptureRequest key controls.
* </p>
* <p>
* Several test categories are covered: manual sensor control, 3A control,
* manual ISP control and other per-frame control and synchronization.
* </p>
*/
public class CaptureRequestTest extends Camera2SurfaceViewTestCase {
private static final String TAG = "CaptureRequestTest";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final int NUM_FRAMES_VERIFIED = 15;
private static final int NUM_FACE_DETECTION_FRAMES_VERIFIED = 60;
/** 30ms exposure time must be supported by full capability devices. */
private static final long DEFAULT_EXP_TIME_NS = 30000000L;
private static final int DEFAULT_SENSITIVITY = 100;
private static final int RGGB_COLOR_CHANNEL_COUNT = 4;
private static final int MAX_SHADING_MAP_SIZE = 64 * 64 * RGGB_COLOR_CHANNEL_COUNT;
private static final int MIN_SHADING_MAP_SIZE = 1 * 1 * RGGB_COLOR_CHANNEL_COUNT;
private static final long IGNORE_REQUESTED_EXPOSURE_TIME_CHECK = -1L;
private static final long EXPOSURE_TIME_BOUNDARY_50HZ_NS = 10000000L; // 10ms
private static final long EXPOSURE_TIME_BOUNDARY_60HZ_NS = 8333333L; // 8.3ms, Approximation.
private static final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000L; // 100us, Approximation.
private static final int SENSITIVITY_ERROR_MARGIN = 10; // 10
private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 3;
private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 16;
private static final int DEFAULT_SENSITIVITY_STEP_SIZE = 100;
private static final int NUM_RESULTS_WAIT_TIMEOUT = 100;
private static final int NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY = 8;
private static final int NUM_TEST_FOCUS_DISTANCES = 10;
// 5 percent error margin for calibrated device
private static final float FOCUS_DISTANCE_ERROR_PERCENT_CALIBRATED = 0.05f;
// 25 percent error margin for uncalibrated device
private static final float FOCUS_DISTANCE_ERROR_PERCENT_UNCALIBRATED = 0.25f;
// 10 percent error margin for approximate device
private static final float FOCUS_DISTANCE_ERROR_PERCENT_APPROXIMATE = 0.10f;
private static final int ANTI_FLICKERING_50HZ = 1;
private static final int ANTI_FLICKERING_60HZ = 2;
// 5 percent error margin for resulting crop regions
private static final float CROP_REGION_ERROR_PERCENT_DELTA = 0.05f;
// 1 percent error margin for centering the crop region
private static final float CROP_REGION_ERROR_PERCENT_CENTERED = 0.01f;
// Linear tone mapping curve example.
private static final float[] TONEMAP_CURVE_LINEAR = {0, 0, 1.0f, 1.0f};
// Standard sRGB tone mapping, per IEC 61966-2-1:1999, with 16 control points.
private static final float[] TONEMAP_CURVE_SRGB = {
0.0000f, 0.0000f, 0.0667f, 0.2864f, 0.1333f, 0.4007f, 0.2000f, 0.4845f,
0.2667f, 0.5532f, 0.3333f, 0.6125f, 0.4000f, 0.6652f, 0.4667f, 0.7130f,
0.5333f, 0.7569f, 0.6000f, 0.7977f, 0.6667f, 0.8360f, 0.7333f, 0.8721f,
0.8000f, 0.9063f, 0.8667f, 0.9389f, 0.9333f, 0.9701f, 1.0000f, 1.0000f
};
private final Rational ZERO_R = new Rational(0, 1);
private final Rational ONE_R = new Rational(1, 1);
private final int NUM_ALGORITHMS = 3; // AE, AWB and AF
private final int INDEX_ALGORITHM_AE = 0;
private final int INDEX_ALGORITHM_AWB = 1;
private final int INDEX_ALGORITHM_AF = 2;
@Override
protected void setUp() throws Exception {
super.setUp();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
/**
* Test black level lock when exposure value change.
* <p>
* When {@link CaptureRequest#BLACK_LEVEL_LOCK} is true in a request, the
* camera device should lock the black level. When the exposure values are changed,
* the camera may require reset black level Since changes to certain capture
* parameters (such as exposure time) may require resetting of black level
* compensation. However, the black level must remain locked after exposure
* value changes (when requests have lock ON).
* </p>
*/
public void testBlackLevelLock() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
openDevice(mCameraIds[i]);
if (!mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
continue;
}
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// Start with default manual exposure time, with black level being locked.
requestBuilder.set(CaptureRequest.BLACK_LEVEL_LOCK, true);
changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, DEFAULT_SENSITIVITY);
Size previewSz =
getMaxPreviewSize(mCamera.getId(), mCameraManager,
getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
startPreview(requestBuilder, previewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// No lock OFF state is allowed as the exposure is not changed.
verifyBlackLevelLockResults(listener, NUM_FRAMES_VERIFIED, /*maxLockOffCnt*/0);
// Double the exposure time and gain, with black level still being locked.
changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS * 2, DEFAULT_SENSITIVITY * 2);
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, previewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// Allow at most one lock OFF state as the exposure is changed once.
verifyBlackLevelLockResults(listener, NUM_FRAMES_VERIFIED, /*maxLockOffCnt*/1);
stopPreview();
} finally {
closeDevice();
}
}
}
/**
* Basic lens shading map request test.
* <p>
* When {@link CaptureRequest#SHADING_MODE} is set to OFF, no lens shading correction will
* be applied by the camera device, and an identity lens shading map data
* will be provided if {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE} is ON.
* </p>
* <p>
* When {@link CaptureRequest#SHADING_MODE} is set to other modes, lens shading correction
* will be applied by the camera device. The lens shading map data can be
* requested by setting {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE} to ON.
* </p>
*/
public void testLensShadingMap() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
openDevice(mCameraIds[i]);
if (!mStaticInfo.isManualLensShadingMapSupported()) {
continue;
}
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// Shading map mode OFF, lensShadingMapMode ON, camera device
// should output unity maps.
requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_OFF);
requestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
STATISTICS_LENS_SHADING_MAP_MODE_ON);
Size previewSz =
getMaxPreviewSize(mCamera.getId(), mCameraManager,
getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, previewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyShadingMap(listener, NUM_FRAMES_VERIFIED, SHADING_MODE_OFF);
// Shading map mode FAST, lensShadingMapMode ON, camera device
// should output valid maps.
requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_FAST);
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, previewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// Allow at most one lock OFF state as the exposure is changed once.
verifyShadingMap(listener, NUM_FRAMES_VERIFIED, SHADING_MODE_FAST);
// Shading map mode HIGH_QUALITY, lensShadingMapMode ON, camera device
// should output valid maps.
requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_HIGH_QUALITY);
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, previewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyShadingMap(listener, NUM_FRAMES_VERIFIED, SHADING_MODE_HIGH_QUALITY);
stopPreview();
} finally {
closeDevice();
}
}
}
/**
* Test {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE} control.
* <p>
* Test all available anti-banding modes, check if the exposure time adjustment is
* correct.
* </p>
*/
public void testAntiBandingModes() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
openDevice(mCameraIds[i]);
// Without manual sensor control, exposure time cannot be verified
if (!mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
return;
}
int[] modes = mStaticInfo.getAeAvailableAntiBandingModesChecked();
Size previewSz =
getMaxPreviewSize(mCamera.getId(), mCameraManager,
getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
for (int mode : modes) {
antiBandingTestByMode(previewSz, mode);
}
} finally {
closeDevice();
}
}
}
/**
* Test AE mode and lock.
*
* <p>
* For AE lock, when it is locked, exposure parameters shouldn't be changed.
* For AE modes, each mode should satisfy the per frame controls defined in
* API specifications.
* </p>
*/
public void testAeModeAndLock() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
openDevice(mCameraIds[i]);
if (mStaticInfo.isHardwareLevelLegacy()) {
Log.i(TAG, "Skipping test on legacy devices");
continue;
}
Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
// Update preview surface with given size for all sub-tests.
updatePreviewSurface(maxPreviewSz);
// Test aeMode and lock
int[] aeModes = mStaticInfo.getAeAvailableModesChecked();
for (int mode : aeModes) {
aeModeAndLockTestByMode(mode);
}
} finally {
closeDevice();
}
}
}
/** Test {@link CaptureRequest#FLASH_MODE} control.
* <p>
* For each {@link CaptureRequest#FLASH_MODE} mode, test the flash control
* and {@link CaptureResult#FLASH_STATE} result.
* </p>
*/
public void testFlashControl() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
openDevice(mCameraIds[i]);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
startPreview(requestBuilder, maxPreviewSz, listener);
// Flash control can only be used when the AE mode is ON or OFF.
flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_ON);
// LEGACY won't support AE mode OFF
boolean aeOffModeSupported = false;
for (int aeMode : mStaticInfo.getAeAvailableModesChecked()) {
if (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF) {
aeOffModeSupported = true;
}
}
if (aeOffModeSupported) {
flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_OFF);
}
stopPreview();
} finally {
closeDevice();
}
}
}
/**
* Test face detection modes and results.
*/
public void testFaceDetection() throws Exception {
for (int i = 0; i < mCameraIds.length; i++) {
try {
openDevice(mCameraIds[i]);
faceDetectionTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test tone map modes and controls.
*/
public void testToneMapControl() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
if (!mStaticInfo.isManualToneMapSupported()) {
Log.i(TAG, "Camera " + id +
" doesn't support tone mapping controls, skipping test");
continue;
}
toneMapTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test color correction modes and controls.
*/
public void testColorCorrectionControl() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
if (!mStaticInfo.isManualColorCorrectionSupported()) {
Log.i(TAG, "Camera " + id +
" doesn't support color correction controls, skipping test");
continue;
}
colorCorrectionTestByCamera();
} finally {
closeDevice();
}
}
}
public void testEdgeModeControl() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
if (!mStaticInfo.isEdgeModeControlSupported()) {
Log.i(TAG, "Camera " + id +
" doesn't support EDGE_MODE controls, skipping test");
continue;
}
edgeModesTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test focus distance control.
*/
public void testFocusDistanceControl() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
if (!mStaticInfo.hasFocuser()) {
Log.i(TAG, "Camera " + id + " has no focuser, skipping test");
continue;
}
if (!mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
Log.i(TAG, "Camera " + id +
" does not support MANUAL_SENSOR, skipping test");
continue;
}
focusDistanceTestByCamera();
} finally {
closeDevice();
}
}
}
public void testNoiseReductionModeControl() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
if (!mStaticInfo.isNoiseReductionModeControlSupported()) {
Log.i(TAG, "Camera " + id +
" doesn't support noise reduction mode, skipping test");
continue;
}
noiseReductionModeTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test AWB lock control.
*
* <p>The color correction gain and transform shouldn't be changed when AWB is locked.</p>
*/
public void testAwbModeAndLock() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
if (mStaticInfo.isHardwareLevelLegacy()) {
Log.i(TAG, "Skipping test on legacy devices");
continue;
}
awbModeAndLockTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test different AF modes.
*/
public void testAfModes() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
afModeTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test video and optical stabilizations.
*/
public void testCameraStabilizations() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
List<Key<?>> keys = mStaticInfo.getCharacteristics().getKeys();
if (!(keys.contains(
CameraCharacteristics.CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES) ||
keys.contains(
CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION))) {
Log.i(TAG, "Camera " + id + " doesn't support any stabilization modes");
continue;
}
stabilizationTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test digitalZoom (center wise and non-center wise), validate the returned crop regions.
* The max preview size is used for each camera.
*/
public void testDigitalZoom() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
digitalZoomTestByCamera(maxPreviewSize);
} finally {
closeDevice();
}
}
}
/**
* Test digital zoom and all preview size combinations.
* TODO: this and above test should all be moved to preview test class.
*/
public void testDigitalZoomPreviewCombinations() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
digitalZoomPreviewCombinationTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test scene mode controls.
*/
public void testSceneModes() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
sceneModeTestByCamera();
} finally {
closeDevice();
}
}
}
/**
* Test effect mode controls.
*/
public void testEffectModes() throws Exception {
for (String id : mCameraIds) {
try {
openDevice(id);
effectModeTestByCamera();
} finally {
closeDevice();
}
}
}
// TODO: add 3A state machine test.
private void noiseReductionModeTestByCamera() throws Exception {
Size maxPrevSize = mOrderedPreviewSizes.get(0);
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
int[] availableModes = mStaticInfo.getAvailableNoiseReductionModesChecked();
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPrevSize, resultListener);
for (int mode : availableModes) {
requestBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, mode);
resultListener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyCaptureResultForKey(CaptureResult.NOISE_REDUCTION_MODE, mode,
resultListener, NUM_FRAMES_VERIFIED);
// Test that OFF and FAST mode should not slow down the frame rate.
if (mode == CaptureRequest.NOISE_REDUCTION_MODE_OFF ||
mode == CaptureRequest.NOISE_REDUCTION_MODE_FAST) {
verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
}
}
stopPreview();
}
private void focusDistanceTestByCamera() throws Exception {
Size maxPrevSize = mOrderedPreviewSizes.get(0);
float[] testDistances = getFocusDistanceTestValuesInOrder();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPrevSize, resultListener);
CaptureRequest request;
float[] resultDistances = new float[testDistances.length];
for (int i = 0; i < testDistances.length; i++) {
requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, testDistances[i]);
request = requestBuilder.build();
resultListener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(request, resultListener, mHandler);
waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
resultDistances[i] = verifyFocusDistanceControl(testDistances[i], request,
resultListener);
if (VERBOSE) {
Log.v(TAG, "Capture request focus distance: " + testDistances[i] + " result: "
+ resultDistances[i]);
}
}
// Verify the monotonicity
mCollector.checkArrayMonotonicityAndNotAllEqual(CameraTestUtils.toObject(resultDistances),
/*ascendingOrder*/true);
if (mStaticInfo.getCharacteristics().getKeys().
contains(CameraCharacteristics.LENS_INFO_HYPERFOCAL_DISTANCE)) {
// Test hyperfocal distance optionally
float hyperFocalDistance = mStaticInfo.getHyperfocalDistanceChecked();
if (hyperFocalDistance > 0) {
requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, hyperFocalDistance);
request = requestBuilder.build();
resultListener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(request, resultListener, mHandler);
waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// Then wait for the lens.state to be stationary.
waitForResultValue(resultListener, CaptureResult.LENS_STATE,
CaptureResult.LENS_STATE_STATIONARY, NUM_RESULTS_WAIT_TIMEOUT);
// Need get reasonably accurate value.
CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
Float focusDistance = getValueNotNull(result, CaptureResult.LENS_FOCUS_DISTANCE);
float errorMargin = FOCUS_DISTANCE_ERROR_PERCENT_UNCALIBRATED;
int calibrationStatus = mStaticInfo.getFocusDistanceCalibrationChecked();
if (calibrationStatus ==
CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED) {
errorMargin = FOCUS_DISTANCE_ERROR_PERCENT_CALIBRATED;
} else if (calibrationStatus ==
CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_APPROXIMATE) {
errorMargin = FOCUS_DISTANCE_ERROR_PERCENT_APPROXIMATE;
}
mCollector.expectInRange("Focus distance for hyper focal should be close enough to" +
"requested value", focusDistance,
hyperFocalDistance * (1.0f - errorMargin),
hyperFocalDistance * (1.0f + errorMargin)
);
}
}
}
/**
* Verify focus distance control.
*
* @param distance The focus distance requested
* @param request The capture request to control the manual focus distance
* @param resultListener The capture listener to recieve capture result callbacks
* @return the result focus distance
*/
private float verifyFocusDistanceControl(float distance, CaptureRequest request,
SimpleCaptureCallback resultListener) {
// Need make sure the result corresponding to the request is back, then check.
CaptureResult result =
resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
// Then wait for the lens.state to be stationary.
waitForResultValue(resultListener, CaptureResult.LENS_STATE,
CaptureResult.LENS_STATE_STATIONARY, NUM_RESULTS_WAIT_TIMEOUT);
// Then check the focus distance.
result = resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
Float resultDistance = getValueNotNull(result, CaptureResult.LENS_FOCUS_DISTANCE);
if (mStaticInfo.getFocusDistanceCalibrationChecked() ==
CameraCharacteristics.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED) {
// TODO: what's more to test for CALIBRATED devices?
}
float minValue = 0;
float maxValue = mStaticInfo.getMinimumFocusDistanceChecked();
mCollector.expectInRange("Result focus distance is out of range",
resultDistance, minValue, maxValue);
return resultDistance;
}
/**
* Verify edge mode control results.
*/
private void edgeModesTestByCamera() throws Exception {
Size maxPrevSize = mOrderedPreviewSizes.get(0);
int[] edgeModes = mStaticInfo.getAvailableEdgeModesChecked();
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPrevSize, resultListener);
for (int mode : edgeModes) {
requestBuilder.set(CaptureRequest.EDGE_MODE, mode);
resultListener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyCaptureResultForKey(CaptureResult.EDGE_MODE, mode, resultListener,
NUM_FRAMES_VERIFIED);
// Test that OFF and FAST mode should not slow down the frame rate.
if (mode == CaptureRequest.EDGE_MODE_OFF ||
mode == CaptureRequest.EDGE_MODE_FAST) {
verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED);
}
}
stopPreview();
}
/**
* Test color correction controls.
*
* <p>Test different color correction modes. For TRANSFORM_MATRIX, only test
* the unit gain and identity transform.</p>
*/
private void colorCorrectionTestByCamera() throws Exception {
CaptureRequest request;
CaptureResult result;
Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
updatePreviewSurface(maxPreviewSz);
CaptureRequest.Builder manualRequestBuilder = createRequestForPreview();
CaptureRequest.Builder previewRequestBuilder = createRequestForPreview();
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startPreview(previewRequestBuilder, maxPreviewSz, listener);
// Default preview result should give valid color correction metadata.
result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
validateColorCorrectionResult(result,
previewRequestBuilder.get(CaptureRequest.COLOR_CORRECTION_MODE));
// TRANSFORM_MATRIX mode
// Only test unit gain and identity transform
RggbChannelVector UNIT_GAIN = new RggbChannelVector(1.0f, 1.0f, 1.0f, 1.0f);
ColorSpaceTransform IDENTITY_TRANSFORM = new ColorSpaceTransform(
new Rational[] {
ONE_R, ZERO_R, ZERO_R,
ZERO_R, ONE_R, ZERO_R,
ZERO_R, ZERO_R, ONE_R
});
int colorCorrectionMode = CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX;
manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE, colorCorrectionMode);
manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_GAINS, UNIT_GAIN);
manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_TRANSFORM, IDENTITY_TRANSFORM);
request = manualRequestBuilder.build();
mSession.capture(request, listener, mHandler);
result = listener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
RggbChannelVector gains = result.get(CaptureResult.COLOR_CORRECTION_GAINS);
ColorSpaceTransform transform = result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM);
validateColorCorrectionResult(result, colorCorrectionMode);
mCollector.expectEquals("control mode result/request mismatch",
CaptureResult.CONTROL_MODE_OFF, result.get(CaptureResult.CONTROL_MODE));
mCollector.expectEquals("Color correction gain result/request mismatch",
UNIT_GAIN, gains);
mCollector.expectEquals("Color correction gain result/request mismatch",
IDENTITY_TRANSFORM, transform);
// FAST mode
colorCorrectionMode = CaptureRequest.COLOR_CORRECTION_MODE_FAST;
manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE, colorCorrectionMode);
request = manualRequestBuilder.build();
mSession.capture(request, listener, mHandler);
result = listener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
validateColorCorrectionResult(result, colorCorrectionMode);
mCollector.expectEquals("control mode result/request mismatch",
CaptureResult.CONTROL_MODE_AUTO, result.get(CaptureResult.CONTROL_MODE));
// HIGH_QUALITY mode
colorCorrectionMode = CaptureRequest.COLOR_CORRECTION_MODE_HIGH_QUALITY;
manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE, colorCorrectionMode);
request = manualRequestBuilder.build();
mSession.capture(request, listener, mHandler);
result = listener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT);
validateColorCorrectionResult(result, colorCorrectionMode);
mCollector.expectEquals("control mode result/request mismatch",
CaptureResult.CONTROL_MODE_AUTO, result.get(CaptureResult.CONTROL_MODE));
}
private void validateColorCorrectionResult(CaptureResult result, int colorCorrectionMode) {
final RggbChannelVector ZERO_GAINS = new RggbChannelVector(0, 0, 0, 0);
final int TRANSFORM_SIZE = 9;
Rational[] zeroTransform = new Rational[TRANSFORM_SIZE];
Arrays.fill(zeroTransform, ZERO_R);
final ColorSpaceTransform ZERO_TRANSFORM = new ColorSpaceTransform(zeroTransform);
RggbChannelVector resultGain;
if ((resultGain = mCollector.expectKeyValueNotNull(result,
CaptureResult.COLOR_CORRECTION_GAINS)) != null) {
mCollector.expectKeyValueNotEquals(result,
CaptureResult.COLOR_CORRECTION_GAINS, ZERO_GAINS);
}
ColorSpaceTransform resultTransform;
if ((resultTransform = mCollector.expectKeyValueNotNull(result,
CaptureResult.COLOR_CORRECTION_TRANSFORM)) != null) {
mCollector.expectKeyValueNotEquals(result,
CaptureResult.COLOR_CORRECTION_TRANSFORM, ZERO_TRANSFORM);
}
mCollector.expectEquals("color correction mode result/request mismatch",
colorCorrectionMode, result.get(CaptureResult.COLOR_CORRECTION_MODE));
}
/**
* Test flash mode control by AE mode.
* <p>
* Only allow AE mode ON or OFF, because other AE mode could run into conflict with
* flash manual control. This function expects the camera to already have an active
* repeating request and be sending results to the listener.
* </p>
*
* @param listener The Capture listener that is used to wait for capture result
* @param aeMode The AE mode for flash to test with
*/
private void flashTestByAeMode(SimpleCaptureCallback listener, int aeMode) throws Exception {
CaptureResult result;
final int NUM_FLASH_REQUESTS_TESTED = 10;
CaptureRequest.Builder requestBuilder = createRequestForPreview();
if (aeMode == CaptureRequest.CONTROL_AE_MODE_ON) {
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, aeMode);
} else if (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF) {
changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, DEFAULT_SENSITIVITY);
} else {
throw new IllegalArgumentException("This test only works when AE mode is ON or OFF");
}
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// For camera that doesn't have flash unit, flash state should always be UNAVAILABLE.
if (mStaticInfo.getFlashInfoChecked() == false) {
for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
mCollector.expectEquals("No flash unit available, flash state must be UNAVAILABLE"
+ "for AE mode " + aeMode, CaptureResult.FLASH_STATE_UNAVAILABLE,
result.get(CaptureResult.FLASH_STATE));
}
return;
}
// Test flash SINGLE mode control. Wait for flash state to be READY first.
if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
waitForResultValue(listener, CaptureResult.FLASH_STATE, CaptureResult.FLASH_STATE_READY,
NUM_RESULTS_WAIT_TIMEOUT);
} // else the settings were already waited on earlier
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);
CaptureRequest flashSinglerequest = requestBuilder.build();
int flashModeSingleRequests = captureRequestsSynchronized(
flashSinglerequest, listener, mHandler);
waitForNumResults(listener, flashModeSingleRequests - 1);
result = listener.getCaptureResultForRequest(flashSinglerequest, NUM_RESULTS_WAIT_TIMEOUT);
// Result mode must be SINGLE, state must be FIRED.
mCollector.expectEquals("Flash mode result must be SINGLE",
CaptureResult.FLASH_MODE_SINGLE, result.get(CaptureResult.FLASH_MODE));
mCollector.expectEquals("Flash state result must be FIRED",
CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE));
// Test flash TORCH mode control.
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
CaptureRequest torchRequest = requestBuilder.build();
int flashModeTorchRequests = captureRequestsSynchronized(torchRequest,
NUM_FLASH_REQUESTS_TESTED, listener, mHandler);
waitForNumResults(listener, flashModeTorchRequests - NUM_FLASH_REQUESTS_TESTED);
// Verify the results
for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) {
result = listener.getCaptureResultForRequest(torchRequest,
NUM_RESULTS_WAIT_TIMEOUT);
// Result mode must be TORCH, state must be FIRED
mCollector.expectEquals("Flash mode result must be TORCH",
CaptureResult.FLASH_MODE_TORCH, result.get(CaptureResult.FLASH_MODE));
mCollector.expectEquals("Flash state result must be FIRED",
CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE));
}
// Test flash OFF mode control
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
CaptureRequest flashOffrequest = requestBuilder.build();
int flashModeOffRequests = captureRequestsSynchronized(flashOffrequest, listener, mHandler);
waitForNumResults(listener, flashModeOffRequests - 1);
result = listener.getCaptureResultForRequest(flashOffrequest, NUM_RESULTS_WAIT_TIMEOUT);
mCollector.expectEquals("Flash mode result must be OFF", CaptureResult.FLASH_MODE_OFF,
result.get(CaptureResult.FLASH_MODE));
}
private void verifyAntiBandingMode(SimpleCaptureCallback listener, int numFramesVerified,
int mode, boolean isAeManual, long requestExpTime) throws Exception {
// Skip the first a couple of frames as antibanding may not be fully up yet.
final int NUM_FRAMES_SKIPPED = 5;
for (int i = 0; i < NUM_FRAMES_SKIPPED; i++) {
listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
}
for (int i = 0; i < numFramesVerified; i++) {
CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
Long resultExpTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
assertNotNull("Exposure time shouldn't be null", resultExpTime);
Integer flicker = result.get(CaptureResult.STATISTICS_SCENE_FLICKER);
// Scene flicker result should be always available.
assertNotNull("Scene flicker must not be null", flicker);
assertTrue("Scene flicker is invalid", flicker >= STATISTICS_SCENE_FLICKER_NONE &&
flicker <= STATISTICS_SCENE_FLICKER_60HZ);
if (isAeManual) {
// First, round down not up, second, need close enough.
validateExposureTime(requestExpTime, resultExpTime);
return;
}
long expectedExpTime = resultExpTime; // Default, no exposure adjustment.
if (mode == CONTROL_AE_ANTIBANDING_MODE_50HZ) {
// result exposure time must be adjusted by 50Hz illuminant source.
expectedExpTime =
getAntiFlickeringExposureTime(ANTI_FLICKERING_50HZ, resultExpTime);
} else if (mode == CONTROL_AE_ANTIBANDING_MODE_60HZ) {
// result exposure time must be adjusted by 60Hz illuminant source.
expectedExpTime =
getAntiFlickeringExposureTime(ANTI_FLICKERING_60HZ, resultExpTime);
} else if (mode == CONTROL_AE_ANTIBANDING_MODE_AUTO){
/**
* Use STATISTICS_SCENE_FLICKER to tell the illuminant source
* and do the exposure adjustment.
*/
expectedExpTime = resultExpTime;
if (flicker == STATISTICS_SCENE_FLICKER_60HZ) {
expectedExpTime =
getAntiFlickeringExposureTime(ANTI_FLICKERING_60HZ, resultExpTime);
} else if (flicker == STATISTICS_SCENE_FLICKER_50HZ) {
expectedExpTime =
getAntiFlickeringExposureTime(ANTI_FLICKERING_50HZ, resultExpTime);
}
}
if (Math.abs(resultExpTime - expectedExpTime) > EXPOSURE_TIME_ERROR_MARGIN_NS) {
mCollector.addMessage(String.format("Result exposure time %dns diverges too much"
+ " from expected exposure time %dns for mode %d when AE is auto",
resultExpTime, expectedExpTime, mode));
}
}
}
private void antiBandingTestByMode(Size size, int mode)
throws Exception {
if(VERBOSE) {
Log.v(TAG, "Anti-banding test for mode " + mode + " for camera " + mCamera.getId());
}
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, mode);
// Test auto AE mode anti-banding behavior
SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
startPreview(requestBuilder, size, resultListener);
waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyAntiBandingMode(resultListener, NUM_FRAMES_VERIFIED, mode, /*isAeManual*/false,
IGNORE_REQUESTED_EXPOSURE_TIME_CHECK);
// Test manual AE mode anti-banding behavior
// 65ms, must be supported by full capability devices.
final long TEST_MANUAL_EXP_TIME_NS = 65000000L;
long manualExpTime = mStaticInfo.getExposureClampToRange(TEST_MANUAL_EXP_TIME_NS);
changeExposure(requestBuilder, manualExpTime);
resultListener = new SimpleCaptureCallback();
startPreview(requestBuilder, size, resultListener);
waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyAntiBandingMode(resultListener, NUM_FRAMES_VERIFIED, mode, /*isAeManual*/true,
manualExpTime);
stopPreview();
}
/**
* Test the all available AE modes and AE lock.
* <p>
* For manual AE mode, test iterates through different sensitivities and
* exposure times, validate the result exposure time correctness. For
* CONTROL_AE_MODE_ON_ALWAYS_FLASH mode, the AE lock and flash are tested.
* For the rest of the AUTO mode, AE lock is tested.
* </p>
*
* @param mode
*/
private void aeModeAndLockTestByMode(int mode)
throws Exception {
switch (mode) {
case CONTROL_AE_MODE_OFF:
if (mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
// Test manual exposure control.
aeManualControlTest();
} else {
Log.w(TAG,
"aeModeAndLockTestByMode - can't test AE mode OFF without " +
"manual sensor control");
}
break;
case CONTROL_AE_MODE_ON:
case CONTROL_AE_MODE_ON_AUTO_FLASH:
case CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
case CONTROL_AE_MODE_ON_ALWAYS_FLASH:
// Test AE lock for above AUTO modes.
aeAutoModeTestLock(mode);
break;
default:
throw new UnsupportedOperationException("Unhandled AE mode " + mode);
}
}
/**
* Test AE auto modes.
* <p>
* Use single request rather than repeating request to test AE lock per frame control.
* </p>
*/
private void aeAutoModeTestLock(int mode) throws Exception {
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode);
configurePreviewOutput(requestBuilder);
final int MAX_NUM_CAPTURES_DURING_LOCK = 5;
for (int i = 1; i <= MAX_NUM_CAPTURES_DURING_LOCK; i++) {
autoAeMultipleCapturesThenTestLock(requestBuilder, mode, i);
}
}
/**
* Issue multiple auto AE captures, then lock AE, validate the AE lock vs.
* the first capture result after the AE lock. The right AE lock behavior is:
* When it is locked, it locks to the current exposure value, and all subsequent
* request with lock ON will have the same exposure value locked.
*/
private void autoAeMultipleCapturesThenTestLock(
CaptureRequest.Builder requestBuilder, int aeMode, int numCapturesDuringLock)
throws Exception {
if (numCapturesDuringLock < 1) {
throw new IllegalArgumentException("numCapturesBeforeLock must be no less than 1");
}
if (VERBOSE) {
Log.v(TAG, "Camera " + mCamera.getId() + ": Testing auto AE mode and lock for mode "
+ aeMode + " with " + numCapturesDuringLock + " captures before lock");
}
final int NUM_CAPTURES_BEFORE_LOCK = 2;
SimpleCaptureCallback listener = new SimpleCaptureCallback();
CaptureResult[] resultsDuringLock = new CaptureResult[numCapturesDuringLock];
// Reset the AE lock to OFF, since we are reusing this builder many times
requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false);
// Just send several captures with auto AE, lock off.
CaptureRequest request = requestBuilder.build();
for (int i = 0; i < NUM_CAPTURES_BEFORE_LOCK; i++) {
mSession.capture(request, listener, mHandler);
}
waitForNumResults(listener, NUM_CAPTURES_BEFORE_LOCK);
// Then fire several capture to lock the AE.
requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
int requestCount = captureRequestsSynchronized(
requestBuilder.build(), numCapturesDuringLock, listener, mHandler);
int[] sensitivities = new int[numCapturesDuringLock];
long[] expTimes = new long[numCapturesDuringLock];
Arrays.fill(sensitivities, -1);
Arrays.fill(expTimes, -1L);
// Get the AE lock on result and validate the exposure values.
waitForNumResults(listener, requestCount - numCapturesDuringLock);
for (int i = 0; i < resultsDuringLock.length; i++) {
resultsDuringLock[i] = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
}
for (int i = 0; i < numCapturesDuringLock; i++) {
mCollector.expectKeyValueEquals(
resultsDuringLock[i], CaptureResult.CONTROL_AE_LOCK, true);
}
// Can't read manual sensor/exposure settings without manual sensor
if (mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
int sensitivityLocked =
getValueNotNull(resultsDuringLock[0], CaptureResult.SENSOR_SENSITIVITY);
long expTimeLocked =
getValueNotNull(resultsDuringLock[0], CaptureResult.SENSOR_EXPOSURE_TIME);
for (int i = 1; i < resultsDuringLock.length; i++) {
mCollector.expectKeyValueEquals(
resultsDuringLock[i], CaptureResult.SENSOR_EXPOSURE_TIME, expTimeLocked);
mCollector.expectKeyValueEquals(
resultsDuringLock[i], CaptureResult.SENSOR_SENSITIVITY, sensitivityLocked);
}
}
}
/**
* Iterate through exposure times and sensitivities for manual AE control.
* <p>
* Use single request rather than repeating request to test manual exposure
* value change per frame control.
* </p>
*/
private void aeManualControlTest()
throws Exception {
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
configurePreviewOutput(requestBuilder);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
long[] expTimes = getExposureTimeTestValues();
int[] sensitivities = getSensitivityTestValues();
// Submit single request at a time, then verify the result.
for (int i = 0; i < expTimes.length; i++) {
for (int j = 0; j < sensitivities.length; j++) {
if (VERBOSE) {
Log.v(TAG, "Camera " + mCamera.getId() + ": Testing sensitivity "
+ sensitivities[j] + ", exposure time " + expTimes[i] + "ns");
}
changeExposure(requestBuilder, expTimes[i], sensitivities[j]);
mSession.capture(requestBuilder.build(), listener, mHandler);
// make sure timeout is long enough for long exposure time
long timeout = WAIT_FOR_RESULT_TIMEOUT_MS + expTimes[i];
CaptureResult result = listener.getCaptureResult(timeout);
long resultExpTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
int resultSensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY);
validateExposureTime(expTimes[i], resultExpTime);
validateSensitivity(sensitivities[j], resultSensitivity);
validateFrameDurationForCapture(result);
}
}
// TODO: Add another case to test where we can submit all requests, then wait for
// results, which will hide the pipeline latency. this is not only faster, but also
// test high speed per frame control and synchronization.
}
/**
* Verify black level lock control.
*/
private void verifyBlackLevelLockResults(SimpleCaptureCallback listener, int numFramesVerified,
int maxLockOffCnt) throws Exception {
int noLockCnt = 0;
for (int i = 0; i < numFramesVerified; i++) {
CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
Boolean blackLevelLock = result.get(CaptureResult.BLACK_LEVEL_LOCK);
assertNotNull("Black level lock result shouldn't be null", blackLevelLock);
// Count the lock == false result, which could possibly occur at most once.
if (blackLevelLock == false) {
noLockCnt++;
}
if(VERBOSE) {
Log.v(TAG, "Black level lock result: " + blackLevelLock);
}
}
assertTrue("Black level lock OFF occurs " + noLockCnt + " times, expect at most "
+ maxLockOffCnt + " for camera " + mCamera.getId(), noLockCnt <= maxLockOffCnt);
}
/**
* Verify shading map for different shading modes.
*/
private void verifyShadingMap(SimpleCaptureCallback listener, int numFramesVerified,
int shadingMode) throws Exception {
for (int i = 0; i < numFramesVerified; i++) {
CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
mCollector.expectEquals("Shading mode result doesn't match request",
shadingMode, result.get(CaptureResult.SHADING_MODE));
LensShadingMap mapObj = result.get(
CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP);
assertNotNull("Map object must not be null", mapObj);
int numElementsInMap = mapObj.getGainFactorCount();
float[] map = new float[numElementsInMap];
mapObj.copyGainFactors(map, /*offset*/0);
assertNotNull("Map must not be null", map);
assertFalse(String.format(
"Map size %d should be less than %d", numElementsInMap, MAX_SHADING_MAP_SIZE),
numElementsInMap >= MAX_SHADING_MAP_SIZE);
assertFalse(String.format("Map size %d should be no less than %d", numElementsInMap,
MIN_SHADING_MAP_SIZE), numElementsInMap < MIN_SHADING_MAP_SIZE);
if (shadingMode == CaptureRequest.SHADING_MODE_FAST ||
shadingMode == CaptureRequest.SHADING_MODE_HIGH_QUALITY) {
// shading mode is FAST or HIGH_QUALITY, expect to receive a map with all
// elements >= 1.0f
int badValueCnt = 0;
// Detect the bad values of the map data.
for (int j = 0; j < numElementsInMap; j++) {
if (Float.isNaN(map[j]) || map[j] < 1.0f) {
badValueCnt++;
}
}
assertEquals("Number of value in the map is " + badValueCnt + " out of "
+ numElementsInMap, /*expected*/0, /*actual*/badValueCnt);
} else if (shadingMode == CaptureRequest.SHADING_MODE_OFF) {
float[] unityMap = new float[numElementsInMap];
Arrays.fill(unityMap, 1.0f);
// shading mode is OFF, expect to receive a unity map.
assertTrue("Result map " + Arrays.toString(map) + " must be an unity map",
Arrays.equals(unityMap, map));
}
}
}
/**
* Test face detection for a camera.
*/
private void faceDetectionTestByCamera() throws Exception {
int[] faceDetectModes = mStaticInfo.getAvailableFaceDetectModesChecked();
SimpleCaptureCallback listener;
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
for (int mode : faceDetectModes) {
requestBuilder.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, mode);
if (VERBOSE) {
Log.v(TAG, "Start testing face detection mode " + mode);
}
// Create a new listener for each run to avoid the results from one run spill
// into another run.
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPreviewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyFaceDetectionResults(listener, NUM_FACE_DETECTION_FRAMES_VERIFIED, mode);
}
stopPreview();
}
/**
* Verify face detection results for different face detection modes.
*
* @param listener The listener to get capture result
* @param numFramesVerified Number of results to be verified
* @param faceDetectionMode Face detection mode to be verified against
*/
private void verifyFaceDetectionResults(SimpleCaptureCallback listener, int numFramesVerified,
int faceDetectionMode) {
for (int i = 0; i < numFramesVerified; i++) {
CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
mCollector.expectEquals("Result face detection mode should match the request",
faceDetectionMode, result.get(CaptureResult.STATISTICS_FACE_DETECT_MODE));
Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
List<Integer> faceIds = new ArrayList<Integer>(faces.length);
List<Integer> faceScores = new ArrayList<Integer>(faces.length);
if (faceDetectionMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
mCollector.expectEquals("Number of detection faces should always 0 for OFF mode",
0, faces.length);
} else if (faceDetectionMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE) {
for (Face face : faces) {
mCollector.expectNotNull("Face rectangle shouldn't be null", face.getBounds());
faceScores.add(face.getScore());
mCollector.expectTrue("Face id is expected to be -1 for SIMPLE mode",
face.getId() == Face.ID_UNSUPPORTED);
}
} else if (faceDetectionMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
if (VERBOSE) {
Log.v(TAG, "Number of faces detected: " + faces.length);
}
for (Face face : faces) {
Rect faceBound;
boolean faceRectAvailable = mCollector.expectTrue("Face rectangle "
+ "shouldn't be null", face.getBounds() != null);
if (!faceRectAvailable) {
continue;
}
faceBound = face.getBounds();
faceScores.add(face.getScore());
faceIds.add(face.getId());
mCollector.expectTrue("Face id is shouldn't be -1 for FULL mode",
face.getId() != Face.ID_UNSUPPORTED);
boolean leftEyeAvailable =
mCollector.expectTrue("Left eye position shouldn't be null",
face.getLeftEyePosition() != null);
boolean rightEyeAvailable =
mCollector.expectTrue("Right eye position shouldn't be null",
face.getRightEyePosition() != null);
boolean mouthAvailable =
mCollector.expectTrue("Mouth position shouldn't be null",
face.getMouthPosition() != null);
// Eyes/mouth position should be inside of the face rect.
if (leftEyeAvailable) {
Point leftEye = face.getLeftEyePosition();
mCollector.expectTrue("Left eye " + leftEye + "should be"
+ "inside of face rect " + faceBound,
faceBound.contains(leftEye.x, leftEye.y));
}
if (rightEyeAvailable) {
Point rightEye = face.getRightEyePosition();
mCollector.expectTrue("Right eye " + rightEye + "should be"
+ "inside of face rect " + faceBound,
faceBound.contains(rightEye.x, rightEye.y));
}
if (mouthAvailable) {
Point mouth = face.getMouthPosition();
mCollector.expectTrue("Mouth " + mouth + " should be inside of"
+ " face rect " + faceBound,
faceBound.contains(mouth.x, mouth.y));
}
}
}
mCollector.expectValuesInRange("Face scores are invalid", faceIds,
Face.SCORE_MIN, Face.SCORE_MAX);
mCollector.expectValuesUnique("Face ids are invalid", faceIds);
}
}
/**
* Test tone map mode and result by camera
*/
private void toneMapTestByCamera() throws Exception {
if (!mStaticInfo.isManualToneMapSupported()) {
return;
}
SimpleCaptureCallback listener;
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
int[] toneMapModes = mStaticInfo.getAvailableToneMapModesChecked();
for (int mode : toneMapModes) {
requestBuilder.set(CaptureRequest.TONEMAP_MODE, mode);
if (VERBOSE) {
Log.v(TAG, "Testing tonemap mode " + mode);
}
if (mode == CaptureRequest.TONEMAP_MODE_CONTRAST_CURVE) {
TonemapCurve tcLinear = new TonemapCurve(
TONEMAP_CURVE_LINEAR, TONEMAP_CURVE_LINEAR, TONEMAP_CURVE_LINEAR);
requestBuilder.set(CaptureRequest.TONEMAP_CURVE, tcLinear);
// Create a new listener for each run to avoid the results from one run spill
// into another run.
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPreviewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyToneMapModeResults(listener, NUM_FRAMES_VERIFIED, mode,
TONEMAP_CURVE_LINEAR);
TonemapCurve tcSrgb = new TonemapCurve(
TONEMAP_CURVE_SRGB, TONEMAP_CURVE_SRGB, TONEMAP_CURVE_SRGB);
requestBuilder.set(CaptureRequest.TONEMAP_CURVE, tcSrgb);
// Create a new listener for each run to avoid the results from one run spill
// into another run.
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPreviewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyToneMapModeResults(listener, NUM_FRAMES_VERIFIED, mode,
TONEMAP_CURVE_SRGB);
} else {
// Create a new listener for each run to avoid the results from one run spill
// into another run.
listener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPreviewSz, listener);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyToneMapModeResults(listener, NUM_FRAMES_VERIFIED, mode,
/*inputToneCurve*/null);
}
}
stopPreview();
}
/**
* Verify tonemap results.
* <p>
* Assumes R,G,B channels use the same tone curve
* </p>
*
* @param listener The capture listener used to get the capture results
* @param numFramesVerified Number of results to be verified
* @param tonemapMode Tonemap mode to verify
* @param inputToneCurve Tonemap curve used by all 3 channels, ignored when
* map mode is not CONTRAST_CURVE.
*/
private void verifyToneMapModeResults(SimpleCaptureCallback listener, int numFramesVerified,
int tonemapMode, float[] inputToneCurve) {
final int MIN_TONEMAP_CURVE_POINTS = 2;
final Float ZERO = new Float(0);
final Float ONE = new Float(1.0f);
int maxCurvePoints = mStaticInfo.getMaxTonemapCurvePointChecked();
for (int i = 0; i < numFramesVerified; i++) {
CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
mCollector.expectEquals("Capture result tonemap mode should match request", tonemapMode,
result.get(CaptureResult.TONEMAP_MODE));
TonemapCurve tc = getValueNotNull(result, CaptureResult.TONEMAP_CURVE);
int pointCount = tc.getPointCount(TonemapCurve.CHANNEL_RED);
float[] mapRed = new float[pointCount * TonemapCurve.POINT_SIZE];
pointCount = tc.getPointCount(TonemapCurve.CHANNEL_GREEN);
float[] mapGreen = new float[pointCount * TonemapCurve.POINT_SIZE];
pointCount = tc.getPointCount(TonemapCurve.CHANNEL_BLUE);
float[] mapBlue = new float[pointCount * TonemapCurve.POINT_SIZE];
tc.copyColorCurve(TonemapCurve.CHANNEL_RED, mapRed, 0);
tc.copyColorCurve(TonemapCurve.CHANNEL_GREEN, mapGreen, 0);
tc.copyColorCurve(TonemapCurve.CHANNEL_BLUE, mapBlue, 0);
if (tonemapMode == CaptureResult.TONEMAP_MODE_CONTRAST_CURVE) {
/**
* TODO: need figure out a good way to measure the difference
* between request and result, as they may have different array
* size.
*/
}
// Tonemap curve result availability and basic sanity check for all modes.
mCollector.expectValuesInRange("Tonemap curve red values are out of range",
CameraTestUtils.toObject(mapRed), /*min*/ZERO, /*max*/ONE);
mCollector.expectInRange("Tonemap curve red length is out of range",
mapRed.length, MIN_TONEMAP_CURVE_POINTS, maxCurvePoints * 2);
mCollector.expectValuesInRange("Tonemap curve green values are out of range",
CameraTestUtils.toObject(mapGreen), /*min*/ZERO, /*max*/ONE);
mCollector.expectInRange("Tonemap curve green length is out of range",
mapGreen.length, MIN_TONEMAP_CURVE_POINTS, maxCurvePoints * 2);
mCollector.expectValuesInRange("Tonemap curve blue values are out of range",
CameraTestUtils.toObject(mapBlue), /*min*/ZERO, /*max*/ONE);
mCollector.expectInRange("Tonemap curve blue length is out of range",
mapBlue.length, MIN_TONEMAP_CURVE_POINTS, maxCurvePoints * 2);
}
}
/**
* Test awb mode control.
* <p>
* Test each supported AWB mode, verify the AWB mode in capture result
* matches request. When AWB is locked, the color correction gains and
* transform should remain unchanged.
* </p>
*/
private void awbModeAndLockTestByCamera() throws Exception {
int[] awbModes = mStaticInfo.getAwbAvailableModesChecked();
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
startPreview(requestBuilder, maxPreviewSize, /*listener*/null);
for (int mode : awbModes) {
SimpleCaptureCallback listener;
requestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, mode);
listener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// Verify AWB mode in capture result.
verifyCaptureResultForKey(CaptureResult.CONTROL_AWB_MODE, mode, listener,
NUM_FRAMES_VERIFIED);
if (mode == CameraMetadata.CONTROL_AWB_MODE_AUTO) {
// Verify color correction transform and gains stay unchanged after a lock.
requestBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
listener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
if (mStaticInfo.areKeysAvailable(CaptureResult.CONTROL_AWB_STATE)) {
waitForResultValue(listener, CaptureResult.CONTROL_AWB_STATE,
CaptureResult.CONTROL_AWB_STATE_LOCKED, NUM_RESULTS_WAIT_TIMEOUT);
}
}
verifyAwbCaptureResultUnchanged(listener, NUM_FRAMES_VERIFIED);
}
}
private void verifyAwbCaptureResultUnchanged(SimpleCaptureCallback listener,
int numFramesVerified) {
// Skip check if cc gains/transform/mode are not available
if (!mStaticInfo.areKeysAvailable(
CaptureResult.COLOR_CORRECTION_GAINS,
CaptureResult.COLOR_CORRECTION_TRANSFORM,
CaptureResult.COLOR_CORRECTION_MODE)) {
return;
}
CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
RggbChannelVector lockedGains =
getValueNotNull(result, CaptureResult.COLOR_CORRECTION_GAINS);
ColorSpaceTransform lockedTransform =
getValueNotNull(result, CaptureResult.COLOR_CORRECTION_TRANSFORM);
for (int i = 0; i < numFramesVerified; i++) {
result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
// Color correction mode check is skipped here, as it is checked in colorCorrectionTest.
validateColorCorrectionResult(result, result.get(CaptureResult.COLOR_CORRECTION_MODE));
RggbChannelVector gains = getValueNotNull(result, CaptureResult.COLOR_CORRECTION_GAINS);
ColorSpaceTransform transform =
getValueNotNull(result, CaptureResult.COLOR_CORRECTION_TRANSFORM);
mCollector.expectEquals("Color correction gains should remain unchanged after awb lock",
lockedGains, gains);
mCollector.expectEquals("Color correction transform should remain unchanged after"
+ " awb lock", lockedTransform, transform);
}
}
/**
* Test AF mode control.
* <p>
* Test all supported AF modes, verify the AF mode in capture result matches
* request. When AF mode is one of the CONTROL_AF_MODE_CONTINUOUS_* mode,
* verify if the AF can converge to PASSIVE_FOCUSED or PASSIVE_UNFOCUSED
* state within certain amount of frames.
* </p>
*/
private void afModeTestByCamera() throws Exception {
int[] afModes = mStaticInfo.getAfAvailableModesChecked();
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
startPreview(requestBuilder, maxPreviewSize, /*listener*/null);
for (int mode : afModes) {
SimpleCaptureCallback listener;
requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mode);
listener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// Verify AF mode in capture result.
verifyCaptureResultForKey(CaptureResult.CONTROL_AF_MODE, mode, listener,
NUM_FRAMES_VERIFIED);
// Verify AF can finish a scan for CONTROL_AF_MODE_CONTINUOUS_* modes.
// In LEGACY mode, a transition to one of the continuous AF modes does not necessarily
// result in a passive AF call if the camera has already been focused, and the scene has
// not changed enough to trigger an AF pass. Skip this constraint for LEGACY.
if (mStaticInfo.isHardwareLevelLimitedOrBetter() &&
(mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE ||
mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO)) {
List<Integer> afStateList = new ArrayList<Integer>();
afStateList.add(CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED);
afStateList.add(CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED);
waitForAnyResultValue(listener, CaptureResult.CONTROL_AF_STATE, afStateList,
NUM_RESULTS_WAIT_TIMEOUT);
}
}
}
/**
* Test video and optical stabilizations if they are supported by a given camera.
*/
private void stabilizationTestByCamera() throws Exception {
// video stabilization test.
List<Key<?>> keys = mStaticInfo.getCharacteristics().getKeys();
int[] videoStabModes = (keys.contains(CameraCharacteristics.
CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES)) ?
mStaticInfo.getAvailableVideoStabilizationModesChecked() : new int[0];
int[] opticalStabModes = (keys.contains(
CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION)) ?
mStaticInfo.getAvailableOpticalStabilizationChecked() : new int[0];
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPreviewSize, listener);
for (int mode : videoStabModes) {
listener = new SimpleCaptureCallback();
requestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, mode);
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyCaptureResultForKey(CaptureResult.CONTROL_VIDEO_STABILIZATION_MODE, mode,
listener, NUM_FRAMES_VERIFIED);
}
for (int mode : opticalStabModes) {
listener = new SimpleCaptureCallback();
requestBuilder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, mode);
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyCaptureResultForKey(CaptureResult.LENS_OPTICAL_STABILIZATION_MODE, mode,
listener, NUM_FRAMES_VERIFIED);
}
stopPreview();
}
private void digitalZoomTestByCamera(Size previewSize) throws Exception {
final int ZOOM_STEPS = 15;
final PointF[] TEST_ZOOM_CENTERS;
final int croppingType = mStaticInfo.getScalerCroppingTypeChecked();
if (croppingType ==
CameraCharacteristics.SCALER_CROPPING_TYPE_FREEFORM) {
TEST_ZOOM_CENTERS = new PointF[] {
new PointF(0.5f, 0.5f), // Center point
new PointF(0.25f, 0.25f), // top left corner zoom, minimal zoom: 2x
new PointF(0.75f, 0.25f), // top right corner zoom, minimal zoom: 2x
new PointF(0.25f, 0.75f), // bottom left corner zoom, minimal zoom: 2x
new PointF(0.75f, 0.75f), // bottom right corner zoom, minimal zoom: 2x
};
if (VERBOSE) {
Log.v(TAG, "Testing zoom with CROPPING_TYPE = FREEFORM");
}
} else {
// CENTER_ONLY
TEST_ZOOM_CENTERS = new PointF[] {
new PointF(0.5f, 0.5f), // Center point
};
if (VERBOSE) {
Log.v(TAG, "Testing zoom with CROPPING_TYPE = CENTER_ONLY");
}
}
final float maxZoom = mStaticInfo.getAvailableMaxDigitalZoomChecked();
final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
Rect[] cropRegions = new Rect[ZOOM_STEPS];
MeteringRectangle[][] expectRegions = new MeteringRectangle[ZOOM_STEPS][];
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
updatePreviewSurface(previewSize);
configurePreviewOutput(requestBuilder);
CaptureRequest[] requests = new CaptureRequest[ZOOM_STEPS];
// Set algorithm regions to full active region
// TODO: test more different 3A regions
final MeteringRectangle[] defaultMeteringRect = new MeteringRectangle[] {
new MeteringRectangle (
/*x*/0, /*y*/0, activeArraySize.width(), activeArraySize.height(),
/*meteringWeight*/1)
};
for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
update3aRegion(requestBuilder, algo, defaultMeteringRect);
}
final int CAPTURE_SUBMIT_REPEAT;
{
int maxLatency = mStaticInfo.getSyncMaxLatency();
if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) {
CAPTURE_SUBMIT_REPEAT = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY + 1;
} else {
CAPTURE_SUBMIT_REPEAT = maxLatency + 1;
}
}
if (VERBOSE) {
Log.v(TAG, "Testing zoom with CAPTURE_SUBMIT_REPEAT = " + CAPTURE_SUBMIT_REPEAT);
}
for (PointF center : TEST_ZOOM_CENTERS) {
Rect previousCrop = null;
for (int i = 0; i < ZOOM_STEPS; i++) {
/*
* Submit capture request
*/
float zoomFactor = (float) (1.0f + (maxZoom - 1.0) * i / ZOOM_STEPS);
cropRegions[i] = getCropRegionForZoom(zoomFactor, center, maxZoom, activeArraySize);
if (VERBOSE) {
Log.v(TAG, "Testing Zoom for factor " + zoomFactor + " and center " +
center + " The cropRegion is " + cropRegions[i] +
" Preview size is " + previewSize);
}
requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, cropRegions[i]);
requests[i] = requestBuilder.build();
for (int j = 0; j < CAPTURE_SUBMIT_REPEAT; ++j) {
if (VERBOSE) {
Log.v(TAG, "submit crop region " + cropRegions[i]);
}
mSession.capture(requests[i], listener, mHandler);
}
/*
* Validate capture result
*/
waitForNumResults(listener, CAPTURE_SUBMIT_REPEAT - 1); // Drop first few frames
CaptureResult result = listener.getCaptureResultForRequest(
requests[i], NUM_RESULTS_WAIT_TIMEOUT);
Rect cropRegion = getValueNotNull(result, CaptureResult.SCALER_CROP_REGION);
/*
* Validate resulting crop regions
*/
if (previousCrop != null) {
Rect currentCrop = cropRegion;
mCollector.expectTrue(String.format(
"Crop region should shrink or stay the same " +
"(previous = %s, current = %s)",
previousCrop, currentCrop),
previousCrop.equals(currentCrop) ||
(previousCrop.width() > currentCrop.width() &&
previousCrop.height() > currentCrop.height()));
}
if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
mCollector.expectRectsAreSimilar(
"Request and result crop region should be similar",
cropRegions[i], cropRegion, CROP_REGION_ERROR_PERCENT_DELTA);
}
if (croppingType == SCALER_CROPPING_TYPE_CENTER_ONLY) {
mCollector.expectRectCentered(
"Result crop region should be centered inside the active array",
new Size(activeArraySize.width(), activeArraySize.height()),
cropRegion, CROP_REGION_ERROR_PERCENT_CENTERED);
}
/*
* Validate resulting metering regions
*/
// Use the actual reported crop region to calculate the resulting metering region
expectRegions[i] = getExpectedOutputRegion(
/*requestRegion*/defaultMeteringRect,
/*cropRect*/ cropRegion);
// Verify Output 3A region is intersection of input 3A region and crop region
for (int algo = 0; algo < NUM_ALGORITHMS; algo++) {
validate3aRegion(result, algo, expectRegions[i]);
}
previousCrop = cropRegion;
}
if (maxZoom > 1.0f) {
mCollector.expectTrue(
String.format("Most zoomed-in crop region should be smaller" +
"than active array w/h" +
"(last crop = %s, active array = %s)",
previousCrop, activeArraySize),
(previousCrop.width() < activeArraySize.width() &&
previousCrop.height() < activeArraySize.height()));
}
}
}
private void digitalZoomPreviewCombinationTestByCamera() throws Exception {
final double ASPECT_RATIO_THRESHOLD = 0.001;
List<Double> aspectRatiosTested = new ArrayList<Double>();
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
aspectRatiosTested.add((double)(maxPreviewSize.getWidth()) / maxPreviewSize.getHeight());
for (Size size : mOrderedPreviewSizes) {
// Max preview size was already tested in testDigitalZoom test. skip it.
if (size.equals(maxPreviewSize)) {
continue;
}
// Only test the largest size for each aspect ratio.
double aspectRatio = (double)(size.getWidth()) / size.getHeight();
if (isAspectRatioContained(aspectRatiosTested, aspectRatio, ASPECT_RATIO_THRESHOLD)) {
continue;
}
if (VERBOSE) {
Log.v(TAG, "Test preview size " + size.toString() + " digital zoom");
}
aspectRatiosTested.add(aspectRatio);
digitalZoomTestByCamera(size);
}
}
private static boolean isAspectRatioContained(List<Double> aspectRatioList,
double aspectRatio, double delta) {
for (Double ratio : aspectRatioList) {
if (Math.abs(ratio - aspectRatio) < delta) {
return true;
}
}
return false;
}
private void sceneModeTestByCamera() throws Exception {
int[] sceneModes = mStaticInfo.getAvailableSceneModesChecked();
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
requestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
startPreview(requestBuilder, maxPreviewSize, listener);
for(int mode : sceneModes) {
requestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, mode);
listener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyCaptureResultForKey(CaptureResult.CONTROL_SCENE_MODE,
mode, listener, NUM_FRAMES_VERIFIED);
// This also serves as purpose of showing preview for NUM_FRAMES_VERIFIED
verifyCaptureResultForKey(CaptureResult.CONTROL_MODE,
CaptureRequest.CONTROL_MODE_USE_SCENE_MODE, listener, NUM_FRAMES_VERIFIED);
}
}
private void effectModeTestByCamera() throws Exception {
int[] effectModes = mStaticInfo.getAvailableEffectModesChecked();
Size maxPreviewSize = mOrderedPreviewSizes.get(0);
CaptureRequest.Builder requestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
requestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
SimpleCaptureCallback listener = new SimpleCaptureCallback();
startPreview(requestBuilder, maxPreviewSize, listener);
for(int mode : effectModes) {
requestBuilder.set(CaptureRequest.CONTROL_EFFECT_MODE, mode);
listener = new SimpleCaptureCallback();
mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
verifyCaptureResultForKey(CaptureResult.CONTROL_EFFECT_MODE,
mode, listener, NUM_FRAMES_VERIFIED);
// This also serves as purpose of showing preview for NUM_FRAMES_VERIFIED
verifyCaptureResultForKey(CaptureResult.CONTROL_MODE,
CaptureRequest.CONTROL_MODE_AUTO, listener, NUM_FRAMES_VERIFIED);
}
}
//----------------------------------------------------------------
//---------Below are common functions for all tests.--------------
//----------------------------------------------------------------
/**
* Enable exposure manual control and change exposure and sensitivity and
* clamp the value into the supported range.
*/
private void changeExposure(CaptureRequest.Builder requestBuilder,
long expTime, int sensitivity) {
// Check if the max analog sensitivity is available and no larger than max sensitivity.
// The max analog sensitivity is not actually used here. This is only an extra sanity check.
mStaticInfo.getMaxAnalogSensitivityChecked();
expTime = mStaticInfo.getExposureClampToRange(expTime);
sensitivity = mStaticInfo.getSensitivityClampToRange(sensitivity);
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF);
requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTime);
requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity);
}
/**
* Enable exposure manual control and change exposure time and
* clamp the value into the supported range.
*
* <p>The sensitivity is set to default value.</p>
*/
private void changeExposure(CaptureRequest.Builder requestBuilder, long expTime) {
changeExposure(requestBuilder, expTime, DEFAULT_SENSITIVITY);
}
/**
* Get the exposure time array that contains multiple exposure time steps in
* the exposure time range.
*/
private long[] getExposureTimeTestValues() {
long[] testValues = new long[DEFAULT_NUM_EXPOSURE_TIME_STEPS + 1];
long maxExpTime = mStaticInfo.getExposureMaximumOrDefault(DEFAULT_EXP_TIME_NS);
long minxExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS);
long range = maxExpTime - minxExpTime;
double stepSize = range / (double)DEFAULT_NUM_EXPOSURE_TIME_STEPS;
for (int i = 0; i < testValues.length; i++) {
testValues[i] = minxExpTime + (long)(stepSize * i);
testValues[i] = mStaticInfo.getExposureClampToRange(testValues[i]);
}
return testValues;
}
/**
* Generate test focus distances in range of [0, minFocusDistance] in increasing order.
*/
private float[] getFocusDistanceTestValuesInOrder() {
float[] testValues = new float[NUM_TEST_FOCUS_DISTANCES + 1];
float minValue = 0;
float maxValue = mStaticInfo.getMinimumFocusDistanceChecked();
float range = maxValue - minValue;
float stepSize = range / NUM_TEST_FOCUS_DISTANCES;
for (int i = 0; i < testValues.length; i++) {
testValues[i] = minValue + stepSize * i;
}
return testValues;
}
/**
* Get the sensitivity array that contains multiple sensitivity steps in the
* sensitivity range.
* <p>
* Sensitivity number of test values is determined by
* {@value #DEFAULT_SENSITIVITY_STEP_SIZE} and sensitivity range, and
* bounded by {@value #DEFAULT_NUM_SENSITIVITY_STEPS}.
* </p>
*/
private int[] getSensitivityTestValues() {
int maxSensitivity = mStaticInfo.getSensitivityMaximumOrDefault(
DEFAULT_SENSITIVITY);
int minSensitivity = mStaticInfo.getSensitivityMinimumOrDefault(
DEFAULT_SENSITIVITY);
int range = maxSensitivity - minSensitivity;
int stepSize = DEFAULT_SENSITIVITY_STEP_SIZE;
int numSteps = range / stepSize;
// Bound the test steps to avoid supper long test.
if (numSteps > DEFAULT_NUM_SENSITIVITY_STEPS) {
numSteps = DEFAULT_NUM_SENSITIVITY_STEPS;
stepSize = range / numSteps;
}
int[] testValues = new int[numSteps + 1];
for (int i = 0; i < testValues.length; i++) {
testValues[i] = minSensitivity + stepSize * i;
testValues[i] = mStaticInfo.getSensitivityClampToRange(testValues[i]);
}
return testValues;
}
/**
* Validate the AE manual control exposure time.
*
* <p>Exposure should be close enough, and only round down if they are not equal.</p>
*
* @param request Request exposure time
* @param result Result exposure time
*/
private void validateExposureTime(long request, long result) {
long expTimeDelta = request - result;
// First, round down not up, second, need close enough.
mCollector.expectTrue("Exposture time is invalid for AE manaul control test, request: "
+ request + " result: " + result,
expTimeDelta < EXPOSURE_TIME_ERROR_MARGIN_NS && expTimeDelta >= 0);
}
/**
* Validate AE manual control sensitivity.
*
* @param request Request sensitivity
* @param result Result sensitivity
*/
private void validateSensitivity(int request, int result) {
int sensitivityDelta = request - result;
// First, round down not up, second, need close enough.
mCollector.expectTrue("Sensitivity is invalid for AE manaul control test, request: "
+ request + " result: " + result,
sensitivityDelta < SENSITIVITY_ERROR_MARGIN && sensitivityDelta >= 0);
}
/**
* Validate frame duration for a given capture.
*
* <p>Frame duration should be longer than exposure time.</p>
*
* @param result The capture result for a given capture
*/
private void validateFrameDurationForCapture(CaptureResult result) {
long expTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
if (VERBOSE) {
Log.v(TAG, "frame duration: " + frameDuration + " Exposure time: " + expTime);
}
mCollector.expectTrue(String.format("Frame duration (%d) should be longer than exposure"
+ " time (%d) for a given capture", frameDuration, expTime),
frameDuration >= expTime);
validatePipelineDepth(result);
}
private <T> T getValueNotNull(CaptureResult result, CaptureResult.Key<T> key) {
T value = result.get(key);
assertNotNull("Value of Key " + key.getName() + " shouldn't be null", value);
return value;
}
/**
* Basic verification for the control mode capture result.
*
* @param key The capture result key to be verified against
* @param requestMode The request mode for this result
* @param listener The capture listener to get capture results
* @param numFramesVerified The number of capture results to be verified
*/
private <T> void verifyCaptureResultForKey(CaptureResult.Key<T> key, T requestMode,
SimpleCaptureCallback listener, int numFramesVerified) {
for (int i = 0; i < numFramesVerified; i++) {
CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
validatePipelineDepth(result);
T resultMode = getValueNotNull(result, key);
if (VERBOSE) {
Log.v(TAG, "Expect value: " + requestMode.toString() + " result value: "
+ resultMode.toString());
}
mCollector.expectEquals("Key " + key.getName() + " result should match request",
requestMode, resultMode);
}
}
/**
* Verify if the fps is slow down for given input request with certain
* controls inside.
* <p>
* This method selects a max preview size for each fps range, and then
* configure the preview stream. Preview is started with the max preview
* size, and then verify if the result frame duration is in the frame
* duration range.
* </p>
*
* @param requestBuilder The request builder that contains post-processing
* controls that could impact the output frame rate, such as
* {@link CaptureRequest.NOISE_REDUCTION_MODE}. The value of
* these controls must be set to some values such that the frame
* rate is not slow down.
* @param numFramesVerified The number of frames to be verified
*/
private void verifyFpsNotSlowDown(CaptureRequest.Builder requestBuilder,
int numFramesVerified) throws Exception {
boolean frameDurationAvailable = true;
// Allow a few frames for AE to settle on target FPS range
final int NUM_FRAME_TO_SKIP = 6;
float frameDurationErrorMargin = FRAME_DURATION_ERROR_MARGIN;
if (!mStaticInfo.areKeysAvailable(CaptureResult.SENSOR_FRAME_DURATION)) {
frameDurationAvailable = false;
// Allow a larger error margin (1.5%) for timestamps
frameDurationErrorMargin = 0.015f;
}
Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
Range<Integer> fpsRange;
SimpleCaptureCallback resultListener;
for (int i = 0; i < fpsRanges.length; i += 1) {
fpsRange = fpsRanges[i];
Size previewSz = getMaxPreviewSizeForFpsRange(fpsRange);
// If unable to find a preview size, then log the failure, and skip this run.
if (previewSz == null) {
if (mStaticInfo.isCapabilitySupported(
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
mCollector.addMessage(String.format(
"Unable to find a preview size supporting given fps range %s",
fpsRange));
}
continue;
}
if (VERBOSE) {
Log.v(TAG, String.format("Test fps range %s for preview size %s",
fpsRange, previewSz.toString()));
}
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
// Turn off auto antibanding to avoid exposure time and frame duration interference
// from antibanding algorithm.
if (antiBandingOffIsSupported) {
requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE,
CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_OFF);
} else {
// The device doesn't implement the OFF mode, test continues. It need make sure
// that the antibanding algorithm doesn't slow down the fps.
Log.i(TAG, "OFF antibanding mode is not supported, the camera device output must" +
" not slow down the frame rate regardless of its current antibanding" +
" mode");
}
resultListener = new SimpleCaptureCallback();
startPreview(requestBuilder, previewSz, resultListener);
waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
// Wait several more frames for AE to settle on target FPS range
waitForNumResults(resultListener, NUM_FRAME_TO_SKIP);
long[] frameDurationRange = new long[]{
(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
long captureTime = 0, prevCaptureTime = 0;
for (int j = 0; j < numFramesVerified; j++) {
long frameDuration = frameDurationRange[0];
CaptureResult result =
resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
validatePipelineDepth(result);
if (frameDurationAvailable) {
frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
} else {
// if frame duration is not available, check timestamp instead
captureTime = getValueNotNull(result, CaptureResult.SENSOR_TIMESTAMP);
if (j > 0) {
frameDuration = captureTime - prevCaptureTime;
}
prevCaptureTime = captureTime;
}
mCollector.expectInRange(
"Frame duration must be in the range of " +
Arrays.toString(frameDurationRange),
frameDuration,
(long) (frameDurationRange[0] * (1 - frameDurationErrorMargin)),
(long) (frameDurationRange[1] * (1 + frameDurationErrorMargin)));
}
}
mSession.stopRepeating();
}
/**
* Validate the pipeline depth result.
*
* @param result The capture result to get pipeline depth data
*/
private void validatePipelineDepth(CaptureResult result) {
final byte MIN_PIPELINE_DEPTH = 1;
byte maxPipelineDepth = mStaticInfo.getPipelineMaxDepthChecked();
Byte pipelineDepth = getValueNotNull(result, CaptureResult.REQUEST_PIPELINE_DEPTH);
mCollector.expectInRange(String.format("Pipeline depth must be in the range of [%d, %d]",
MIN_PIPELINE_DEPTH, maxPipelineDepth), pipelineDepth, MIN_PIPELINE_DEPTH,
maxPipelineDepth);
}
/**
* Calculate the anti-flickering corrected exposure time.
* <p>
* If the input exposure time is very short (shorter than flickering
* boundary), which indicate the scene is bright and very likely at outdoor
* environment, skip the correction, as it doesn't make much sense by doing so.
* </p>
* <p>
* For long exposure time (larger than the flickering boundary), find the
* exposure time that is closest to the flickering boundary.
* </p>
*
* @param flickeringMode The flickering mode
* @param exposureTime The input exposureTime to be corrected
* @return anti-flickering corrected exposure time
*/
private long getAntiFlickeringExposureTime(int flickeringMode, long exposureTime) {
if (flickeringMode != ANTI_FLICKERING_50HZ && flickeringMode != ANTI_FLICKERING_60HZ) {
throw new IllegalArgumentException("Input anti-flickering mode must be 50 or 60Hz");
}
long flickeringBoundary = EXPOSURE_TIME_BOUNDARY_50HZ_NS;
if (flickeringMode == ANTI_FLICKERING_60HZ) {
flickeringBoundary = EXPOSURE_TIME_BOUNDARY_60HZ_NS;
}
if (exposureTime <= flickeringBoundary) {
return exposureTime;
}
// Find the closest anti-flickering corrected exposure time
long correctedExpTime = exposureTime + (flickeringBoundary / 2);
correctedExpTime = correctedExpTime - (correctedExpTime % flickeringBoundary);
return correctedExpTime;
}
/**
* Update one 3A region in capture request builder if that region is supported. Do nothing
* if the specified 3A region is not supported by camera device.
* @param requestBuilder The request to be updated
* @param algoIdx The index to the algorithm. (AE: 0, AWB: 1, AF: 2)
* @param regions The 3A regions to be set
*/
private void update3aRegion(
CaptureRequest.Builder requestBuilder, int algoIdx, MeteringRectangle[] regions)
{
int maxRegions;
CaptureRequest.Key<MeteringRectangle[]> key;
if (regions == null || regions.length == 0) {
throw new IllegalArgumentException("Invalid input 3A region!");
}
switch (algoIdx) {
case INDEX_ALGORITHM_AE:
maxRegions = mStaticInfo.getAeMaxRegionsChecked();
key = CaptureRequest.CONTROL_AE_REGIONS;
break;
case INDEX_ALGORITHM_AWB:
maxRegions = mStaticInfo.getAwbMaxRegionsChecked();
key = CaptureRequest.CONTROL_AWB_REGIONS;
break;
case INDEX_ALGORITHM_AF:
maxRegions = mStaticInfo.getAfMaxRegionsChecked();
key = CaptureRequest.CONTROL_AF_REGIONS;
break;
default:
throw new IllegalArgumentException("Unknown 3A Algorithm!");
}
if (maxRegions >= regions.length) {
requestBuilder.set(key, regions);
}
}
/**
* Validate one 3A region in capture result equals to expected region if that region is
* supported. Do nothing if the specified 3A region is not supported by camera device.
* @param result The capture result to be validated
* @param algoIdx The index to the algorithm. (AE: 0, AWB: 1, AF: 2)
* @param expectRegions The 3A regions expected in capture result
*/
private void validate3aRegion(
CaptureResult result, int algoIdx, MeteringRectangle[] expectRegions)
{
int maxRegions;
CaptureResult.Key<MeteringRectangle[]> key;
MeteringRectangle[] actualRegion;
switch (algoIdx) {
case INDEX_ALGORITHM_AE:
maxRegions = mStaticInfo.getAeMaxRegionsChecked();
key = CaptureResult.CONTROL_AE_REGIONS;
break;
case INDEX_ALGORITHM_AWB:
maxRegions = mStaticInfo.getAwbMaxRegionsChecked();
key = CaptureResult.CONTROL_AWB_REGIONS;
break;
case INDEX_ALGORITHM_AF:
maxRegions = mStaticInfo.getAfMaxRegionsChecked();
key = CaptureResult.CONTROL_AF_REGIONS;
break;
default:
throw new IllegalArgumentException("Unknown 3A Algorithm!");
}
if (maxRegions > 0)
{
actualRegion = getValueNotNull(result, key);
mCollector.expectEquals(
"Expected 3A regions: " + Arrays.toString(expectRegions) +
" does not match actual one: " + Arrays.toString(actualRegion),
expectRegions, actualRegion);
}
}
}