/* * 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.CameraCharacteristics.*; import static android.hardware.camera2.cts.CameraTestUtils.*; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.SurfaceTexture; 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.TotalCaptureResult; import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback; import android.hardware.camera2.cts.helpers.StaticMetadata; import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; import android.hardware.camera2.params.BlackLevelPattern; import android.hardware.camera2.params.Capability; 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.hardware.cts.helpers.CameraUtils; import android.media.Image; import android.os.Build; import android.os.Parcel; import android.platform.test.annotations.AppModeFull; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.Range; import android.util.Rational; import android.util.Size; import android.view.Surface; import com.android.compatibility.common.util.PropertyUtil; import com.android.internal.camera.flags.Flags; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** *

* Basic test for camera CaptureRequest key controls. *

*

* Several test categories are covered: manual sensor control, 3A control, * manual ISP control and other per-frame control and synchronization. *

*/ @RunWith(Parameterized.class) 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; // 30ms 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 float EXPOSURE_TIME_ERROR_MARGIN_RATE = 0.03f; // 3%, Approximation. private static final float SENSITIVITY_ERROR_MARGIN_RATE = 0.06f; // 6%, Approximation. private static final int DEFAULT_NUM_EXPOSURE_TIME_STEPS = 3; private static final int DEFAULT_NUM_SENSITIVITY_STEPS = 8; 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_FRAMES_WAITED_FOR_TORCH = 100; private static final int NUM_PARTIAL_FRAMES_PFC = 2; private static final int NUM_PARTIAL_FRAMES_NPFC = 6; private static final int NUM_TEST_FOCUS_DISTANCES = 10; private static final int NUM_FOCUS_DISTANCES_REPEAT = 3; // 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; // 1 percent boundary margin for focus range verify private static final float FOCUS_RANGE_BOUNDARY_MARGIN_PERCENT = 0.01f; 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; private static final float ZOOM_RATIO_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; private static final float DYNAMIC_VS_FIXED_BLK_WH_LVL_ERROR_MARGIN = 0.25f; private static final float DYNAMIC_VS_OPTICAL_BLK_LVL_ERROR_MARGIN = 0.2f; // 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 static final int ZOOM_STEPS = 15; private enum TorchSeqState { RAMPING_UP, FIRED, RAMPING_DOWN } @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); @Override public void setUp() throws Exception { super.setUp(); } @Override public void tearDown() throws Exception { super.tearDown(); } /** * Test CaptureRequest settings parcelling. */ @Test public void testSettingsBinderParcel() throws Exception { SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5); Surface surface = new Surface(outputTexture); String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { openDevice(cameraIdsUnderTest[i]); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(surface); // Check regular/default case CaptureRequest captureRequestOriginal = requestBuilder.build(); Parcel p; p = Parcel.obtain(); captureRequestOriginal.writeToParcel(p, 0); p.setDataPosition(0); CaptureRequest captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p); assertEquals("Parcelled camera settings should match", captureRequestParcelled.get(CaptureRequest.CONTROL_CAPTURE_INTENT), new Integer(CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW)); p.recycle(); // Check capture request with additional physical camera settings String physicalId; if (TextUtils.isDigitsOnly(cameraIdsUnderTest[i])) { physicalId = new String( Integer.toString(Integer.valueOf(cameraIdsUnderTest[i]) + 1)); } else { physicalId = new String(Integer.toString(i + 1)); } ArraySet physicalIds = new ArraySet (); physicalIds.add(physicalId); requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIds); requestBuilder.addTarget(surface); captureRequestOriginal = requestBuilder.build(); p = Parcel.obtain(); captureRequestOriginal.writeToParcel(p, 0); p.setDataPosition(0); captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p); assertEquals("Parcelled camera settings should match", captureRequestParcelled.get(CaptureRequest.CONTROL_CAPTURE_INTENT), new Integer(CameraMetadata.CONTROL_CAPTURE_INTENT_PREVIEW)); p.recycle(); // Check consistency between parcel write and read by stacking 2 // CaptureRequest objects when writing and reading. p = Parcel.obtain(); captureRequestOriginal.writeToParcel(p, 0); captureRequestOriginal.writeToParcel(p, 0); p.setDataPosition(0); captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p); captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p); p.recycle(); // Check various invalid cases p = Parcel.obtain(); p.writeInt(-1); p.setDataPosition(0); try { captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p); fail("should get RuntimeException due to invalid number of settings"); } catch (RuntimeException e) { // Expected } p.recycle(); p = Parcel.obtain(); p.writeInt(0); p.setDataPosition(0); try { captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p); fail("should get RuntimeException due to invalid number of settings"); } catch (RuntimeException e) { // Expected } p.recycle(); p = Parcel.obtain(); p.writeInt(1); p.setDataPosition(0); try { captureRequestParcelled = CaptureRequest.CREATOR.createFromParcel(p); fail("should get RuntimeException due to absent settings"); } catch (RuntimeException e) { // Expected } p.recycle(); } finally { closeDevice(); } } } /** * Test black level lock when exposure value change. *

* 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). *

*/ @Test public void testBlackLevelLock() throws Exception { String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).isCapabilitySupported( CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { continue; } openDevice(cameraIdsUnderTest[i]); 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(); } } } /** * Test dynamic black/white levels if they are supported. * *

* If the dynamic black and white levels are reported, test below: * 1. the dynamic black and white levels shouldn't deviate from the global value too much * for different sensitivities. * 2. If the RAW_SENSOR and optical black regions are supported, capture RAW images and * calculate the optical black level values. The reported dynamic black level should be * close enough to the optical black level values. *

*/ @Test public void testDynamicBlackWhiteLevel() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isDynamicBlackLevelSupported()) { continue; } openDevice(id); dynamicBlackWhiteLevelTestByCamera(); } finally { closeDevice(); } } } /** * Basic lens shading map request test. *

* 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. *

*

* 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. *

*/ @Test public void testLensShadingMap() throws Exception { String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { StaticMetadata staticInfo = mAllStaticInfo.get(cameraIdsUnderTest[i]); if (!staticInfo.isManualLensShadingMapSupported()) { Log.i(TAG, "Camera " + cameraIdsUnderTest[i] + " doesn't support lens shading controls, skipping test"); continue; } List lensShadingMapModes = Arrays.asList(CameraTestUtils.toObject( staticInfo.getAvailableLensShadingMapModesChecked())); if (!lensShadingMapModes.contains(STATISTICS_LENS_SHADING_MAP_MODE_ON)) { continue; } openDevice(cameraIdsUnderTest[i]); SimpleCaptureCallback listener = new SimpleCaptureCallback(); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 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)); List lensShadingModes = Arrays.asList(CameraTestUtils.toObject( mStaticInfo.getAvailableLensShadingModesChecked())); // Shading map mode OFF, lensShadingMapMode ON, camera device // should output unity maps. if (lensShadingModes.contains(SHADING_MODE_OFF)) { requestBuilder.set(CaptureRequest.SHADING_MODE, SHADING_MODE_OFF); 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. if (lensShadingModes.contains(SHADING_MODE_FAST)) { 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. if (lensShadingModes.contains(SHADING_MODE_HIGH_QUALITY)) { 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. *

* Test all available anti-banding modes, check if the exposure time adjustment is * correct. *

