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