1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hardware.camera2.cts; 18 19 import static android.hardware.camera2.cts.CameraTestUtils.*; 20 21 import android.graphics.ImageFormat; 22 import android.hardware.camera2.CameraDevice; 23 import android.hardware.camera2.CaptureRequest; 24 import android.hardware.camera2.CaptureResult; 25 import android.hardware.camera2.CameraCharacteristics; 26 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; 27 import android.hardware.camera2.params.StreamConfigurationMap; 28 import android.media.Image; 29 import android.platform.test.annotations.AppModeFull; 30 import android.util.Log; 31 import android.util.Range; 32 import android.util.Size; 33 34 import java.util.List; 35 import java.util.ArrayList; 36 37 @AppModeFull 38 public class BurstCaptureTest extends Camera2SurfaceViewTestCase { 39 private static final String TAG = "BurstCaptureTest"; 40 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 41 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 42 43 /** 44 * Test YUV burst capture with full-AUTO control. 45 * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available. 46 */ 47 public void testYuvBurst() throws Exception { 48 for (int i = 0; i < mCameraIds.length; i++) { 49 try { 50 String id = mCameraIds[i]; 51 Log.i(TAG, "Testing YUV Burst for camera " + id); 52 openDevice(id); 53 54 if (!mStaticInfo.isColorOutputSupported()) { 55 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 56 } 57 if (!mStaticInfo.isAeLockSupported() || !mStaticInfo.isAwbLockSupported()) { 58 Log.i(TAG, "AE/AWB lock is not supported in camera " + id + 59 ". Skip the test"); 60 continue; 61 } 62 63 if (mStaticInfo.isHardwareLevelLegacy()) { 64 Log.i(TAG, "Legacy camera doesn't report min frame duration" + 65 ". Skip the test"); 66 continue; 67 } 68 69 yuvBurstTestByCamera(id); 70 } finally { 71 closeDevice(); 72 closeImageReader(); 73 } 74 } 75 } 76 77 private void yuvBurstTestByCamera(String cameraId) throws Exception { 78 // Parameters 79 final int MAX_CONVERGENCE_FRAMES = 150; // 5 sec at 30fps 80 final long MAX_PREVIEW_RESULT_TIMEOUT_MS = 1000; 81 final int BURST_SIZE = 100; 82 final float FRAME_DURATION_MARGIN_FRACTION = 0.1f; 83 84 // Find a good preview size (bound to 1080p) 85 final Size previewSize = mOrderedPreviewSizes.get(0); 86 87 // Get maximum YUV_420_888 size 88 final Size stillSize = getSortedSizesForFormat( 89 cameraId, mCameraManager, ImageFormat.YUV_420_888, /*bound*/null).get(0); 90 91 // Find max pipeline depth and sync latency 92 final int maxPipelineDepth = mStaticInfo.getCharacteristics().get( 93 CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH); 94 final int maxSyncLatency = mStaticInfo.getCharacteristics().get( 95 CameraCharacteristics.SYNC_MAX_LATENCY); 96 97 // Find minimum frame duration for full-res YUV_420_888 98 StreamConfigurationMap config = mStaticInfo.getCharacteristics().get( 99 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 100 final long minStillFrameDuration = 101 config.getOutputMinFrameDuration(ImageFormat.YUV_420_888, stillSize); 102 103 104 Range<Integer> targetRange = getSuitableFpsRangeForDuration(cameraId, minStillFrameDuration); 105 106 Log.i(TAG, String.format("Selected frame rate range %d - %d for YUV burst", 107 targetRange.getLower(), targetRange.getUpper())); 108 109 // Check if READ_SENSOR_SETTINGS is supported 110 final boolean checkSensorSettings = mStaticInfo.isCapabilitySupported( 111 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS); 112 113 // Configure basic preview and burst settings 114 115 CaptureRequest.Builder previewBuilder = 116 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 117 CaptureRequest.Builder burstBuilder = 118 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 119 120 previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 121 targetRange); 122 burstBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 123 targetRange); 124 burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); 125 burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true); 126 127 // Create session and start up preview 128 129 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 130 ImageDropperListener imageDropper = new ImageDropperListener(); 131 132 prepareCaptureAndStartPreview( 133 previewBuilder, burstBuilder, 134 previewSize, stillSize, 135 ImageFormat.YUV_420_888, resultListener, 136 /*maxNumImages*/ 3, imageDropper); 137 138 // Create burst 139 140 List<CaptureRequest> burst = new ArrayList<>(); 141 for (int i = 0; i < BURST_SIZE; i++) { 142 burst.add(burstBuilder.build()); 143 } 144 145 // Converge AE/AWB 146 147 int frameCount = 0; 148 while (true) { 149 CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 150 int aeState = result.get(CaptureResult.CONTROL_AE_STATE); 151 int awbState = result.get(CaptureResult.CONTROL_AWB_STATE); 152 153 if (DEBUG) { 154 Log.d(TAG, "aeState: " + aeState + ". awbState: " + awbState); 155 } 156 157 if ((aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || 158 aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) && 159 awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED) { 160 break; 161 } 162 frameCount++; 163 assertTrue(String.format("Cam %s: Can not converge AE and AWB within %d frames", 164 cameraId, MAX_CONVERGENCE_FRAMES), 165 frameCount < MAX_CONVERGENCE_FRAMES); 166 } 167 168 // Lock AF if there's a focuser 169 170 if (mStaticInfo.hasFocuser()) { 171 previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 172 CaptureRequest.CONTROL_AF_TRIGGER_START); 173 mSession.capture(previewBuilder.build(), resultListener, mHandler); 174 previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 175 CaptureRequest.CONTROL_AF_TRIGGER_IDLE); 176 177 frameCount = 0; 178 while (true) { 179 CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 180 int afState = result.get(CaptureResult.CONTROL_AF_STATE); 181 182 if (DEBUG) { 183 Log.d(TAG, "afState: " + afState); 184 } 185 186 if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || 187 afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { 188 break; 189 } 190 frameCount++; 191 assertTrue(String.format("Cam %s: Cannot lock AF within %d frames", cameraId, 192 MAX_CONVERGENCE_FRAMES), 193 frameCount < MAX_CONVERGENCE_FRAMES); 194 } 195 } 196 197 // Lock AE/AWB 198 199 previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); 200 previewBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true); 201 202 CaptureRequest lockedRequest = previewBuilder.build(); 203 mSession.setRepeatingRequest(lockedRequest, resultListener, mHandler); 204 205 // Wait for first result with locking 206 resultListener.drain(); 207 CaptureResult lockedResult = 208 resultListener.getCaptureResultForRequest(lockedRequest, maxPipelineDepth); 209 210 int pipelineDepth = lockedResult.get(CaptureResult.REQUEST_PIPELINE_DEPTH); 211 212 // Then start waiting on results to get the first result that should be synced 213 // up, and also fire the burst as soon as possible 214 215 if (maxSyncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL) { 216 // The locked result we have is already synchronized so start the burst 217 mSession.captureBurst(burst, resultListener, mHandler); 218 } else { 219 // Need to get a synchronized result, and may need to start burst later to 220 // be synchronized correctly 221 222 boolean burstSent = false; 223 224 // Calculate how many requests we need to still send down to camera before we 225 // know the settings have settled for the burst 226 227 int numFramesWaited = maxSyncLatency; 228 if (numFramesWaited == CameraCharacteristics.SYNC_MAX_LATENCY_UNKNOWN) { 229 numFramesWaited = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY; 230 } 231 232 int requestsNeededToSync = numFramesWaited - pipelineDepth; 233 for (int i = 0; i < numFramesWaited; i++) { 234 if (!burstSent && requestsNeededToSync <= 0) { 235 mSession.captureBurst(burst, resultListener, mHandler); 236 burstSent = true; 237 } 238 lockedResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 239 requestsNeededToSync--; 240 } 241 242 assertTrue("Cam " + cameraId + ": Burst failed to fire!", burstSent); 243 } 244 245 // Read in locked settings if supported 246 247 long burstExposure = 0; 248 long burstFrameDuration = 0; 249 int burstSensitivity = 0; 250 if (checkSensorSettings) { 251 burstExposure = lockedResult.get(CaptureResult.SENSOR_EXPOSURE_TIME); 252 burstFrameDuration = lockedResult.get(CaptureResult.SENSOR_FRAME_DURATION); 253 burstSensitivity = lockedResult.get(CaptureResult.SENSOR_SENSITIVITY); 254 255 assertTrue(String.format("Cam %s: Frame duration %d ns too short compared to " + 256 "exposure time %d ns", cameraId, burstFrameDuration, burstExposure), 257 burstFrameDuration >= burstExposure); 258 259 assertTrue(String.format("Cam %s: Exposure time is not valid: %d", 260 cameraId, burstExposure), 261 burstExposure > 0); 262 assertTrue(String.format("Cam %s: Frame duration is not valid: %d", 263 cameraId, burstFrameDuration), 264 burstFrameDuration > 0); 265 assertTrue(String.format("Cam %s: Sensitivity is not valid: %d", 266 cameraId, burstSensitivity), 267 burstSensitivity > 0); 268 } 269 270 // Process burst results 271 int burstIndex = 0; 272 CaptureResult burstResult = 273 resultListener.getCaptureResultForRequest(burst.get(burstIndex), 274 maxPipelineDepth + 1); 275 long prevTimestamp = -1; 276 final long frameDurationBound = (long) 277 (minStillFrameDuration * (1 + FRAME_DURATION_MARGIN_FRACTION) ); 278 279 List<Long> frameDurations = new ArrayList<>(); 280 281 while(true) { 282 // Verify the result 283 assertTrue("Cam " + cameraId + ": Result doesn't match expected request", 284 burstResult.getRequest() == burst.get(burstIndex)); 285 286 // Verify locked settings 287 if (checkSensorSettings) { 288 long exposure = burstResult.get(CaptureResult.SENSOR_EXPOSURE_TIME); 289 int sensitivity = burstResult.get(CaptureResult.SENSOR_SENSITIVITY); 290 assertTrue("Cam " + cameraId + ": Exposure not locked!", 291 exposure == burstExposure); 292 assertTrue("Cam " + cameraId + ": Sensitivity not locked!", 293 sensitivity == burstSensitivity); 294 } 295 296 // Collect inter-frame durations 297 long timestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP); 298 if (prevTimestamp != -1) { 299 long frameDuration = timestamp - prevTimestamp; 300 frameDurations.add(frameDuration); 301 if (DEBUG) { 302 Log.i(TAG, String.format("Frame %03d Duration %.2f ms", burstIndex, 303 frameDuration/1e6)); 304 } 305 } 306 prevTimestamp = timestamp; 307 308 // Get next result 309 burstIndex++; 310 if (burstIndex == BURST_SIZE) break; 311 burstResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 312 } 313 314 // Verify inter-frame durations 315 316 long meanFrameSum = 0; 317 for (Long duration : frameDurations) { 318 meanFrameSum += duration; 319 } 320 float meanFrameDuration = (float) meanFrameSum / frameDurations.size(); 321 322 float stddevSum = 0; 323 for (Long duration : frameDurations) { 324 stddevSum += (duration - meanFrameDuration) * (duration - meanFrameDuration); 325 } 326 float stddevFrameDuration = (float) 327 Math.sqrt(1.f / (frameDurations.size() - 1 ) * stddevSum); 328 329 Log.i(TAG, String.format("Cam %s: Burst frame duration mean: %.1f, stddev: %.1f", cameraId, 330 meanFrameDuration, stddevFrameDuration)); 331 332 assertTrue( 333 String.format("Cam %s: Burst frame duration mean %.1f ns is larger than acceptable, " + 334 "expecting below %d ns, allowing below %d", cameraId, 335 meanFrameDuration, minStillFrameDuration, frameDurationBound), 336 meanFrameDuration <= frameDurationBound); 337 338 // Calculate upper 97.5% bound (assuming durations are normally distributed...) 339 float limit95FrameDuration = meanFrameDuration + 2 * stddevFrameDuration; 340 341 // Don't enforce this yet, but warn 342 if (limit95FrameDuration > frameDurationBound) { 343 Log.w(TAG, 344 String.format("Cam %s: Standard deviation is too large compared to limit: " + 345 "mean: %.1f ms, stddev: %.1f ms: 95%% bound: %f ms", cameraId, 346 meanFrameDuration/1e6, stddevFrameDuration/1e6, 347 limit95FrameDuration/1e6)); 348 } 349 } 350 } 351