*/ @Test public void testAntiBandingModes() throws Exception { String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { // Without manual sensor control, exposure time cannot be verified if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).isCapabilitySupported( CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { continue; } openDevice(cameraIdsUnderTest[i]); 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 and AE priority modes with AE lock. * *

* 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. *

*/ @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests public void testAeModeAndLock() throws Exception { String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).isColorOutputSupported()) { Log.i(TAG, "Camera " + cameraIdsUnderTest[i] + " does not support color outputs, skipping"); continue; } openDevice(cameraIdsUnderTest[i]); 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 aeMode : aeModes) { // Test ae mode with lock without priority mode enabled aeModeAndLockTestByMode(aeMode, CameraMetadata.CONTROL_AE_PRIORITY_MODE_OFF); if (Flags.aePriority()) { int[] aePriorityModes = mStaticInfo.getAeAvailablePriorityModesChecked(); // LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY not supported with AE priority mode if (aeMode == CameraMetadata .CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY || aeMode == CameraMetadata.CONTROL_AE_PRIORITY_MODE_OFF) { continue; } for (int aePriorityMode : aePriorityModes) { // Test ae mode with lock and priority mode enabled aeModeAndLockTestByMode(aeMode, aePriorityMode); } } } } finally { closeDevice(); } } } /** Test {@link CaptureRequest#FLASH_MODE} control. *

* For each {@link CaptureRequest#FLASH_MODE} mode, test the flash control * and {@link CaptureResult#FLASH_STATE} result. *

*/ @Test public void testFlashControl() throws Exception { String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).isColorOutputSupported()) { Log.i(TAG, "Camera " + cameraIdsUnderTest[i] + " does not support color outputs, skipping"); continue; } openDevice(cameraIdsUnderTest[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 that the flash can be successfully turned off given various initial and final * AE_CONTROL modes for repeating CaptureRequests. */ @Test public void testFlashTurnOff() throws Exception { String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).isColorOutputSupported()) { Log.i(TAG, "Camera " + cameraIdsUnderTest[i] + " does not support color outputs, skipping"); continue; } if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).hasFlash()) { Log.i(TAG, "Camera " + cameraIdsUnderTest[i] + " does not support flash, skipping"); continue; } openDevice(cameraIdsUnderTest[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); boolean isLegacy = CameraUtils.isLegacyHAL(mCameraManager, cameraIdsUnderTest[i]); flashTurnOffTest(listener, isLegacy, /* initiaAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH, /* offAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH); flashTurnOffTest(listener, isLegacy, /* initiaAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH, /* offAeControl */CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); flashTurnOffTest(listener, isLegacy, /* initiaAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH, /* offAeControl */CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE); stopPreview(); } finally { closeDevice(); } } } /** * Test face detection modes and results. */ @Test public void testFaceDetection() throws Exception { String[] cameraIdsUnderTest = getCameraIdsUnderTest(); for (int i = 0; i < cameraIdsUnderTest.length; i++) { try { if (!mAllStaticInfo.get(cameraIdsUnderTest[i]).isColorOutputSupported()) { Log.i(TAG, "Camera " + cameraIdsUnderTest[i] + " does not support color outputs, skipping"); continue; } openDevice(cameraIdsUnderTest[i]); faceDetectionTestByCamera(); } finally { closeDevice(); } } } /** * Test tone map modes and controls. */ @Test public void testToneMapControl() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isManualToneMapSupported()) { Log.i(TAG, "Camera " + id + " doesn't support tone mapping controls, skipping test"); continue; } openDevice(id); toneMapTestByCamera(); } finally { closeDevice(); } } } /** * Test CCT color correction mode and color temperature, color tint controls */ @Test @RequiresFlagsEnabled(Flags.FLAG_COLOR_TEMPERATURE) public void testCctColorCorrectionControl() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isCctModeSupported()) { Log.i(TAG, "Camera " + id + " doesn't support CCT color correction mode, skipping test"); continue; } openDevice(id); cctColorCorrectionTestByCamera(); } finally { closeDevice(); } } } /** * Test color correction modes and controls. */ @Test public void testColorCorrectionControl() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorCorrectionSupported()) { Log.i(TAG, "Camera " + id + " doesn't support color correction controls, skipping test"); continue; } openDevice(id); colorCorrectionTestByCamera(); } finally { closeDevice(); } } } /** * Test edge mode control for Fps not exceeding 30. */ @Test public void testEdgeModeControl() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) { Log.i(TAG, "Camera " + id + " doesn't support EDGE_MODE controls, skipping test"); continue; } openDevice(id); List> fpsRanges = getTargetFpsRangesUpTo30(mStaticInfo); edgeModesTestByCamera(fpsRanges); } finally { closeDevice(); } } } /** * Test edge mode control for Fps greater than 30. */ @Test public void testEdgeModeControlFastFps() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) { Log.i(TAG, "Camera " + id + " doesn't support EDGE_MODE controls, skipping test"); continue; } openDevice(id); List> fpsRanges = getTargetFpsRangesGreaterThan30(mStaticInfo); edgeModesTestByCamera(fpsRanges); } finally { closeDevice(); } } } /** * Test focus distance control. */ @Test @AppModeFull(reason = "PropertyUtil methods don't work for instant apps") public void testFocusDistanceControl() throws Exception { for (String id : getCameraIdsUnderTest()) { try { StaticMetadata staticInfo = mAllStaticInfo.get(id); if (!staticInfo.hasFocuser()) { Log.i(TAG, "Camera " + id + " has no focuser, skipping test"); continue; } if (!staticInfo.isCapabilitySupported( CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { Log.i(TAG, "Camera " + id + " does not support MANUAL_SENSOR, skipping test"); continue; } openDevice(id); focusDistanceTestByCamera(); } finally { closeDevice(); } } } /** * Test noise reduction mode for fps ranges not exceeding 30 */ @Test public void testNoiseReductionModeControl() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) { Log.i(TAG, "Camera " + id + " doesn't support noise reduction mode, skipping test"); continue; } openDevice(id); List> fpsRanges = getTargetFpsRangesUpTo30(mStaticInfo); noiseReductionModeTestByCamera(fpsRanges); } finally { closeDevice(); } } } /** * Test noise reduction mode for fps ranges greater than 30 */ @Test public void testNoiseReductionModeControlFastFps() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) { Log.i(TAG, "Camera " + id + " doesn't support noise reduction mode, skipping test"); continue; } openDevice(id); List> fpsRanges = getTargetFpsRangesGreaterThan30(mStaticInfo); noiseReductionModeTestByCamera(fpsRanges); } finally { closeDevice(); } } } /** * Test AWB lock control. * *

The color correction gain and transform shouldn't be changed when AWB is locked.

*/ @Test public void testAwbModeAndLock() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); awbModeAndLockTestByCamera(); } finally { closeDevice(); } } } /** * Test different AF modes. */ @Test public void testAfModes() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); afModeTestByCamera(); } finally { closeDevice(); } } } /** * Test video and optical stabilizations. */ @Test public void testCameraStabilizations() throws Exception { for (String id : getCameraIdsUnderTest()) { try { StaticMetadata staticInfo = mAllStaticInfo.get(id); List> keys = staticInfo.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; } if (!staticInfo.isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); 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. */ @Test public void testDigitalZoom() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); Size maxPreviewSize = mOrderedPreviewSizes.get(0); digitalZoomTestByCamera(maxPreviewSize, /*repeating*/false); digitalZoomTestByCamera(maxPreviewSize, /*repeating*/true); } finally { closeDevice(); } } } /** * Test zoom using CONTROL_ZOOM_RATIO, validate the returned crop regions and zoom ratio. * The max preview size is used for each camera. */ @Test public void testZoomRatio() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); Size maxPreviewSize = mOrderedPreviewSizes.get(0); zoomRatioTestByCamera(maxPreviewSize, /*useZoomRatioMethod*/false); } finally { closeDevice(); } } } /** * Test zoom using CONTROL_ZOOM_RATIO with CONTROL_ZOOM_METHOD set explicitly to ZOOM_RATIO, * validate the returned crop regions and zoom ratio. * * The max preview size is used for each camera. */ @Test @RequiresFlagsEnabled(Flags.FLAG_ZOOM_METHOD) public void testZoomRatioWithMethod() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); Size maxPreviewSize = mOrderedPreviewSizes.get(0); zoomRatioTestByCamera(maxPreviewSize, /*useZoomRatioMethod*/true); } finally { closeDevice(); } } } /** * Test that zoom doesn't incur non-monotonic timestamp sequence * * Camera API requires that camera timestamps monotonically increase. */ @Test @AppModeFull(reason = "PropertyUtil methods don't work for instant apps") public void testZoomTimestampIncrease() throws Exception { if (PropertyUtil.getFirstApiLevel() <= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Only run test for first API level V or higher return; } for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); zoomTimestampIncreaseTestByCamera(); } finally { closeDevice(); } } } /** * Test digital zoom and all preview size combinations. * TODO: this and above test should all be moved to preview test class. */ @Test public void testDigitalZoomPreviewCombinations() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); digitalZoomPreviewCombinationTestByCamera(); } finally { closeDevice(); } } } /** * Test scene mode controls. */ @Test public void testSceneModes() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (mAllStaticInfo.get(id).isSceneModeSupported()) { openDevice(id); sceneModeTestByCamera(); } } finally { closeDevice(); } } } /** * Test effect mode controls. */ @Test public void testEffectModes() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); effectModeTestByCamera(); } finally { closeDevice(); } } } /** * Test extended scene mode controls. */ @Test public void testExtendedSceneModes() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } openDevice(id); List> fpsRanges = getTargetFpsRangesUpTo30(mStaticInfo); extendedSceneModeTestByCamera(fpsRanges); } finally { closeDevice(); } } } /** * Test basic auto-framing. */ @Test public void testAutoframing() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isAutoframingSupported()) { Log.i(TAG, "Camera " + id + " does not support auto-framing, skipping"); continue; } openDevice(id); autoframingTestByCamera(); } finally { closeDevice(); } } } /** * Test manual flash strength level control. */ @Test public void testManualFlashStrengthLevelControl() throws Exception { for (String id : getCameraIdsUnderTest()) { try { if (!mAllStaticInfo.get(id).isManualFlashStrengthControlSupported()) { Log.i(TAG, "Camera " + id + " does not support manual flash " + "strength control, skipping"); continue; } openDevice(id); manualFlashStrengthControlTestByCamera(); } finally { closeDevice(); } } } /** * Test AE mode ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY. */ @Test @RequiresFlagsEnabled(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST) public void testAeModeOnLowLightBoostBrightnessPriority() throws Exception { for (String id : getCameraIdsUnderTest()) { try { StaticMetadata staticInfo = mAllStaticInfo.get(id); if (!staticInfo.isAeModeLowLightBoostSupported()) { Log.i(TAG, "Camera " + id + " does not have AE mode " + "ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY, skipping"); continue; } openDevice(id); testAeModeOnLowLightBoostBrightnessPriorityTestByCamera(); } finally { closeDevice(); } } } /** * Test AE priority modes */ @Test @RequiresFlagsEnabled(Flags.FLAG_AE_PRIORITY) public void testAePriorityModes() throws Exception { for (String id : getCameraIdsUnderTest()) { try { StaticMetadata staticInfo = mAllStaticInfo.get(id); int[] aePriorityModes = staticInfo.getAeAvailablePriorityModesChecked(); openDevice(id); for (int aePriorityMode : aePriorityModes) { if (aePriorityMode == CameraMetadata.CONTROL_AE_PRIORITY_MODE_OFF) { continue; } testAePriorityModesByCamera(aePriorityMode); } } finally { closeDevice(); } } } /** * Test settings override controls. */ @Test public void testSettingsOverrides() throws Exception { for (String id : getCameraIdsUnderTest()) { try { StaticMetadata staticInfo = mAllStaticInfo.get(id); if (!staticInfo.isColorOutputSupported()) { Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); continue; } if (!staticInfo.isZoomSettingsOverrideSupported()) { Log.i(TAG, "Camera " + id + " does not support zoom overrides, skipping"); continue; } openDevice(id); settingsOverrideTestByCamera(); } finally { closeDevice(); } } } // TODO: add 3A state machine test. /** * Per camera dynamic black and white level test. */ private void dynamicBlackWhiteLevelTestByCamera() throws Exception { SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); SimpleImageReaderListener imageListener = null; CaptureRequest.Builder previewBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); CaptureRequest.Builder rawBuilder = null; Size previewSize = getMaxPreviewSize(mCamera.getId(), mCameraManager, getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND)); Size rawSize = null; boolean canCaptureBlackRaw = mStaticInfo.isCapabilitySupported( CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW) && mStaticInfo.isOpticalBlackRegionSupported(); if (canCaptureBlackRaw) { // Capture Raw16, then calculate the optical black, and use it to check with the dynamic // black level. rawBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); rawSize = mStaticInfo.getRawDimensChecked(); imageListener = new SimpleImageReaderListener(); prepareRawCaptureAndStartPreview(previewBuilder, rawBuilder, previewSize, rawSize, resultListener, imageListener); } else { startPreview(previewBuilder, previewSize, resultListener); } // Capture a sequence of frames with different sensitivities and validate the black/white // level values int[] sensitivities = getSensitivityTestValuesSorted(); float[][] dynamicBlackLevels = new float[sensitivities.length][]; int[] dynamicWhiteLevels = new int[sensitivities.length]; float[][] opticalBlackLevels = new float[sensitivities.length][]; for (int i = 0; i < sensitivities.length; i++) { CaptureResult result = null; if (canCaptureBlackRaw) { changeExposure(rawBuilder, DEFAULT_EXP_TIME_NS, sensitivities[i]); CaptureRequest rawRequest = rawBuilder.build(); mSession.capture(rawRequest, resultListener, mHandler); result = resultListener.getCaptureResultForRequest(rawRequest, NUM_RESULTS_WAIT_TIMEOUT); Image rawImage = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS); // Get max (area-wise) optical black region Rect[] opticalBlackRegions = mStaticInfo.getCharacteristics().get( CameraCharacteristics.SENSOR_OPTICAL_BLACK_REGIONS); Rect maxRegion = opticalBlackRegions[0]; for (Rect region : opticalBlackRegions) { if (region.width() * region.height() > maxRegion.width() * maxRegion.height()) { maxRegion = region; } } // Get average black pixel values in the region (region is multiple of 2x2) Image.Plane rawPlane = rawImage.getPlanes()[0]; ByteBuffer rawBuffer = rawPlane.getBuffer(); float[] avgBlackLevels = {0, 0, 0, 0}; final int rowSize = rawPlane.getRowStride(); final int bytePerPixel = rawPlane.getPixelStride(); if (VERBOSE) { Log.v(TAG, "maxRegion: " + maxRegion + ", Row stride: " + rawPlane.getRowStride()); } for (int row = maxRegion.top; row < maxRegion.bottom; row += 2) { for (int col = maxRegion.left; col < maxRegion.right; col += 2) { int startOffset = row * rowSize + col * bytePerPixel; avgBlackLevels[0] += rawBuffer.getShort(startOffset); avgBlackLevels[1] += rawBuffer.getShort(startOffset + bytePerPixel); startOffset += rowSize; avgBlackLevels[2] += rawBuffer.getShort(startOffset); avgBlackLevels[3] += rawBuffer.getShort(startOffset + bytePerPixel); } } int numBlackBlocks = maxRegion.width() * maxRegion.height() / (2 * 2); for (int m = 0; m < avgBlackLevels.length; m++) { avgBlackLevels[m] /= numBlackBlocks; } opticalBlackLevels[i] = avgBlackLevels; if (VERBOSE) { Log.v(TAG, String.format("Optical black level results for sensitivity (%d): %s", sensitivities[i], Arrays.toString(avgBlackLevels))); } rawImage.close(); } else { changeExposure(previewBuilder, DEFAULT_EXP_TIME_NS, sensitivities[i]); CaptureRequest previewRequest = previewBuilder.build(); mSession.capture(previewRequest, resultListener, mHandler); result = resultListener.getCaptureResultForRequest(previewRequest, NUM_RESULTS_WAIT_TIMEOUT); } dynamicBlackLevels[i] = getValueNotNull(result, CaptureResult.SENSOR_DYNAMIC_BLACK_LEVEL); dynamicWhiteLevels[i] = getValueNotNull(result, CaptureResult.SENSOR_DYNAMIC_WHITE_LEVEL); } if (VERBOSE) { Log.v(TAG, "Different sensitivities tested: " + Arrays.toString(sensitivities)); Log.v(TAG, "Dynamic black level results: " + Arrays.deepToString(dynamicBlackLevels)); Log.v(TAG, "Dynamic white level results: " + Arrays.toString(dynamicWhiteLevels)); if (canCaptureBlackRaw) { Log.v(TAG, "Optical black level results " + Arrays.deepToString(opticalBlackLevels)); } } // check the dynamic black level against global black level. // Implicit guarantee: if the dynamic black level is supported, fixed black level must be // supported as well (tested in ExtendedCameraCharacteristicsTest#testOpticalBlackRegions). BlackLevelPattern blackPattern = mStaticInfo.getCharacteristics().get( CameraCharacteristics.SENSOR_BLACK_LEVEL_PATTERN); int[] fixedBlackLevels = new int[4]; int fixedWhiteLevel = mStaticInfo.getCharacteristics().get( CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL); blackPattern.copyTo(fixedBlackLevels, 0); float maxBlackDeviation = 0; int maxWhiteDeviation = 0; for (int i = 0; i < dynamicBlackLevels.length; i++) { for (int j = 0; j < dynamicBlackLevels[i].length; j++) { if (maxBlackDeviation < Math.abs(fixedBlackLevels[j] - dynamicBlackLevels[i][j])) { maxBlackDeviation = Math.abs(fixedBlackLevels[j] - dynamicBlackLevels[i][j]); } } if (maxWhiteDeviation < Math.abs(dynamicWhiteLevels[i] - fixedWhiteLevel)) { maxWhiteDeviation = Math.abs(dynamicWhiteLevels[i] - fixedWhiteLevel); } } mCollector.expectLessOrEqual("Max deviation of the dynamic black level vs fixed black level" + " exceed threshold." + " Dynamic black level results: " + Arrays.deepToString(dynamicBlackLevels), fixedBlackLevels[0] * DYNAMIC_VS_FIXED_BLK_WH_LVL_ERROR_MARGIN, maxBlackDeviation); mCollector.expectLessOrEqual("Max deviation of the dynamic white level exceed threshold." + " Dynamic white level results: " + Arrays.toString(dynamicWhiteLevels), fixedWhiteLevel * DYNAMIC_VS_FIXED_BLK_WH_LVL_ERROR_MARGIN, (float)maxWhiteDeviation); // Validate against optical black levels if it is available if (canCaptureBlackRaw) { maxBlackDeviation = 0; for (int i = 0; i < dynamicBlackLevels.length; i++) { for (int j = 0; j < dynamicBlackLevels[i].length; j++) { if (maxBlackDeviation < Math.abs(opticalBlackLevels[i][j] - dynamicBlackLevels[i][j])) { maxBlackDeviation = Math.abs(opticalBlackLevels[i][j] - dynamicBlackLevels[i][j]); } } } mCollector.expectLessOrEqual("Max deviation of the dynamic black level vs optical black" + " exceed threshold." + " Dynamic black level results: " + Arrays.deepToString(dynamicBlackLevels) + " Optical black level results: " + Arrays.deepToString(opticalBlackLevels), fixedBlackLevels[0] * DYNAMIC_VS_OPTICAL_BLK_LVL_ERROR_MARGIN, maxBlackDeviation); } } private void noiseReductionModeTestByCamera(List> fpsRanges) throws Exception { Size maxPrevSize = mOrderedPreviewSizes.get(0); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); int[] availableModes = mStaticInfo.getAvailableNoiseReductionModesChecked(); for (int mode : availableModes) { requestBuilder.set(CaptureRequest.NOISE_REDUCTION_MODE, mode); // 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, fpsRanges); } SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPrevSize, resultListener); mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler); waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); verifyCaptureResultForKey(CaptureResult.NOISE_REDUCTION_MODE, mode, resultListener, NUM_FRAMES_VERIFIED); } stopPreview(); } private void focusDistanceTestByCamera() throws Exception { CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF); int calibrationStatus = mStaticInfo.getFocusDistanceCalibrationChecked(); float errorMargin = FOCUS_DISTANCE_ERROR_PERCENT_UNCALIBRATED; 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; } // Test changing focus distance with repeating request focusDistanceTestRepeating(requestBuilder, errorMargin); if (calibrationStatus == CameraMetadata.LENS_INFO_FOCUS_DISTANCE_CALIBRATION_CALIBRATED) { // Test changing focus distance with burst request focusDistanceTestBurst(requestBuilder, errorMargin); } } private void verifyFocusRange(CaptureResult result, float focusDistance) { if (PropertyUtil.getVendorApiLevel() < 33) { // Skip, as this only applies to UDC and above if (VERBOSE) { Log.v(TAG, "Skipping FOCUS_RANGE verification due to API level"); } return; } Pair focusRange = result.get(CaptureResult.LENS_FOCUS_RANGE); if (focusRange != null) { // Prevent differences in floating point precision between manual request and HAL // result, some margin need to be considered for focusRange.near and far check float focusRangeNear = focusRange.first * (1.0f + FOCUS_RANGE_BOUNDARY_MARGIN_PERCENT); float focusRangeFar = focusRange.second * (1.0f - FOCUS_RANGE_BOUNDARY_MARGIN_PERCENT); mCollector.expectLessOrEqual("Focus distance should be less than or equal to " + "FOCUS_RANGE.near (with margin)", focusRangeNear, focusDistance); mCollector.expectGreaterOrEqual("Focus distance should be greater than or equal to " + "FOCUS_RANGE.far (with margin)", focusRangeFar, focusDistance); } else if (VERBOSE) { Log.v(TAG, "FOCUS_RANGE undefined, skipping verification"); } } private void focusDistanceTestRepeating(CaptureRequest.Builder requestBuilder, float errorMargin) throws Exception { CaptureRequest request; float[] testDistances = getFocusDistanceTestValuesInOrder(0, 0); Size maxPrevSize = mOrderedPreviewSizes.get(0); SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPrevSize, resultListener); float[] resultDistances = new float[testDistances.length]; int[] resultLensStates = new int[testDistances.length]; // Collect results 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); waitForResultValue(resultListener, CaptureResult.LENS_STATE, CaptureResult.LENS_STATE_STATIONARY, NUM_RESULTS_WAIT_TIMEOUT); CaptureResult result = resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT); resultDistances[i] = getValueNotNull(result, CaptureResult.LENS_FOCUS_DISTANCE); resultLensStates[i] = getValueNotNull(result, CaptureResult.LENS_STATE); verifyFocusRange(result, resultDistances[i]); if (VERBOSE) { Log.v(TAG, "Capture repeating request focus distance: " + testDistances[i] + " result: " + resultDistances[i] + " lens state " + resultLensStates[i]); } } verifyFocusDistance(testDistances, resultDistances, resultLensStates, /*ascendingOrder*/true, /*noOvershoot*/false, /*repeatStart*/0, /*repeatEnd*/0, errorMargin); if (mStaticInfo.areKeysAvailable(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); CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); Float focusDistance = getValueNotNull(result, CaptureResult.LENS_FOCUS_DISTANCE); mCollector.expectInRange("Focus distance for hyper focal should be close enough to" + " requested value", focusDistance, hyperFocalDistance * (1.0f - errorMargin), hyperFocalDistance * (1.0f + errorMargin)); } } } private void focusDistanceTestBurst(CaptureRequest.Builder requestBuilder, float errorMargin) throws Exception { Size maxPrevSize = mOrderedPreviewSizes.get(0); float[] testDistances = getFocusDistanceTestValuesInOrder(NUM_FOCUS_DISTANCES_REPEAT, NUM_FOCUS_DISTANCES_REPEAT); SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPrevSize, resultListener); float[] resultDistances = new float[testDistances.length]; int[] resultLensStates = new int[testDistances.length]; final int maxPipelineDepth = mStaticInfo.getCharacteristics().get( CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH); // Move lens to starting position, and wait for the lens.state to be stationary. CaptureRequest request; requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, testDistances[0]); request = requestBuilder.build(); mSession.setRepeatingRequest(request, resultListener, mHandler); waitForResultValue(resultListener, CaptureResult.LENS_STATE, CaptureResult.LENS_STATE_STATIONARY, NUM_RESULTS_WAIT_TIMEOUT); // Submit burst of requests with different focus distances List burst = new ArrayList<>(); for (int i = 0; i < testDistances.length; i ++) { requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, testDistances[i]); burst.add(requestBuilder.build()); } mSession.captureBurst(burst, resultListener, mHandler); for (int i = 0; i < testDistances.length; i++) { CaptureResult result = resultListener.getCaptureResultForRequest( burst.get(i), maxPipelineDepth+1); resultDistances[i] = getValueNotNull(result, CaptureResult.LENS_FOCUS_DISTANCE); resultLensStates[i] = getValueNotNull(result, CaptureResult.LENS_STATE); verifyFocusRange(result, resultDistances[i]); if (VERBOSE) { Log.v(TAG, "Capture burst request focus distance: " + testDistances[i] + " result: " + resultDistances[i] + " lens state " + resultLensStates[i]); } } verifyFocusDistance(testDistances, resultDistances, resultLensStates, /*ascendingOrder*/true, /*noOvershoot*/true, /*repeatStart*/NUM_FOCUS_DISTANCES_REPEAT, /*repeatEnd*/NUM_FOCUS_DISTANCES_REPEAT, errorMargin); } /** * Verify focus distance control. * * Assumption: * - First repeatStart+1 elements of requestedDistances share the same value * - Last repeatEnd+1 elements of requestedDistances share the same value * - All elements in between are monotonically increasing/decreasing depending on ascendingOrder. * - Focuser is at requestedDistances[0] at the beginning of the test. * * @param requestedDistances The requested focus distances * @param resultDistances The result focus distances * @param lensStates The result lens states * @param ascendingOrder The order of the expected focus distance request/output * @param noOvershoot Assert that focus control doesn't overshoot the requested value * @param repeatStart The number of times the starting focus distance is repeated * @param repeatEnd The number of times the ending focus distance is repeated * @param errorMargin The error margin between request and result */ private void verifyFocusDistance(float[] requestedDistances, float[] resultDistances, int[] lensStates, boolean ascendingOrder, boolean noOvershoot, int repeatStart, int repeatEnd, float errorMargin) { float minValue = 0; float maxValue = mStaticInfo.getMinimumFocusDistanceChecked(); float hyperfocalDistance = 0; if (mStaticInfo.areKeysAvailable(CameraCharacteristics.LENS_INFO_HYPERFOCAL_DISTANCE)) { hyperfocalDistance = mStaticInfo.getHyperfocalDistanceChecked(); } // Verify lens and focus distance do not change for first repeatStart // results. for (int i = 0; i < repeatStart; i ++) { float marginMin = requestedDistances[i] * (1.0f - errorMargin); // HAL may choose to use hyperfocal distance for all distances between [0, hyperfocal]. float marginMax = Math.max(requestedDistances[i], hyperfocalDistance) * (1.0f + errorMargin); mCollector.expectEquals("Lens moves even though focus_distance didn't change", lensStates[i], CaptureResult.LENS_STATE_STATIONARY); if (noOvershoot) { mCollector.expectInRange("Focus distance in result should be close enough to " + "requested value", resultDistances[i], marginMin, marginMax); } mCollector.expectInRange("Result focus distance is out of range", resultDistances[i], minValue, maxValue); } for (int i = repeatStart; i < resultDistances.length-1; i ++) { float marginMin = requestedDistances[i] * (1.0f - errorMargin); // HAL may choose to use hyperfocal distance for all distances between [0, hyperfocal]. float marginMax = Math.max(requestedDistances[i], hyperfocalDistance) * (1.0f + errorMargin); if (noOvershoot) { // Result focus distance shouldn't overshoot the request boolean condition; if (ascendingOrder) { condition = resultDistances[i] <= marginMax; } else { condition = resultDistances[i] >= marginMin; } mCollector.expectTrue(String.format( "Lens shouldn't move past request focus distance. result " + resultDistances[i] + " vs target of " + (ascendingOrder ? marginMax : marginMin)), condition); } // Verify monotonically increased focus distance setting boolean condition; float compareDistance = resultDistances[i+1] - resultDistances[i]; if (i < resultDistances.length-1-repeatEnd) { condition = (ascendingOrder ? compareDistance > 0 : compareDistance < 0); } else { condition = (ascendingOrder ? compareDistance >= 0 : compareDistance <= 0); } mCollector.expectTrue(String.format("Adjacent [resultDistances, lens_state] results [" + resultDistances[i] + "," + lensStates[i] + "], [" + resultDistances[i+1] + "," + lensStates[i+1] + "] monotonicity is broken"), condition); } mCollector.expectTrue(String.format("All values of this array are equal: " + resultDistances[0] + " " + resultDistances[resultDistances.length-1]), resultDistances[0] != resultDistances[resultDistances.length-1]); // Verify lens moved to destination location. mCollector.expectInRange("Focus distance " + resultDistances[resultDistances.length-1] + " for minFocusDistance should be closed enough to requested value " + requestedDistances[requestedDistances.length-1], resultDistances[resultDistances.length-1], requestedDistances[requestedDistances.length-1] * (1.0f - errorMargin), requestedDistances[requestedDistances.length-1] * (1.0f + errorMargin)); } /** * Verify edge mode control results for fpsRanges */ private void edgeModesTestByCamera(List> fpsRanges) throws Exception { Size maxPrevSize = mOrderedPreviewSizes.get(0); int[] edgeModes = mStaticInfo.getAvailableEdgeModesChecked(); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); for (int mode : edgeModes) { requestBuilder.set(CaptureRequest.EDGE_MODE, mode); // 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, fpsRanges); } SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPrevSize, resultListener); mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler); waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); verifyCaptureResultForKey(CaptureResult.EDGE_MODE, mode, resultListener, NUM_FRAMES_VERIFIED); } stopPreview(); } /** * Test CCT color correction controls. * *

