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