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