1 /* 2 * iCopyright 2021 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.media.Image; 23 import android.media.ImageReader; 24 import android.media.ImageWriter; 25 import android.hardware.camera2.CameraCharacteristics; 26 import android.hardware.camera2.CameraCaptureSession; 27 import android.hardware.camera2.CameraDevice; 28 import android.hardware.camera2.CaptureFailure; 29 import android.hardware.camera2.CaptureRequest; 30 import android.hardware.camera2.CaptureResult; 31 import android.hardware.camera2.MultiResolutionImageReader; 32 import android.hardware.camera2.TotalCaptureResult; 33 import android.hardware.camera2.cts.helpers.StaticMetadata; 34 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel; 35 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase; 36 import android.hardware.camera2.params.MandatoryStreamCombination; 37 import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation; 38 import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap; 39 import android.hardware.camera2.params.MultiResolutionStreamInfo; 40 import android.hardware.camera2.params.InputConfiguration; 41 import android.hardware.camera2.params.OutputConfiguration; 42 import android.hardware.camera2.params.SessionConfiguration; 43 import android.hardware.camera2.params.StreamConfigurationMap; 44 import android.util.Log; 45 import android.util.Size; 46 import android.view.Surface; 47 import android.view.SurfaceHolder; 48 49 import com.android.ex.camera2.blocking.BlockingSessionCallback; 50 51 import java.util.Arrays; 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.List; 55 import java.util.Map; 56 import java.util.Set; 57 58 import org.junit.runners.Parameterized; 59 import org.junit.runner.RunWith; 60 import org.junit.Test; 61 import static org.mockito.Mockito.*; 62 63 /** 64 * Tests for multi-resolution size reprocessing. 65 */ 66 67 @RunWith(Parameterized.class) 68 public class MultiResolutionReprocessCaptureTest extends Camera2AndroidTestCase { 69 private static final String TAG = "MultiResolutionReprocessCaptureTest"; 70 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 71 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 72 private static final int CAPTURE_TIMEOUT_FRAMES = 100; 73 private static final int CAPTURE_TIMEOUT_MS = 3000; 74 private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000; 75 private static final int CAPTURE_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG; 76 private int mDumpFrameCount = 0; 77 78 // The image reader for the regular captures 79 private MultiResolutionImageReader mMultiResImageReader; 80 // The image reader for the reprocess capture 81 private MultiResolutionImageReader mSecondMultiResImageReader; 82 // A flag indicating whether the regular capture and the reprocess capture share the same 83 // multi-resolution image reader. If it's true, the mMultiResImageReader should be used for 84 // both regular and reprocess outputs. 85 private boolean mShareOneReader; 86 private SimpleMultiResolutionImageReaderListener mMultiResImageReaderListener; 87 private SimpleMultiResolutionImageReaderListener mSecondMultiResImageReaderListener; 88 private Surface mInputSurface; 89 private ImageWriter mImageWriter; 90 private SimpleImageWriterListener mImageWriterListener; 91 92 @Test testMultiResolutionReprocessCharacteristics()93 public void testMultiResolutionReprocessCharacteristics() throws Exception { 94 for (String id : getCameraIdsUnderTest()) { 95 if (VERBOSE) { 96 Log.v(TAG, "Testing multi-resolution reprocess characteristics for Camera " + id); 97 } 98 StaticMetadata info = mAllStaticInfo.get(id); 99 CameraCharacteristics c = info.getCharacteristics(); 100 StreamConfigurationMap config = c.get( 101 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 102 int[] inputFormats = config.getInputFormats(); 103 int[] capabilities = CameraTestUtils.getValueNotNull( 104 c, CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES); 105 boolean isLogicalCamera = CameraTestUtils.contains(capabilities, 106 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA); 107 boolean isUltraHighResCamera = info.isUltraHighResolutionSensor(); 108 Set<String> physicalCameraIds = c.getPhysicalCameraIds(); 109 110 MultiResolutionStreamConfigurationMap multiResolutionMap = c.get( 111 CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP); 112 if (multiResolutionMap == null) { 113 Log.i(TAG, "Camera " + id + " doesn't support multi-resolution reprocessing."); 114 continue; 115 } 116 if (VERBOSE) { 117 Log.v(TAG, "MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP: " 118 + multiResolutionMap.toString()); 119 } 120 121 // Find multi-resolution input and output formats 122 int[] multiResolutionInputFormats = multiResolutionMap.getInputFormats(); 123 int[] multiResolutionOutputFormats = multiResolutionMap.getOutputFormats(); 124 125 assertTrue("Camera " + id + " must be a logical multi-camera or ultra high res camera " 126 + "to support multi-resolution reprocessing.", 127 isLogicalCamera || isUltraHighResCamera); 128 129 for (int format : multiResolutionInputFormats) { 130 assertTrue(String.format("Camera %s: multi-resolution input format %d " 131 + "isn't a supported format", id, format), 132 CameraTestUtils.contains(inputFormats, format)); 133 134 Collection<MultiResolutionStreamInfo> multiResolutionStreams = 135 multiResolutionMap.getInputInfo(format); 136 assertTrue(String.format("Camera %s supports %d multi-resolution " 137 + "input stream info, expected at least 2", id, 138 multiResolutionStreams.size()), 139 multiResolutionStreams.size() >= 2); 140 141 // Make sure that each multi-resolution input stream info has the maximum size 142 // for that format. 143 for (MultiResolutionStreamInfo streamInfo : multiResolutionStreams) { 144 String physicalCameraId = streamInfo.getPhysicalCameraId(); 145 Size streamSize = new Size(streamInfo.getWidth(), streamInfo.getHeight()); 146 if (!isLogicalCamera) { 147 assertTrue("Camera " + id + " is ultra high resolution camera, but " 148 + "the multi-resolution reprocessing stream info camera Id " 149 + physicalCameraId + " doesn't match", 150 physicalCameraId.equals(id)); 151 } else { 152 assertTrue("Camera " + id + "'s multi-resolution input info " 153 + "physical camera id " + physicalCameraId + " isn't valid", 154 physicalCameraIds.contains(physicalCameraId)); 155 } 156 157 StaticMetadata pInfo = mAllStaticInfo.get(physicalCameraId); 158 CameraCharacteristics pChar = pInfo.getCharacteristics(); 159 StreamConfigurationMap pConfig = pChar.get( 160 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 161 Size[] sizes = pConfig.getInputSizes(format); 162 163 assertTrue(String.format("Camera %s must " 164 + "support at least one input size for multi-resolution input " 165 + "format %d.", physicalCameraId, format), 166 sizes != null && sizes.length > 0); 167 168 List<Size> maxSizes = new ArrayList<Size>(); 169 maxSizes.add(CameraTestUtils.getMaxSize(sizes)); 170 StreamConfigurationMap pMaxResConfig = pChar.get(CameraCharacteristics. 171 SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION); 172 if (pMaxResConfig != null) { 173 Size[] maxResSizes = pMaxResConfig.getInputSizes(format); 174 if (maxResSizes != null && maxResSizes.length > 0) { 175 maxSizes.add(CameraTestUtils.getMaxSize(maxResSizes)); 176 } 177 } 178 179 assertTrue(String.format("Camera %s's supported multi-resolution" 180 + " input size %s for physical camera %s is not one of the largest " 181 + "supported input sizes %s for format %d", id, streamSize, 182 physicalCameraId, maxSizes, format), maxSizes.contains(streamSize)); 183 } 184 } 185 186 // YUV reprocessing capabilities check 187 if (CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.YUV_422_888) && 188 CameraTestUtils.contains(multiResolutionInputFormats, 189 ImageFormat.YUV_420_888)) { 190 assertTrue("The camera device must have YUV_REPROCESSING capability if it " 191 + "supports multi-resolution YUV input and YUV output", 192 CameraTestUtils.contains(capabilities, 193 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING)); 194 195 assertTrue("The camera device must supports multi-resolution JPEG output if " 196 + "supports multi-resolution YUV input and YUV output", 197 CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.JPEG)); 198 } 199 200 // OPAQUE reprocessing capabilities check 201 if (CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.PRIVATE) && 202 CameraTestUtils.contains(multiResolutionInputFormats, ImageFormat.PRIVATE)) { 203 assertTrue("The camera device must have PRIVATE_REPROCESSING capability if it " 204 + "supports multi-resolution PRIVATE input and PRIVATE output", 205 CameraTestUtils.contains(capabilities, 206 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)); 207 208 assertTrue("The camera device must supports multi-resolution JPEG output if " 209 + "supports multi-resolution PRIVATE input and PRIVATE output", 210 CameraTestUtils.contains(multiResolutionOutputFormats, ImageFormat.JPEG)); 211 assertTrue("The camera device must supports multi-resolution YUV output if " 212 + "supports multi-resolution PRIVATE input and PRIVATE output", 213 CameraTestUtils.contains(multiResolutionOutputFormats, 214 ImageFormat.YUV_420_888)); 215 } 216 } 217 } 218 219 /** 220 * Test YUV_420_888 -> YUV_420_888 multi-resolution reprocessing 221 */ 222 @Test testMultiResolutionYuvToYuvReprocessing()223 public void testMultiResolutionYuvToYuvReprocessing() throws Exception { 224 for (String id : getCameraIdsUnderTest()) { 225 testMultiResolutionReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.YUV_420_888); 226 } 227 } 228 229 /** 230 * Test YUV_420_888 -> JPEG multi-resolution reprocessing 231 */ 232 @Test testMultiResolutionYuvToJpegReprocessing()233 public void testMultiResolutionYuvToJpegReprocessing() throws Exception { 234 for (String id : getCameraIdsUnderTest()) { 235 testMultiResolutionReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.JPEG); 236 } 237 } 238 239 /** 240 * Test OPAQUE -> YUV_420_888 multi-resolution reprocessing 241 */ 242 @Test testMultiResolutionOpaqueToYuvReprocessing()243 public void testMultiResolutionOpaqueToYuvReprocessing() throws Exception { 244 for (String id : getCameraIdsUnderTest()) { 245 // Opaque -> YUV_420_888 must be supported. 246 testMultiResolutionReprocessing(id, ImageFormat.PRIVATE, ImageFormat.YUV_420_888); 247 } 248 } 249 250 /** 251 * Test OPAQUE -> JPEG multi-resolution reprocessing 252 */ 253 @Test testMultiResolutionOpaqueToJpegReprocessing()254 public void testMultiResolutionOpaqueToJpegReprocessing() throws Exception { 255 for (String id : getCameraIdsUnderTest()) { 256 // OPAQUE -> JPEG must be supported. 257 testMultiResolutionReprocessing(id, ImageFormat.PRIVATE, ImageFormat.JPEG); 258 } 259 } 260 261 /** 262 * Test for making sure the mandatory stream combinations work for multi-resolution 263 * reprocessing. 264 */ 265 @Test testMultiResolutionMandatoryStreamCombinationTest()266 public void testMultiResolutionMandatoryStreamCombinationTest() throws Exception { 267 for (String id : getCameraIdsUnderTest()) { 268 StaticMetadata info = mAllStaticInfo.get(id); 269 CameraCharacteristics c = info.getCharacteristics(); 270 MandatoryStreamCombination[] combinations = c.get( 271 CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS); 272 if (combinations == null) { 273 Log.i(TAG, "No mandatory stream combinations for camera: " + id + " skip test"); 274 continue; 275 } 276 MultiResolutionStreamConfigurationMap multiResolutionMap = c.get( 277 CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP); 278 if (multiResolutionMap == null) { 279 Log.i(TAG, "Camera " + id + " doesn't support multi-resolution capture."); 280 continue; 281 } 282 int[] multiResolutionInputFormats = multiResolutionMap.getInputFormats(); 283 int[] multiResolutionOutputFormats = multiResolutionMap.getOutputFormats(); 284 if (multiResolutionInputFormats.length == 0 285 || multiResolutionOutputFormats.length == 0) { 286 Log.i(TAG, "Camera " + id + " doesn't support multi-resolution reprocess " 287 + "input/output."); 288 continue; 289 } 290 291 try { 292 openDevice(id); 293 for (MandatoryStreamCombination combination : combinations) { 294 if (!combination.isReprocessable()) { 295 continue; 296 } 297 298 MandatoryStreamCombination.MandatoryStreamInformation firstStreamInfo = 299 combination.getStreamsInformation().get(0); 300 int inputFormat = firstStreamInfo.getFormat(); 301 boolean supportMultiResReprocess = firstStreamInfo.isInput() && 302 CameraTestUtils.contains(multiResolutionOutputFormats, inputFormat) && 303 CameraTestUtils.contains(multiResolutionInputFormats, inputFormat); 304 if (!supportMultiResReprocess) { 305 continue; 306 } 307 308 testMultiResolutionMandatoryStreamCombination(id, info, combination, 309 multiResolutionMap); 310 } 311 } finally { 312 closeDevice(id); 313 } 314 } 315 } 316 testMultiResolutionMandatoryStreamCombination(String cameraId, StaticMetadata staticInfo, MandatoryStreamCombination combination, MultiResolutionStreamConfigurationMap multiResStreamConfig)317 private void testMultiResolutionMandatoryStreamCombination(String cameraId, 318 StaticMetadata staticInfo, MandatoryStreamCombination combination, 319 MultiResolutionStreamConfigurationMap multiResStreamConfig) throws Exception { 320 String log = "Testing multi-resolution mandatory stream combination: " + 321 combination.getDescription() + " on camera: " + cameraId; 322 Log.i(TAG, log); 323 324 final int TIMEOUT_FOR_RESULT_MS = 5000; 325 final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3; 326 327 // Set up outputs 328 List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>(); 329 List<Surface> outputSurfaces = new ArrayList<Surface>(); 330 StreamCombinationTargets targets = new StreamCombinationTargets(); 331 MultiResolutionImageReader inputReader = null; 332 ImageWriter inputWriter = null; 333 SimpleImageReaderListener inputReaderListener = new SimpleImageReaderListener(); 334 SimpleCaptureCallback inputCaptureListener = new SimpleCaptureCallback(); 335 SimpleCaptureCallback reprocessOutputCaptureListener = new SimpleCaptureCallback(); 336 337 List<MandatoryStreamInformation> streamInfo = combination.getStreamsInformation(); 338 assertTrue("Reprocessable stream combinations should have at least 3 or more streams", 339 (streamInfo != null) && (streamInfo.size() >= 3)); 340 assertTrue("The first mandatory stream information in a reprocessable combination must " + 341 "always be input", streamInfo.get(0).isInput()); 342 343 int inputFormat = streamInfo.get(0).getFormat(); 344 345 CameraTestUtils.setupConfigurationTargets(streamInfo.subList(2, streamInfo.size()), 346 targets, outputConfigs, outputSurfaces, NUM_REPROCESS_CAPTURES_PER_CONFIG, 347 /*substituteY8*/false, /*substituteHeic*/false, /*physicalCameraId*/null, 348 multiResStreamConfig, mHandler); 349 350 Collection<MultiResolutionStreamInfo> multiResInputs = 351 multiResStreamConfig.getInputInfo(inputFormat); 352 InputConfiguration inputConfig = new InputConfiguration(multiResInputs, inputFormat); 353 354 try { 355 // For each config, YUV and JPEG outputs will be tested. (For YUV reprocessing, 356 // the YUV ImageReader for input is also used for output.) 357 final boolean inputIsYuv = inputConfig.getFormat() == ImageFormat.YUV_420_888; 358 final boolean useYuv = inputIsYuv || targets.mYuvTargets.size() > 0 || 359 targets.mYuvMultiResTargets.size() > 0; 360 final int totalNumReprocessCaptures = NUM_REPROCESS_CAPTURES_PER_CONFIG * ( 361 (inputIsYuv ? 1 : 0) + targets.mJpegMultiResTargets.size() + 362 targets.mJpegTargets.size() + 363 (useYuv ? targets.mYuvMultiResTargets.size() + targets.mYuvTargets.size() : 0)); 364 365 // It needs 1 input buffer for each reprocess capture + the number of buffers 366 // that will be used as outputs. 367 inputReader = new MultiResolutionImageReader(multiResInputs, inputFormat, 368 totalNumReprocessCaptures + NUM_REPROCESS_CAPTURES_PER_CONFIG); 369 inputReader.setOnImageAvailableListener( 370 inputReaderListener, new HandlerExecutor(mHandler)); 371 outputConfigs.addAll( 372 OutputConfiguration.createInstancesForMultiResolutionOutput(inputReader)); 373 outputSurfaces.add(inputReader.getSurface()); 374 375 CameraCaptureSession.CaptureCallback mockCaptureCallback = 376 mock(CameraCaptureSession.CaptureCallback.class); 377 378 checkSessionConfigurationSupported(mCamera, mHandler, outputConfigs, 379 inputConfig, SessionConfiguration.SESSION_REGULAR, 380 mCameraManager, true/*defaultSupport*/, String.format( 381 "Session configuration query for multi-res combination: %s failed", 382 combination.getDescription())); 383 384 // Verify we can create a reprocessable session with the input and all outputs. 385 BlockingSessionCallback sessionListener = new BlockingSessionCallback(); 386 CameraCaptureSession session = configureReprocessableCameraSessionWithConfigurations( 387 mCamera, inputConfig, outputConfigs, sessionListener, mHandler); 388 inputWriter = ImageWriter.newInstance( 389 session.getInputSurface(), totalNumReprocessCaptures); 390 391 // Prepare a request for reprocess input 392 CaptureRequest.Builder builder = 393 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG); 394 builder.addTarget(inputReader.getSurface()); 395 396 for (int i = 0; i < totalNumReprocessCaptures; i++) { 397 session.capture(builder.build(), inputCaptureListener, mHandler); 398 } 399 400 List<CaptureRequest> reprocessRequests = new ArrayList<>(); 401 List<Surface> reprocessOutputs = new ArrayList<>(); 402 403 if (inputIsYuv) { 404 reprocessOutputs.add(inputReader.getSurface()); 405 } 406 for (MultiResolutionImageReader reader : targets.mJpegMultiResTargets) { 407 reprocessOutputs.add(reader.getSurface()); 408 } 409 for (ImageReader reader : targets.mJpegTargets) { 410 reprocessOutputs.add(reader.getSurface()); 411 } 412 for (MultiResolutionImageReader reader : targets.mYuvMultiResTargets) { 413 reprocessOutputs.add(reader.getSurface()); 414 } 415 for (ImageReader reader : targets.mYuvTargets) { 416 reprocessOutputs.add(reader.getSurface()); 417 } 418 419 for (int i = 0; i < NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) { 420 for (Surface output : reprocessOutputs) { 421 TotalCaptureResult result = inputCaptureListener.getTotalCaptureResult( 422 TIMEOUT_FOR_RESULT_MS); 423 Map<String, TotalCaptureResult> physicalResults = 424 result.getPhysicalCameraTotalResults(); 425 for (Map.Entry<String, TotalCaptureResult> entry : physicalResults.entrySet()) { 426 String physicalCameraId = entry.getKey(); 427 TotalCaptureResult physicalResult = entry.getValue(); 428 String activePhysicalId = physicalResult.get( 429 CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID); 430 mCollector.expectEquals(String.format( 431 "Physical camera result metadata must contain activePhysicalId " + 432 "(%s) matching with physical camera Id (%s).", activePhysicalId, 433 physicalCameraId), physicalCameraId, activePhysicalId); 434 } 435 436 String activePhysicalCameraId = result.get( 437 CaptureResult.LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID); 438 if (activePhysicalCameraId != null) { 439 result = physicalResults.get(activePhysicalCameraId); 440 } 441 442 builder = mCamera.createReprocessCaptureRequest(result); 443 inputWriter.queueInputImage( 444 inputReaderListener.getImage(TIMEOUT_FOR_RESULT_MS)); 445 builder.addTarget(output); 446 reprocessRequests.add(builder.build()); 447 } 448 } 449 450 session.captureBurst(reprocessRequests, reprocessOutputCaptureListener, mHandler); 451 452 for (int i = 0; i < reprocessOutputs.size() * NUM_REPROCESS_CAPTURES_PER_CONFIG; i++) { 453 TotalCaptureResult result = reprocessOutputCaptureListener.getTotalCaptureResult( 454 TIMEOUT_FOR_RESULT_MS); 455 } 456 } catch (Throwable e) { 457 mCollector.addMessage( 458 String.format("Mandatory multi-res stream combination: %s failed due: %s", 459 combination.getDescription(), e.getMessage())); 460 } finally { 461 inputReaderListener.drain(); 462 reprocessOutputCaptureListener.drain(); 463 targets.close(); 464 465 if (inputReader != null) { 466 inputReader.close(); 467 } 468 469 if (inputWriter != null) { 470 inputWriter.close(); 471 } 472 } 473 } 474 475 /** 476 * Test multi-resolution reprocessing from the input format to the output format 477 */ testMultiResolutionReprocessing(String cameraId, int inputFormat, int outputFormat)478 private void testMultiResolutionReprocessing(String cameraId, int inputFormat, 479 int outputFormat) throws Exception { 480 if (VERBOSE) { 481 Log.v(TAG, "testMultiResolutionReprocessing: cameraId: " + cameraId + " inputFormat: " 482 + inputFormat + " outputFormat: " + outputFormat); 483 } 484 485 Collection<MultiResolutionStreamInfo> inputStreamInfo = 486 getMultiResReprocessInfo(cameraId, inputFormat, /*input*/ true); 487 Collection<MultiResolutionStreamInfo> regularOutputStreamInfo = 488 getMultiResReprocessInfo(cameraId, inputFormat, /*input*/ false); 489 Collection<MultiResolutionStreamInfo> reprocessOutputStreamInfo = 490 getMultiResReprocessInfo(cameraId, outputFormat, /*input*/ false); 491 if (inputStreamInfo == null || regularOutputStreamInfo == null || 492 reprocessOutputStreamInfo == null) { 493 return; 494 } 495 assertTrue("The multi-resolution stream info for format " + inputFormat 496 + " must be equal between input and output", 497 inputStreamInfo.containsAll(regularOutputStreamInfo) 498 && regularOutputStreamInfo.containsAll(inputStreamInfo)); 499 500 try { 501 openDevice(cameraId); 502 503 testMultiResolutionReprocessWithStreamInfo(cameraId, inputFormat, inputStreamInfo, 504 outputFormat, reprocessOutputStreamInfo); 505 } finally { 506 closeDevice(cameraId); 507 } 508 } 509 510 /** 511 * Test multi-resolution reprocess with multi-resolution stream info lists for a particular 512 * format combination. 513 */ testMultiResolutionReprocessWithStreamInfo(String cameraId, int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo, int outputFormat, Collection<MultiResolutionStreamInfo> outputInfo)514 private void testMultiResolutionReprocessWithStreamInfo(String cameraId, 515 int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo, 516 int outputFormat, Collection<MultiResolutionStreamInfo> outputInfo) 517 throws Exception { 518 try { 519 setupMultiResImageReaders(inputFormat, inputInfo, outputFormat, outputInfo, 520 /*maxImages*/1); 521 setupReprocessableSession(inputFormat, inputInfo, outputInfo, 522 /*numImageWriterImages*/1); 523 524 List<Float> zoomRatioList = CameraTestUtils.getCandidateZoomRatios(mStaticInfo); 525 for (Float zoomRatio : zoomRatioList) { 526 ImageResultSizeHolder imageResultSizeHolder = null; 527 528 try { 529 imageResultSizeHolder = doMultiResReprocessCapture(zoomRatio); 530 Image reprocessedImage = imageResultSizeHolder.getImage(); 531 Size outputSize = imageResultSizeHolder.getExpectedSize(); 532 TotalCaptureResult result = imageResultSizeHolder.getTotalCaptureResult(); 533 534 mCollector.expectImageProperties("testMultiResolutionReprocess", 535 reprocessedImage, outputFormat, outputSize, 536 result.get(CaptureResult.SENSOR_TIMESTAMP)); 537 538 if (DEBUG) { 539 Log.d(TAG, String.format("camera %s %d zoom %f out %dx%d %d", 540 cameraId, inputFormat, zoomRatio, 541 outputSize.getWidth(), outputSize.getHeight(), 542 outputFormat)); 543 544 dumpImage(reprocessedImage, 545 "/testMultiResolutionReprocess_camera" + cameraId 546 + "_" + mDumpFrameCount); 547 mDumpFrameCount++; 548 } 549 } finally { 550 if (imageResultSizeHolder != null) { 551 imageResultSizeHolder.getImage().close(); 552 } 553 } 554 } 555 } finally { 556 closeReprossibleSession(); 557 closeMultiResImageReaders(); 558 } 559 } 560 561 /** 562 * Set up multi-resolution image readers for regular and reprocess output 563 * 564 * <p>If the reprocess input format is equal to output format, share one multi-resolution 565 * image reader.</p> 566 */ setupMultiResImageReaders(int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo, int outputFormat, Collection<MultiResolutionStreamInfo> outputInfo, int maxImages)567 private void setupMultiResImageReaders(int inputFormat, 568 Collection<MultiResolutionStreamInfo> inputInfo, int outputFormat, 569 Collection<MultiResolutionStreamInfo> outputInfo, int maxImages) { 570 571 mShareOneReader = false; 572 // If the regular output and reprocess output have the same format, 573 // they can share one MultiResolutionImageReader. 574 if (inputFormat == outputFormat) { 575 maxImages *= 2; 576 mShareOneReader = true; 577 } 578 579 // create an MultiResolutionImageReader for the regular capture 580 mMultiResImageReader = new MultiResolutionImageReader(inputInfo, 581 inputFormat, maxImages); 582 mMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener( 583 mMultiResImageReader, 1, /*repeating*/false); 584 mMultiResImageReader.setOnImageAvailableListener(mMultiResImageReaderListener, 585 new HandlerExecutor(mHandler)); 586 587 if (!mShareOneReader) { 588 // create an MultiResolutionImageReader for the reprocess capture 589 mSecondMultiResImageReader = new MultiResolutionImageReader( 590 outputInfo, outputFormat, maxImages); 591 mSecondMultiResImageReaderListener = new SimpleMultiResolutionImageReaderListener( 592 mSecondMultiResImageReader, maxImages, /*repeating*/ false); 593 mSecondMultiResImageReader.setOnImageAvailableListener( 594 mSecondMultiResImageReaderListener, new HandlerExecutor(mHandler)); 595 } 596 } 597 598 /** 599 * Close two multi-resolution image readers. 600 */ closeMultiResImageReaders()601 private void closeMultiResImageReaders() { 602 mMultiResImageReader.close(); 603 mMultiResImageReader = null; 604 605 if (!mShareOneReader) { 606 mSecondMultiResImageReader.close(); 607 mSecondMultiResImageReader = null; 608 } 609 } 610 611 /** 612 * Get the MultiResolutionImageReader for reprocess output. 613 */ getOutputMultiResImageReader()614 private MultiResolutionImageReader getOutputMultiResImageReader() { 615 if (mShareOneReader) { 616 return mMultiResImageReader; 617 } else { 618 return mSecondMultiResImageReader; 619 } 620 } 621 622 /** 623 * Get the MultiResolutionImageReaderListener for reprocess output. 624 */ getOutputMultiResImageReaderListener()625 private SimpleMultiResolutionImageReaderListener getOutputMultiResImageReaderListener() { 626 if (mShareOneReader) { 627 return mMultiResImageReaderListener; 628 } else { 629 return mSecondMultiResImageReaderListener; 630 } 631 } 632 633 /** 634 * Set up a reprocessable session and create an ImageWriter with the session's input surface. 635 */ setupReprocessableSession(int inputFormat, Collection<MultiResolutionStreamInfo> inputInfo, Collection<MultiResolutionStreamInfo> outputInfo, int numImageWriterImages)636 private void setupReprocessableSession(int inputFormat, 637 Collection<MultiResolutionStreamInfo> inputInfo, 638 Collection<MultiResolutionStreamInfo> outputInfo, 639 int numImageWriterImages) throws Exception { 640 // create a reprocessable capture session 641 Collection<OutputConfiguration> outConfigs = 642 OutputConfiguration.createInstancesForMultiResolutionOutput( 643 mMultiResImageReader); 644 ArrayList<OutputConfiguration> outputConfigsList = new ArrayList<OutputConfiguration>( 645 outConfigs); 646 647 if (!mShareOneReader) { 648 Collection<OutputConfiguration> secondOutputConfigs = 649 OutputConfiguration.createInstancesForMultiResolutionOutput( 650 mSecondMultiResImageReader); 651 outputConfigsList.addAll(secondOutputConfigs); 652 } 653 654 InputConfiguration inputConfig = new InputConfiguration(inputInfo, inputFormat); 655 if (VERBOSE) { 656 String inputConfigString = inputConfig.toString(); 657 Log.v(TAG, "InputConfiguration: " + inputConfigString); 658 } 659 660 mCameraSessionListener = new BlockingSessionCallback(); 661 mCameraSession = configureReprocessableCameraSessionWithConfigurations( 662 mCamera, inputConfig, outputConfigsList, mCameraSessionListener, mHandler); 663 664 // create an ImageWriter 665 mInputSurface = mCameraSession.getInputSurface(); 666 mImageWriter = ImageWriter.newInstance(mInputSurface, 667 numImageWriterImages); 668 669 mImageWriterListener = new SimpleImageWriterListener(mImageWriter); 670 mImageWriter.setOnImageReleasedListener(mImageWriterListener, mHandler); 671 } 672 673 /** 674 * Close the reprocessable session and ImageWriter. 675 */ closeReprossibleSession()676 private void closeReprossibleSession() { 677 mInputSurface = null; 678 679 if (mCameraSession != null) { 680 mCameraSession.close(); 681 mCameraSession = null; 682 } 683 684 if (mImageWriter != null) { 685 mImageWriter.close(); 686 mImageWriter = null; 687 } 688 } 689 690 /** 691 * Do one multi-resolution reprocess capture for the specified zoom ratio 692 */ doMultiResReprocessCapture(float zoomRatio)693 private ImageResultSizeHolder doMultiResReprocessCapture(float zoomRatio) throws Exception { 694 // submit a regular capture and get the result 695 TotalCaptureResult totalResult = submitCaptureRequest( 696 zoomRatio, mMultiResImageReader.getSurface(), /*inputResult*/null); 697 Map<String, TotalCaptureResult> physicalResults = 698 totalResult.getPhysicalCameraTotalResults(); 699 700 ImageAndMultiResStreamInfo inputImageAndInfo = 701 mMultiResImageReaderListener.getAnyImageAndInfoAvailable(CAPTURE_TIMEOUT_MS); 702 assertNotNull("Failed to capture input image", inputImageAndInfo); 703 Image inputImage = inputImageAndInfo.image; 704 MultiResolutionStreamInfo inputStreamInfo = inputImageAndInfo.streamInfo; 705 TotalCaptureResult inputSettings = 706 physicalResults.get(inputStreamInfo.getPhysicalCameraId()); 707 assertTrue("Regular capture's TotalCaptureResult doesn't contain capture result for " 708 + "physical camera id " + inputStreamInfo.getPhysicalCameraId(), 709 inputSettings != null); 710 711 // Submit a reprocess capture and get the result 712 mImageWriter.queueInputImage(inputImage); 713 714 TotalCaptureResult finalResult = submitCaptureRequest(zoomRatio, 715 getOutputMultiResImageReader().getSurface(), inputSettings); 716 717 ImageAndMultiResStreamInfo outputImageAndInfo = 718 getOutputMultiResImageReaderListener().getAnyImageAndInfoAvailable( 719 CAPTURE_TIMEOUT_MS); 720 Image outputImage = outputImageAndInfo.image; 721 MultiResolutionStreamInfo outputStreamInfo = outputImageAndInfo.streamInfo; 722 723 assertTrue("The regular output and reprocess output's stream info must be the same", 724 outputStreamInfo.equals(inputStreamInfo)); 725 726 ImageResultSizeHolder holder = new ImageResultSizeHolder(outputImageAndInfo.image, 727 finalResult, new Size(outputStreamInfo.getWidth(), outputStreamInfo.getHeight())); 728 729 return holder; 730 } 731 732 /** 733 * Issue a capture request and return the result for a particular zoom ratio. 734 * 735 * <p>If inputResult is null, it's a regular request. Otherwise, it's a reprocess request.</p> 736 */ submitCaptureRequest(float zoomRatio, Surface output, TotalCaptureResult inputResult)737 private TotalCaptureResult submitCaptureRequest(float zoomRatio, 738 Surface output, TotalCaptureResult inputResult) throws Exception { 739 740 SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); 741 742 // Prepare a list of capture requests. Whether it's a regular or reprocess capture request 743 // is based on inputResult. 744 CaptureRequest.Builder builder; 745 boolean isReprocess = (inputResult != null); 746 if (isReprocess) { 747 builder = mCamera.createReprocessCaptureRequest(inputResult); 748 } else { 749 builder = mCamera.createCaptureRequest(CAPTURE_TEMPLATE); 750 builder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomRatio); 751 } 752 builder.addTarget(output); 753 CaptureRequest request = builder.build(); 754 assertTrue("Capture request reprocess type " + request.isReprocess() + " is wrong.", 755 request.isReprocess() == isReprocess); 756 757 mCameraSession.capture(request, captureCallback, mHandler); 758 759 TotalCaptureResult result = captureCallback.getTotalCaptureResultForRequest( 760 request, CAPTURE_TIMEOUT_FRAMES); 761 762 // make sure all input surfaces are released. 763 if (isReprocess) { 764 mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS); 765 } 766 767 return result; 768 } 769 getMaxSize(int format, StaticMetadata.StreamDirection direction)770 private Size getMaxSize(int format, StaticMetadata.StreamDirection direction) { 771 Size[] sizes = mStaticInfo.getAvailableSizesForFormatChecked(format, direction); 772 return getAscendingOrderSizes(Arrays.asList(sizes), /*ascending*/false).get(0); 773 } 774 getMultiResReprocessInfo(String cameraId, int format, boolean input)775 private Collection<MultiResolutionStreamInfo> getMultiResReprocessInfo(String cameraId, 776 int format, boolean input) throws Exception { 777 StaticMetadata staticInfo = mAllStaticInfo.get(cameraId); 778 CameraCharacteristics characteristics = staticInfo.getCharacteristics(); 779 MultiResolutionStreamConfigurationMap configs = characteristics.get( 780 CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP); 781 if (configs == null) { 782 Log.i(TAG, "Camera " + cameraId + " doesn't support multi-resolution streams"); 783 return null; 784 } 785 786 String streamType = input ? "input" : "output"; 787 int[] formats = input ? configs.getInputFormats() : 788 configs.getOutputFormats(); 789 if (!CameraTestUtils.contains(formats, format)) { 790 Log.i(TAG, "Camera " + cameraId + " doesn't support multi-resolution " 791 + streamType + " stream for format " + format + ". Supported formats are " 792 + Arrays.toString(formats)); 793 return null; 794 } 795 Collection<MultiResolutionStreamInfo> streams = 796 input ? configs.getInputInfo(format) : configs.getOutputInfo(format); 797 mCollector.expectTrue(String.format("Camera %s supported 0 multi-resolution " 798 + streamType + " stream info, expected at least 1", cameraId), 799 streams.size() > 0); 800 801 return streams; 802 } 803 dumpImage(Image image, String name)804 private void dumpImage(Image image, String name) { 805 String filename = mDebugFileNameBase + name; 806 switch(image.getFormat()) { 807 case ImageFormat.JPEG: 808 filename += ".jpg"; 809 break; 810 case ImageFormat.YUV_420_888: 811 filename += ".yuv"; 812 break; 813 default: 814 filename += "." + image.getFormat(); 815 break; 816 } 817 818 Log.d(TAG, "dumping an image to " + filename); 819 dumpFile(filename , getDataFromImage(image)); 820 } 821 822 /** 823 * A class that holds an Image, a TotalCaptureResult, and expected image size. 824 */ 825 public static class ImageResultSizeHolder { 826 private final Image mImage; 827 private final TotalCaptureResult mResult; 828 private final Size mExpectedSize; 829 ImageResultSizeHolder(Image image, TotalCaptureResult result, Size expectedSize)830 public ImageResultSizeHolder(Image image, TotalCaptureResult result, Size expectedSize) { 831 mImage = image; 832 mResult = result; 833 mExpectedSize = expectedSize; 834 } 835 getImage()836 public Image getImage() { 837 return mImage; 838 } 839 getTotalCaptureResult()840 public TotalCaptureResult getTotalCaptureResult() { 841 return mResult; 842 } 843 getExpectedSize()844 public Size getExpectedSize() { 845 return mExpectedSize; 846 } 847 } 848 849 } 850