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.view.Surface; 23 import android.hardware.camera2.CameraCaptureSession; 24 import android.hardware.camera2.CameraCaptureSession.CaptureCallback; 25 import android.hardware.camera2.CameraDevice; 26 import android.hardware.camera2.CaptureFailure; 27 import android.hardware.camera2.CaptureRequest; 28 import android.hardware.camera2.CaptureResult; 29 import android.hardware.camera2.TotalCaptureResult; 30 import android.util.Size; 31 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback; 32 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; 33 import android.util.Log; 34 import android.util.Pair; 35 import android.util.Range; 36 37 import org.mockito.ArgumentCaptor; 38 import org.mockito.ArgumentMatcher; 39 40 import static org.mockito.Mockito.*; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.List; 45 46 /** 47 * CameraDevice preview test by using SurfaceView. 48 */ 49 public class SurfaceViewPreviewTest extends Camera2SurfaceViewTestCase { 50 private static final String TAG = "SurfaceViewPreviewTest"; 51 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 52 private static final int FRAME_TIMEOUT_MS = 1000; 53 private static final int NUM_FRAMES_VERIFIED = 30; 54 private static final int NUM_TEST_PATTERN_FRAMES_VERIFIED = 60; 55 private static final float FRAME_DURATION_ERROR_MARGIN = 0.005f; // 0.5 percent error margin. 56 57 @Override setUp()58 protected void setUp() throws Exception { 59 super.setUp(); 60 } 61 62 @Override tearDown()63 protected void tearDown() throws Exception { 64 super.tearDown(); 65 } 66 67 /** 68 * Test all supported preview sizes for each camera device. 69 * <p> 70 * For the first {@link #NUM_FRAMES_VERIFIED} of capture results, 71 * the {@link CaptureCallback} callback availability and the capture timestamp 72 * (monotonically increasing) ordering are verified. 73 * </p> 74 */ testCameraPreview()75 public void testCameraPreview() throws Exception { 76 for (int i = 0; i < mCameraIds.length; i++) { 77 try { 78 Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]); 79 openDevice(mCameraIds[i]); 80 if (!mStaticInfo.isColorOutputSupported()) { 81 Log.i(TAG, "Camera " + mCameraIds[i] + 82 " does not support color outputs, skipping"); 83 continue; 84 } 85 previewTestByCamera(); 86 } finally { 87 closeDevice(); 88 } 89 } 90 } 91 92 /** 93 * Basic test pattern mode preview. 94 * <p> 95 * Only test the test pattern preview and capture result, the image buffer 96 * is not validated. 97 * </p> 98 */ testBasicTestPatternPreview()99 public void testBasicTestPatternPreview() throws Exception{ 100 for (int i = 0; i < mCameraIds.length; i++) { 101 try { 102 Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]); 103 openDevice(mCameraIds[i]); 104 if (!mStaticInfo.isColorOutputSupported()) { 105 Log.i(TAG, "Camera " + mCameraIds[i] + 106 " does not support color outputs, skipping"); 107 continue; 108 } 109 previewTestPatternTestByCamera(); 110 } finally { 111 closeDevice(); 112 } 113 } 114 } 115 116 /** 117 * Test {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE} for preview, validate the preview 118 * frame duration and exposure time. 119 */ testPreviewFpsRange()120 public void testPreviewFpsRange() throws Exception { 121 for (String id : mCameraIds) { 122 try { 123 openDevice(id); 124 if (!mStaticInfo.isColorOutputSupported()) { 125 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 126 continue; 127 } 128 previewFpsRangeTestByCamera(); 129 } finally { 130 closeDevice(); 131 } 132 } 133 } 134 135 /** 136 * Test to verify the {@link CameraCaptureSession#prepare} method works correctly, and has the 137 * expected effects on performance. 138 * 139 * - Ensure that prepare() results in onSurfacePrepared() being invoked 140 * - Ensure that prepare() does not cause preview glitches while operating 141 * - Ensure that starting to use a newly-prepared output does not cause additional 142 * preview glitches to occur 143 */ testPreparePerformance()144 public void testPreparePerformance() throws Throwable { 145 for (int i = 0; i < mCameraIds.length; i++) { 146 try { 147 openDevice(mCameraIds[i]); 148 if (!mStaticInfo.isColorOutputSupported()) { 149 Log.i(TAG, "Camera " + mCameraIds[i] + 150 " does not support color outputs, skipping"); 151 continue; 152 } 153 preparePerformanceTestByCamera(mCameraIds[i]); 154 } 155 finally { 156 closeDevice(); 157 } 158 } 159 } 160 preparePerformanceTestByCamera(String cameraId)161 private void preparePerformanceTestByCamera(String cameraId) throws Exception { 162 final int MAX_IMAGES_TO_PREPARE = 10; 163 final int UNKNOWN_LATENCY_RESULT_WAIT = 5; 164 final int MAX_RESULTS_TO_WAIT = 10; 165 final int FRAMES_FOR_AVERAGING = 100; 166 final int PREPARE_TIMEOUT_MS = 10000; // 10 s 167 final float PREPARE_FRAME_RATE_BOUNDS = 0.05f; // fraction allowed difference 168 final float PREPARE_PEAK_RATE_BOUNDS = 0.5f; // fraction allowed difference 169 170 Size maxYuvSize = getSupportedPreviewSizes(cameraId, mCameraManager, null).get(0); 171 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 172 173 // Don't need image data, just drop it right away to minimize overhead 174 ImageDropperListener imageListener = new ImageDropperListener(); 175 176 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 177 178 CaptureRequest.Builder previewRequest = 179 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 180 181 // Configure outputs and session 182 183 updatePreviewSurface(maxPreviewSize); 184 185 createImageReader(maxYuvSize, ImageFormat.YUV_420_888, MAX_IMAGES_TO_PREPARE, imageListener); 186 187 List<Surface> outputSurfaces = new ArrayList<Surface>(); 188 outputSurfaces.add(mPreviewSurface); 189 outputSurfaces.add(mReaderSurface); 190 191 CameraCaptureSession.StateCallback mockSessionListener = 192 mock(CameraCaptureSession.StateCallback.class); 193 194 mSession = configureCameraSession(mCamera, outputSurfaces, mockSessionListener, mHandler); 195 196 previewRequest.addTarget(mPreviewSurface); 197 Range<Integer> maxFpsTarget = mStaticInfo.getAeMaxTargetFpsRange(); 198 previewRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, maxFpsTarget); 199 200 mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler); 201 202 // Converge AE 203 waitForAeStable(resultListener, UNKNOWN_LATENCY_RESULT_WAIT); 204 205 if (mStaticInfo.isAeLockSupported()) { 206 // Lock AE if possible to improve stability 207 previewRequest.set(CaptureRequest.CONTROL_AE_LOCK, true); 208 mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler); 209 if (mStaticInfo.isHardwareLevelLimitedOrBetter()) { 210 // Legacy mode doesn't output AE state 211 waitForResultValue(resultListener, CaptureResult.CONTROL_AE_STATE, 212 CaptureResult.CONTROL_AE_STATE_LOCKED, MAX_RESULTS_TO_WAIT); 213 } 214 } 215 216 // Measure frame rate for a bit 217 Pair<Long, Long> frameDurationStats = 218 measureMeanFrameInterval(resultListener, FRAMES_FOR_AVERAGING, /*prevTimestamp*/ 0); 219 220 Log.i(TAG, String.format("Frame interval avg during normal preview: %f ms, peak %f ms", 221 frameDurationStats.first / 1e6, frameDurationStats.second / 1e6)); 222 223 // Drain results, do prepare 224 resultListener.drain(); 225 226 mSession.prepare(mReaderSurface); 227 228 verify(mockSessionListener, 229 timeout(PREPARE_TIMEOUT_MS).times(1)). 230 onSurfacePrepared(eq(mSession), eq(mReaderSurface)); 231 232 // Calculate frame rate during prepare 233 234 int resultsReceived = (int) resultListener.getTotalNumFrames(); 235 if (resultsReceived > 2) { 236 // Only verify frame rate if there are a couple of results 237 Pair<Long, Long> whilePreparingFrameDurationStats = 238 measureMeanFrameInterval(resultListener, resultsReceived, /*prevTimestamp*/ 0); 239 240 Log.i(TAG, String.format("Frame interval during prepare avg: %f ms, peak %f ms", 241 whilePreparingFrameDurationStats.first / 1e6, 242 whilePreparingFrameDurationStats.second / 1e6)); 243 244 if (mStaticInfo.isHardwareLevelLimitedOrBetter()) { 245 mCollector.expectTrue( 246 String.format("Camera %s: Preview peak frame interval affected by prepare " + 247 "call: preview avg frame duration: %f ms, peak during prepare: %f ms", 248 cameraId, 249 frameDurationStats.first / 1e6, 250 whilePreparingFrameDurationStats.second / 1e6), 251 (whilePreparingFrameDurationStats.second <= 252 frameDurationStats.first * (1 + PREPARE_PEAK_RATE_BOUNDS))); 253 mCollector.expectTrue( 254 String.format("Camera %s: Preview average frame interval affected by prepare " + 255 "call: preview avg frame duration: %f ms, during prepare: %f ms", 256 cameraId, 257 frameDurationStats.first / 1e6, 258 whilePreparingFrameDurationStats.first / 1e6), 259 (whilePreparingFrameDurationStats.first <= 260 frameDurationStats.first * (1 + PREPARE_FRAME_RATE_BOUNDS))); 261 } 262 } 263 264 resultListener.drain(); 265 266 // Get at least one more preview result without prepared target 267 CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 268 long prevTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP); 269 270 // Now use the prepared stream and ensure there are no hiccups from using it 271 previewRequest.addTarget(mReaderSurface); 272 273 mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler); 274 275 Pair<Long, Long> preparedFrameDurationStats = 276 measureMeanFrameInterval(resultListener, MAX_IMAGES_TO_PREPARE*2, prevTimestamp); 277 278 Log.i(TAG, String.format("Frame interval with prepared stream added avg: %f ms, peak %f ms", 279 preparedFrameDurationStats.first / 1e6, 280 preparedFrameDurationStats.second / 1e6)); 281 282 if (mStaticInfo.isHardwareLevelLimitedOrBetter()) { 283 mCollector.expectTrue( 284 String.format("Camera %s: Preview peak frame interval affected by use of new " + 285 " stream: preview avg frame duration: %f ms, peak with new stream: %f ms", 286 cameraId, 287 frameDurationStats.first / 1e6, preparedFrameDurationStats.second / 1e6), 288 (preparedFrameDurationStats.second <= 289 frameDurationStats.first * (1 + PREPARE_PEAK_RATE_BOUNDS))); 290 mCollector.expectTrue( 291 String.format("Camera %s: Preview average frame interval affected by use of new " + 292 "stream: preview avg frame duration: %f ms, with new stream: %f ms", 293 cameraId, 294 frameDurationStats.first / 1e6, preparedFrameDurationStats.first / 1e6), 295 (preparedFrameDurationStats.first <= 296 frameDurationStats.first * (1 + PREPARE_FRAME_RATE_BOUNDS))); 297 } 298 } 299 300 /** 301 * Measure the inter-frame interval based on SENSOR_TIMESTAMP for frameCount frames from the 302 * provided capture listener. If prevTimestamp is positive, it is used for the first interval 303 * calculation; otherwise, the first result is used to establish the starting time. 304 * 305 * Returns the mean interval in the first pair entry, and the largest interval in the second 306 * pair entry 307 */ measureMeanFrameInterval(SimpleCaptureCallback resultListener, int frameCount, long prevTimestamp)308 Pair<Long, Long> measureMeanFrameInterval(SimpleCaptureCallback resultListener, int frameCount, 309 long prevTimestamp) throws Exception { 310 long summedIntervals = 0; 311 long maxInterval = 0; 312 int measurementCount = frameCount - ((prevTimestamp > 0) ? 0 : 1); 313 314 for (int i = 0; i < frameCount; i++) { 315 CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 316 long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP); 317 if (prevTimestamp > 0) { 318 long interval = timestamp - prevTimestamp; 319 if (interval > maxInterval) maxInterval = interval; 320 summedIntervals += interval; 321 } 322 prevTimestamp = timestamp; 323 } 324 return new Pair<Long, Long>(summedIntervals / measurementCount, maxInterval); 325 } 326 327 328 /** 329 * Test preview fps range for all supported ranges. The exposure time are frame duration are 330 * validated. 331 */ previewFpsRangeTestByCamera()332 private void previewFpsRangeTestByCamera() throws Exception { 333 final int FPS_RANGE_SIZE = 2; 334 Size maxPreviewSz = mOrderedPreviewSizes.get(0); 335 Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked(); 336 boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported(); 337 Range<Integer> fpsRange; 338 CaptureRequest.Builder requestBuilder = 339 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 340 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 341 startPreview(requestBuilder, maxPreviewSz, resultListener); 342 343 for (int i = 0; i < fpsRanges.length; i += 1) { 344 fpsRange = fpsRanges[i]; 345 346 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 347 // Turn off auto antibanding to avoid exposure time and frame duration interference 348 // from antibanding algorithm. 349 if (antiBandingOffIsSupported) { 350 requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE, 351 CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_OFF); 352 } else { 353 // The device doesn't implement the OFF mode, test continues. It need make sure 354 // that the antibanding algorithm doesn't interfere with the fps range control. 355 Log.i(TAG, "OFF antibanding mode is not supported, the camera device output must" + 356 " satisfy the specified fps range regardless of its current antibanding" + 357 " mode"); 358 } 359 360 resultListener = new SimpleCaptureCallback(); 361 mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler); 362 363 verifyPreviewTargetFpsRange(resultListener, NUM_FRAMES_VERIFIED, fpsRange, 364 maxPreviewSz); 365 } 366 367 stopPreview(); 368 } 369 verifyPreviewTargetFpsRange(SimpleCaptureCallback resultListener, int numFramesVerified, Range<Integer> fpsRange, Size previewSz)370 private void verifyPreviewTargetFpsRange(SimpleCaptureCallback resultListener, 371 int numFramesVerified, Range<Integer> fpsRange, Size previewSz) { 372 CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 373 List<Integer> capabilities = mStaticInfo.getAvailableCapabilitiesChecked(); 374 375 if (capabilities.contains(CaptureRequest.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { 376 long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION); 377 long[] frameDurationRange = 378 new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())}; 379 mCollector.expectInRange( 380 "Frame duration must be in the range of " + Arrays.toString(frameDurationRange), 381 frameDuration, (long) (frameDurationRange[0] * (1 - FRAME_DURATION_ERROR_MARGIN)), 382 (long) (frameDurationRange[1] * (1 + FRAME_DURATION_ERROR_MARGIN))); 383 long expTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME); 384 mCollector.expectTrue(String.format("Exposure time %d must be no larger than frame" 385 + "duration %d", expTime, frameDuration), expTime <= frameDuration); 386 387 Long minFrameDuration = mMinPreviewFrameDurationMap.get(previewSz); 388 boolean findDuration = mCollector.expectTrue("Unable to find minFrameDuration for size " 389 + previewSz.toString(), minFrameDuration != null); 390 if (findDuration) { 391 mCollector.expectTrue("Frame duration " + frameDuration + " must be no smaller than" 392 + " minFrameDuration " + minFrameDuration, frameDuration >= minFrameDuration); 393 } 394 } else { 395 Log.i(TAG, "verifyPreviewTargetFpsRange - MANUAL_SENSOR control is not supported," + 396 " skipping duration and exposure time check."); 397 } 398 } 399 400 /** 401 * Test all supported preview sizes for a camera device 402 * 403 * @throws Exception 404 */ previewTestByCamera()405 private void previewTestByCamera() throws Exception { 406 List<Size> previewSizes = getSupportedPreviewSizes( 407 mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND); 408 409 for (final Size sz : previewSizes) { 410 if (VERBOSE) { 411 Log.v(TAG, "Testing camera preview size: " + sz.toString()); 412 } 413 414 // TODO: vary the different settings like crop region to cover more cases. 415 CaptureRequest.Builder requestBuilder = 416 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 417 CaptureCallback mockCaptureCallback = 418 mock(CameraCaptureSession.CaptureCallback.class); 419 420 startPreview(requestBuilder, sz, mockCaptureCallback); 421 verifyCaptureResults(mSession, mockCaptureCallback, NUM_FRAMES_VERIFIED, 422 NUM_FRAMES_VERIFIED * FRAME_TIMEOUT_MS); 423 stopPreview(); 424 } 425 } 426 previewTestPatternTestByCamera()427 private void previewTestPatternTestByCamera() throws Exception { 428 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 429 int[] testPatternModes = mStaticInfo.getAvailableTestPatternModesChecked(); 430 CaptureRequest.Builder requestBuilder = 431 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 432 CaptureCallback mockCaptureCallback; 433 434 final int[] TEST_PATTERN_DATA = {0, 0xFFFFFFFF, 0xFFFFFFFF, 0}; // G:100%, RB:0. 435 for (int mode : testPatternModes) { 436 if (VERBOSE) { 437 Log.v(TAG, "Test pattern mode: " + mode); 438 } 439 requestBuilder.set(CaptureRequest.SENSOR_TEST_PATTERN_MODE, mode); 440 if (mode == CaptureRequest.SENSOR_TEST_PATTERN_MODE_SOLID_COLOR) { 441 // Assign color pattern to SENSOR_TEST_PATTERN_MODE_DATA 442 requestBuilder.set(CaptureRequest.SENSOR_TEST_PATTERN_DATA, TEST_PATTERN_DATA); 443 } 444 mockCaptureCallback = mock(CaptureCallback.class); 445 startPreview(requestBuilder, maxPreviewSize, mockCaptureCallback); 446 verifyCaptureResults(mSession, mockCaptureCallback, NUM_TEST_PATTERN_FRAMES_VERIFIED, 447 NUM_TEST_PATTERN_FRAMES_VERIFIED * FRAME_TIMEOUT_MS); 448 } 449 450 stopPreview(); 451 } 452 453 private class IsCaptureResultValid extends ArgumentMatcher<TotalCaptureResult> { 454 @Override matches(Object obj)455 public boolean matches(Object obj) { 456 TotalCaptureResult result = (TotalCaptureResult)obj; 457 Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP); 458 if (timeStamp != null && timeStamp.longValue() > 0L) { 459 return true; 460 } 461 return false; 462 } 463 } 464 verifyCaptureResults( CameraCaptureSession session, CaptureCallback mockListener, int expectResultCount, int timeOutMs)465 private void verifyCaptureResults( 466 CameraCaptureSession session, 467 CaptureCallback mockListener, 468 int expectResultCount, 469 int timeOutMs) { 470 // Should receive expected number of onCaptureStarted callbacks. 471 ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class); 472 ArgumentCaptor<Long> frameNumbers = ArgumentCaptor.forClass(Long.class); 473 verify(mockListener, 474 timeout(timeOutMs).atLeast(expectResultCount)) 475 .onCaptureStarted( 476 eq(session), 477 isA(CaptureRequest.class), 478 timestamps.capture(), 479 frameNumbers.capture()); 480 481 // Validate timestamps: all timestamps should be larger than 0 and monotonically increase. 482 long timestamp = 0; 483 for (Long nextTimestamp : timestamps.getAllValues()) { 484 assertNotNull("Next timestamp is null!", nextTimestamp); 485 assertTrue("Captures are out of order", timestamp < nextTimestamp); 486 timestamp = nextTimestamp; 487 } 488 489 // Validate framenumbers: all framenumbers should be consecutive and positive 490 long frameNumber = -1; 491 for (Long nextFrameNumber : frameNumbers.getAllValues()) { 492 assertNotNull("Next frame number is null!", nextFrameNumber); 493 assertTrue("Captures are out of order", 494 (frameNumber == -1) || (frameNumber + 1 == nextFrameNumber)); 495 frameNumber = nextFrameNumber; 496 } 497 498 // Should receive expected number of capture results. 499 verify(mockListener, 500 timeout(timeOutMs).atLeast(expectResultCount)) 501 .onCaptureCompleted( 502 eq(session), 503 isA(CaptureRequest.class), 504 argThat(new IsCaptureResultValid())); 505 506 // Should not receive any capture failed callbacks. 507 verify(mockListener, never()) 508 .onCaptureFailed( 509 eq(session), 510 isA(CaptureRequest.class), 511 isA(CaptureFailure.class)); 512 } 513 514 } 515