Test CCT color correction mode and control keys for color temperaure * and color tint.

*/ private void cctColorCorrectionTestByCamera() 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)); int colorCorrectionMode = CaptureRequest.COLOR_CORRECTION_MODE_CCT; // Check if the color temperature range is advertised and // supports the minimum required range. Range colorTemperatureRange = mStaticInfo.getCharacteristics().get(CameraCharacteristics. COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE); assertNotNull("CCT mode is supported but color temperature range is null", colorTemperatureRange); assertTrue("Color temperature range should advertise at least [2856, 6500]", colorTemperatureRange.getLower() <= 2856 && colorTemperatureRange.getUpper() >= 6500); assertTrue("Color temperature range should advertise between [1000, 40000]", colorTemperatureRange.getLower() >= 1000 && colorTemperatureRange.getUpper() <= 40000); List availableControlModes = Arrays.asList( CameraTestUtils.toObject(mStaticInfo.getAvailableControlModesChecked())); List availableAwbModes = Arrays.asList( CameraTestUtils.toObject(mStaticInfo.getAwbAvailableModesChecked())); boolean isManualCCSupported = availableControlModes.contains(CaptureRequest.CONTROL_MODE_OFF) || availableAwbModes.contains(CaptureRequest.CONTROL_AWB_MODE_OFF); if (isManualCCSupported) { // Turn off AWB through either CONTROL_AWB_MODE_OFF or CONTROL_MODE_OFF if (!availableControlModes.contains(CaptureRequest.CONTROL_MODE_OFF)) { // Only manual AWB mode is supported manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); manualRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF); } else { // All 3A manual controls are supported, it doesn't matter what we set for AWB mode manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF); } int[] TEST_COLOR_TEMPERATURE_VALUES = {2500, 4500, 6500}; int[] TEST_COLOR_TINT_VALUES = {-25, 0, 25}; for (int i = 0; i < TEST_COLOR_TEMPERATURE_VALUES.length; i++) { manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_MODE, colorCorrectionMode); manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_COLOR_TEMPERATURE, TEST_COLOR_TEMPERATURE_VALUES[i]); manualRequestBuilder.set(CaptureRequest.COLOR_CORRECTION_COLOR_TINT, TEST_COLOR_TINT_VALUES[i]); request = manualRequestBuilder.build(); mSession.capture(request, listener, mHandler); result = listener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT); validateColorCorrectionResult(result, colorCorrectionMode); int colorTemperatureResult = result.get(CaptureResult.COLOR_CORRECTION_COLOR_TEMPERATURE); int colorTintResult = result.get(CaptureResult.COLOR_CORRECTION_COLOR_TINT); mCollector.expectEquals("Color temperature result/request mismatch", TEST_COLOR_TEMPERATURE_VALUES[i], colorTemperatureResult); // The actual color tint applied may be clamped so the result // may differ from the request, so we just check if it is null mCollector.expectNotNull("Color tint result null", colorTintResult); if (!availableControlModes.contains(CaptureRequest.CONTROL_MODE_OFF)) { mCollector.expectEquals("Control mode result/request mismatch", CaptureResult.CONTROL_MODE_AUTO, result.get(CaptureResult.CONTROL_MODE)); mCollector.expectEquals("AWB mode result/request mismatch", CaptureResult.CONTROL_AWB_MODE_OFF, result.get(CaptureResult.CONTROL_AWB_MODE)); } else { mCollector.expectEquals("Control mode result/request mismatch", CaptureResult.CONTROL_MODE_OFF, result.get(CaptureResult.CONTROL_MODE)); } } } } /** * Test color correction controls. * *

