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.CameraMetadata; 27 import android.hardware.camera2.cts.helpers.StaticMetadata; 28 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; 29 import android.hardware.camera2.params.Capability; 30 import android.hardware.camera2.params.StreamConfigurationMap; 31 import android.media.Image; 32 import android.util.Log; 33 import android.util.Range; 34 import android.util.Size; 35 36 import java.util.List; 37 import java.util.ArrayList; 38 39 import org.junit.runner.RunWith; 40 import org.junit.runners.Parameterized; 41 import org.junit.Test; 42 43 @RunWith(Parameterized.class) 44 public class BurstCaptureTest extends Camera2SurfaceViewTestCase { 45 private static final String TAG = "BurstCaptureTest"; 46 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 47 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 48 49 /** 50 * Test YUV burst capture with full-AUTO control. 51 * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available. 52 */ 53 @Test testYuvBurst()54 public void testYuvBurst() throws Exception { 55 final int YUV_BURST_SIZE = 100; 56 testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/, 57 false/*testStillBokeh*/); 58 } 59 60 /** 61 * Test JPEG burst capture with full-AUTO control. 62 * 63 * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available. 64 * Compared to testYuvBurst, this test uses STILL_CAPTURE intent, and exercises path where 65 * enableZsl is enabled. 66 */ 67 @Test testJpegBurst()68 public void testJpegBurst() throws Exception { 69 final int JPEG_BURST_SIZE = 10; 70 testBurst(ImageFormat.JPEG, JPEG_BURST_SIZE, false/*checkFrameRate*/, 71 false/*testStillBokeh*/); 72 } 73 74 /** 75 * Test YUV burst capture with full-AUTO control and STILL_CAPTURE bokeh mode. 76 * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available. 77 */ 78 @Test testYuvBurstWithStillBokeh()79 public void testYuvBurstWithStillBokeh() throws Exception { 80 final int YUV_BURST_SIZE = 100; 81 testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/, 82 true/*testStillBokeh*/); 83 } 84 testBurst(int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh)85 private void testBurst(int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh) 86 throws Exception { 87 String[] cameraIdsUnderTest = getCameraIdsUnderTest(); 88 for (int i = 0; i < cameraIdsUnderTest.length; i++) { 89 try { 90 String id = cameraIdsUnderTest[i]; 91 92 StaticMetadata staticInfo = mAllStaticInfo.get(id); 93 if (!staticInfo.isColorOutputSupported()) { 94 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 95 } 96 if (!staticInfo.isAeLockSupported() || !staticInfo.isAwbLockSupported()) { 97 Log.i(TAG, "AE/AWB lock is not supported in camera " + id + 98 ". Skip the test"); 99 continue; 100 } 101 102 if (staticInfo.isHardwareLevelLegacy()) { 103 Log.i(TAG, "Legacy camera doesn't report min frame duration" + 104 ". Skip the test"); 105 continue; 106 } 107 108 Capability[] extendedSceneModeCaps = 109 staticInfo.getAvailableExtendedSceneModeCapsChecked(); 110 boolean supportStillBokeh = false; 111 for (Capability cap : extendedSceneModeCaps) { 112 if (cap.getMode() == 113 CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE) { 114 supportStillBokeh = true; 115 break; 116 } 117 } 118 if (testStillBokeh && !supportStillBokeh) { 119 Log.v(TAG, "Device doesn't support STILL_CAPTURE bokeh. Skip the test"); 120 continue; 121 } 122 123 openDevice(id); 124 burstTestByCamera(id, fmt, burstSize, checkFrameRate, testStillBokeh); 125 } finally { 126 closeDevice(); 127 closeImageReader(); 128 } 129 } 130 } 131 burstTestByCamera(String cameraId, int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh)132 private void burstTestByCamera(String cameraId, int fmt, int burstSize, 133 boolean checkFrameRate, boolean testStillBokeh) throws Exception { 134 // Parameters 135 final int MAX_CONVERGENCE_FRAMES = 150; // 5 sec at 30fps 136 final long MAX_PREVIEW_RESULT_TIMEOUT_MS = 2000; 137 final float FRAME_DURATION_MARGIN_FRACTION = 0.1f; 138 139 // Find a good preview size (bound to 1080p) 140 final Size previewSize = mOrderedPreviewSizes.get(0); 141 142 // Get maximum size for fmt 143 final Size stillSize = getSortedSizesForFormat( 144 cameraId, mCameraManager, fmt, /*bound*/null).get(0); 145 146 // Find max pipeline depth and sync latency 147 final int maxPipelineDepth = mStaticInfo.getCharacteristics().get( 148 CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH); 149 final int maxSyncLatency = mStaticInfo.getCharacteristics().get( 150 CameraCharacteristics.SYNC_MAX_LATENCY); 151 152 // Find minimum frame duration for full-res resolution 153 StreamConfigurationMap config = mStaticInfo.getCharacteristics().get( 154 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 155 final long minStillFrameDuration = 156 config.getOutputMinFrameDuration(fmt, stillSize); 157 158 159 Range<Integer> targetRange = getSuitableFpsRangeForDuration(cameraId, minStillFrameDuration); 160 161 Log.i(TAG, String.format("Selected frame rate range %d - %d for YUV burst", 162 targetRange.getLower(), targetRange.getUpper())); 163 164 // Check if READ_SENSOR_SETTINGS is supported 165 final boolean checkSensorSettings = mStaticInfo.isCapabilitySupported( 166 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS); 167 168 // Configure basic preview and burst settings 169 170 CaptureRequest.Builder previewBuilder = 171 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 172 int burstTemplate = (fmt == ImageFormat.JPEG) ? 173 CameraDevice.TEMPLATE_STILL_CAPTURE : CameraDevice.TEMPLATE_PREVIEW; 174 CaptureRequest.Builder burstBuilder = mCamera.createCaptureRequest(burstTemplate); 175 Boolean enableZsl = burstBuilder.get(CaptureRequest.CONTROL_ENABLE_ZSL); 176 boolean zslStillEnabled = enableZsl != null && enableZsl && 177 burstTemplate == CameraDevice.TEMPLATE_STILL_CAPTURE; 178 179 previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 180 targetRange); 181 burstBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 182 targetRange); 183 burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); 184 burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true); 185 if (testStillBokeh) { 186 previewBuilder.set(CaptureRequest.CONTROL_EXTENDED_SCENE_MODE, 187 CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE); 188 burstBuilder.set(CaptureRequest.CONTROL_EXTENDED_SCENE_MODE, 189 CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE); 190 } 191 192 // Create session and start up preview 193 194 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 195 SimpleCaptureCallback burstResultListener = new SimpleCaptureCallback(); 196 ImageDropperListener imageDropper = new ImageDropperListener(); 197 198 prepareCaptureAndStartPreview( 199 previewBuilder, burstBuilder, 200 previewSize, stillSize, 201 fmt, resultListener, 202 /*maxNumImages*/ 3, imageDropper); 203 204 // Create burst 205 206 List<CaptureRequest> burst = new ArrayList<>(); 207 for (int i = 0; i < burstSize; i++) { 208 burst.add(burstBuilder.build()); 209 } 210 211 // Converge AE/AWB 212 213 int frameCount = 0; 214 while (true) { 215 CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 216 int aeState = result.get(CaptureResult.CONTROL_AE_STATE); 217 int awbState = result.get(CaptureResult.CONTROL_AWB_STATE); 218 219 if (DEBUG) { 220 Log.d(TAG, "aeState: " + aeState + ". awbState: " + awbState); 221 } 222 223 if ((aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED || 224 aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) && 225 awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED) { 226 break; 227 } 228 frameCount++; 229 assertTrue(String.format("Cam %s: Can not converge AE and AWB within %d frames", 230 cameraId, MAX_CONVERGENCE_FRAMES), 231 frameCount < MAX_CONVERGENCE_FRAMES); 232 } 233 234 // Lock AF if there's a focuser 235 236 if (mStaticInfo.hasFocuser()) { 237 previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 238 CaptureRequest.CONTROL_AF_TRIGGER_START); 239 mSession.capture(previewBuilder.build(), resultListener, mHandler); 240 previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, 241 CaptureRequest.CONTROL_AF_TRIGGER_IDLE); 242 243 frameCount = 0; 244 while (true) { 245 CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 246 int afState = result.get(CaptureResult.CONTROL_AF_STATE); 247 248 if (DEBUG) { 249 Log.d(TAG, "afState: " + afState); 250 } 251 252 if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || 253 afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) { 254 break; 255 } 256 frameCount++; 257 assertTrue(String.format("Cam %s: Cannot lock AF within %d frames", cameraId, 258 MAX_CONVERGENCE_FRAMES), 259 frameCount < MAX_CONVERGENCE_FRAMES); 260 } 261 } 262 263 // Lock AE/AWB 264 265 previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); 266 previewBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true); 267 268 CaptureRequest lockedRequest = previewBuilder.build(); 269 mSession.setRepeatingRequest(lockedRequest, resultListener, mHandler); 270 271 // Wait for first result with locking 272 resultListener.drain(); 273 // Add 1 extra frame to wait due to earlier repeating request could introduce 274 // 1 more frame delay. 275 CaptureResult lockedResult = 276 resultListener.getCaptureResultForRequest(lockedRequest, maxPipelineDepth + 1); 277 278 int pipelineDepth = lockedResult.get(CaptureResult.REQUEST_PIPELINE_DEPTH); 279 280 // Then start waiting on results to get the first result that should be synced 281 // up, and also fire the burst as soon as possible 282 283 if (maxSyncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL) { 284 // The locked result we have is already synchronized so start the burst 285 mSession.captureBurst(burst, burstResultListener, mHandler); 286 } else { 287 // Need to get a synchronized result, and may need to start burst later to 288 // be synchronized correctly 289 290 boolean burstSent = false; 291 292 // Calculate how many requests we need to still send down to camera before we 293 // know the settings have settled for the burst 294 295 int numFramesWaited = maxSyncLatency; 296 if (numFramesWaited == CameraCharacteristics.SYNC_MAX_LATENCY_UNKNOWN) { 297 numFramesWaited = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY; 298 } 299 300 int requestsNeededToSync = numFramesWaited - pipelineDepth; 301 for (int i = 0; i < numFramesWaited; i++) { 302 if (!burstSent && requestsNeededToSync <= 0) { 303 mSession.captureBurst(burst, burstResultListener, mHandler); 304 burstSent = true; 305 } 306 lockedResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 307 requestsNeededToSync--; 308 } 309 310 assertTrue("Cam " + cameraId + ": Burst failed to fire!", burstSent); 311 } 312 313 // Read in locked settings if supported 314 315 long burstExposure = 0; 316 long burstFrameDuration = 0; 317 int burstSensitivity = 0; 318 if (checkSensorSettings) { 319 burstExposure = lockedResult.get(CaptureResult.SENSOR_EXPOSURE_TIME); 320 burstFrameDuration = lockedResult.get(CaptureResult.SENSOR_FRAME_DURATION); 321 burstSensitivity = lockedResult.get(CaptureResult.SENSOR_SENSITIVITY); 322 323 assertTrue(String.format("Cam %s: Frame duration %d ns too short compared to " + 324 "exposure time %d ns", cameraId, burstFrameDuration, burstExposure), 325 burstFrameDuration >= burstExposure); 326 327 assertTrue(String.format("Cam %s: Exposure time is not valid: %d", 328 cameraId, burstExposure), 329 burstExposure > 0); 330 assertTrue(String.format("Cam %s: Frame duration is not valid: %d", 331 cameraId, burstFrameDuration), 332 burstFrameDuration > 0); 333 assertTrue(String.format("Cam %s: Sensitivity is not valid: %d", 334 cameraId, burstSensitivity), 335 burstSensitivity > 0); 336 } 337 338 // Process burst results 339 int burstIndex = 0; 340 CaptureResult burstResult = 341 burstResultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 342 long prevTimestamp = -1; 343 final long frameDurationBound = (long) 344 (minStillFrameDuration * (1 + FRAME_DURATION_MARGIN_FRACTION) ); 345 346 long burstStartTimestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP); 347 long burstEndTimeStamp = 0; 348 349 List<Long> frameDurations = new ArrayList<>(); 350 351 while(true) { 352 // Verify the result 353 assertTrue("Cam " + cameraId + ": Result doesn't match expected request", 354 burstResult.getRequest() == burst.get(burstIndex)); 355 356 // Verify locked settings 357 if (checkSensorSettings) { 358 long exposure = burstResult.get(CaptureResult.SENSOR_EXPOSURE_TIME); 359 int sensitivity = burstResult.get(CaptureResult.SENSOR_SENSITIVITY); 360 assertTrue("Cam " + cameraId + ": Exposure not locked!", 361 exposure == burstExposure); 362 assertTrue("Cam " + cameraId + ": Sensitivity not locked!", 363 sensitivity == burstSensitivity); 364 } 365 366 // Collect inter-frame durations 367 long timestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP); 368 if (prevTimestamp != -1) { 369 long frameDuration = timestamp - prevTimestamp; 370 frameDurations.add(frameDuration); 371 if (DEBUG) { 372 Log.i(TAG, String.format("Frame %03d Duration %.2f ms", burstIndex, 373 frameDuration/1e6)); 374 } 375 } 376 prevTimestamp = timestamp; 377 378 // Get next result 379 burstIndex++; 380 if (burstIndex == burstSize) { 381 burstEndTimeStamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP); 382 break; 383 } 384 burstResult = burstResultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 385 } 386 387 // Verify no preview frames interleaved in burst results 388 while (true) { 389 CaptureResult previewResult = 390 resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS); 391 long previewTimestamp = previewResult.get(CaptureResult.SENSOR_TIMESTAMP); 392 if (!zslStillEnabled && previewTimestamp >= burstStartTimestamp 393 && previewTimestamp <= burstEndTimeStamp) { 394 fail("Preview frame is interleaved with burst frames! Preview timestamp:" + 395 previewTimestamp + ", burst [" + burstStartTimestamp + ", " + 396 burstEndTimeStamp + "]"); 397 } else if (previewTimestamp > burstEndTimeStamp) { 398 break; 399 } 400 } 401 402 // Verify inter-frame durations 403 if (checkFrameRate) { 404 long meanFrameSum = 0; 405 for (Long duration : frameDurations) { 406 meanFrameSum += duration; 407 } 408 float meanFrameDuration = (float) meanFrameSum / frameDurations.size(); 409 410 float stddevSum = 0; 411 for (Long duration : frameDurations) { 412 stddevSum += (duration - meanFrameDuration) * (duration - meanFrameDuration); 413 } 414 float stddevFrameDuration = (float) 415 Math.sqrt(1.f / (frameDurations.size() - 1 ) * stddevSum); 416 417 Log.i(TAG, String.format("Cam %s: Burst frame duration mean: %.1f, stddev: %.1f", 418 cameraId, meanFrameDuration, stddevFrameDuration)); 419 420 assertTrue( 421 String.format("Cam %s: Burst frame duration mean %.1f ns is larger than " + 422 "acceptable, expecting below %d ns, allowing below %d", cameraId, 423 meanFrameDuration, minStillFrameDuration, frameDurationBound), 424 meanFrameDuration <= frameDurationBound); 425 426 // Calculate upper 97.5% bound (assuming durations are normally distributed...) 427 float limit95FrameDuration = meanFrameDuration + 2 * stddevFrameDuration; 428 429 // Don't enforce this yet, but warn 430 if (limit95FrameDuration > frameDurationBound) { 431 Log.w(TAG, 432 String.format("Cam %s: Standard deviation is too large compared to limit: " + 433 "mean: %.1f ms, stddev: %.1f ms: 95%% bound: %f ms", cameraId, 434 meanFrameDuration/1e6, stddevFrameDuration/1e6, 435 limit95FrameDuration/1e6)); 436 } 437 } 438 } 439 } 440