1 /* 2 * Copyright 2018 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.ImageDropperListener; 20 import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND; 21 import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CONFIGURE_TIMEOUT_MS; 22 import static android.hardware.camera2.cts.CameraTestUtils.SessionConfigSupport; 23 import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback; 24 import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener; 25 import static android.hardware.camera2.cts.CameraTestUtils.assertFalse; 26 import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull; 27 import static android.hardware.camera2.cts.CameraTestUtils.assertTrue; 28 import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSessionWithConfig; 29 import static android.hardware.camera2.cts.CameraTestUtils.fail; 30 import static android.hardware.camera2.cts.CameraTestUtils.getCropRegionForZoom; 31 import static android.hardware.camera2.cts.CameraTestUtils.getMaxPreviewSize; 32 import static android.hardware.camera2.cts.CameraTestUtils.getPreviewSizeBound; 33 import static android.hardware.camera2.cts.CameraTestUtils.getSupportedPreviewSizes; 34 import static android.hardware.camera2.cts.CameraTestUtils.getUnavailablePhysicalCameras; 35 import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported; 36 37 import static org.mockito.Mockito.any; 38 import static org.mockito.Mockito.mock; 39 import static org.mockito.Mockito.never; 40 import static org.mockito.Mockito.timeout; 41 import static org.mockito.Mockito.verify; 42 43 import android.content.Context; 44 import android.content.Intent; 45 import android.content.IntentFilter; 46 import android.graphics.ImageFormat; 47 import android.graphics.PointF; 48 import android.graphics.Rect; 49 import android.hardware.camera2.CameraCaptureSession; 50 import android.hardware.camera2.CameraCharacteristics; 51 import android.hardware.camera2.CameraDevice; 52 import android.hardware.camera2.CaptureFailure; 53 import android.hardware.camera2.CaptureRequest; 54 import android.hardware.camera2.CaptureResult; 55 import android.hardware.camera2.TotalCaptureResult; 56 import android.hardware.camera2.cts.helpers.StaticMetadata; 57 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; 58 import android.hardware.camera2.params.OutputConfiguration; 59 import android.hardware.camera2.params.SessionConfiguration; 60 import android.hardware.camera2.params.StreamConfigurationMap; 61 import android.media.Image; 62 import android.media.ImageReader; 63 import android.os.BatteryManager; 64 import android.util.ArraySet; 65 import android.util.DisplayMetrics; 66 import android.util.Log; 67 import android.util.Pair; 68 import android.util.Range; 69 import android.util.Size; 70 import android.util.SizeF; 71 import android.view.WindowManager; 72 73 import com.android.compatibility.common.util.CddTest; 74 import com.android.ex.camera2.blocking.BlockingSessionCallback; 75 import com.android.ex.camera2.utils.StateWaiter; 76 77 import org.junit.Test; 78 import org.junit.runner.RunWith; 79 import org.junit.runners.Parameterized; 80 81 import java.util.ArrayList; 82 import java.util.Arrays; 83 import java.util.HashMap; 84 import java.util.List; 85 import java.util.Map; 86 import java.util.Set; 87 88 /** 89 * Tests exercising logical camera setup, configuration, and usage. 90 */ 91 @RunWith(Parameterized.class) 92 public final class LogicalCameraDeviceTest extends Camera2SurfaceViewTestCase { 93 private static final String TAG = "LogicalCameraDeviceTest"; 94 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 95 96 private static final int CONFIGURE_TIMEOUT = 5000; //ms 97 98 private static final double NS_PER_MS = 1000000.0; 99 private static final int MAX_IMAGE_COUNT = 3; 100 private static final int NUM_FRAMES_CHECKED = 300; 101 102 private static final double FRAME_DURATION_THRESHOLD = 0.03; 103 private static final double FOV_THRESHOLD = 0.03; 104 private static final double ZOOM_RATIO_THRESHOLD = 0.01; 105 private static final long MAX_TIMESTAMP_DIFFERENCE_THRESHOLD = 10000000; // 10ms 106 107 private StateWaiter mSessionWaiter; 108 109 private final int[] sTemplates = new int[] { 110 CameraDevice.TEMPLATE_PREVIEW, 111 CameraDevice.TEMPLATE_RECORD, 112 CameraDevice.TEMPLATE_STILL_CAPTURE, 113 CameraDevice.TEMPLATE_VIDEO_SNAPSHOT, 114 CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG, 115 CameraDevice.TEMPLATE_MANUAL, 116 }; 117 118 @Override setUp()119 public void setUp() throws Exception { 120 super.setUp(); 121 } 122 123 /** 124 * Test that passing in invalid physical camera ids in OutputConfiguragtion behaves as expected 125 * for logical multi-camera and non-logical multi-camera. 126 */ 127 @Test testInvalidPhysicalCameraIdInOutputConfiguration()128 public void testInvalidPhysicalCameraIdInOutputConfiguration() throws Exception { 129 for (String id : getCameraIdsUnderTest()) { 130 try { 131 Log.i(TAG, "Testing Camera " + id); 132 if (mAllStaticInfo.get(id).isHardwareLevelLegacy()) { 133 Log.i(TAG, "Camera " + id + " is legacy, skipping"); 134 continue; 135 } 136 if (!mAllStaticInfo.get(id).isColorOutputSupported()) { 137 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 138 continue; 139 } 140 141 openDevice(id); 142 Size yuvSize = mOrderedPreviewSizes.get(0); 143 // Create a YUV image reader. 144 ImageReader imageReader = ImageReader.newInstance(yuvSize.getWidth(), 145 yuvSize.getHeight(), ImageFormat.YUV_420_888, /*maxImages*/1); 146 147 CameraCaptureSession.StateCallback sessionListener = 148 mock(CameraCaptureSession.StateCallback.class); 149 List<OutputConfiguration> outputs = new ArrayList<>(); 150 OutputConfiguration outputConfig = new OutputConfiguration( 151 imageReader.getSurface()); 152 OutputConfiguration outputConfigCopy = 153 new OutputConfiguration(imageReader.getSurface()); 154 assertTrue("OutputConfiguration must be equal to its copy", 155 outputConfig.equals(outputConfigCopy)); 156 outputConfig.setPhysicalCameraId(id); 157 assertFalse("OutputConfigurations with different physical Ids must be different", 158 outputConfig.equals(outputConfigCopy)); 159 String idCopy = new String(id); 160 outputConfigCopy.setPhysicalCameraId(idCopy); 161 assertTrue("OutputConfigurations with same physical Ids must be equal", 162 outputConfig.equals(outputConfigCopy)); 163 164 // Regardless of logical camera or non-logical camera, create a session of an 165 // output configuration with invalid physical camera id, verify that the 166 // createCaptureSession fails. 167 outputs.add(outputConfig); 168 CameraCaptureSession session = 169 CameraTestUtils.configureCameraSessionWithConfig(mCamera, outputs, 170 sessionListener, mHandler); 171 verify(sessionListener, timeout(CONFIGURE_TIMEOUT).atLeastOnce()). 172 onConfigureFailed(any(CameraCaptureSession.class)); 173 verify(sessionListener, never()).onConfigured(any(CameraCaptureSession.class)); 174 verify(sessionListener, never()).onReady(any(CameraCaptureSession.class)); 175 verify(sessionListener, never()).onActive(any(CameraCaptureSession.class)); 176 verify(sessionListener, never()).onClosed(any(CameraCaptureSession.class)); 177 } finally { 178 closeDevice(); 179 } 180 } 181 } 182 183 /** 184 * Test for making sure that streaming from physical streams work as expected. 185 */ 186 @Test testBasicPhysicalStreaming()187 public void testBasicPhysicalStreaming() throws Exception { 188 Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras( 189 mCameraManager, mHandler); 190 for (String id : getCameraIdsUnderTest()) { 191 try { 192 Log.i(TAG, "Testing Camera " + id); 193 194 StaticMetadata staticInfo = mAllStaticInfo.get(id); 195 if (!staticInfo.isColorOutputSupported()) { 196 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 197 continue; 198 } 199 200 if (!staticInfo.isLogicalMultiCamera()) { 201 Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping"); 202 continue; 203 } 204 205 openDevice(id); 206 assertTrue("Logical multi-camera must be LIMITED or higher", 207 mStaticInfo.isHardwareLevelAtLeastLimited()); 208 209 // Figure out preview size and physical cameras to use. 210 ArrayList<String> dualPhysicalCameraIds = new ArrayList<String>(); 211 Size previewSize = findCommonPreviewSize(id, dualPhysicalCameraIds, 212 unavailablePhysicalCameras); 213 if (previewSize == null) { 214 Log.i(TAG, "Camera " + id + ": No matching physical preview streams, skipping"); 215 continue; 216 } 217 218 testBasicPhysicalStreamingForCamera( 219 id, dualPhysicalCameraIds, previewSize); 220 } finally { 221 closeDevice(); 222 } 223 } 224 } 225 226 /** 227 * Test for making sure that logical/physical stream requests work when both logical stream 228 * and physical stream are configured. 229 */ 230 @Test testBasicLogicalPhysicalStreamCombination()231 public void testBasicLogicalPhysicalStreamCombination() throws Exception { 232 Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras( 233 mCameraManager, mHandler); 234 for (String id : getCameraIdsUnderTest()) { 235 try { 236 Log.i(TAG, "Testing Camera " + id); 237 238 StaticMetadata staticInfo = mAllStaticInfo.get(id); 239 if (!staticInfo.isColorOutputSupported()) { 240 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 241 continue; 242 } 243 244 if (!staticInfo.isLogicalMultiCamera()) { 245 Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping"); 246 continue; 247 } 248 249 openDevice(id); 250 assertTrue("Logical multi-camera must be LIMITED or higher", 251 mStaticInfo.isHardwareLevelAtLeastLimited()); 252 253 // Figure out yuv size and physical cameras to use. 254 List<String> dualPhysicalCameraIds = new ArrayList<String>(); 255 Size yuvSize = findCommonPreviewSize(id, dualPhysicalCameraIds, 256 unavailablePhysicalCameras); 257 if (yuvSize == null) { 258 Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping"); 259 continue; 260 } 261 262 if (VERBOSE) { 263 Log.v(TAG, "Camera " + id + ": Testing YUV size of " + yuvSize.getWidth() + 264 " x " + yuvSize.getHeight()); 265 } 266 267 List<OutputConfiguration> outputConfigs = new ArrayList<>(); 268 List<SimpleImageReaderListener> imageReaderListeners = new ArrayList<>(); 269 SimpleImageReaderListener readerListenerPhysical = new SimpleImageReaderListener(); 270 ImageReader yuvTargetPhysical = CameraTestUtils.makeImageReader(yuvSize, 271 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 272 readerListenerPhysical, mHandler); 273 OutputConfiguration config = new OutputConfiguration(yuvTargetPhysical.getSurface()); 274 config.setPhysicalCameraId(dualPhysicalCameraIds.get(0)); 275 outputConfigs.add(config); 276 277 SimpleImageReaderListener readerListenerLogical = new SimpleImageReaderListener(); 278 ImageReader yuvTargetLogical = CameraTestUtils.makeImageReader(yuvSize, 279 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 280 readerListenerLogical, mHandler); 281 outputConfigs.add(new OutputConfiguration(yuvTargetLogical.getSurface())); 282 imageReaderListeners.add(readerListenerLogical); 283 284 SessionConfigSupport sessionConfigSupport = isSessionConfigSupported( 285 mCamera, mHandler, outputConfigs, /*inputConfig*/ null, 286 SessionConfiguration.SESSION_REGULAR, mCameraManager, 287 false/*defaultSupport*/); 288 assertTrue("Session configuration query for logical camera failed with error", 289 !sessionConfigSupport.error); 290 if (!sessionConfigSupport.callSupported) { 291 continue; 292 } 293 294 mSessionListener = new BlockingSessionCallback(); 295 mSessionWaiter = mSessionListener.getStateWaiter(); 296 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, 297 mSessionListener, mHandler); 298 if (!sessionConfigSupport.configSupported) { 299 mSessionWaiter.waitForState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED, 300 SESSION_CONFIGURE_TIMEOUT_MS); 301 continue; 302 } 303 304 // Test request logical stream with an idle physical stream. 305 CaptureRequest.Builder requestBuilder = 306 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 307 requestBuilder.addTarget(yuvTargetLogical.getSurface()); 308 309 for (int i = 0; i < MAX_IMAGE_COUNT; i++) { 310 mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler); 311 for (SimpleImageReaderListener readerListener : imageReaderListeners) { 312 Image image = readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS); 313 image.close(); 314 } 315 } 316 317 // Test request physical stream with an idle logical stream. 318 imageReaderListeners.clear(); 319 imageReaderListeners.add(readerListenerPhysical); 320 321 requestBuilder.removeTarget(yuvTargetLogical.getSurface()); 322 requestBuilder.addTarget(yuvTargetPhysical.getSurface()); 323 324 for (int i = 0; i < MAX_IMAGE_COUNT; i++) { 325 mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler); 326 for (SimpleImageReaderListener readerListener : imageReaderListeners) { 327 Image image = readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS); 328 image.close(); 329 } 330 } 331 332 // Test request logical and physical streams at the same time 333 imageReaderListeners.clear(); 334 readerListenerLogical.drain(); 335 imageReaderListeners.add(readerListenerLogical); 336 readerListenerPhysical.drain(); 337 imageReaderListeners.add(readerListenerPhysical); 338 339 requestBuilder.addTarget(yuvTargetLogical.getSurface()); 340 for (int i = 0; i < MAX_IMAGE_COUNT; i++) { 341 mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler); 342 for (SimpleImageReaderListener readerListener : imageReaderListeners) { 343 Image image = readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS); 344 image.close(); 345 } 346 } 347 348 if (mSession != null) { 349 mSession.close(); 350 } 351 352 // Image readers need to be referenced so that they aren't garbage collected 353 // before the function exit. 354 CameraTestUtils.closeImageReader(yuvTargetPhysical); 355 CameraTestUtils.closeImageReader(yuvTargetLogical); 356 } finally { 357 closeDevice(); 358 } 359 } 360 } 361 362 /** 363 * Test for making sure that multiple requests for physical cameras work as expected. 364 */ 365 @Test testBasicPhysicalRequests()366 public void testBasicPhysicalRequests() throws Exception { 367 Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras( 368 mCameraManager, mHandler); 369 for (String id : getCameraIdsUnderTest()) { 370 try { 371 Log.i(TAG, "Testing Camera " + id); 372 373 StaticMetadata staticInfo = mAllStaticInfo.get(id); 374 if (!staticInfo.isColorOutputSupported()) { 375 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 376 continue; 377 } 378 379 if (!staticInfo.isLogicalMultiCamera()) { 380 Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping"); 381 continue; 382 } 383 384 openDevice(id); 385 assertTrue("Logical multi-camera must be LIMITED or higher", 386 mStaticInfo.isHardwareLevelAtLeastLimited()); 387 388 // Figure out yuv size and physical cameras to use. 389 List<String> dualPhysicalCameraIds = new ArrayList<String>(); 390 Size yuvSize = findCommonPreviewSize(id, dualPhysicalCameraIds, 391 unavailablePhysicalCameras); 392 if (yuvSize == null) { 393 Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping"); 394 continue; 395 } 396 ArraySet<String> physicalIdSet = new ArraySet<String>(dualPhysicalCameraIds.size()); 397 physicalIdSet.addAll(dualPhysicalCameraIds); 398 399 if (VERBOSE) { 400 Log.v(TAG, "Camera " + id + ": Testing YUV size of " + yuvSize.getWidth() + 401 " x " + yuvSize.getHeight()); 402 } 403 List<CaptureRequest.Key<?>> physicalRequestKeys = 404 mStaticInfo.getCharacteristics().getAvailablePhysicalCameraRequestKeys(); 405 if (physicalRequestKeys == null) { 406 Log.i(TAG, "Camera " + id + ": no available physical request keys, skipping"); 407 continue; 408 } 409 410 List<OutputConfiguration> outputConfigs = new ArrayList<>(); 411 List<ImageReader> imageReaders = new ArrayList<>(); 412 List<SimpleImageReaderListener> imageReaderListeners = new ArrayList<>(); 413 for (String physicalCameraId : dualPhysicalCameraIds) { 414 SimpleImageReaderListener readerListener = new SimpleImageReaderListener(); 415 ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize, 416 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 417 readerListener, mHandler); 418 OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface()); 419 config.setPhysicalCameraId(physicalCameraId); 420 outputConfigs.add(config); 421 imageReaders.add(yuvTarget); 422 imageReaderListeners.add(readerListener); 423 } 424 425 SessionConfigSupport sessionConfigSupport = isSessionConfigSupported( 426 mCamera, mHandler, outputConfigs, /*inputConfig*/ null, 427 SessionConfiguration.SESSION_REGULAR, mCameraManager, 428 false/*defaultSupport*/); 429 assertTrue("Session configuration query for logical camera failed with error", 430 !sessionConfigSupport.error); 431 if (!sessionConfigSupport.callSupported) { 432 continue; 433 } 434 435 CaptureRequest.Builder requestBuilder = 436 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet); 437 for (OutputConfiguration c : outputConfigs) { 438 requestBuilder.addTarget(c.getSurface()); 439 } 440 441 mSessionListener = new BlockingSessionCallback(); 442 mSessionWaiter = mSessionListener.getStateWaiter(); 443 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, 444 mSessionListener, mHandler); 445 if (!sessionConfigSupport.configSupported) { 446 mSessionWaiter.waitForState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED, 447 SESSION_CONFIGURE_TIMEOUT_MS); 448 continue; 449 } 450 451 for (int i = 0; i < MAX_IMAGE_COUNT; i++) { 452 mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), mHandler); 453 for (SimpleImageReaderListener readerListener : imageReaderListeners) { 454 readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS); 455 } 456 } 457 458 // Test streaming requests 459 imageReaders.clear(); 460 outputConfigs.clear(); 461 462 // Add physical YUV streams 463 List<ImageReader> physicalTargets = new ArrayList<>(); 464 for (String physicalCameraId : dualPhysicalCameraIds) { 465 ImageReader physicalTarget = CameraTestUtils.makeImageReader(yuvSize, 466 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 467 new ImageDropperListener(), mHandler); 468 OutputConfiguration config = new OutputConfiguration( 469 physicalTarget.getSurface()); 470 config.setPhysicalCameraId(physicalCameraId); 471 outputConfigs.add(config); 472 physicalTargets.add(physicalTarget); 473 } 474 475 mSessionListener = new BlockingSessionCallback(); 476 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, 477 mSessionListener, mHandler); 478 479 requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, 480 physicalIdSet); 481 for (OutputConfiguration c : outputConfigs) { 482 requestBuilder.addTarget(c.getSurface()); 483 } 484 485 SimpleCaptureCallback simpleResultListener = new SimpleCaptureCallback(); 486 mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener, 487 mHandler); 488 489 // Converge AE 490 waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); 491 492 if (mSession != null) { 493 mSession.close(); 494 } 495 physicalTargets.clear(); 496 497 } finally { 498 closeDevice(); 499 } 500 } 501 } 502 503 /** 504 * Tests invalid/incorrect multiple physical capture request cases. 505 */ 506 @Test testInvalidPhysicalCameraRequests()507 public void testInvalidPhysicalCameraRequests() throws Exception { 508 Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras( 509 mCameraManager, mHandler); 510 for (String id : getCameraIdsUnderTest()) { 511 try { 512 Log.i(TAG, "Testing Camera " + id); 513 514 StaticMetadata staticInfo = mAllStaticInfo.get(id); 515 if (staticInfo.isHardwareLevelLegacy()) { 516 Log.i(TAG, "Camera " + id + " is legacy, skipping"); 517 continue; 518 } 519 if (!staticInfo.isColorOutputSupported()) { 520 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 521 continue; 522 } 523 524 openDevice(id); 525 Size yuvSize = mOrderedPreviewSizes.get(0); 526 List<OutputConfiguration> outputConfigs = new ArrayList<>(); 527 List<ImageReader> imageReaders = new ArrayList<>(); 528 SimpleImageReaderListener readerListener = new SimpleImageReaderListener(); 529 ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize, 530 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 531 readerListener, mHandler); 532 imageReaders.add(yuvTarget); 533 OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface()); 534 outputConfigs.add(new OutputConfiguration(yuvTarget.getSurface())); 535 536 ArraySet<String> physicalIdSet = new ArraySet<String>(); 537 // Invalid physical id 538 physicalIdSet.add("-2"); 539 540 CaptureRequest.Builder requestBuilder = 541 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet); 542 requestBuilder.addTarget(config.getSurface()); 543 544 // Check for invalid setting get/set 545 try { 546 requestBuilder.getPhysicalCameraKey(CaptureRequest.CONTROL_CAPTURE_INTENT, "-1"); 547 fail("No exception for invalid physical camera id"); 548 } catch (IllegalArgumentException e) { 549 //expected 550 } 551 552 try { 553 requestBuilder.setPhysicalCameraKey(CaptureRequest.CONTROL_CAPTURE_INTENT, 554 new Integer(0), "-1"); 555 fail("No exception for invalid physical camera id"); 556 } catch (IllegalArgumentException e) { 557 //expected 558 } 559 560 mSessionListener = new BlockingSessionCallback(); 561 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, 562 mSessionListener, mHandler); 563 564 try { 565 mSession.capture(requestBuilder.build(), new SimpleCaptureCallback(), 566 mHandler); 567 fail("No exception for invalid physical camera id"); 568 } catch (IllegalArgumentException e) { 569 //expected 570 } 571 572 if (mStaticInfo.isLogicalMultiCamera()) { 573 // Figure out yuv size to use. 574 List<String> dualPhysicalCameraIds = new ArrayList<String>(); 575 Size sharedSize = findCommonPreviewSize(id, dualPhysicalCameraIds, 576 unavailablePhysicalCameras); 577 if (sharedSize == null) { 578 Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping"); 579 continue; 580 } 581 582 physicalIdSet.clear(); 583 physicalIdSet.addAll(dualPhysicalCameraIds); 584 requestBuilder = 585 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet); 586 requestBuilder.addTarget(config.getSurface()); 587 CaptureRequest request = requestBuilder.build(); 588 589 try { 590 mSession.capture(request, new SimpleCaptureCallback(), mHandler); 591 fail("Capture requests that don't configure outputs with corresponding " + 592 "physical camera id should fail"); 593 } catch (IllegalArgumentException e) { 594 //expected 595 } 596 } 597 598 if (mSession != null) { 599 mSession.close(); 600 } 601 imageReaders.clear(); 602 } finally { 603 closeDevice(); 604 } 605 } 606 } 607 608 /** 609 * Test for physical camera switch based on focal length (optical zoom) and crop region 610 * (digital zoom). 611 * 612 * - Focal length and crop region change must be synchronized to not have sudden jump in field 613 * of view. 614 * - Main physical id must be valid. 615 */ 616 @Test testLogicalCameraZoomSwitch()617 public void testLogicalCameraZoomSwitch() throws Exception { 618 619 for (String id : getCameraIdsUnderTest()) { 620 try { 621 Log.i(TAG, "Testing Camera " + id); 622 623 StaticMetadata staticInfo = mAllStaticInfo.get(id); 624 if (!staticInfo.isColorOutputSupported()) { 625 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 626 continue; 627 } 628 629 if (!staticInfo.isLogicalMultiCamera()) { 630 Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping"); 631 continue; 632 } 633 634 openDevice(id); 635 Size yuvSize = mOrderedPreviewSizes.get(0); 636 // Create a YUV image reader. 637 ImageReader imageReader = CameraTestUtils.makeImageReader(yuvSize, 638 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 639 new ImageDropperListener(), mHandler); 640 641 List<OutputConfiguration> outputConfigs = new ArrayList<>(); 642 OutputConfiguration config = new OutputConfiguration(imageReader.getSurface()); 643 outputConfigs.add(config); 644 645 mSessionListener = new BlockingSessionCallback(); 646 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, 647 mSessionListener, mHandler); 648 649 final float FOV_MARGIN = 0.01f; 650 final float[] focalLengths = staticInfo.getAvailableFocalLengthsChecked(); 651 final int zoomSteps = focalLengths.length; 652 final float maxZoom = staticInfo.getAvailableMaxDigitalZoomChecked(); 653 final Rect activeArraySize = staticInfo.getActiveArraySizeChecked(); 654 final Set<String> physicalIds = 655 staticInfo.getCharacteristics().getPhysicalCameraIds(); 656 657 CaptureRequest.Builder requestBuilder = 658 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 659 requestBuilder.addTarget(imageReader.getSurface()); 660 661 // For each adjacent focal lengths, set different crop region such that the 662 // resulting angle of view is the same. This is to make sure that no sudden FOV 663 // (field of view) jump when switching between different focal lengths. 664 for (int i = 0; i < zoomSteps-1; i++) { 665 // Start with larger focal length + full active array crop region. 666 requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i+1]); 667 requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, activeArraySize); 668 SimpleCaptureCallback simpleResultListener = new SimpleCaptureCallback(); 669 mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener, 670 mHandler); 671 waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); 672 673 // This is an approximate, assuming that subject distance >> focal length. 674 float zoomFactor = focalLengths[i+1]/focalLengths[i]; 675 PointF zoomCenter = new PointF(0.5f, 0.5f); 676 Rect requestCropRegion = getCropRegionForZoom(zoomFactor, 677 zoomCenter, maxZoom, activeArraySize); 678 if (VERBOSE) { 679 Log.v(TAG, "Switching from crop region " + activeArraySize + ", focal " + 680 "length " + focalLengths[i+1] + " to crop region " + requestCropRegion + 681 ", focal length " + focalLengths[i]); 682 } 683 684 // Create a burst capture to switch between different focal_length/crop_region 685 // combination with same field of view. 686 List<CaptureRequest> requests = new ArrayList<CaptureRequest>(); 687 SimpleCaptureCallback listener = new SimpleCaptureCallback(); 688 689 requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i]); 690 requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, requestCropRegion); 691 requests.add(requestBuilder.build()); 692 requests.add(requestBuilder.build()); 693 694 requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i+1]); 695 requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, activeArraySize); 696 requests.add(requestBuilder.build()); 697 requests.add(requestBuilder.build()); 698 699 requestBuilder.set(CaptureRequest.LENS_FOCAL_LENGTH, focalLengths[i]); 700 requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, requestCropRegion); 701 requests.add(requestBuilder.build()); 702 requests.add(requestBuilder.build()); 703 704 mSession.captureBurst(requests, listener, mHandler); 705 TotalCaptureResult[] results = listener.getTotalCaptureResultsForRequests( 706 requests, /*numResultsWait*/0, 707 /*timeoutForResult*/WAIT_FOR_RESULT_TIMEOUT_MS); 708 709 // Verify result metadata to produce similar field of view. 710 float fov = activeArraySize.width()/(2*focalLengths[i+1]); 711 for (int j = 0; j < results.length; j++) { 712 TotalCaptureResult result = results[j]; 713 Float resultFocalLength = result.get(CaptureResult.LENS_FOCAL_LENGTH); 714 Rect resultCropRegion = result.get(CaptureResult.SCALER_CROP_REGION); 715 716 if (VERBOSE) { 717 Log.v(TAG, "Result crop region " + resultCropRegion + ", focal length " 718 + resultFocalLength + " for result " + j); 719 } 720 float newFov = resultCropRegion.width()/(2*resultFocalLength); 721 722 mCollector.expectTrue("Field of view must be consistent with focal " + 723 "length and crop region change cancelling out each other.", 724 Math.abs(newFov - fov)/fov < FOV_MARGIN); 725 726 if (j + 1 < results.length) { 727 TotalCaptureResult nextResult = results[j+1]; 728 float[] lensIntrinsics = result.get( 729 CaptureResult.LENS_INTRINSIC_CALIBRATION); 730 float[] nextLensIntrinsics = nextResult.get( 731 CaptureResult.LENS_INTRINSIC_CALIBRATION); 732 if ((lensIntrinsics != null) && (nextLensIntrinsics != null)) { 733 mCollector.expectTrue("The lens intrinsics between two " + 734 "different focal lengths are not expected to " + 735 " match", 736 !Arrays.equals(lensIntrinsics, nextLensIntrinsics)); 737 } else { 738 mCollector.expectNull("If not supported, lens intrinsics " + 739 "must always remain invalid regardless of the " + 740 "focal length", lensIntrinsics); 741 mCollector.expectNull("If not supported, lens intrinsics " + 742 "must always remain invalid regardless of the " + 743 "focal length", nextLensIntrinsics); 744 } 745 } 746 747 if (staticInfo.isActivePhysicalCameraIdSupported()) { 748 String activePhysicalId = result.get( 749 CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID); 750 assertTrue(physicalIds.contains(activePhysicalId)); 751 752 StaticMetadata physicalCameraStaticInfo = 753 mAllStaticInfo.get(activePhysicalId); 754 float[] physicalCameraFocalLengths = 755 physicalCameraStaticInfo.getAvailableFocalLengthsChecked(); 756 mCollector.expectTrue("Current focal length " + resultFocalLength 757 + " must be supported by active physical camera " 758 + activePhysicalId, Arrays.asList(CameraTestUtils.toObject( 759 physicalCameraFocalLengths)).contains(resultFocalLength)); 760 } 761 } 762 } 763 764 if (mSession != null) { 765 mSession.close(); 766 } 767 CameraTestUtils.closeImageReader(imageReader); 768 769 } finally { 770 closeDevice(); 771 } 772 } 773 } 774 775 /** 776 * Test that for logical multi-camera, the activePhysicalId is valid, and is the same 777 * for all capture templates. 778 */ 779 @Test testActivePhysicalId()780 public void testActivePhysicalId() throws Exception { 781 Set<Pair<String, String>> unavailablePhysicalCameras = getUnavailablePhysicalCameras( 782 mCameraManager, mHandler); 783 784 for (String id : getCameraIdsUnderTest()) { 785 try { 786 Log.i(TAG, "Testing Camera " + id); 787 788 StaticMetadata staticInfo = mAllStaticInfo.get(id); 789 if (!staticInfo.isColorOutputSupported()) { 790 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 791 continue; 792 } 793 794 if (!staticInfo.isLogicalMultiCamera()) { 795 Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping"); 796 continue; 797 } 798 799 if (!staticInfo.isActivePhysicalCameraIdSupported()) { 800 continue; 801 } 802 803 final Set<String> physicalIds = 804 staticInfo.getCharacteristics().getPhysicalCameraIds(); 805 openDevice(id); 806 Size previewSz = 807 getMaxPreviewSize(mCamera.getId(), mCameraManager, 808 getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND)); 809 810 String storedActiveId = null; 811 for (int template : sTemplates) { 812 try { 813 CaptureRequest.Builder requestBuilder = 814 mCamera.createCaptureRequest(template); 815 SimpleCaptureCallback listener = new SimpleCaptureCallback(); 816 startPreview(requestBuilder, previewSz, listener); 817 waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); 818 819 CaptureResult result = listener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 820 String activePhysicalId = result.get( 821 CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID); 822 823 assertNotNull("activePhysicalId must not be null", activePhysicalId); 824 if (storedActiveId == null) { 825 storedActiveId = activePhysicalId; 826 assertTrue( 827 "Camera device reported invalid activePhysicalId: " + 828 activePhysicalId, physicalIds.contains(activePhysicalId)); 829 } else { 830 assertTrue( 831 "Camera device reported different activePhysicalId " + 832 activePhysicalId + " vs " + storedActiveId + 833 " for different capture templates", 834 storedActiveId.equals(activePhysicalId)); 835 } 836 } catch (IllegalArgumentException e) { 837 if (template == CameraDevice.TEMPLATE_MANUAL && 838 !staticInfo.isCapabilitySupported(CameraCharacteristics. 839 REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { 840 // OK 841 } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG && 842 !staticInfo.isCapabilitySupported(CameraCharacteristics. 843 REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) { 844 // OK. 845 } else { 846 throw e; // rethrow 847 } 848 } 849 } 850 851 // Query unavailable physical cameras, and make sure the active physical id 852 // isn't an unavailable physical camera. 853 for (Pair<String, String> unavailPhysicalCamera: unavailablePhysicalCameras) { 854 assertFalse(unavailPhysicalCamera.first.equals(id) && 855 unavailPhysicalCamera.second.equals(storedActiveId)); 856 } 857 } finally { 858 closeDevice(); 859 } 860 } 861 862 } 863 864 /** 865 * Test that for logical multi-camera of a Handheld device, the default FOV is 866 * between 50 and 95 degrees for all capture templates. 867 */ 868 @Test 869 @CddTest(requirement="7.5.4/H-1-1") testDefaultFov()870 public void testDefaultFov() throws Exception { 871 final double MIN_FOV = 50; 872 final double MAX_FOV = 95; 873 if (!isHandheldDevice()) { 874 return; 875 } 876 for (String id : getCameraIdsUnderTest()) { 877 try { 878 Log.i(TAG, "Testing Camera " + id); 879 880 StaticMetadata staticInfo = mAllStaticInfo.get(id); 881 if (!staticInfo.isColorOutputSupported()) { 882 Log.i(TAG, "Camera " + id + " does not support color outputs, skipping"); 883 continue; 884 } 885 886 if (!staticInfo.isLogicalMultiCamera()) { 887 Log.i(TAG, "Camera " + id + " is not a logical multi-camera, skipping"); 888 continue; 889 } 890 891 SizeF physicalSize = staticInfo.getCharacteristics().get( 892 CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE); 893 double physicalDiag = Math.sqrt(Math.pow(physicalSize.getWidth(), 2) 894 + Math.pow(physicalSize.getHeight(), 2)); 895 Rect activeArraySize = staticInfo.getCharacteristics().get( 896 CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE); 897 898 openDevice(id); 899 for (int template : sTemplates) { 900 try { 901 CaptureRequest.Builder requestBuilder = 902 mCamera.createCaptureRequest(template); 903 Float requestFocalLength = requestBuilder.get( 904 CaptureRequest.LENS_FOCAL_LENGTH); 905 assertNotNull("LENS_FOCAL_LENGTH must not be null", requestFocalLength); 906 907 Float requestZoomRatio = requestBuilder.get( 908 CaptureRequest.CONTROL_ZOOM_RATIO); 909 assertNotNull("CONTROL_ZOOM_RATIO must not be null", requestZoomRatio); 910 Rect requestCropRegion = requestBuilder.get( 911 CaptureRequest.SCALER_CROP_REGION); 912 assertNotNull("SCALER_CROP_REGION must not be null", requestCropRegion); 913 float totalZoomRatio = Math.min( 914 1.0f * activeArraySize.width() / requestCropRegion.width(), 915 1.0f * activeArraySize.height() / requestCropRegion.height()) * 916 requestZoomRatio; 917 918 double fov = 2 * 919 Math.toDegrees(Math.atan2(physicalDiag/(2 * totalZoomRatio), 920 requestFocalLength)); 921 922 Log.v(TAG, "Camera " + id + " template " + template + 923 "'s default FOV is " + fov); 924 mCollector.expectInRange("Camera " + id + " template " + template + 925 "'s default FOV must fall between [50, 95] degrees", 926 fov, MIN_FOV, MAX_FOV); 927 } catch (IllegalArgumentException e) { 928 if (template == CameraDevice.TEMPLATE_MANUAL && 929 !staticInfo.isCapabilitySupported(CameraCharacteristics. 930 REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) { 931 // OK 932 } else if (template == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG && 933 !staticInfo.isCapabilitySupported(CameraCharacteristics. 934 REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) { 935 // OK. 936 } else { 937 throw e; // rethrow 938 } 939 } 940 } 941 } finally { 942 closeDevice(); 943 } 944 } 945 } 946 947 /** 948 * Find a common preview size that's supported by both the logical camera and 949 * two of the underlying physical cameras. 950 */ findCommonPreviewSize(String cameraId, List<String> dualPhysicalCameraIds, Set<Pair<String, String>> unavailablePhysicalCameras)951 private Size findCommonPreviewSize(String cameraId, 952 List<String> dualPhysicalCameraIds, 953 Set<Pair<String, String>> unavailablePhysicalCameras) throws Exception { 954 Set<String> physicalCameraIds = 955 mStaticInfo.getCharacteristics().getPhysicalCameraIds(); 956 assertTrue("Logical camera must contain at least 2 physical camera ids", 957 physicalCameraIds.size() >= 2); 958 959 List<Size> previewSizes = getSupportedPreviewSizes( 960 cameraId, mCameraManager, PREVIEW_SIZE_BOUND); 961 HashMap<String, List<Size>> physicalPreviewSizesMap = new HashMap<String, List<Size>>(); 962 HashMap<String, StreamConfigurationMap> physicalConfigs = new HashMap<>(); 963 for (String physicalCameraId : physicalCameraIds) { 964 if (unavailablePhysicalCameras.contains(new Pair<>(cameraId, physicalCameraId))) { 965 continue; 966 } 967 CameraCharacteristics properties = 968 mCameraManager.getCameraCharacteristics(physicalCameraId); 969 assertNotNull("Can't get camera characteristics!", properties); 970 if (!mAllStaticInfo.get(physicalCameraId).isColorOutputSupported()) { 971 // No color output support, skip. 972 continue; 973 } 974 StreamConfigurationMap configMap = 975 properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 976 physicalConfigs.put(physicalCameraId, configMap); 977 physicalPreviewSizesMap.put(physicalCameraId, 978 getSupportedPreviewSizes(physicalCameraId, mCameraManager, PREVIEW_SIZE_BOUND)); 979 } 980 981 // Find display size from window service. 982 Context context = mActivityRule.getActivity(); 983 WindowManager windowManager = 984 (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 985 Rect windowBounds = windowManager.getCurrentWindowMetrics().getBounds(); 986 987 int windowWidth = windowBounds.width(); 988 int windowHeight = windowBounds.height(); 989 990 if (windowHeight > windowWidth) { 991 windowHeight = windowWidth; 992 windowWidth = windowBounds.height(); 993 } 994 995 StreamConfigurationMap config = mStaticInfo.getCharacteristics().get( 996 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 997 for (Size previewSize : previewSizes) { 998 dualPhysicalCameraIds.clear(); 999 // Skip preview sizes larger than screen size 1000 if (previewSize.getWidth() > windowWidth 1001 || previewSize.getHeight() > windowHeight) { 1002 continue; 1003 } 1004 1005 final long minFrameDuration = config.getOutputMinFrameDuration( 1006 ImageFormat.YUV_420_888, previewSize); 1007 1008 ArrayList<String> supportedPhysicalCameras = new ArrayList<String>(); 1009 for (String physicalCameraId : physicalCameraIds) { 1010 List<Size> physicalPreviewSizes = physicalPreviewSizesMap.get(physicalCameraId); 1011 if (physicalPreviewSizes != null && physicalPreviewSizes.contains(previewSize)) { 1012 long minDurationPhysical = 1013 physicalConfigs.get(physicalCameraId).getOutputMinFrameDuration( 1014 ImageFormat.YUV_420_888, previewSize); 1015 if (minDurationPhysical <= minFrameDuration) { 1016 dualPhysicalCameraIds.add(physicalCameraId); 1017 if (dualPhysicalCameraIds.size() == 2) { 1018 return previewSize; 1019 } 1020 } 1021 } 1022 } 1023 } 1024 return null; 1025 } 1026 1027 /** 1028 * Validate that physical cameras are not cropping too much. 1029 * 1030 * This is to make sure physical processed streams have at least the same field of view as 1031 * the logical stream, or the maximum field of view of the physical camera, whichever is 1032 * smaller. 1033 * 1034 * Note that the FOV is calculated in the directio of sensor width. 1035 */ validatePhysicalCamerasFov(TotalCaptureResult totalCaptureResult, List<String> physicalCameraIds)1036 private void validatePhysicalCamerasFov(TotalCaptureResult totalCaptureResult, 1037 List<String> physicalCameraIds) { 1038 Rect cropRegion = totalCaptureResult.get(CaptureResult.SCALER_CROP_REGION); 1039 Float focalLength = totalCaptureResult.get(CaptureResult.LENS_FOCAL_LENGTH); 1040 Float zoomRatio = totalCaptureResult.get(CaptureResult.CONTROL_ZOOM_RATIO); 1041 Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked(); 1042 SizeF sensorSize = mStaticInfo.getValueFromKeyNonNull( 1043 CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE); 1044 Size logicalPixelArraySize = mStaticInfo.getValueFromKeyNonNull( 1045 CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); 1046 Rect logicalPreCorrActiveArraySize = mStaticInfo.getValueFromKeyNonNull( 1047 CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); 1048 Float fovTransferRatio = logicalPreCorrActiveArraySize.width() * 1f / 1049 logicalPixelArraySize.getWidth(); 1050 Float logicalSensorWidth = sensorSize.getWidth() * fovTransferRatio; 1051 1052 // Assume subject distance >> focal length, and subject distance >> camera baseline. 1053 double fov = 2 * Math.toDegrees(Math.atan2(logicalSensorWidth * cropRegion.width() / 1054 (2 * zoomRatio * activeArraySize.width()), focalLength)); 1055 1056 Map<String, CaptureResult> physicalResultsDual = 1057 totalCaptureResult.getPhysicalCameraResults(); 1058 for (String physicalId : physicalCameraIds) { 1059 StaticMetadata physicalStaticInfo = mAllStaticInfo.get(physicalId); 1060 CaptureResult physicalResult = physicalResultsDual.get(physicalId); 1061 Rect physicalCropRegion = physicalResult.get(CaptureResult.SCALER_CROP_REGION); 1062 Float physicalFocalLength = physicalResult.get(CaptureResult.LENS_FOCAL_LENGTH); 1063 Float physicalZoomRatio = physicalResult.get(CaptureResult.CONTROL_ZOOM_RATIO); 1064 Rect physicalActiveArraySize = physicalStaticInfo.getActiveArraySizeChecked(); 1065 SizeF physicalSensorSize = physicalStaticInfo.getValueFromKeyNonNull( 1066 CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE); 1067 1068 Size physicalPixelArraySize = physicalStaticInfo.getValueFromKeyNonNull( 1069 CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE); 1070 Rect physicalPreCorrActiveArraySize = physicalStaticInfo.getValueFromKeyNonNull( 1071 CameraCharacteristics.SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); 1072 Float transferRatio = physicalPreCorrActiveArraySize.width() * 1f / 1073 physicalPixelArraySize.getWidth(); 1074 Float physicalSensorWidth = physicalSensorSize.getWidth() * transferRatio; 1075 1076 // Physical result metadata's ZOOM_RATIO is 1.0f. 1077 assertTrue("Physical result metadata ZOOM_RATIO should be 1.0f, but is " + 1078 physicalZoomRatio, Math.abs(physicalZoomRatio - 1.0f) < ZOOM_RATIO_THRESHOLD); 1079 1080 double physicalFov = 2 * Math.toDegrees(Math.atan2( 1081 physicalSensorWidth * physicalCropRegion.width() / 1082 (2 * physicalZoomRatio * physicalActiveArraySize.width()), physicalFocalLength)); 1083 1084 double maxPhysicalFov = 2 * Math.toDegrees(Math.atan2(physicalSensorWidth / 2, 1085 physicalFocalLength)); 1086 double expectedPhysicalFov = Math.min(maxPhysicalFov, fov); 1087 1088 if (VERBOSE) { 1089 Log.v(TAG, "Logical camera Fov: " + fov + ", maxPhyiscalFov: " + maxPhysicalFov + 1090 ", physicalFov: " + physicalFov); 1091 } 1092 assertTrue("Physical stream FOV (Field of view) should be greater or equal to" 1093 + " min(logical stream FOV, max physical stream FOV). Physical FOV: " 1094 + physicalFov + " vs min(" + fov + ", " + maxPhysicalFov, 1095 physicalFov - expectedPhysicalFov > -FOV_THRESHOLD); 1096 } 1097 } 1098 1099 /** 1100 * Test physical camera YUV streaming within a particular logical camera. 1101 * 1102 * Use 2 YUV streams with PREVIEW or smaller size. 1103 */ testBasicPhysicalStreamingForCamera(String logicalCameraId, List<String> physicalCameraIds, Size previewSize)1104 private void testBasicPhysicalStreamingForCamera(String logicalCameraId, 1105 List<String> physicalCameraIds, Size previewSize) throws Exception { 1106 List<OutputConfiguration> outputConfigs = new ArrayList<>(); 1107 List<ImageReader> imageReaders = new ArrayList<>(); 1108 1109 // Add 1 logical YUV stream 1110 ImageReader logicalTarget = CameraTestUtils.makeImageReader(previewSize, 1111 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 1112 new ImageDropperListener(), mHandler); 1113 imageReaders.add(logicalTarget); 1114 outputConfigs.add(new OutputConfiguration(logicalTarget.getSurface())); 1115 1116 // Add physical YUV streams 1117 if (physicalCameraIds.size() != 2) { 1118 throw new IllegalArgumentException("phyiscalCameraIds must contain 2 camera ids"); 1119 } 1120 List<ImageReader> physicalTargets = new ArrayList<>(); 1121 for (String physicalCameraId : physicalCameraIds) { 1122 ImageReader physicalTarget = CameraTestUtils.makeImageReader(previewSize, 1123 ImageFormat.YUV_420_888, MAX_IMAGE_COUNT, 1124 new ImageDropperListener(), mHandler); 1125 OutputConfiguration config = new OutputConfiguration(physicalTarget.getSurface()); 1126 config.setPhysicalCameraId(physicalCameraId); 1127 outputConfigs.add(config); 1128 physicalTargets.add(physicalTarget); 1129 } 1130 1131 SessionConfigSupport sessionConfigSupport = isSessionConfigSupported( 1132 mCamera, mHandler, outputConfigs, /*inputConfig*/ null, 1133 SessionConfiguration.SESSION_REGULAR, mCameraManager, 1134 false/*defaultSupport*/); 1135 assertTrue("Session configuration query for logical camera failed with error", 1136 !sessionConfigSupport.error); 1137 if (!sessionConfigSupport.callSupported) { 1138 return; 1139 } 1140 1141 mSessionListener = new BlockingSessionCallback(); 1142 mSessionWaiter = mSessionListener.getStateWaiter(); 1143 mSession = configureCameraSessionWithConfig(mCamera, outputConfigs, 1144 mSessionListener, mHandler); 1145 if (!sessionConfigSupport.configSupported) { 1146 mSessionWaiter.waitForState(BlockingSessionCallback.SESSION_CONFIGURE_FAILED, 1147 SESSION_CONFIGURE_TIMEOUT_MS); 1148 return; 1149 } 1150 1151 // Stream logical YUV stream and note down the FPS 1152 CaptureRequest.Builder requestBuilder = 1153 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 1154 requestBuilder.addTarget(logicalTarget.getSurface()); 1155 1156 SimpleCaptureCallback simpleResultListener = 1157 new SimpleCaptureCallback(); 1158 StreamConfigurationMap config = mStaticInfo.getCharacteristics().get( 1159 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1160 final long minFrameDuration = config.getOutputMinFrameDuration( 1161 ImageFormat.YUV_420_888, previewSize); 1162 if (minFrameDuration > 0) { 1163 Range<Integer> targetRange = getSuitableFpsRangeForDuration(logicalCameraId, 1164 minFrameDuration); 1165 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, targetRange); 1166 } 1167 mSession.setRepeatingRequest(requestBuilder.build(), 1168 simpleResultListener, mHandler); 1169 1170 // Converge AE 1171 waitForAeStable(simpleResultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY); 1172 1173 if (mStaticInfo.isAeLockSupported()) { 1174 // Lock AE if supported. 1175 requestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true); 1176 mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener, 1177 mHandler); 1178 waitForResultValue(simpleResultListener, CaptureResult.CONTROL_AE_STATE, 1179 CaptureResult.CONTROL_AE_STATE_LOCKED, NUM_RESULTS_WAIT_TIMEOUT); 1180 } 1181 1182 // Verify results 1183 CaptureResultTest.validateCaptureResult(mCollector, simpleResultListener, 1184 mStaticInfo, mAllStaticInfo, null/*requestedPhysicalIds*/, 1185 requestBuilder, NUM_FRAMES_CHECKED); 1186 1187 // Collect timestamps for one logical stream only. 1188 long prevTimestamp = -1; 1189 long[] logicalTimestamps = new long[NUM_FRAMES_CHECKED]; 1190 for (int i = 0; i < NUM_FRAMES_CHECKED; i++) { 1191 TotalCaptureResult totalCaptureResult = 1192 simpleResultListener.getTotalCaptureResult( 1193 CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS); 1194 logicalTimestamps[i] = totalCaptureResult.get(CaptureResult.SENSOR_TIMESTAMP); 1195 } 1196 1197 double logicalAvgDurationMs = (logicalTimestamps[NUM_FRAMES_CHECKED-1] - 1198 logicalTimestamps[0])/(NS_PER_MS*(NUM_FRAMES_CHECKED-1)); 1199 1200 // Request one logical stream and one physical stream 1201 simpleResultListener = new SimpleCaptureCallback(); 1202 requestBuilder.addTarget(physicalTargets.get(1).getSurface()); 1203 mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListener, 1204 mHandler); 1205 1206 // Verify results for physical streams request. 1207 CaptureResultTest.validateCaptureResult(mCollector, simpleResultListener, 1208 mStaticInfo, mAllStaticInfo, physicalCameraIds.subList(1, 2), requestBuilder, 1209 NUM_FRAMES_CHECKED); 1210 1211 1212 // Start requesting on both logical and physical streams 1213 SimpleCaptureCallback simpleResultListenerDual = 1214 new SimpleCaptureCallback(); 1215 for (ImageReader physicalTarget : physicalTargets) { 1216 requestBuilder.addTarget(physicalTarget.getSurface()); 1217 } 1218 mSession.setRepeatingRequest(requestBuilder.build(), simpleResultListenerDual, 1219 mHandler); 1220 1221 // Verify results for physical streams request. 1222 CaptureResultTest.validateCaptureResult(mCollector, simpleResultListenerDual, 1223 mStaticInfo, mAllStaticInfo, physicalCameraIds, requestBuilder, 1224 NUM_FRAMES_CHECKED); 1225 1226 // Acquire the timestamps of the physical camera. 1227 long[] logicalTimestamps2 = new long[NUM_FRAMES_CHECKED]; 1228 long [][] physicalTimestamps = new long[physicalTargets.size()][]; 1229 for (int i = 0; i < physicalTargets.size(); i++) { 1230 physicalTimestamps[i] = new long[NUM_FRAMES_CHECKED]; 1231 } 1232 for (int i = 0; i < NUM_FRAMES_CHECKED; i++) { 1233 TotalCaptureResult totalCaptureResultDual = 1234 simpleResultListenerDual.getTotalCaptureResult( 1235 CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS); 1236 logicalTimestamps2[i] = totalCaptureResultDual.get(CaptureResult.SENSOR_TIMESTAMP); 1237 1238 int index = 0; 1239 Map<String, CaptureResult> physicalResultsDual = 1240 totalCaptureResultDual.getPhysicalCameraResults(); 1241 for (String physicalId : physicalCameraIds) { 1242 assertTrue("Physical capture result camera ID must match the right camera", 1243 physicalResultsDual.get(physicalId).getCameraId().equals(physicalId)); 1244 if (physicalResultsDual.containsKey(physicalId)) { 1245 physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get( 1246 CaptureResult.SENSOR_TIMESTAMP); 1247 } else { 1248 physicalTimestamps[index][i] = -1; 1249 } 1250 index++; 1251 } 1252 } 1253 1254 // Check both logical and physical streams' crop region, and make sure their FOVs 1255 // are similar. 1256 TotalCaptureResult totalCaptureResult = 1257 simpleResultListenerDual.getTotalCaptureResult( 1258 CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS); 1259 validatePhysicalCamerasFov(totalCaptureResult, physicalCameraIds); 1260 1261 // Check timestamp monolithity for individual camera and across cameras 1262 for (int i = 0; i < NUM_FRAMES_CHECKED-1; i++) { 1263 assertTrue("Logical camera timestamp must monolithically increase", 1264 logicalTimestamps2[i] < logicalTimestamps2[i+1]); 1265 } 1266 for (int i = 0; i < physicalCameraIds.size(); i++) { 1267 for (int j = 0 ; j < NUM_FRAMES_CHECKED-1; j++) { 1268 if (physicalTimestamps[i][j] != -1 && physicalTimestamps[i][j+1] != -1) { 1269 assertTrue("Physical camera timestamp must monolithically increase", 1270 physicalTimestamps[i][j] < physicalTimestamps[i][j+1]); 1271 } 1272 if (j > 0 && physicalTimestamps[i][j] != -1) { 1273 assertTrue("Physical camera's timestamp N must be greater than logical " + 1274 "camera's timestamp N-1", 1275 physicalTimestamps[i][j] > logicalTimestamps[j-1]); 1276 } 1277 if (physicalTimestamps[i][j] != -1) { 1278 assertTrue("Physical camera's timestamp N must be less than logical camera's " + 1279 "timestamp N+1", physicalTimestamps[i][j] > logicalTimestamps[j+1]); 1280 } 1281 } 1282 } 1283 1284 double logicalAvgDurationMs2 = (logicalTimestamps2[NUM_FRAMES_CHECKED-1] - 1285 logicalTimestamps2[0])/(NS_PER_MS*(NUM_FRAMES_CHECKED-1)); 1286 // Check CALIBRATED synchronization between physical cameras 1287 Integer syncType = mStaticInfo.getCharacteristics().get( 1288 CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE); 1289 double fpsRatio = (logicalAvgDurationMs2 - logicalAvgDurationMs)/logicalAvgDurationMs; 1290 if (syncType == CameraCharacteristics.LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED) { 1291 // Check framerate doesn't slow down with physical streams 1292 mCollector.expectTrue( 1293 "The average frame duration with concurrent physical streams is" + 1294 logicalAvgDurationMs2 + " ms vs " + logicalAvgDurationMs + 1295 " ms for logical streams only", fpsRatio <= FRAME_DURATION_THRESHOLD); 1296 1297 long maxTimestampDelta = 0; 1298 for (int i = 0; i < NUM_FRAMES_CHECKED; i++) { 1299 long delta = Math.abs(physicalTimestamps[0][i] - physicalTimestamps[1][i]); 1300 if (delta > maxTimestampDelta) { 1301 maxTimestampDelta = delta; 1302 } 1303 } 1304 1305 Log.i(TAG, "Maximum difference between physical camera timestamps: " 1306 + maxTimestampDelta); 1307 1308 // The maximum timestamp difference should not be larger than the threshold. 1309 mCollector.expectTrue( 1310 "The maximum timestamp deltas between the physical cameras " 1311 + maxTimestampDelta + " is larger than " + MAX_TIMESTAMP_DIFFERENCE_THRESHOLD, 1312 maxTimestampDelta <= MAX_TIMESTAMP_DIFFERENCE_THRESHOLD); 1313 } else { 1314 // Do not enforce fps check for APPROXIMATE synced device. 1315 if (fpsRatio > FRAME_DURATION_THRESHOLD) { 1316 Log.w(TAG, "The average frame duration with concurrent physical streams is" + 1317 logicalAvgDurationMs2 + " ms vs " + logicalAvgDurationMs + 1318 " ms for logical streams only"); 1319 } 1320 } 1321 1322 if (VERBOSE) { 1323 while (simpleResultListenerDual.hasMoreFailures()) { 1324 ArrayList<CaptureFailure> failures = 1325 simpleResultListenerDual.getCaptureFailures(/*maxNumFailures*/ 1); 1326 for (CaptureFailure failure : failures) { 1327 String physicalCameraId = failure.getPhysicalCameraId(); 1328 if (physicalCameraId != null) { 1329 Log.v(TAG, "Capture result failure for physical camera id: " + 1330 physicalCameraId); 1331 } 1332 } 1333 } 1334 } 1335 1336 // Stop preview 1337 if (mSession != null) { 1338 mSession.close(); 1339 } 1340 // Image readers need to be referenced so that they aren't garbage collected 1341 // before the function exit. 1342 CameraTestUtils.closeImageReader(logicalTarget); 1343 physicalTargets.clear(); 1344 } 1345 1346 /** 1347 * The CDD defines a handheld device as one that has a battery and a screen size between 1348 * 2.5 and 8 inches. 1349 */ isHandheldDevice()1350 private boolean isHandheldDevice() throws Exception { 1351 double screenInches = getScreenSizeInInches(); 1352 return deviceHasBattery() && screenInches >= 2.5 && screenInches <= 8.0; 1353 } 1354 deviceHasBattery()1355 private boolean deviceHasBattery() { 1356 final Intent batteryInfo = mContext.registerReceiver(null, 1357 new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 1358 return batteryInfo != null && batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); 1359 } 1360 getScreenSizeInInches()1361 private double getScreenSizeInInches() { 1362 DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); 1363 double widthInInchesSquared = Math.pow(dm.widthPixels / dm.xdpi, 2); 1364 double heightInInchesSquared = Math.pow(dm.heightPixels / dm.ydpi, 2); 1365 return Math.sqrt(widthInInchesSquared + heightInInchesSquared); 1366 } 1367 } 1368