Test different color correction modes. For TRANSFORM_MATRIX, only test * the unit gain and identity transform.

*/ 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)); int colorCorrectionMode = CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX; // TRANSFORM_MATRIX mode // Only test unit gain and identity transform List availableControlModes = Arrays.asList( CameraTestUtils.toObject(mStaticInfo.getAvailableControlModesChecked())); List availableAwbModes = Arrays.asList( CameraTestUtils.toObject(mStaticInfo.getAwbAvailableModesChecked())); boolean isManualCCSupported = availableControlModes.contains(CaptureRequest.CONTROL_MODE_OFF) || availableAwbModes.contains(CaptureRequest.CONTROL_AWB_MODE_OFF); if (isManualCCSupported) { if (!availableControlModes.contains(CaptureRequest.CONTROL_MODE_OFF)) { // Only manual AWB mode is supported manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); manualRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF); } else { // All 3A manual controls are supported, it doesn't matter what we set for AWB mode. manualRequestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF); } 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 }); 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 that flash can be turned off successfully with a given initial and final AE_CONTROL * states. * * This function expects that initialAeControl and flashOffAeControl will not be either * CaptureRequest.CONTROL_AE_MODE_ON or CaptureRequest.CONTROL_AE_MODE_OFF * * @param listener The Capture listener that is used to wait for capture result * @param initialAeControl The initial AE_CONTROL mode to start repeating requests with. * @param flashOffAeControl The final AE_CONTROL mode which is expected to turn flash off for * TEMPLATE_PREVIEW repeating requests. */ private void flashTurnOffTest(SimpleCaptureCallback listener, boolean isLegacy, int initialAeControl, int flashOffAeControl) throws Exception { CaptureResult result; final int NUM_FLASH_REQUESTS_TESTED = 10; CaptureRequest.Builder requestBuilder = createRequestForPreview(); requestBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, initialAeControl); mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); // Turn on torch using FLASH_MODE_TORCH requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); CaptureRequest torchOnRequest = requestBuilder.build(); mSession.setRepeatingRequest(torchOnRequest, listener, mHandler); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_TORCH); result = listener.getCaptureResultForRequest(torchOnRequest, NUM_RESULTS_WAIT_TIMEOUT); // Test that the flash actually turned on continuously. mCollector.expectEquals("Flash state result must be FIRED", CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE)); mSession.stopRepeating(); // Turn off the torch requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, flashOffAeControl); // TODO: jchowdhary@, b/130323585, this line can be removed. requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF); int numAllowedTransitionStates = NUM_PARTIAL_FRAMES_NPFC; if (mStaticInfo.isPerFrameControlSupported()) { numAllowedTransitionStates = NUM_PARTIAL_FRAMES_PFC; } // We submit 2 * numAllowedTransitionStates + 1 requests since we have two torch mode // transitions. The additional request is to check for at least 1 expected (FIRED / READY) // state. int numTorchTestSamples = 2 * numAllowedTransitionStates + 1; CaptureRequest flashOffRequest = requestBuilder.build(); int flashModeOffRequests = captureRequestsSynchronizedBurst(flashOffRequest, numTorchTestSamples, listener, mHandler); // Turn it on again. requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); // We need to have CONTROL_AE_MODE be either CONTROL_AE_MODE_ON or CONTROL_AE_MODE_OFF to // turn the torch on again. requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); CaptureRequest flashModeTorchRequest = requestBuilder.build(); int flashModeTorchRequests = captureRequestsSynchronizedBurst(flashModeTorchRequest, numTorchTestSamples, listener, mHandler); CaptureResult[] torchStateResults = new CaptureResult[flashModeTorchRequests + flashModeOffRequests]; Arrays.fill(torchStateResults, null); int i = 0; for (; i < flashModeOffRequests; i++) { torchStateResults[i] = listener.getCaptureResultForRequest(flashOffRequest, NUM_RESULTS_WAIT_TIMEOUT); mCollector.expectNotEquals("Result for flashModeOff request null", torchStateResults[i], null); } for (int j = i; j < torchStateResults.length; j++) { torchStateResults[j] = listener.getCaptureResultForRequest(flashModeTorchRequest, NUM_RESULTS_WAIT_TIMEOUT); mCollector.expectNotEquals("Result for flashModeTorch request null", torchStateResults[j], null); } if (isLegacy) { // For LEGACY devices, flash state is null for all situations except: // android.control.aeMode == ON_ALWAYS_FLASH, where flash.state will be FIRED // android.flash.mode == TORCH, where flash.state will be FIRED testLegacyTorchStates(torchStateResults, 0, flashModeOffRequests - 1, flashOffRequest); testLegacyTorchStates(torchStateResults, flashModeOffRequests, torchStateResults.length -1, flashModeTorchRequest); } else { checkTorchStates(torchStateResults, numAllowedTransitionStates, flashModeOffRequests, flashModeTorchRequests); } } private void testLegacyTorchStates(CaptureResult []torchStateResults, int beg, int end, CaptureRequest request) { for (int i = beg; i <= end; i++) { Integer requestControlAeMode = request.get(CaptureRequest.CONTROL_AE_MODE); Integer requestFlashMode = request.get(CaptureRequest.FLASH_MODE); Integer resultFlashState = torchStateResults[i].get(CaptureResult.FLASH_STATE); if (requestControlAeMode == CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH || requestFlashMode == CaptureRequest.FLASH_MODE_TORCH) { mCollector.expectEquals("For LEGACY devices, flash state must be FIRED when" + "CONTROL_AE_MODE == CONTROL_AE_MODE_ON_ALWAYS_FLASH or FLASH_MODE == " + "TORCH, CONTROL_AE_MODE = " + requestControlAeMode + " FLASH_MODE = " + requestFlashMode, CaptureResult.FLASH_STATE_FIRED, resultFlashState); continue; } mCollector.expectTrue("For LEGACY devices, flash state must be null when" + "CONTROL_AE_MODE != CONTROL_AE_MODE_ON_ALWAYS_FLASH or FLASH_MODE != " + "TORCH, CONTROL_AE_MODE = " + requestControlAeMode + " FLASH_MODE = " + requestFlashMode, resultFlashState == null); } } // We check that torch states appear in the order expected. We don't necessarily know how many // times each state might appear, however we make sure that the states do not appear out of // order. private void checkTorchTransitionStates(CaptureResult []torchStateResults, int beg, int end, List stateOrder, boolean isTurningOff) { Integer flashState; Integer curIndex = 0; for (int i = beg; i <= end; i++) { flashState = torchStateResults[i].get(CaptureResult.FLASH_STATE); int index = stateOrder.indexOf(flashState); mCollector.expectNotEquals("Invalid state " + flashState + " not in expected list" + stateOrder, index, -1); mCollector.expectGreaterOrEqual("state " + flashState + " index " + index + " is expected to be >= " + curIndex, curIndex, index); curIndex = index; } } private void checkTorchStates(CaptureResult []torchResults, int numAllowedTransitionStates, int numTorchOffSamples, int numTorchOnSamples) { // We test for flash states from request: // Request: O(0) O(1) O(2) O(n)....O(nOFF) T(0) T(1) T(2) ....T(n) .... T(nON) // Valid Result : P/R P/R P/R R R R...P/R P/R P/F P/F P/F F F // For the FLASH_STATE_OFF requests, once FLASH_STATE READY has been seen, for the // transition states while switching the torch off, it must not transition to // FLASH_STATE_PARTIAL again till the next transition period which turns the torch on. // P - FLASH_STATE_PARTIAL // R - FLASH_STATE_READY // F - FLASH_STATE_FIRED // O(k) - kth FLASH_MODE_OFF request // T(k) - kth FLASH_MODE_TORCH request // nOFF - number of torch off samples // nON - number of torch on samples Integer flashState; // Check on -> off transition states List onToOffStateOrderList = new ArrayList(); onToOffStateOrderList.add(CaptureRequest.FLASH_STATE_PARTIAL); onToOffStateOrderList.add(CaptureRequest.FLASH_STATE_READY); checkTorchTransitionStates(torchResults, 0, numAllowedTransitionStates, onToOffStateOrderList, true); // The next frames (before transition) must have its flash state as FLASH_STATE_READY for (int i = numAllowedTransitionStates + 1; i < numTorchOffSamples - numAllowedTransitionStates; i++) { flashState = torchResults[numAllowedTransitionStates].get(CaptureResult.FLASH_STATE); mCollector.expectEquals("flash state result must be READY", CaptureResult.FLASH_STATE_READY, flashState); } // check off -> on transition states, before the FLASH_MODE_TORCH request was sent List offToOnPreStateOrderList = new ArrayList(); offToOnPreStateOrderList.add(CaptureRequest.FLASH_STATE_READY); offToOnPreStateOrderList.add(CaptureRequest.FLASH_STATE_PARTIAL); checkTorchTransitionStates(torchResults, numTorchOffSamples - numAllowedTransitionStates, numTorchOffSamples - 1, offToOnPreStateOrderList, false); // check off -> on transition states List offToOnPostStateOrderList = new ArrayList(); offToOnPostStateOrderList.add(CaptureRequest.FLASH_STATE_PARTIAL); offToOnPostStateOrderList.add(CaptureRequest.FLASH_STATE_FIRED); checkTorchTransitionStates(torchResults, numTorchOffSamples, numTorchOffSamples + numAllowedTransitionStates, offToOnPostStateOrderList, false); // check on states after off -> on transition // The next frames must have its flash state as FLASH_STATE_FIRED for (int i = numTorchOffSamples + numAllowedTransitionStates + 1; i < torchResults.length - 1; i++) { flashState = torchResults[i].get(CaptureResult.FLASH_STATE); mCollector.expectEquals("flash state result must be FIRED for frame " + i, CaptureRequest.FLASH_STATE_FIRED, flashState); } } /** * Test flash mode control by AE mode. *

* 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. *

* * @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.isHardwareLevelAtLeastLimited()) { 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 TorchSeqState state = TorchSeqState.RAMPING_UP; for (int i = 0; i < NUM_FLASH_REQUESTS_TESTED; i++) { result = listener.getCaptureResultForRequest(torchRequest, NUM_RESULTS_WAIT_TIMEOUT); int flashMode = result.get(CaptureResult.FLASH_MODE); int flashState = result.get(CaptureResult.FLASH_STATE); // Result mode must be TORCH mCollector.expectEquals("Flash mode result " + i + " must be TORCH", CaptureResult.FLASH_MODE_TORCH, result.get(CaptureResult.FLASH_MODE)); if (state == TorchSeqState.RAMPING_UP && flashState == CaptureResult.FLASH_STATE_FIRED) { state = TorchSeqState.FIRED; } else if (state == TorchSeqState.FIRED && flashState == CaptureResult.FLASH_STATE_PARTIAL) { state = TorchSeqState.RAMPING_DOWN; } if (i == 0 && mStaticInfo.isPerFrameControlSupported()) { mCollector.expectTrue( "Per frame control device must enter FIRED state on first torch request", state == TorchSeqState.FIRED); } if (state == TorchSeqState.FIRED) { mCollector.expectEquals("Flash state result " + i + " must be FIRED", CaptureResult.FLASH_STATE_FIRED, result.get(CaptureResult.FLASH_STATE)); } else { mCollector.expectEquals("Flash state result " + i + " must be PARTIAL", CaptureResult.FLASH_STATE_PARTIAL, result.get(CaptureResult.FLASH_STATE)); } } mCollector.expectTrue("Torch state FIRED never seen", state == TorchSeqState.FIRED || state == TorchSeqState.RAMPING_DOWN); // 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); Integer antiBandMode = result.get(CaptureResult.CONTROL_AE_ANTIBANDING_MODE); assertNotNull("antiBanding mode shouldn't be null", antiBandMode); assertTrue("antiBanding Mode invalid, should be == " + mode + ", is: " + antiBandMode, antiBandMode == mode); 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. *

* 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. *

* * @param mode corresponding to AE_MODE_* * @param priorityMode corresponding to AE_PRIORITY_MODE_* */ private void aeModeAndLockTestByMode(int mode, int priorityMode) 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: case CONTROL_AE_MODE_ON_EXTERNAL_FLASH: // Test AE lock for above AUTO modes. aeAutoModeTestLock(mode, priorityMode); break; default: throw new UnsupportedOperationException("Unhandled AE mode " + mode); } } /** * Test AE auto modes. *

* Use single request rather than repeating request to test AE lock per frame control. *

*/ private void aeAutoModeTestLock(int mode, int priorityMode) throws Exception { CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); if (mStaticInfo.isAeLockSupported()) { requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, false); } requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mode); if (Flags.aePriority()) { requestBuilder.set(CaptureRequest.CONTROL_AE_PRIORITY_MODE, priorityMode); } 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, priorityMode); } } /** * 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, int priorityMode) 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]; boolean canSetAeLock = mStaticInfo.isAeLockSupported(); // Reset the AE lock to OFF, since we are reusing this builder many times if (canSetAeLock) { 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); if (!canSetAeLock) { // Without AE lock, the remaining tests items won't work return; } // 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_READ_SENSOR_SETTINGS)) { 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++) { if (Flags.aePriority()) { switch (priorityMode) { case CONTROL_AE_PRIORITY_MODE_OFF: mCollector.expectKeyValueEquals( resultsDuringLock[i], CaptureResult.SENSOR_EXPOSURE_TIME, expTimeLocked); mCollector.expectKeyValueEquals( resultsDuringLock[i], CaptureResult.SENSOR_SENSITIVITY, sensitivityLocked); break; case CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY: mCollector.expectKeyValueEquals( resultsDuringLock[i], CaptureResult.SENSOR_EXPOSURE_TIME, expTimeLocked); break; case CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY: mCollector.expectKeyValueEquals( resultsDuringLock[i], CaptureResult.SENSOR_SENSITIVITY, sensitivityLocked); break; default: throw new UnsupportedOperationException("Unhandled AE priority mode " + priorityMode); } } else { 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. *

* Use single request rather than repeating request to test manual exposure * value change per frame control. *

*/ private void aeManualControlTest() throws Exception { CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); configurePreviewOutput(requestBuilder); // Warm up pipeline for more accurate timing SimpleCaptureCallback warmupListener = new SimpleCaptureCallback(); mSession.setRepeatingRequest(requestBuilder.build(), warmupListener, mHandler); warmupListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); // Do manual captures requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CONTROL_AE_MODE_OFF); SimpleCaptureCallback listener = new SimpleCaptureCallback(); long[] expTimesNs = getExposureTimeTestValuesSorted(); int[] sensitivities = getSensitivityTestValuesSorted(); assertTrue(expTimesNs.length > 0); assertTrue(sensitivities.length > 0); // For multiple exposure times, make smart combinations of exposure and sensitivity to // reduce test time and still have exhaustive coverage. List> exposureSensitivityTestValues = new ArrayList>(); // Min exposure should be tested with all sensitivity values. for (int i = 0; i < sensitivities.length; i++) { exposureSensitivityTestValues.add( new Pair(expTimesNs[0], sensitivities[i])); } // All other exposure values should be tested only with min and max sensitivity. for (int i = 1; i < expTimesNs.length; i++) { exposureSensitivityTestValues.add( new Pair(expTimesNs[i], sensitivities[0])); if (sensitivities.length > 1) { exposureSensitivityTestValues.add( new Pair(expTimesNs[i], sensitivities[sensitivities.length - 1])); } } // Submit single request at a time, then verify the result. for (int i = 0; i < exposureSensitivityTestValues.size(); i++) { long exposure = exposureSensitivityTestValues.get(i).first; int sensitivity = exposureSensitivityTestValues.get(i).second; if (VERBOSE) { Log.v(TAG, "Camera " + mCamera.getId() + ": Testing sensitivity " + sensitivity + ", exposure time " + exposure + "ns"); } changeExposure(requestBuilder, exposure, sensitivity); mSession.capture(requestBuilder.build(), listener, mHandler); // make sure timeout is long enough for long exposure time - add a 2x safety margin // to exposure time long timeoutMs = WAIT_FOR_RESULT_TIMEOUT_MS + 2 * exposure / 1000000; CaptureResult result = listener.getCaptureResult(timeoutMs); long resultExpTimeNs = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME); int resultSensitivity = getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY); validateExposureTime(exposure, resultExpTimeNs); validateSensitivity(sensitivity, resultSensitivity); validateFrameDurationForCapture(result); } mSession.stopRepeating(); // 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 faceIds = new ArrayList(faces.length); List faceScores = new ArrayList(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", faceScores, 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; } CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); int[] toneMapModes = mStaticInfo.getAvailableToneMapModesChecked(); // Test AUTO modes first. Note that FAST/HQ must both present or not present for (int i = 0; i < toneMapModes.length; i++) { if (toneMapModes[i] == CaptureRequest.TONEMAP_MODE_FAST && i > 0) { int tmpMode = toneMapModes[0]; toneMapModes[0] = CaptureRequest.TONEMAP_MODE_FAST; toneMapModes[i] = tmpMode; } if (toneMapModes[i] == CaptureRequest.TONEMAP_MODE_HIGH_QUALITY && i > 1) { int tmpMode = toneMapModes[1]; toneMapModes[1] = CaptureRequest.TONEMAP_MODE_HIGH_QUALITY; toneMapModes[i] = tmpMode; } } for (int mode : toneMapModes) { if (VERBOSE) { Log.v(TAG, "Testing tonemap mode " + mode); } requestBuilder.set(CaptureRequest.TONEMAP_MODE, mode); switch (mode) { case CaptureRequest.TONEMAP_MODE_CONTRAST_CURVE: TonemapCurve toneCurve = new TonemapCurve(TONEMAP_CURVE_LINEAR, TONEMAP_CURVE_LINEAR, TONEMAP_CURVE_LINEAR); requestBuilder.set(CaptureRequest.TONEMAP_CURVE, toneCurve); testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); toneCurve = new TonemapCurve(TONEMAP_CURVE_SRGB, TONEMAP_CURVE_SRGB, TONEMAP_CURVE_SRGB); requestBuilder.set(CaptureRequest.TONEMAP_CURVE, toneCurve); testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); break; case CaptureRequest.TONEMAP_MODE_GAMMA_VALUE: requestBuilder.set(CaptureRequest.TONEMAP_GAMMA, 1.0f); testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); requestBuilder.set(CaptureRequest.TONEMAP_GAMMA, 2.2f); testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); requestBuilder.set(CaptureRequest.TONEMAP_GAMMA, 5.0f); testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); break; case CaptureRequest.TONEMAP_MODE_PRESET_CURVE: requestBuilder.set(CaptureRequest.TONEMAP_PRESET_CURVE, CaptureRequest.TONEMAP_PRESET_CURVE_REC709); testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); requestBuilder.set(CaptureRequest.TONEMAP_PRESET_CURVE, CaptureRequest.TONEMAP_PRESET_CURVE_SRGB); testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); break; default: testToneMapMode(NUM_FRAMES_VERIFIED, requestBuilder); break; } } } /** * Test tonemap mode with speficied request settings * * @param numFramesVerified Number of results to be verified * @param requestBuilder the request builder of settings to be tested */ private void testToneMapMode (int numFramesVerified, CaptureRequest.Builder requestBuilder) throws Exception { final int MIN_TONEMAP_CURVE_POINTS = 2; final Float ZERO = new Float(0); final Float ONE = new Float(1.0f); SimpleCaptureCallback listener = new SimpleCaptureCallback(); int tonemapMode = requestBuilder.get(CaptureRequest.TONEMAP_MODE); Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size. startPreview(requestBuilder, maxPreviewSz, listener); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); 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. */ } else if (tonemapMode == CaptureResult.TONEMAP_MODE_GAMMA_VALUE) { mCollector.expectEquals("Capture result gamma value should match request", requestBuilder.get(CaptureRequest.TONEMAP_GAMMA), result.get(CaptureResult.TONEMAP_GAMMA)); } else if (tonemapMode == CaptureResult.TONEMAP_MODE_PRESET_CURVE) { mCollector.expectEquals("Capture result preset curve should match request", requestBuilder.get(CaptureRequest.TONEMAP_PRESET_CURVE), result.get(CaptureResult.TONEMAP_PRESET_CURVE)); } // Tonemap curve result availability and basic validity 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); // Make sure capture result tonemap has identical channels. if (mStaticInfo.isMonochromeCamera()) { mCollector.expectEquals("Capture result tonemap of monochrome camera should " + "have same dimension for all channels", mapRed.length, mapGreen.length); mCollector.expectEquals("Capture result tonemap of monochrome camera should " + "have same dimension for all channels", mapRed.length, mapBlue.length); if (mapRed.length == mapGreen.length && mapRed.length == mapBlue.length) { boolean isIdentical = true; for (int j = 0; j < mapRed.length; j++) { isIdentical = (mapRed[j] == mapGreen[j] && mapRed[j] == mapBlue[j]); if (!isIdentical) break; } mCollector.expectTrue("Capture result tonemap of monochrome camera should " + "be identical between all channels", isIdentical); } } } stopPreview(); } /** * Test awb mode control. *

* 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. *

*/ private void awbModeAndLockTestByCamera() throws Exception { int[] awbModes = mStaticInfo.getAwbAvailableModesChecked(); Size maxPreviewSize = mOrderedPreviewSizes.get(0); boolean canSetAwbLock = mStaticInfo.isAwbLockSupported(); 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 && canSetAwbLock) { // 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); } } // Don't verify auto mode result if AWB lock is not supported if (mode != CameraMetadata.CONTROL_AWB_MODE_AUTO || canSetAwbLock) { 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. *

* 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. *

*/ 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.isHardwareLevelAtLeastLimited() && (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE || mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO)) { List afStateList = new ArrayList(); 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> keys = mStaticInfo.getCharacteristics().getKeys(); Integer[] videoStabModes = (keys.contains(CameraCharacteristics. CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES)) ? CameraTestUtils.toObject(mStaticInfo.getAvailableVideoStabilizationModesChecked()) : new Integer[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 (Integer 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); // Video stabilization could return any modes. verifyAnyCaptureResultForKey(CaptureResult.CONTROL_VIDEO_STABILIZATION_MODE, videoStabModes, 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, boolean repeating) throws Exception { final PointF[] TEST_ZOOM_CENTERS; final float maxZoom = mStaticInfo.getAvailableMaxDigitalZoomChecked(); final float ZOOM_ERROR_MARGIN = 0.01f; if (Math.abs(maxZoom - 1.0f) < ZOOM_ERROR_MARGIN) { // It doesn't make much sense to test the zoom if the device effectively supports // no zoom. return; } final int croppingType = mStaticInfo.getScalerCroppingTypeChecked(); if (croppingType == CameraCharacteristics.SCALER_CROPPING_TYPE_FREEFORM) { // Set the four corners in a way that the minimally allowed zoom factor is 2x. float normalizedLeft = 0.25f; float normalizedTop = 0.25f; float normalizedRight = 0.75f; float normalizedBottom = 0.75f; // If the max supported zoom is too small, make sure we at least test the max // Zoom is tested for the four corners. if (maxZoom < 2.0f) { normalizedLeft = 0.5f / maxZoom; normalizedTop = 0.5f / maxZoom; normalizedRight = 1.0f - normalizedLeft; normalizedBottom = 1.0f - normalizedTop; } TEST_ZOOM_CENTERS = new PointF[] { new PointF(0.5f, 0.5f), // Center point new PointF(normalizedLeft, normalizedTop), // top left corner zoom new PointF(normalizedRight, normalizedTop), // top right corner zoom new PointF(normalizedLeft, normalizedBottom), // bottom left corner zoom new PointF(normalizedRight, normalizedBottom), // bottom right corner zoom }; 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 Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked(); final Rect defaultCropRegion = new Rect(0, 0, activeArraySize.width(), activeArraySize.height()); 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 final int METERING_RECT_RATIO = 10; final MeteringRectangle[][] defaultMeteringRects = new MeteringRectangle[][] { { new MeteringRectangle ( /*x*/0, /*y*/0, activeArraySize.width(), activeArraySize.height(), /*meteringWeight*/1), /* full active region */ }, { new MeteringRectangle ( /*x*/0, /*y*/0, activeArraySize.width()/METERING_RECT_RATIO, activeArraySize.height()/METERING_RECT_RATIO, /*meteringWeight*/1), }, { new MeteringRectangle ( /*x*/(int)(activeArraySize.width() * (0.5f - 0.5f/METERING_RECT_RATIO)), /*y*/(int)(activeArraySize.height() * (0.5f - 0.5f/METERING_RECT_RATIO)), activeArraySize.width()/METERING_RECT_RATIO, activeArraySize.height()/METERING_RECT_RATIO, /*meteringWeight*/1), }, }; final int CAPTURE_SUBMIT_REPEAT; final int NUM_RESULTS_TO_SKIP; { 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 (repeating) { NUM_RESULTS_TO_SKIP = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY + 1; } else { NUM_RESULTS_TO_SKIP = CAPTURE_SUBMIT_REPEAT - 1; } } if (VERBOSE) { Log.v(TAG, "Testing zoom with CAPTURE_SUBMIT_REPEAT = " + CAPTURE_SUBMIT_REPEAT); } for (MeteringRectangle[] meteringRect : defaultMeteringRects) { for (int algo = 0; algo < NUM_ALGORITHMS; algo++) { update3aRegion(requestBuilder, algo, meteringRect, mStaticInfo); } 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, defaultCropRegion); if (VERBOSE) { Log.v(TAG, "Testing Zoom for factor " + zoomFactor + " and center " + center + " The cropRegion is " + cropRegions[i] + " Preview size is " + previewSize + ", repeating is " + repeating); } requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, cropRegions[i]); requests[i] = requestBuilder.build(); if (VERBOSE) { Log.v(TAG, "submit crop region " + cropRegions[i]); } if (repeating) { mSession.setRepeatingRequest(requests[i], listener, mHandler); // Drop first few frames waitForNumResults(listener, NUM_RESULTS_TO_SKIP); // Interleave a regular capture mSession.capture(requests[0], listener, mHandler); } else { for (int j = 0; j < CAPTURE_SUBMIT_REPEAT; ++j) { mSession.capture(requests[i], listener, mHandler); } } /* * Validate capture result */ waitForNumResults(listener, NUM_RESULTS_TO_SKIP); // Drop first few frames TotalCaptureResult result = listener.getTotalCaptureResultForRequest( requests[i], NUM_RESULTS_WAIT_TIMEOUT); List partialResults = result.getPartialResults(); Rect cropRegion = getValueNotNull(result, CaptureResult.SCALER_CROP_REGION); for (CaptureResult partialResult : partialResults) { Rect cropRegionInPartial = partialResult.get(CaptureResult.SCALER_CROP_REGION); if (cropRegionInPartial != null) { mCollector.expectEquals("SCALER_CROP_REGION in partial result must " + "match in final result", cropRegionInPartial, cropRegion); } } if (CameraTestUtils.isStabilizationOff(requests[i])) { /* * 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.isHardwareLevelAtLeastLimited()) { 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*/meteringRect, /*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, partialResults, algo, expectRegions[i], false/*scaleByZoomRatio*/, mStaticInfo); } } 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 zoomRatioTestByCamera(Size previewSize, boolean useZoomRatioMethod) throws Exception { final Range zoomRatioRange = mStaticInfo.getZoomRatioRangeChecked(); // The error margin is derive from a VGA size camera zoomed all the way to 10x, in which // case the cropping error can be as large as 480/46 - 480/48 = 0.435. final float ZOOM_ERROR_MARGIN = 0.05f; final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked(); final Rect defaultCropRegion = new Rect(0, 0, activeArraySize.width(), activeArraySize.height()); final Rect zoom2xCropRegion = new Rect(activeArraySize.width()/4, activeArraySize.height()/4, activeArraySize.width()*3/4, activeArraySize.height()*3/4); MeteringRectangle[][] expectRegions = new MeteringRectangle[ZOOM_STEPS][]; CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, defaultCropRegion); if (Flags.zoomMethod() && useZoomRatioMethod) { requestBuilder.set(CaptureRequest.CONTROL_ZOOM_METHOD, CameraMetadata.CONTROL_ZOOM_METHOD_ZOOM_RATIO); } SimpleCaptureCallback listener = new SimpleCaptureCallback(); updatePreviewSurface(previewSize); configurePreviewOutput(requestBuilder); // Set algorithm regions to full active region 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, mStaticInfo); } final int captureSubmitRepeat; { int maxLatency = mStaticInfo.getSyncMaxLatency(); if (maxLatency == CameraMetadata.SYNC_MAX_LATENCY_UNKNOWN) { captureSubmitRepeat = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY + 1; } else { captureSubmitRepeat = maxLatency + 1; } } float previousRatio = zoomRatioRange.getLower(); for (int i = 0; i < ZOOM_STEPS; i++) { /* * Submit capture request */ float zoomFactor = zoomRatioRange.getLower() + (zoomRatioRange.getUpper() - zoomRatioRange.getLower()) * i / ZOOM_STEPS; if (VERBOSE) { Log.v(TAG, "Testing Zoom ratio " + zoomFactor + " Preview size is " + previewSize); } requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor); requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, defaultCropRegion); CaptureRequest request = requestBuilder.build(); for (int j = 0; j < captureSubmitRepeat; ++j) { mSession.capture(request, listener, mHandler); } /* * Validate capture result */ waitForNumResults(listener, captureSubmitRepeat - 1); // Drop first few frames TotalCaptureResult result = listener.getTotalCaptureResultForRequest( request, NUM_RESULTS_WAIT_TIMEOUT); List partialResults = result.getPartialResults(); float resultZoomRatio = getValueNotNull(result, CaptureResult.CONTROL_ZOOM_RATIO); Rect cropRegion = getValueNotNull(result, CaptureResult.SCALER_CROP_REGION); for (CaptureResult partialResult : partialResults) { Rect cropRegionInPartial = partialResult.get(CaptureResult.SCALER_CROP_REGION); if (cropRegionInPartial != null) { mCollector.expectEquals("SCALER_CROP_REGION in partial result must " + "match in final result", cropRegionInPartial, cropRegion); } Float zoomRatioInPartial = partialResult.get(CaptureResult.CONTROL_ZOOM_RATIO); if (zoomRatioInPartial != null) { mCollector.expectEquals("CONTROL_ZOOM_RATIO in partial result must match" + " that in final result", resultZoomRatio, zoomRatioInPartial); } } /* * Validate resulting crop regions and zoom ratio */ mCollector.expectTrue(String.format( "Zoom ratio should increase or stay the same " + "(previous = %f, current = %f)", previousRatio, resultZoomRatio), Math.abs(previousRatio - resultZoomRatio) < ZOOM_ERROR_MARGIN || (previousRatio < resultZoomRatio)); if (CameraTestUtils.isStabilizationOff(request)) { mCollector.expectTrue(String.format( "Request and result zoom ratio should be similar " + "(requested = %f, result = %f", zoomFactor, resultZoomRatio), Math.abs(zoomFactor - resultZoomRatio) / zoomFactor <= ZOOM_ERROR_MARGIN); //In case zoom ratio is converted to crop region at HAL, due to error magnification //when converting to post-zoom crop region, scale the error threshold for crop //region check. float errorMultiplier = Math.max(1.0f, zoomFactor); if (mStaticInfo.isHardwareLevelAtLeastLimited()) { mCollector.expectRectsAreSimilar( "Request and result crop region should be similar", defaultCropRegion, cropRegion, CROP_REGION_ERROR_PERCENT_DELTA * errorMultiplier); } mCollector.expectRectCentered( "Result crop region should be centered inside the active array", new Size(activeArraySize.width(), activeArraySize.height()), cropRegion, CROP_REGION_ERROR_PERCENT_CENTERED * errorMultiplier); /* * 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 boolean scaleByZoomRatio = zoomFactor > 1.0f; for (int algo = 0; algo < NUM_ALGORITHMS; algo++) { validate3aRegion(result, partialResults, algo, expectRegions[i], scaleByZoomRatio, mStaticInfo); } } previousRatio = resultZoomRatio; /* * Set windowboxing cropRegion while zoomRatio is not 1.0x or zoomRatio method * is used, and make sure the crop region was overwritten. */ if (zoomFactor != 1.0f || useZoomRatioMethod) { requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom2xCropRegion); CaptureRequest requestWithCrop = requestBuilder.build(); for (int j = 0; j < captureSubmitRepeat; ++j) { mSession.capture(requestWithCrop, listener, mHandler); } waitForNumResults(listener, captureSubmitRepeat - 1); // Drop first few frames CaptureResult resultWithCrop = listener.getCaptureResultForRequest( requestWithCrop, NUM_RESULTS_WAIT_TIMEOUT); float resultZoomRatioWithCrop = getValueNotNull(resultWithCrop, CaptureResult.CONTROL_ZOOM_RATIO); Rect cropRegionWithCrop = getValueNotNull(resultWithCrop, CaptureResult.SCALER_CROP_REGION); mCollector.expectTrue(String.format( "Result zoom ratio should remain the same (activeArrayCrop: %f, " + "zoomedCrop: %f)", resultZoomRatio, resultZoomRatioWithCrop), Math.abs(resultZoomRatio - resultZoomRatioWithCrop) < ZOOM_ERROR_MARGIN); if (mStaticInfo.isHardwareLevelAtLeastLimited()) { mCollector.expectRectsAreSimilar( "Result crop region should remain the same with or without crop", cropRegion, cropRegionWithCrop, CROP_REGION_ERROR_PERCENT_DELTA); } } } } private void zoomTimestampIncreaseTestByCamera() throws Exception { final Range zoomRatioRange = mStaticInfo.getZoomRatioRangeChecked(); Size maxPreviewSize = mOrderedPreviewSizes.get(0); updatePreviewSurface(maxPreviewSize); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); configurePreviewOutput(requestBuilder); // Submit a sequence of requests first zooming in then zooming out. List requests = new ArrayList(); SimpleCaptureCallback listener = new SimpleCaptureCallback(); float zoomRange = zoomRatioRange.getUpper() - zoomRatioRange.getLower(); for (int i = 0; i <= ZOOM_STEPS; i++) { float zoomFactor = zoomRatioRange.getUpper() - (zoomRange * i / ZOOM_STEPS); requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor); // Add each ratio to both the beginning and end of the list. requests.add(requestBuilder.build()); requests.add(0, requestBuilder.build()); } int seqId = mSession.captureBurst(requests, listener, mHandler); // onCaptureSequenceCompleted() trails all capture results. Upon its return, // we make sure we've received all results/errors. listener.getCaptureSequenceLastFrameNumber( seqId, WAIT_FOR_RESULT_TIMEOUT_MS * ZOOM_STEPS); // Check timestamp monotonically increase for the whole sequence long prevTimestamp = 0; while (listener.hasMoreResults()) { TotalCaptureResult result = listener.getTotalCaptureResult( WAIT_FOR_RESULT_TIMEOUT_MS); long timestamp = getValueNotNull(result, CaptureResult.SENSOR_TIMESTAMP); mCollector.expectGreater("Sensor timestamp must monotonically increase, " + "but changed from " + prevTimestamp + " to " + timestamp, prevTimestamp, timestamp); prevTimestamp = timestamp; } } private void digitalZoomPreviewCombinationTestByCamera() throws Exception { final double ASPECT_RATIO_THRESHOLD = 0.001; List aspectRatiosTested = new ArrayList(); 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, /*repeating*/false); } } private static boolean isAspectRatioContained(List 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); } } private void extendedSceneModeTestByCamera(List> fpsRanges) throws Exception { Capability[] extendedSceneModeCaps = mStaticInfo.getAvailableExtendedSceneModeCapsChecked(); if (extendedSceneModeCaps.length == 0) { return; } Size maxPreviewSize = mOrderedPreviewSizes.get(0); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); for (Capability cap : extendedSceneModeCaps) { int mode = cap.getMode(); requestBuilder.set(CaptureRequest.CONTROL_EXTENDED_SCENE_MODE, mode); // Test that DISABLED and BOKEH_CONTINUOUS mode doesn't slow down the frame rate if (mode == CaptureRequest.CONTROL_EXTENDED_SCENE_MODE_DISABLED || mode == CaptureRequest.CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS) { verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED, fpsRanges); } Range zoomRange = cap.getZoomRatioRange(); float[] zoomRatios = new float[]{zoomRange.getLower(), zoomRange.getUpper()}; for (float ratio : zoomRatios) { SimpleCaptureCallback listener = new SimpleCaptureCallback(); requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, ratio); startPreview(requestBuilder, maxPreviewSize, listener); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); verifyCaptureResultForKey(CaptureResult.CONTROL_EXTENDED_SCENE_MODE, mode, listener, NUM_FRAMES_VERIFIED); float zoomRatioDelta = ZOOM_RATIO_ERROR_PERCENT_DELTA * ratio; if (CameraTestUtils.isStabilizationOff(requestBuilder.build())) { verifyCaptureResultForKey(CaptureResult.CONTROL_ZOOM_RATIO, ratio, listener, NUM_FRAMES_VERIFIED, zoomRatioDelta); } } } } private void manualFlashStrengthControlTestByCamera() throws Exception { Size maxPrevSize = mOrderedPreviewSizes.get(0); int singleMaxLevel = mStaticInfo.getCharacteristics().get( CameraCharacteristics.FLASH_SINGLE_STRENGTH_MAX_LEVEL); int torchMaxLevel = mStaticInfo.getCharacteristics().get( CameraCharacteristics.FLASH_TORCH_STRENGTH_MAX_LEVEL); int strengthLevel = singleMaxLevel - 1; SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); // Single mode requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE); requestBuilder.set(CaptureRequest.FLASH_STRENGTH_LEVEL, strengthLevel); CaptureRequest request; updatePreviewSurface(maxPrevSize); configurePreviewOutput(requestBuilder); request = requestBuilder.build(); mSession.capture(request, resultListener, mHandler); waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); CaptureResult result = resultListener.getCaptureResultForRequest(request, NUM_RESULTS_WAIT_TIMEOUT); int resultStrengthLevel = getValueNotNull(result, CaptureResult.FLASH_STRENGTH_LEVEL); assertTrue(resultStrengthLevel == strengthLevel); assertTrue(resultStrengthLevel <= singleMaxLevel); // Torch mode strengthLevel = torchMaxLevel - 1; requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH); requestBuilder.set(CaptureRequest.FLASH_STRENGTH_LEVEL, strengthLevel); CaptureRequest torchRequest = requestBuilder.build(); mSession.setRepeatingRequest(torchRequest, resultListener, mHandler); waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); result = resultListener.getCaptureResultForRequest( torchRequest, NUM_RESULTS_WAIT_TIMEOUT); resultStrengthLevel = getValueNotNull(result, CaptureResult.FLASH_STRENGTH_LEVEL); assertTrue(resultStrengthLevel == strengthLevel); assertTrue(resultStrengthLevel <= torchMaxLevel); } private void autoframingTestByCamera() throws Exception { // Verify autoframing state, zoom ratio and video stabilizations controls for autoframing // modes ON and OFF int[] autoframingModes = {CameraMetadata.CONTROL_AUTOFRAMING_OFF, CameraMetadata.CONTROL_AUTOFRAMING_ON}; final int zoomSteps = 5; final float zoomErrorMargin = 0.05f; final int kMaxNumFrames = 200; Size maxPreviewSize = mOrderedPreviewSizes.get(0); // Max preview size. CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); SimpleCaptureCallback listener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPreviewSize, listener); for (int mode : autoframingModes) { float expectedZoomRatio = 0.0f; final Range zoomRatioRange = mStaticInfo.getZoomRatioRangeChecked(); for (int i = 0; i < zoomSteps; i++) { float testZoomRatio = zoomRatioRange.getLower() + (zoomRatioRange.getUpper() - zoomRatioRange.getLower()) * i / zoomSteps; // Zoom ratio 1.0f is a special case. The ZoomRatioMapper in framework maintains the // 1.0f ratio in the CaptureResult if (testZoomRatio == 1.0f) { continue; } requestBuilder.set(CaptureRequest.CONTROL_AUTOFRAMING, mode); requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, testZoomRatio); listener = new SimpleCaptureCallback(); mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); Float resultZoomRatio = getValueNotNull(result, CaptureResult.CONTROL_ZOOM_RATIO); int autoframingState = getValueNotNull(result, CaptureResult.CONTROL_AUTOFRAMING_STATE); int videoStabilizationMode = getValueNotNull(result, CaptureResult.CONTROL_VIDEO_STABILIZATION_MODE); if (mode == CameraMetadata.CONTROL_AUTOFRAMING_ON) { int numFrames = 0; while (numFrames < kMaxNumFrames) { result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); autoframingState = getValueNotNull(result, CaptureResult.CONTROL_AUTOFRAMING_STATE); assertTrue("Autoframing state should be FRAMING or CONVERGED when " + "AUTOFRAMING is ON", autoframingState == CameraMetadata.CONTROL_AUTOFRAMING_STATE_FRAMING || autoframingState == CameraMetadata.CONTROL_AUTOFRAMING_STATE_CONVERGED); assertTrue("Video Stablization should be OFF when AUTOFRAMING is ON", videoStabilizationMode == CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF); resultZoomRatio = getValueNotNull(result, CaptureResult.CONTROL_ZOOM_RATIO); if (autoframingState == CameraMetadata.CONTROL_AUTOFRAMING_STATE_CONVERGED) { break; } numFrames++; } if (autoframingState == CameraMetadata.CONTROL_AUTOFRAMING_STATE_CONVERGED && expectedZoomRatio == 0.0f) { expectedZoomRatio = resultZoomRatio; } } else { expectedZoomRatio = testZoomRatio; assertTrue("Autoframing state should be INACTIVE when AUTOFRAMING is OFF", autoframingState == CameraMetadata.CONTROL_AUTOFRAMING_STATE_INACTIVE); } verifyCaptureResultForKey(CaptureResult.CONTROL_AUTOFRAMING, mode, listener, NUM_FRAMES_VERIFIED); // If autoframing was OFF, or the framing state CONVERGED, the zoom ratio in result // should be within the margin of error. if (autoframingState != CameraMetadata.CONTROL_AUTOFRAMING_STATE_FRAMING) { mCollector.expectTrue(String.format( "Zoom Ratio in Capture Request does not match the expected zoom" + "ratio in Capture Result (expected = %f, actual = %f)", expectedZoomRatio, resultZoomRatio), Math.abs(expectedZoomRatio - resultZoomRatio) / expectedZoomRatio <= zoomErrorMargin); } } } } private void settingsOverrideTestByCamera() throws Exception { // Verify that settings override is OFF by default Size maxPreviewSize = mOrderedPreviewSizes.get(0); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); SimpleCaptureCallback listener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPreviewSize, listener); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); verifyCaptureResultForKey(CaptureResult.CONTROL_SETTINGS_OVERRIDE, CameraMetadata.CONTROL_SETTINGS_OVERRIDE_OFF, listener, NUM_FRAMES_VERIFIED); // Turn settings override to ZOOM, and make sure it's reflected in result requestBuilder.set(CaptureRequest.CONTROL_SETTINGS_OVERRIDE, CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM); SimpleCaptureCallback listenerZoom = new SimpleCaptureCallback(); mSession.setRepeatingRequest(requestBuilder.build(), listenerZoom, mHandler); waitForSettingsApplied(listenerZoom, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); verifyCaptureResultForKey(CaptureResult.CONTROL_SETTINGS_OVERRIDE, CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM, listenerZoom, NUM_FRAMES_VERIFIED); // Verify that settings override result is ON if turned on from the beginning listenerZoom = new SimpleCaptureCallback(); stopPreviewAndDrain(); startPreview(requestBuilder, maxPreviewSize, listenerZoom); waitForSettingsApplied(listenerZoom, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); // Wait additional 2 frames to allow non-overridden // results during startup. final int ZOOM_SOME_FRAMES = 2; waitForNumResults(listenerZoom, ZOOM_SOME_FRAMES); verifyCaptureResultForKey(CaptureResult.CONTROL_SETTINGS_OVERRIDE, CameraMetadata.CONTROL_SETTINGS_OVERRIDE_ZOOM, listenerZoom, NUM_FRAMES_VERIFIED); } private void testAeModeOnLowLightBoostBrightnessPriorityTestByCamera() throws Exception { Size maxPreviewSize = mOrderedPreviewSizes.get(0); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY); SimpleCaptureCallback listener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPreviewSize, listener); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); // Expect that AE_MODE is ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY int resultAeMode = getValueNotNull(result, CaptureResult.CONTROL_AE_MODE); assertTrue("AE Mode should be ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY", resultAeMode == CameraMetadata.CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY); // Expect that CaptureResult.CONTROL_LOW_LIGHT_BOOST_STATE is present int resultLowLightBoostState = getValueNotNull(result, CaptureResult.CONTROL_LOW_LIGHT_BOOST_STATE); assertTrue("Low Light Boost State should be ACTIVE or INACTIVE", resultLowLightBoostState == CameraMetadata.CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE || resultLowLightBoostState == CameraMetadata.CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE); } private void testAePriorityModesByCamera(int aePriorityMode) throws Exception { final int TEST_SENSITIVITY_VALUE = mStaticInfo.getSensitivityClampToRange(204); final long TEST_EXPOSURE_TIME_NS = mStaticInfo.getExposureClampToRange(28000000); final long EXPOSURE_TIME_ERROR_MARGIN_NS = 100000; Size maxPreviewSize = mOrderedPreviewSizes.get(0); CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON); requestBuilder.set(CaptureRequest.CONTROL_AE_PRIORITY_MODE, aePriorityMode); switch (aePriorityMode) { case CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY: requestBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, TEST_EXPOSURE_TIME_NS); break; case CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY: requestBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, TEST_SENSITIVITY_VALUE); break; default: throw new UnsupportedOperationException("Unhandled AE priority mode " + aePriorityMode); } SimpleCaptureCallback listener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPreviewSize, listener); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); // Expect that AE Priority mode result matches request int resultAePriorityMode = getValueNotNull(result, CaptureResult.CONTROL_AE_PRIORITY_MODE); assertTrue("AE Mode should be " + aePriorityMode, resultAePriorityMode == aePriorityMode); long exposureTimeDiff = TEST_EXPOSURE_TIME_NS - getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME); int sensitivityDiff = TEST_SENSITIVITY_VALUE - getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY); switch (aePriorityMode) { case CONTROL_AE_PRIORITY_MODE_SENSOR_EXPOSURE_TIME_PRIORITY: validateExposureTime(TEST_EXPOSURE_TIME_NS, getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME)); break; case CONTROL_AE_PRIORITY_MODE_SENSOR_SENSITIVITY_PRIORITY: validateSensitivity(TEST_SENSITIVITY_VALUE, getValueNotNull(result, CaptureResult.SENSOR_SENSITIVITY)); break; default: throw new UnsupportedOperationException("Unhandled AE priority mode " + aePriorityMode); } requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_OFF); listener = new SimpleCaptureCallback(); startPreview(requestBuilder, maxPreviewSize, listener); waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); // Expect that AE priority mode is off when AE mode if off resultAePriorityMode = getValueNotNull(result, CaptureResult.CONTROL_AE_PRIORITY_MODE); assertTrue("AE Priority mode should be off when AE mode is turned off", resultAePriorityMode == CameraMetadata.CONTROL_AE_PRIORITY_MODE_OFF); } //---------------------------------------------------------------- //---------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 correctness // 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. * *

The sensitivity is set to default value.

*/ 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, in nanoseconds. */ private long[] getExposureTimeTestValuesSorted() { long[] testValues = new long[DEFAULT_NUM_EXPOSURE_TIME_STEPS + 1]; long maxExpTime = mStaticInfo.getExposureMaximumOrDefault(DEFAULT_EXP_TIME_NS); long minExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS); long range = maxExpTime - minExpTime; double stepSize = range / (double)DEFAULT_NUM_EXPOSURE_TIME_STEPS; for (int i = 0; i < testValues.length; i++) { // Multiply stepSize by largest to smallest so that the final array is sorted. testValues[i] = maxExpTime - (long) (stepSize * (testValues.length - 1 - i)); testValues[i] = mStaticInfo.getExposureClampToRange(testValues[i]); } return testValues; } /** * Generate test focus distances in range of [0, minFocusDistance] in increasing order. * * @param repeatMin number of times minValue will be repeated. * @param repeatMax number of times maxValue will be repeated. */ private float[] getFocusDistanceTestValuesInOrder(int repeatMin, int repeatMax) { int totalCount = NUM_TEST_FOCUS_DISTANCES + 1 + repeatMin + repeatMax; float[] testValues = new float[totalCount]; float minValue = 0; float maxValue = mStaticInfo.getMinimumFocusDistanceChecked(); float range = maxValue - minValue; float stepSize = range / NUM_TEST_FOCUS_DISTANCES; for (int i = 0; i < repeatMin; i++) { testValues[i] = minValue; } for (int i = 0; i <= NUM_TEST_FOCUS_DISTANCES; i++) { testValues[repeatMin+i] = minValue + stepSize * i; } for (int i = 0; i < repeatMax; i++) { testValues[repeatMin+NUM_TEST_FOCUS_DISTANCES+1+i] = maxValue; } return testValues; } /** * Get the sensitivity array that contains multiple sensitivity steps in the * sensitivity range. *

* Sensitivity number of test values is determined by * {@value #DEFAULT_SENSITIVITY_STEP_SIZE} and sensitivity range, and * bounded by {@value #DEFAULT_NUM_SENSITIVITY_STEPS}. *

*/ private int[] getSensitivityTestValuesSorted() { 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++) { // Multiply stepSize by largest to smallest so that the final array is sorted. testValues[i] = maxSensitivity - stepSize * (testValues.length - 1 - i); testValues[i] = mStaticInfo.getSensitivityClampToRange(testValues[i]); } return testValues; } /** * Validate the AE control exposure time. * *

Exposure should be close enough, and only round down if they are not equal.

* * @param request Request exposure time * @param result Result exposure time */ private void validateExposureTime(long request, long result) { long expTimeDelta = request - result; long expTimeErrorMargin = (long)(Math.max(EXPOSURE_TIME_ERROR_MARGIN_NS, request * EXPOSURE_TIME_ERROR_MARGIN_RATE)); // First, round down not up, second, need close enough. mCollector.expectTrue("Exposure time is invalid, request: " + request + " result: " + result, expTimeDelta < expTimeErrorMargin && expTimeDelta >= 0); } /** * Validate AE control sensitivity. * * @param request Request sensitivity * @param result Result sensitivity */ private void validateSensitivity(int request, int result) { float sensitivityDelta = request - result; float sensitivityErrorMargin = request * SENSITIVITY_ERROR_MARGIN_RATE; // First, round down not up, second, need close enough. mCollector.expectTrue("Sensitivity is invalid, request: " + request + " result: " + result, sensitivityDelta < sensitivityErrorMargin && sensitivityDelta >= 0); } /** * Validate frame duration for a given capture. * *

Frame duration should be longer than exposure time.

* * @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); } /** * 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 * @param threshold The threshold by which the request and result keys can differ */ private void verifyCaptureResultForKey(CaptureResult.Key key, float requestMode, SimpleCaptureCallback listener, int numFramesVerified, float threshold) { for (int i = 0; i < numFramesVerified; i++) { CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); validatePipelineDepth(result); float resultMode = getValueNotNull(result, key); if (VERBOSE) { Log.v(TAG, "Expect value: " + requestMode + " result value: " + resultMode + " threshold " + threshold); } // Check that the request and result are within the given threshold of each other. // (expectEquals isn't the most intuitive function name.) mCollector.expectEquals("Key " + key.getName() + " request: " + requestMode + " result: " + resultMode + " not within threshold " + threshold + " of each other", requestMode, resultMode, threshold); } } /** * 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 void verifyCaptureResultForKey(CaptureResult.Key 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); } } /** * Basic verification that the value of a capture result key should be one of the expected * values. * * @param key The capture result key to be verified against * @param expectedModes The list of any possible expected modes for this result * @param listener The capture listener to get capture results * @param numFramesVerified The number of capture results to be verified */ private void verifyAnyCaptureResultForKey(CaptureResult.Key key, T[] expectedModes, 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 values: " + Arrays.toString(expectedModes) + " result value: " + resultMode.toString()); } // Capture result should be one of the expected values. mCollector.expectContains(expectedModes, resultMode); } } /** * Verify if the fps is slow down for given input request with certain * controls inside. *

* 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. *

* * @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 * @param fpsRanges The fps ranges to be verified */ private void verifyFpsNotSlowDown(CaptureRequest.Builder requestBuilder, int numFramesVerified, List> fpsRanges ) 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; } if (mStaticInfo.isExternalCamera()) { // Allow a even larger error margin (15%) for external camera timestamps frameDurationErrorMargin = 0.15f; } boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported(); Range fpsRange; SimpleCaptureCallback resultListener; for (int i = 0; i < fpsRanges.size(); i += 1) { fpsRange = fpsRanges.get(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; long frameDurationSum = 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; } frameDurationSum += frameDuration; } long frameDurationAvg = frameDurationSum / numFramesVerified; mCollector.expectInRange( "Frame duration must be in the range of " + Arrays.toString(frameDurationRange), frameDurationAvg, (long) (frameDurationRange[0] * (1 - frameDurationErrorMargin)), (long) (frameDurationRange[1] * (1 + frameDurationErrorMargin))); } stopPreview(); } /** * 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. *

* 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. *

*

* For long exposure time (larger than the flickering boundary), find the * exposure time that is closest to the flickering boundary. *

* * @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; } }