1 /* 2 * Copyright 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hardware.camera2.cts; 18 19 import static android.hardware.camera2.cts.CameraTestUtils.*; 20 21 import android.graphics.ImageFormat; 22 import android.hardware.camera2.CameraDevice; 23 import android.hardware.camera2.CameraMetadata; 24 import android.hardware.camera2.CaptureFailure; 25 import android.hardware.camera2.CaptureRequest; 26 import android.hardware.camera2.CaptureResult; 27 import android.hardware.camera2.TotalCaptureResult; 28 import android.hardware.camera2.cts.helpers.StaticMetadata; 29 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; 30 import android.hardware.camera2.params.InputConfiguration; 31 import android.media.Image; 32 import android.media.ImageReader; 33 import android.media.ImageWriter; 34 import android.util.Log; 35 import android.util.Size; 36 import android.view.Surface; 37 38 import com.android.ex.camera2.blocking.BlockingSessionCallback; 39 40 import org.junit.Test; 41 import org.junit.runner.RunWith; 42 import org.junit.runners.Parameterized; 43 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.List; 47 48 /** 49 * <p>Tests for Reprocess API.</p> 50 */ 51 52 @RunWith(Parameterized.class) 53 public class ReprocessCaptureTest extends Camera2SurfaceViewTestCase { 54 private static final String TAG = "ReprocessCaptureTest"; 55 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 56 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 57 private static final int CAPTURE_TIMEOUT_FRAMES = 100; 58 private static final int CAPTURE_TIMEOUT_MS = 3000; 59 private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000; 60 private static final int CAPTURE_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG; 61 private static final int ZSL_TEMPLATE = CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG; 62 private static final int NUM_REPROCESS_TEST_LOOP = 3; 63 private static final int NUM_REPROCESS_CAPTURES = 3; 64 private static final int NUM_REPROCESS_BURST = 3; 65 private int mDumpFrameCount = 0; 66 67 // The image reader for the first regular capture 68 private ImageReader mFirstImageReader; 69 // The image reader for the reprocess capture 70 private ImageReader mSecondImageReader; 71 // A flag indicating whether the regular capture and the reprocess capture share the same image 72 // reader. If it's true, mFirstImageReader should be used for regular and reprocess outputs. 73 private boolean mShareOneImageReader; 74 private SimpleImageReaderListener mFirstImageReaderListener; 75 private SimpleImageReaderListener mSecondImageReaderListener; 76 private Surface mInputSurface; 77 private ImageWriter mImageWriter; 78 private SimpleImageWriterListener mImageWriterListener; 79 80 private enum CaptureTestCase { 81 SINGLE_SHOT, 82 BURST, 83 MIXED_BURST, 84 ABORT_CAPTURE, 85 TIMESTAMPS, 86 JPEG_EXIF, 87 REQUEST_KEYS, 88 } 89 90 /** 91 * Test YUV_420_888 -> YUV_420_888 with maximal supported sizes 92 */ 93 @Test testBasicYuvToYuvReprocessing()94 public void testBasicYuvToYuvReprocessing() throws Exception { 95 for (String id : getCameraIdsUnderTest()) { 96 if (!isYuvReprocessSupported(id)) { 97 continue; 98 } 99 100 // YUV_420_888 -> YUV_420_888 must be supported. 101 testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.YUV_420_888); 102 } 103 } 104 105 /** 106 * Test YUV_420_888 -> JPEG with maximal supported sizes 107 */ 108 @Test testBasicYuvToJpegReprocessing()109 public void testBasicYuvToJpegReprocessing() throws Exception { 110 for (String id : getCameraIdsUnderTest()) { 111 if (!isYuvReprocessSupported(id)) { 112 continue; 113 } 114 115 // YUV_420_888 -> JPEG must be supported. 116 testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.JPEG); 117 } 118 } 119 120 /** 121 * Test YUV_420_888 -> HEIC with maximal supported sizes 122 */ 123 @Test testBasicYuvToHeicReprocessing()124 public void testBasicYuvToHeicReprocessing() throws Exception { 125 for (String id : getCameraIdsUnderTest()) { 126 if (!isYuvReprocessSupported(id)) { 127 continue; 128 } 129 if (!mAllStaticInfo.get(id).isHeicSupported()) { 130 continue; 131 } 132 133 // YUV_420_888 -> HEIC must be supported. 134 testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.HEIC); 135 } 136 } 137 138 /** 139 * Test OPAQUE -> YUV_420_888 with maximal supported sizes 140 */ 141 @Test testBasicOpaqueToYuvReprocessing()142 public void testBasicOpaqueToYuvReprocessing() throws Exception { 143 for (String id : getCameraIdsUnderTest()) { 144 if (!isOpaqueReprocessSupported(id)) { 145 continue; 146 } 147 148 // Opaque -> YUV_420_888 must be supported. 149 testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.YUV_420_888); 150 } 151 } 152 153 /** 154 * Test OPAQUE -> JPEG with maximal supported sizes 155 */ 156 @Test testBasicOpaqueToJpegReprocessing()157 public void testBasicOpaqueToJpegReprocessing() throws Exception { 158 for (String id : getCameraIdsUnderTest()) { 159 if (!isOpaqueReprocessSupported(id)) { 160 continue; 161 } 162 163 // OPAQUE -> JPEG must be supported. 164 testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.JPEG); 165 } 166 } 167 168 /** 169 * Test OPAQUE -> HEIC with maximal supported sizes 170 */ 171 @Test testBasicOpaqueToHeicReprocessing()172 public void testBasicOpaqueToHeicReprocessing() throws Exception { 173 for (String id : getCameraIdsUnderTest()) { 174 if (!isOpaqueReprocessSupported(id)) { 175 continue; 176 } 177 if (!mAllStaticInfo.get(id).isHeicSupported()) { 178 continue; 179 } 180 181 // OPAQUE -> HEIC must be supported. 182 testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.HEIC); 183 } 184 } 185 186 /** 187 * Test all supported size and format combinations. 188 */ 189 @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests testReprocessingSizeFormat()190 public void testReprocessingSizeFormat() throws Exception { 191 for (String id : getCameraIdsUnderTest()) { 192 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 193 continue; 194 } 195 196 try { 197 // open Camera device 198 openDevice(id); 199 // no preview 200 testReprocessingAllCombinations(id, /*previewSize*/null, 201 CaptureTestCase.SINGLE_SHOT); 202 } finally { 203 closeDevice(); 204 } 205 } 206 } 207 208 /** 209 * Test all supported size and format combinations with preview. 210 */ 211 @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests testReprocessingSizeFormatWithPreview()212 public void testReprocessingSizeFormatWithPreview() throws Exception { 213 for (String id : getCameraIdsUnderTest()) { 214 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 215 continue; 216 } 217 218 try { 219 // open Camera device 220 openDevice(id); 221 testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), 222 CaptureTestCase.SINGLE_SHOT); 223 } finally { 224 closeDevice(); 225 } 226 } 227 } 228 229 /** 230 * Test recreating reprocessing sessions. 231 */ 232 @Test testRecreateReprocessingSessions()233 public void testRecreateReprocessingSessions() throws Exception { 234 for (String id : getCameraIdsUnderTest()) { 235 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 236 continue; 237 } 238 239 try { 240 openDevice(id); 241 242 // Test supported input/output formats with the largest sizes. 243 int[] inputFormats = 244 mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); 245 for (int inputFormat : inputFormats) { 246 int[] reprocessOutputFormats = 247 mStaticInfo.getValidOutputFormatsForInput(inputFormat); 248 for (int reprocessOutputFormat : reprocessOutputFormats) { 249 Size maxInputSize = 250 getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input); 251 Size maxReprocessOutputSize = getMaxSize(reprocessOutputFormat, 252 StaticMetadata.StreamDirection.Output); 253 254 for (int i = 0; i < NUM_REPROCESS_TEST_LOOP; i++) { 255 testReprocess(id, maxInputSize, inputFormat, maxReprocessOutputSize, 256 reprocessOutputFormat, 257 /* previewSize */null, NUM_REPROCESS_CAPTURES); 258 } 259 } 260 } 261 } finally { 262 closeDevice(); 263 } 264 } 265 } 266 267 /** 268 * Verify issuing cross session capture requests is invalid. 269 */ 270 @Test testCrossSessionCaptureException()271 public void testCrossSessionCaptureException() throws Exception { 272 for (String id : getCameraIdsUnderTest()) { 273 // Test one supported input format -> JPEG 274 int inputFormat; 275 int reprocessOutputFormat = ImageFormat.JPEG; 276 277 if (isOpaqueReprocessSupported(id)) { 278 inputFormat = ImageFormat.PRIVATE; 279 } else if (isYuvReprocessSupported(id)) { 280 inputFormat = ImageFormat.YUV_420_888; 281 } else { 282 continue; 283 } 284 285 openDevice(id); 286 287 // Test the largest sizes 288 Size inputSize = 289 getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input); 290 Size reprocessOutputSize = 291 getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output); 292 293 try { 294 if (VERBOSE) { 295 Log.v(TAG, "testCrossSessionCaptureException: cameraId: " + id + 296 " inputSize: " + inputSize + " inputFormat: " + inputFormat + 297 " reprocessOutputSize: " + reprocessOutputSize + 298 " reprocessOutputFormat: " + reprocessOutputFormat); 299 } 300 301 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, 302 reprocessOutputFormat, /*maxImages*/1); 303 setupReprocessableSession(/*previewSurface*/null, /*numImageWriterImages*/1); 304 305 TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), 306 /*inputResult*/null); 307 Image image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS); 308 309 // queue the image to image writer 310 mImageWriter.queueInputImage(image); 311 312 // recreate the session 313 closeReprossibleSession(); 314 setupReprocessableSession(/*previewSurface*/null, /*numImageWriterImages*/1); 315 try { 316 TotalCaptureResult reprocessResult; 317 // issue and wait on reprocess capture request 318 reprocessResult = submitCaptureRequest( 319 getReprocessOutputImageReader().getSurface(), result); 320 fail("Camera " + id + ": should get IllegalArgumentException for cross " + 321 "session reprocess capture."); 322 } catch (IllegalArgumentException e) { 323 // expected 324 if (DEBUG) { 325 Log.d(TAG, "Camera " + id + ": get IllegalArgumentException for cross " + 326 "session reprocess capture as expected: " + e.getMessage()); 327 } 328 } 329 } finally { 330 closeReprossibleSession(); 331 closeImageReaders(); 332 closeDevice(); 333 } 334 } 335 } 336 337 /** 338 * Verify queued input images are cleared in new reprocessable capture session. 339 * 340 * This tests the case where an application receives onCaptureBufferLost() for an 341 * output stream, resulting in pending input buffers not having corresponding request. 342 * 343 * For subsequent new reprocessable capture session, ImageWriter.queueInputBuffer may become 344 * stuck due to stale buffers from previous session. 345 */ 346 @Test testQueueImageWithoutRequest()347 public void testQueueImageWithoutRequest() throws Exception { 348 final int MAX_IMAGES = 1; 349 final int ITERATIONS = MAX_IMAGES + 3; 350 for (String id : getCameraIdsUnderTest()) { 351 // Test one supported input format -> JPEG 352 int inputFormat; 353 int reprocessOutputFormat = ImageFormat.JPEG; 354 355 if (isOpaqueReprocessSupported(id)) { 356 inputFormat = ImageFormat.PRIVATE; 357 } else if (isYuvReprocessSupported(id)) { 358 inputFormat = ImageFormat.YUV_420_888; 359 } else { 360 continue; 361 } 362 363 openDevice(id); 364 365 // Test the largest sizes 366 Size inputSize = 367 getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input); 368 Size reprocessOutputSize = 369 getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output); 370 371 try { 372 if (VERBOSE) { 373 Log.v(TAG, "testQueueImageWithoutRequest: cameraId: " + id + 374 " inputSize: " + inputSize + " inputFormat: " + inputFormat + 375 " reprocessOutputSize: " + reprocessOutputSize + 376 " reprocessOutputFormat: " + reprocessOutputFormat); 377 } 378 379 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, 380 reprocessOutputFormat, MAX_IMAGES); 381 382 for (int i = 0; i < ITERATIONS; i++) { 383 setupReprocessableSession(/*previewSurface*/null, /*numImageWriterImages*/1); 384 385 TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), 386 /*inputResult*/null); 387 Image image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS); 388 389 // queue the image to image writer 390 mImageWriter.queueInputImage(image); 391 392 mInputSurface = null; 393 mImageWriter.close(); 394 mImageWriter = null; 395 } 396 } finally { 397 closeReprossibleSession(); 398 closeImageReaders(); 399 closeDevice(); 400 } 401 } 402 } 403 404 /** 405 * Test burst reprocessing captures with and without preview. 406 */ 407 @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests testBurstReprocessing()408 public void testBurstReprocessing() throws Exception { 409 for (String id : getCameraIdsUnderTest()) { 410 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 411 continue; 412 } 413 414 try { 415 // open Camera device 416 openDevice(id); 417 // no preview 418 testReprocessingAllCombinations(id, /*previewSize*/null, CaptureTestCase.BURST); 419 // with preview 420 testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), 421 CaptureTestCase.BURST); 422 } finally { 423 closeDevice(); 424 } 425 } 426 } 427 428 /** 429 * Test burst captures mixed with regular and reprocess captures with and without preview. 430 */ 431 @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests testMixedBurstReprocessing()432 public void testMixedBurstReprocessing() throws Exception { 433 for (String id : getCameraIdsUnderTest()) { 434 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 435 continue; 436 } 437 438 try { 439 // open Camera device 440 openDevice(id); 441 // no preview 442 testReprocessingAllCombinations(id, /*previewSize*/null, 443 CaptureTestCase.MIXED_BURST); 444 // with preview 445 testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0), 446 CaptureTestCase.MIXED_BURST); 447 } finally { 448 closeDevice(); 449 } 450 } 451 } 452 453 /** 454 * Test aborting reprocess capture requests of the largest input and output sizes for each 455 * supported format. 456 */ 457 @Test testReprocessAbort()458 public void testReprocessAbort() throws Exception { 459 for (String id : getCameraIdsUnderTest()) { 460 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 461 continue; 462 } 463 464 try { 465 // open Camera device 466 openDevice(id); 467 468 int[] supportedInputFormats = 469 mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); 470 for (int inputFormat : supportedInputFormats) { 471 int[] supportedReprocessOutputFormats = 472 mStaticInfo.getValidOutputFormatsForInput(inputFormat); 473 for (int reprocessOutputFormat : supportedReprocessOutputFormats) { 474 testReprocessingMaxSizes(id, inputFormat, reprocessOutputFormat, 475 /*previewSize*/null, CaptureTestCase.ABORT_CAPTURE); 476 } 477 } 478 } finally { 479 closeDevice(); 480 } 481 } 482 } 483 484 /** 485 * Test reprocess timestamps for the largest input and output sizes for each supported format. 486 */ 487 @Test testReprocessTimestamps()488 public void testReprocessTimestamps() throws Exception { 489 for (String id : getCameraIdsUnderTest()) { 490 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 491 continue; 492 } 493 494 try { 495 // open Camera device 496 openDevice(id); 497 498 int[] supportedInputFormats = 499 mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); 500 for (int inputFormat : supportedInputFormats) { 501 int[] supportedReprocessOutputFormats = 502 mStaticInfo.getValidOutputFormatsForInput(inputFormat); 503 for (int reprocessOutputFormat : supportedReprocessOutputFormats) { 504 testReprocessingMaxSizes(id, inputFormat, reprocessOutputFormat, 505 /*previewSize*/null, CaptureTestCase.TIMESTAMPS); 506 } 507 } 508 } finally { 509 closeDevice(); 510 } 511 } 512 } 513 514 /** 515 * Test reprocess jpeg output's exif data for the largest input and output sizes for each 516 * supported format. 517 */ 518 @Test testReprocessJpegExif()519 public void testReprocessJpegExif() throws Exception { 520 for (String id : getCameraIdsUnderTest()) { 521 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 522 continue; 523 } 524 525 try { 526 // open Camera device 527 openDevice(id); 528 529 int[] supportedInputFormats = 530 mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); 531 532 for (int inputFormat : supportedInputFormats) { 533 int[] supportedReprocessOutputFormats = 534 mStaticInfo.getValidOutputFormatsForInput(inputFormat); 535 536 for (int reprocessOutputFormat : supportedReprocessOutputFormats) { 537 if (reprocessOutputFormat == ImageFormat.JPEG) { 538 testReprocessingMaxSizes(id, inputFormat, ImageFormat.JPEG, 539 /*previewSize*/null, CaptureTestCase.JPEG_EXIF); 540 } 541 } 542 } 543 } finally { 544 closeDevice(); 545 } 546 } 547 } 548 549 @Test testReprocessRequestKeys()550 public void testReprocessRequestKeys() throws Exception { 551 for (String id : getCameraIdsUnderTest()) { 552 if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) { 553 continue; 554 } 555 556 try { 557 // open Camera device 558 openDevice(id); 559 560 int[] supportedInputFormats = 561 mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); 562 for (int inputFormat : supportedInputFormats) { 563 int[] supportedReprocessOutputFormats = 564 mStaticInfo.getValidOutputFormatsForInput(inputFormat); 565 for (int reprocessOutputFormat : supportedReprocessOutputFormats) { 566 testReprocessingMaxSizes(id, inputFormat, reprocessOutputFormat, 567 /*previewSize*/null, CaptureTestCase.REQUEST_KEYS); 568 } 569 } 570 } finally { 571 closeDevice(); 572 } 573 } 574 } 575 576 /** 577 * Test the input format and output format with the largest input and output sizes. 578 */ testBasicReprocessing(String cameraId, int inputFormat, int reprocessOutputFormat)579 private void testBasicReprocessing(String cameraId, int inputFormat, 580 int reprocessOutputFormat) throws Exception { 581 try { 582 openDevice(cameraId); 583 584 testReprocessingMaxSizes(cameraId, inputFormat, reprocessOutputFormat, 585 /* previewSize */null, CaptureTestCase.SINGLE_SHOT); 586 } finally { 587 closeDevice(); 588 } 589 } 590 591 /** 592 * Test the input format and output format with the largest input and output sizes for a 593 * certain test case. 594 */ testReprocessingMaxSizes(String cameraId, int inputFormat, int reprocessOutputFormat, Size previewSize, CaptureTestCase captureTestCase)595 private void testReprocessingMaxSizes(String cameraId, int inputFormat, 596 int reprocessOutputFormat, Size previewSize, CaptureTestCase captureTestCase) 597 throws Exception { 598 Size maxInputSize = getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input); 599 Size maxReprocessOutputSize = 600 getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output); 601 602 switch (captureTestCase) { 603 case SINGLE_SHOT: 604 testReprocess(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize, 605 reprocessOutputFormat, previewSize, NUM_REPROCESS_CAPTURES); 606 break; 607 case ABORT_CAPTURE: 608 testReprocessAbort(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize, 609 reprocessOutputFormat); 610 break; 611 case TIMESTAMPS: 612 testReprocessTimestamps(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize, 613 reprocessOutputFormat); 614 break; 615 case JPEG_EXIF: 616 testReprocessJpegExif(cameraId, maxInputSize, inputFormat, maxReprocessOutputSize); 617 break; 618 case REQUEST_KEYS: 619 testReprocessRequestKeys(cameraId, maxInputSize, inputFormat, 620 maxReprocessOutputSize, reprocessOutputFormat); 621 break; 622 default: 623 throw new IllegalArgumentException("Invalid test case"); 624 } 625 } 626 627 /** 628 * Test all input format, input size, output format, and output size combinations. 629 */ testReprocessingAllCombinations(String cameraId, Size previewSize, CaptureTestCase captureTestCase)630 private void testReprocessingAllCombinations(String cameraId, Size previewSize, 631 CaptureTestCase captureTestCase) throws Exception { 632 633 Size QCIF = new Size(176, 144); 634 Size VGA = new Size(640, 480); 635 Size FULL_HD = new Size(1920, 1080); 636 int[] supportedInputFormats = 637 mStaticInfo.getAvailableFormats(StaticMetadata.StreamDirection.Input); 638 for (int inputFormat : supportedInputFormats) { 639 Size[] supportedInputSizes = 640 mStaticInfo.getAvailableSizesForFormatChecked(inputFormat, 641 StaticMetadata.StreamDirection.Input); 642 643 for (Size inputSize : supportedInputSizes) { 644 int[] supportedReprocessOutputFormats = 645 mStaticInfo.getValidOutputFormatsForInput(inputFormat); 646 647 for (int reprocessOutputFormat : supportedReprocessOutputFormats) { 648 Size[] supportedReprocessOutputSizes = 649 mStaticInfo.getAvailableSizesForFormatChecked(reprocessOutputFormat, 650 StaticMetadata.StreamDirection.Output); 651 652 for (Size reprocessOutputSize : supportedReprocessOutputSizes) { 653 // Handle QCIF exceptions 654 if (reprocessOutputSize.equals(QCIF) && 655 ((inputSize.getWidth() > FULL_HD.getWidth()) || 656 (inputSize.getHeight() > FULL_HD.getHeight()))) { 657 continue; 658 } 659 if (inputSize.equals(QCIF) && 660 ((reprocessOutputSize.getWidth() > FULL_HD.getWidth()) || 661 (reprocessOutputSize.getHeight() > FULL_HD.getHeight()))) { 662 continue; 663 } 664 if ((previewSize != null) && 665 ((previewSize.getWidth() > FULL_HD.getWidth()) || ( 666 previewSize.getHeight() > FULL_HD.getHeight())) && 667 (inputSize.equals(QCIF) || reprocessOutputSize.equals(QCIF))) { 668 previewSize = VGA; 669 } 670 671 switch (captureTestCase) { 672 case SINGLE_SHOT: 673 testReprocess(cameraId, inputSize, inputFormat, 674 reprocessOutputSize, reprocessOutputFormat, previewSize, 675 NUM_REPROCESS_CAPTURES); 676 break; 677 case BURST: 678 testReprocessBurst(cameraId, inputSize, inputFormat, 679 reprocessOutputSize, reprocessOutputFormat, previewSize, 680 NUM_REPROCESS_BURST); 681 break; 682 case MIXED_BURST: 683 testReprocessMixedBurst(cameraId, inputSize, inputFormat, 684 reprocessOutputSize, reprocessOutputFormat, previewSize, 685 NUM_REPROCESS_BURST); 686 break; 687 default: 688 throw new IllegalArgumentException("Invalid test case"); 689 } 690 } 691 } 692 } 693 } 694 } 695 696 /** 697 * Test burst that is mixed with regular and reprocess capture requests. 698 */ testReprocessMixedBurst(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, int numBurst)699 private void testReprocessMixedBurst(String cameraId, Size inputSize, int inputFormat, 700 Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, 701 int numBurst) throws Exception { 702 if (VERBOSE) { 703 Log.v(TAG, "testReprocessMixedBurst: cameraId: " + cameraId + " inputSize: " + 704 inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + 705 reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat + 706 " previewSize: " + previewSize + " numBurst: " + numBurst); 707 } 708 709 boolean enablePreview = (previewSize != null); 710 ImageResultHolder[] imageResultHolders = new ImageResultHolder[0]; 711 712 try { 713 // totalNumBurst = number of regular burst + number of reprocess burst. 714 int totalNumBurst = numBurst * 2; 715 716 if (enablePreview) { 717 updatePreviewSurface(previewSize); 718 } else { 719 mPreviewSurface = null; 720 } 721 722 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, 723 totalNumBurst); 724 setupReprocessableSession(mPreviewSurface, /*numImageWriterImages*/numBurst); 725 726 if (enablePreview) { 727 startPreview(mPreviewSurface); 728 } 729 730 // Prepare an array of booleans indicating each capture's type (regular or reprocess) 731 boolean[] isReprocessCaptures = new boolean[totalNumBurst]; 732 for (int i = 0; i < totalNumBurst; i++) { 733 if ((i & 1) == 0) { 734 isReprocessCaptures[i] = true; 735 } else { 736 isReprocessCaptures[i] = false; 737 } 738 } 739 740 imageResultHolders = doMixedReprocessBurstCapture(isReprocessCaptures); 741 for (ImageResultHolder holder : imageResultHolders) { 742 Image reprocessedImage = holder.getImage(); 743 TotalCaptureResult result = holder.getTotalCaptureResult(); 744 745 mCollector.expectImageProperties("testReprocessMixedBurst", reprocessedImage, 746 reprocessOutputFormat, reprocessOutputSize, 747 result.get(CaptureResult.SENSOR_TIMESTAMP)); 748 749 if (DEBUG) { 750 Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d", 751 cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat, 752 reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(), 753 reprocessOutputFormat)); 754 dumpImage(reprocessedImage, 755 "/testReprocessMixedBurst_camera" + cameraId + "_" + mDumpFrameCount); 756 mDumpFrameCount++; 757 } 758 } 759 } finally { 760 for (ImageResultHolder holder : imageResultHolders) { 761 holder.getImage().close(); 762 } 763 closeReprossibleSession(); 764 closeImageReaders(); 765 } 766 } 767 768 /** 769 * Test burst of reprocess capture requests. 770 */ testReprocessBurst(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, int numBurst)771 private void testReprocessBurst(String cameraId, Size inputSize, int inputFormat, 772 Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, 773 int numBurst) throws Exception { 774 if (VERBOSE) { 775 Log.v(TAG, "testReprocessBurst: cameraId: " + cameraId + " inputSize: " + 776 inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + 777 reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat + 778 " previewSize: " + previewSize + " numBurst: " + numBurst); 779 } 780 781 boolean enablePreview = (previewSize != null); 782 ImageResultHolder[] imageResultHolders = new ImageResultHolder[0]; 783 784 try { 785 if (enablePreview) { 786 updatePreviewSurface(previewSize); 787 } else { 788 mPreviewSurface = null; 789 } 790 791 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, 792 numBurst); 793 setupReprocessableSession(mPreviewSurface, numBurst); 794 795 if (enablePreview) { 796 startPreview(mPreviewSurface); 797 } 798 799 imageResultHolders = doReprocessBurstCapture(numBurst); 800 for (ImageResultHolder holder : imageResultHolders) { 801 Image reprocessedImage = holder.getImage(); 802 TotalCaptureResult result = holder.getTotalCaptureResult(); 803 804 mCollector.expectImageProperties("testReprocessBurst", reprocessedImage, 805 reprocessOutputFormat, reprocessOutputSize, 806 result.get(CaptureResult.SENSOR_TIMESTAMP)); 807 808 if (DEBUG) { 809 Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d", 810 cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat, 811 reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(), 812 reprocessOutputFormat)); 813 dumpImage(reprocessedImage, 814 "/testReprocessBurst_camera" + cameraId + "_" + mDumpFrameCount); 815 mDumpFrameCount++; 816 } 817 } 818 } finally { 819 for (ImageResultHolder holder : imageResultHolders) { 820 holder.getImage().close(); 821 } 822 closeReprossibleSession(); 823 closeImageReaders(); 824 } 825 } 826 827 /** 828 * Test a sequences of reprocess capture requests. 829 */ testReprocess(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, int numReprocessCaptures)830 private void testReprocess(String cameraId, Size inputSize, int inputFormat, 831 Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, 832 int numReprocessCaptures) throws Exception { 833 if (VERBOSE) { 834 Log.v(TAG, "testReprocess: cameraId: " + cameraId + " inputSize: " + 835 inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + 836 reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat + 837 " previewSize: " + previewSize); 838 } 839 840 boolean enablePreview = (previewSize != null); 841 842 try { 843 if (enablePreview) { 844 updatePreviewSurface(previewSize); 845 } else { 846 mPreviewSurface = null; 847 } 848 849 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, 850 /*maxImages*/1); 851 setupReprocessableSession(mPreviewSurface, /*numImageWriterImages*/1); 852 853 if (enablePreview) { 854 startPreview(mPreviewSurface); 855 } 856 857 for (int i = 0; i < numReprocessCaptures; i++) { 858 ImageResultHolder imageResultHolder = null; 859 860 try { 861 imageResultHolder = doReprocessCapture(); 862 Image reprocessedImage = imageResultHolder.getImage(); 863 TotalCaptureResult result = imageResultHolder.getTotalCaptureResult(); 864 865 mCollector.expectImageProperties("testReprocess", reprocessedImage, 866 reprocessOutputFormat, reprocessOutputSize, 867 result.get(CaptureResult.SENSOR_TIMESTAMP)); 868 869 if (DEBUG) { 870 Log.d(TAG, String.format("camera %s in %dx%d %d out %dx%d %d", 871 cameraId, inputSize.getWidth(), inputSize.getHeight(), inputFormat, 872 reprocessOutputSize.getWidth(), reprocessOutputSize.getHeight(), 873 reprocessOutputFormat)); 874 875 dumpImage(reprocessedImage, 876 "/testReprocess_camera" + cameraId + "_" + mDumpFrameCount); 877 mDumpFrameCount++; 878 } 879 } finally { 880 if (imageResultHolder != null) { 881 imageResultHolder.getImage().close(); 882 } 883 } 884 } 885 } finally { 886 closeReprossibleSession(); 887 closeImageReaders(); 888 } 889 } 890 891 /** 892 * Test aborting a burst reprocess capture and multiple single reprocess captures. 893 */ testReprocessAbort(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat)894 private void testReprocessAbort(String cameraId, Size inputSize, int inputFormat, 895 Size reprocessOutputSize, int reprocessOutputFormat) throws Exception { 896 if (VERBOSE) { 897 Log.v(TAG, "testReprocessAbort: cameraId: " + cameraId + " inputSize: " + 898 inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + 899 reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat); 900 } 901 902 try { 903 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, 904 NUM_REPROCESS_CAPTURES); 905 setupReprocessableSession(/*previewSurface*/null, NUM_REPROCESS_CAPTURES); 906 907 // Wait for session READY state after session creation 908 mSessionListener.getStateWaiter().waitForState( 909 BlockingSessionCallback.SESSION_READY, SESSION_CLOSE_TIMEOUT_MS); 910 911 // Test two cases: submitting reprocess requests one by one and in a burst. 912 boolean submitInBursts[] = {false, true}; 913 for (boolean submitInBurst : submitInBursts) { 914 // Prepare reprocess capture requests. 915 ArrayList<CaptureRequest> reprocessRequests = 916 new ArrayList<>(NUM_REPROCESS_CAPTURES); 917 918 for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { 919 TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), 920 /*inputResult*/null); 921 922 // Wait and drain the READY state for each reprocessing input output. 923 mSessionListener.getStateWaiter().waitForState( 924 BlockingSessionCallback.SESSION_READY, SESSION_CLOSE_TIMEOUT_MS); 925 926 mImageWriter.queueInputImage( 927 mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); 928 CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); 929 builder.addTarget(getReprocessOutputImageReader().getSurface()); 930 reprocessRequests.add(builder.build()); 931 } 932 933 SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); 934 935 // Submit reprocess capture requests. 936 if (submitInBurst) { 937 mSession.captureBurst(reprocessRequests, captureCallback, mHandler); 938 } else { 939 for (CaptureRequest request : reprocessRequests) { 940 mSession.capture(request, captureCallback, mHandler); 941 } 942 } 943 944 // Abort after getting the first result 945 TotalCaptureResult reprocessResult = 946 captureCallback.getTotalCaptureResultForRequest(reprocessRequests.get(0), 947 CAPTURE_TIMEOUT_FRAMES); 948 mSession.abortCaptures(); 949 950 // Wait until the session is ready again. 951 mSessionListener.getStateWaiter().waitForState( 952 BlockingSessionCallback.SESSION_READY, SESSION_CLOSE_TIMEOUT_MS); 953 954 // Gather all failed requests. 955 ArrayList<CaptureFailure> failures = 956 captureCallback.getCaptureFailures(NUM_REPROCESS_CAPTURES - 1); 957 ArrayList<CaptureRequest> failedRequests = new ArrayList<>(); 958 for (CaptureFailure failure : failures) { 959 failedRequests.add(failure.getRequest()); 960 } 961 962 // For each request that didn't fail must have a valid result. 963 for (int i = 1; i < reprocessRequests.size(); i++) { 964 CaptureRequest request = reprocessRequests.get(i); 965 if (!failedRequests.contains(request)) { 966 captureCallback.getTotalCaptureResultForRequest(request, 967 CAPTURE_TIMEOUT_FRAMES); 968 } 969 } 970 971 // Drain the image reader listeners. 972 mFirstImageReaderListener.drain(); 973 if (!mShareOneImageReader) { 974 mSecondImageReaderListener.drain(); 975 } 976 977 // Make sure all input surfaces are released. 978 for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { 979 mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS); 980 } 981 } 982 } finally { 983 closeReprossibleSession(); 984 closeImageReaders(); 985 } 986 } 987 988 /** 989 * Test timestamps for reprocess requests. Reprocess request's shutter timestamp, result's 990 * sensor timestamp, and output image's timestamp should match the reprocess input's timestamp. 991 */ testReprocessTimestamps(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat)992 private void testReprocessTimestamps(String cameraId, Size inputSize, int inputFormat, 993 Size reprocessOutputSize, int reprocessOutputFormat) throws Exception { 994 if (VERBOSE) { 995 Log.v(TAG, "testReprocessTimestamps: cameraId: " + cameraId + " inputSize: " + 996 inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + 997 reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat); 998 } 999 1000 try { 1001 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, 1002 NUM_REPROCESS_CAPTURES); 1003 setupReprocessableSession(/*previewSurface*/null, NUM_REPROCESS_CAPTURES); 1004 1005 // Prepare reprocess capture requests. 1006 ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(NUM_REPROCESS_CAPTURES); 1007 ArrayList<Long> expectedTimestamps = new ArrayList<>(NUM_REPROCESS_CAPTURES); 1008 1009 for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { 1010 TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), 1011 /*inputResult*/null); 1012 1013 mImageWriter.queueInputImage( 1014 mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); 1015 CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); 1016 builder.addTarget(getReprocessOutputImageReader().getSurface()); 1017 reprocessRequests.add(builder.build()); 1018 // Reprocess result's timestamp should match input image's timestamp. 1019 expectedTimestamps.add(result.get(CaptureResult.SENSOR_TIMESTAMP)); 1020 } 1021 1022 // Submit reprocess requests. 1023 SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); 1024 mSession.captureBurst(reprocessRequests, captureCallback, mHandler); 1025 1026 // Verify we get the expected timestamps. 1027 for (int i = 0; i < reprocessRequests.size(); i++) { 1028 captureCallback.waitForCaptureStart(reprocessRequests.get(i), 1029 expectedTimestamps.get(i), CAPTURE_TIMEOUT_FRAMES); 1030 } 1031 1032 TotalCaptureResult[] reprocessResults = 1033 captureCallback.getTotalCaptureResultsForRequests(reprocessRequests, 1034 CAPTURE_TIMEOUT_FRAMES); 1035 1036 for (int i = 0; i < expectedTimestamps.size(); i++) { 1037 // Verify the result timestamps match the input image's timestamps. 1038 long expected = expectedTimestamps.get(i); 1039 long timestamp = reprocessResults[i].get(CaptureResult.SENSOR_TIMESTAMP); 1040 assertEquals("Reprocess result timestamp (" + timestamp + ") doesn't match input " + 1041 "image's timestamp (" + expected + ")", expected, timestamp); 1042 1043 // Verify the reprocess output image timestamps match the input image's timestamps. 1044 Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS); 1045 timestamp = image.getTimestamp(); 1046 image.close(); 1047 1048 assertEquals("Reprocess output timestamp (" + timestamp + ") doesn't match input " + 1049 "image's timestamp (" + expected + ")", expected, timestamp); 1050 } 1051 1052 // Make sure all input surfaces are released. 1053 for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) { 1054 mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS); 1055 } 1056 } finally { 1057 closeReprossibleSession(); 1058 closeImageReaders(); 1059 } 1060 } 1061 1062 /** 1063 * Test JPEG tags for reprocess requests. Reprocess result's JPEG tags and JPEG image's tags 1064 * match reprocess request's JPEG tags. 1065 */ testReprocessJpegExif(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize)1066 private void testReprocessJpegExif(String cameraId, Size inputSize, int inputFormat, 1067 Size reprocessOutputSize) throws Exception { 1068 if (VERBOSE) { 1069 Log.v(TAG, "testReprocessJpegExif: cameraId: " + cameraId + " inputSize: " + 1070 inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + 1071 reprocessOutputSize); 1072 } 1073 1074 Size[] thumbnailSizes = mStaticInfo.getAvailableThumbnailSizesChecked(); 1075 Size[] testThumbnailSizes = new Size[EXIF_TEST_DATA.length]; 1076 Arrays.fill(testThumbnailSizes, thumbnailSizes[thumbnailSizes.length - 1]); 1077 // Make sure thumbnail size (0, 0) is covered. 1078 testThumbnailSizes[0] = new Size(0, 0); 1079 1080 try { 1081 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, ImageFormat.JPEG, 1082 EXIF_TEST_DATA.length); 1083 setupReprocessableSession(/*previewSurface*/null, EXIF_TEST_DATA.length); 1084 1085 // Prepare reprocess capture requests. 1086 ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(EXIF_TEST_DATA.length); 1087 1088 for (int i = 0; i < EXIF_TEST_DATA.length; i++) { 1089 TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), 1090 /*inputResult*/null); 1091 mImageWriter.queueInputImage( 1092 mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); 1093 1094 CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); 1095 builder.addTarget(getReprocessOutputImageReader().getSurface()); 1096 1097 // set jpeg keys 1098 setJpegKeys(builder, EXIF_TEST_DATA[i], testThumbnailSizes[i], mCollector); 1099 reprocessRequests.add(builder.build()); 1100 } 1101 1102 // Submit reprocess requests. 1103 SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); 1104 mSession.captureBurst(reprocessRequests, captureCallback, mHandler); 1105 1106 TotalCaptureResult[] reprocessResults = 1107 captureCallback.getTotalCaptureResultsForRequests(reprocessRequests, 1108 CAPTURE_TIMEOUT_FRAMES); 1109 1110 for (int i = 0; i < EXIF_TEST_DATA.length; i++) { 1111 // Verify output image's and result's JPEG EXIF data. 1112 Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS); 1113 verifyJpegKeys(image, reprocessResults[i], reprocessOutputSize, 1114 testThumbnailSizes[i], EXIF_TEST_DATA[i], mStaticInfo, mAllStaticInfo, 1115 mCollector, mDebugFileNameBase, ImageFormat.JPEG); 1116 image.close(); 1117 1118 } 1119 } finally { 1120 closeReprossibleSession(); 1121 closeImageReaders(); 1122 } 1123 } 1124 1125 1126 1127 /** 1128 * Test the following keys in reprocess results match the keys in reprocess requests: 1129 * 1. EDGE_MODE 1130 * 2. NOISE_REDUCTION_MODE 1131 * 3. REPROCESS_EFFECTIVE_EXPOSURE_FACTOR (only for YUV reprocess) 1132 */ testReprocessRequestKeys(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat)1133 private void testReprocessRequestKeys(String cameraId, Size inputSize, int inputFormat, 1134 Size reprocessOutputSize, int reprocessOutputFormat) throws Exception { 1135 if (VERBOSE) { 1136 Log.v(TAG, "testReprocessRequestKeys: cameraId: " + cameraId + " inputSize: " + 1137 inputSize + " inputFormat: " + inputFormat + " reprocessOutputSize: " + 1138 reprocessOutputSize + " reprocessOutputFormat: " + reprocessOutputFormat); 1139 } 1140 1141 final Integer[] EDGE_MODES = {CaptureRequest.EDGE_MODE_FAST, 1142 CaptureRequest.EDGE_MODE_HIGH_QUALITY, CaptureRequest.EDGE_MODE_OFF, 1143 CaptureRequest.EDGE_MODE_ZERO_SHUTTER_LAG}; 1144 final Integer[] NR_MODES = {CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY, 1145 CaptureRequest.NOISE_REDUCTION_MODE_OFF, 1146 CaptureRequest.NOISE_REDUCTION_MODE_ZERO_SHUTTER_LAG, 1147 CaptureRequest.NOISE_REDUCTION_MODE_FAST}; 1148 final Float[] EFFECTIVE_EXP_FACTORS = {null, 1.0f, 2.5f, 4.0f}; 1149 int numFrames = EDGE_MODES.length; 1150 1151 try { 1152 setupImageReaders(inputSize, inputFormat, reprocessOutputSize, reprocessOutputFormat, 1153 numFrames); 1154 setupReprocessableSession(/*previewSurface*/null, numFrames); 1155 1156 // Prepare reprocess capture requests. 1157 ArrayList<CaptureRequest> reprocessRequests = new ArrayList<>(numFrames); 1158 1159 for (int i = 0; i < numFrames; i++) { 1160 TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(), 1161 /*inputResult*/null); 1162 mImageWriter.queueInputImage( 1163 mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); 1164 1165 CaptureRequest.Builder builder = mCamera.createReprocessCaptureRequest(result); 1166 assertEquals("Default capture intent for a reprocessing request should be " 1167 + "STILL_CAPTURE", (int) builder.get(CaptureRequest.CONTROL_CAPTURE_INTENT), 1168 (int) CameraMetadata.CONTROL_CAPTURE_INTENT_STILL_CAPTURE); 1169 builder.addTarget(getReprocessOutputImageReader().getSurface()); 1170 1171 // Set reprocess request keys 1172 builder.set(CaptureRequest.EDGE_MODE, EDGE_MODES[i]); 1173 builder.set(CaptureRequest.NOISE_REDUCTION_MODE, NR_MODES[i]); 1174 if (inputFormat == ImageFormat.YUV_420_888) { 1175 builder.set(CaptureRequest.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR, 1176 EFFECTIVE_EXP_FACTORS[i]); 1177 } 1178 reprocessRequests.add(builder.build()); 1179 } 1180 1181 // Submit reprocess requests. 1182 SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); 1183 mSession.captureBurst(reprocessRequests, captureCallback, mHandler); 1184 1185 TotalCaptureResult[] reprocessResults = 1186 captureCallback.getTotalCaptureResultsForRequests(reprocessRequests, 1187 CAPTURE_TIMEOUT_FRAMES); 1188 1189 for (int i = 0; i < numFrames; i++) { 1190 // Verify result's keys 1191 Integer resultEdgeMode = reprocessResults[i].get(CaptureResult.EDGE_MODE); 1192 Integer resultNoiseReductionMode = 1193 reprocessResults[i].get(CaptureResult.NOISE_REDUCTION_MODE); 1194 1195 assertEquals("Reprocess result edge mode (" + resultEdgeMode + 1196 ") doesn't match requested edge mode (" + EDGE_MODES[i] + ")", 1197 resultEdgeMode, EDGE_MODES[i]); 1198 assertEquals("Reprocess result noise reduction mode (" + resultNoiseReductionMode + 1199 ") doesn't match requested noise reduction mode (" + 1200 NR_MODES[i] + ")", resultNoiseReductionMode, 1201 NR_MODES[i]); 1202 1203 if (inputFormat == ImageFormat.YUV_420_888) { 1204 Float resultEffectiveExposureFactor = reprocessResults[i].get( 1205 CaptureResult.REPROCESS_EFFECTIVE_EXPOSURE_FACTOR); 1206 assertEquals("Reprocess effective exposure factor (" + 1207 resultEffectiveExposureFactor + ") doesn't match requested " + 1208 "effective exposure factor (" + EFFECTIVE_EXP_FACTORS[i] + ")", 1209 resultEffectiveExposureFactor, EFFECTIVE_EXP_FACTORS[i]); 1210 } 1211 } 1212 } finally { 1213 closeReprossibleSession(); 1214 closeImageReaders(); 1215 } 1216 } 1217 1218 /** 1219 * Set up two image readers: one for regular capture (used for reprocess input) and one for 1220 * reprocess capture. 1221 */ setupImageReaders(Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, int maxImages)1222 private void setupImageReaders(Size inputSize, int inputFormat, Size reprocessOutputSize, 1223 int reprocessOutputFormat, int maxImages) { 1224 1225 mShareOneImageReader = false; 1226 // If the regular output and reprocess output have the same size and format, 1227 // they can share one image reader. 1228 if (inputFormat == reprocessOutputFormat && 1229 inputSize.equals(reprocessOutputSize)) { 1230 maxImages *= 2; 1231 mShareOneImageReader = true; 1232 } 1233 // create an ImageReader for the regular capture 1234 mFirstImageReaderListener = new SimpleImageReaderListener(); 1235 mFirstImageReader = makeImageReader(inputSize, inputFormat, maxImages, 1236 mFirstImageReaderListener, mHandler); 1237 1238 if (!mShareOneImageReader) { 1239 // create an ImageReader for the reprocess capture 1240 mSecondImageReaderListener = new SimpleImageReaderListener(); 1241 mSecondImageReader = makeImageReader(reprocessOutputSize, reprocessOutputFormat, 1242 maxImages, mSecondImageReaderListener, mHandler); 1243 } 1244 } 1245 1246 /** 1247 * Close two image readers. 1248 */ closeImageReaders()1249 private void closeImageReaders() { 1250 CameraTestUtils.closeImageReader(mFirstImageReader); 1251 mFirstImageReader = null; 1252 CameraTestUtils.closeImageReader(mSecondImageReader); 1253 mSecondImageReader = null; 1254 } 1255 1256 /** 1257 * Get the ImageReader for reprocess output. 1258 */ getReprocessOutputImageReader()1259 private ImageReader getReprocessOutputImageReader() { 1260 if (mShareOneImageReader) { 1261 return mFirstImageReader; 1262 } else { 1263 return mSecondImageReader; 1264 } 1265 } 1266 getReprocessOutputImageReaderListener()1267 private SimpleImageReaderListener getReprocessOutputImageReaderListener() { 1268 if (mShareOneImageReader) { 1269 return mFirstImageReaderListener; 1270 } else { 1271 return mSecondImageReaderListener; 1272 } 1273 } 1274 1275 /** 1276 * Set up a reprocessable session and create an ImageWriter with the sessoin's input surface. 1277 */ setupReprocessableSession(Surface previewSurface, int numImageWriterImages)1278 private void setupReprocessableSession(Surface previewSurface, int numImageWriterImages) 1279 throws Exception { 1280 // create a reprocessable capture session 1281 List<Surface> outSurfaces = new ArrayList<Surface>(); 1282 outSurfaces.add(mFirstImageReader.getSurface()); 1283 if (!mShareOneImageReader) { 1284 outSurfaces.add(mSecondImageReader.getSurface()); 1285 } 1286 if (previewSurface != null) { 1287 outSurfaces.add(previewSurface); 1288 } 1289 1290 InputConfiguration inputConfig = new InputConfiguration(mFirstImageReader.getWidth(), 1291 mFirstImageReader.getHeight(), mFirstImageReader.getImageFormat()); 1292 String inputConfigString = inputConfig.toString(); 1293 if (VERBOSE) { 1294 Log.v(TAG, "InputConfiguration: " + inputConfigString); 1295 } 1296 assertTrue(String.format("inputConfig is wrong: %dx%d format %d. Expect %dx%d format %d", 1297 inputConfig.getWidth(), inputConfig.getHeight(), inputConfig.getFormat(), 1298 mFirstImageReader.getWidth(), mFirstImageReader.getHeight(), 1299 mFirstImageReader.getImageFormat()), 1300 inputConfig.getWidth() == mFirstImageReader.getWidth() && 1301 inputConfig.getHeight() == mFirstImageReader.getHeight() && 1302 inputConfig.getFormat() == mFirstImageReader.getImageFormat()); 1303 1304 mSessionListener = new BlockingSessionCallback(); 1305 mSession = configureReprocessableCameraSession(mCamera, inputConfig, outSurfaces, 1306 mSessionListener, mHandler); 1307 1308 // create an ImageWriter 1309 mInputSurface = mSession.getInputSurface(); 1310 mImageWriter = ImageWriter.newInstance(mInputSurface, 1311 numImageWriterImages); 1312 1313 mImageWriterListener = new SimpleImageWriterListener(mImageWriter); 1314 mImageWriter.setOnImageReleasedListener(mImageWriterListener, mHandler); 1315 } 1316 1317 /** 1318 * Close the reprocessable session and ImageWriter. 1319 */ closeReprossibleSession()1320 private void closeReprossibleSession() { 1321 mInputSurface = null; 1322 1323 if (mSession != null) { 1324 mSession.close(); 1325 mSession = null; 1326 } 1327 1328 if (mImageWriter != null) { 1329 mImageWriter.close(); 1330 mImageWriter = null; 1331 } 1332 } 1333 1334 /** 1335 * Do one reprocess capture. 1336 */ doReprocessCapture()1337 private ImageResultHolder doReprocessCapture() throws Exception { 1338 return doReprocessBurstCapture(/*numBurst*/1)[0]; 1339 } 1340 1341 /** 1342 * Do a burst of reprocess captures. 1343 */ doReprocessBurstCapture(int numBurst)1344 private ImageResultHolder[] doReprocessBurstCapture(int numBurst) throws Exception { 1345 boolean[] isReprocessCaptures = new boolean[numBurst]; 1346 for (int i = 0; i < numBurst; i++) { 1347 isReprocessCaptures[i] = true; 1348 } 1349 1350 return doMixedReprocessBurstCapture(isReprocessCaptures); 1351 } 1352 1353 /** 1354 * Do a burst of captures that are mixed with regular and reprocess captures. 1355 * 1356 * @param isReprocessCaptures An array whose elements indicate whether it's a reprocess capture 1357 * request. If the element is true, it represents a reprocess capture 1358 * request. If the element is false, it represents a regular capture 1359 * request. The size of the array is the number of capture requests 1360 * in the burst. 1361 */ doMixedReprocessBurstCapture(boolean[] isReprocessCaptures)1362 private ImageResultHolder[] doMixedReprocessBurstCapture(boolean[] isReprocessCaptures) 1363 throws Exception { 1364 if (isReprocessCaptures == null || isReprocessCaptures.length <= 0) { 1365 throw new IllegalArgumentException("isReprocessCaptures must have at least 1 capture."); 1366 } 1367 1368 boolean hasReprocessRequest = false; 1369 boolean hasRegularRequest = false; 1370 1371 TotalCaptureResult[] results = new TotalCaptureResult[isReprocessCaptures.length]; 1372 for (int i = 0; i < isReprocessCaptures.length; i++) { 1373 // submit a capture and get the result if this entry is a reprocess capture. 1374 if (isReprocessCaptures[i]) { 1375 results[i] = submitCaptureRequest(mFirstImageReader.getSurface(), 1376 /*inputResult*/null); 1377 mImageWriter.queueInputImage( 1378 mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS)); 1379 hasReprocessRequest = true; 1380 } else { 1381 hasRegularRequest = true; 1382 } 1383 } 1384 1385 Surface[] outputSurfaces = new Surface[isReprocessCaptures.length]; 1386 for (int i = 0; i < isReprocessCaptures.length; i++) { 1387 outputSurfaces[i] = getReprocessOutputImageReader().getSurface(); 1388 } 1389 1390 TotalCaptureResult[] finalResults = submitMixedCaptureBurstRequest(outputSurfaces, results); 1391 1392 ImageResultHolder[] holders = new ImageResultHolder[isReprocessCaptures.length]; 1393 for (int i = 0; i < isReprocessCaptures.length; i++) { 1394 Image image = getReprocessOutputImageReaderListener().getImage(CAPTURE_TIMEOUT_MS); 1395 if (hasReprocessRequest && hasRegularRequest) { 1396 // If there are mixed requests, images and results may not be in the same order. 1397 for (int j = 0; j < finalResults.length; j++) { 1398 if (finalResults[j] != null && 1399 finalResults[j].get(CaptureResult.SENSOR_TIMESTAMP) == 1400 image.getTimestamp()) { 1401 holders[i] = new ImageResultHolder(image, finalResults[j]); 1402 finalResults[j] = null; 1403 break; 1404 } 1405 } 1406 1407 assertNotNull("Cannot find a result matching output image's timestamp: " + 1408 image.getTimestamp(), holders[i]); 1409 } else { 1410 // If no mixed requests, images and results should be in the same order. 1411 holders[i] = new ImageResultHolder(image, finalResults[i]); 1412 } 1413 } 1414 1415 return holders; 1416 } 1417 1418 /** 1419 * Start preview without a listener. 1420 */ startPreview(Surface previewSurface)1421 private void startPreview(Surface previewSurface) throws Exception { 1422 CaptureRequest.Builder builder = mCamera.createCaptureRequest(ZSL_TEMPLATE); 1423 builder.addTarget(previewSurface); 1424 mSession.setRepeatingRequest(builder.build(), null, mHandler); 1425 } 1426 1427 /** 1428 * Issue a capture request and return the result. If inputResult is null, it's a regular 1429 * request. Otherwise, it's a reprocess request. 1430 */ submitCaptureRequest(Surface output, TotalCaptureResult inputResult)1431 private TotalCaptureResult submitCaptureRequest(Surface output, 1432 TotalCaptureResult inputResult) throws Exception { 1433 Surface[] outputs = new Surface[1]; 1434 outputs[0] = output; 1435 TotalCaptureResult[] inputResults = new TotalCaptureResult[1]; 1436 inputResults[0] = inputResult; 1437 1438 return submitMixedCaptureBurstRequest(outputs, inputResults)[0]; 1439 } 1440 1441 /** 1442 * Submit a burst request mixed with regular and reprocess requests. 1443 * 1444 * @param outputs An array of output surfaces. One output surface will be used in one request 1445 * so the length of the array is the number of requests in a burst request. 1446 * @param inputResults An array of input results. If it's null, all requests are regular 1447 * requests. If an element is null, that element represents a regular 1448 * request. If an element if not null, that element represents a reprocess 1449 * request. 1450 * 1451 */ submitMixedCaptureBurstRequest(Surface[] outputs, TotalCaptureResult[] inputResults)1452 private TotalCaptureResult[] submitMixedCaptureBurstRequest(Surface[] outputs, 1453 TotalCaptureResult[] inputResults) throws Exception { 1454 if (outputs == null || outputs.length <= 0) { 1455 throw new IllegalArgumentException("outputs must have at least 1 surface"); 1456 } else if (inputResults != null && inputResults.length != outputs.length) { 1457 throw new IllegalArgumentException("The lengths of outputs and inputResults " + 1458 "don't match"); 1459 } 1460 1461 int numReprocessCaptures = 0; 1462 SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); 1463 ArrayList<CaptureRequest> captureRequests = new ArrayList<>(outputs.length); 1464 1465 // Prepare a list of capture requests. Whether it's a regular or reprocess capture request 1466 // is based on inputResults array. 1467 for (int i = 0; i < outputs.length; i++) { 1468 CaptureRequest.Builder builder; 1469 boolean isReprocess = (inputResults != null && inputResults[i] != null); 1470 if (isReprocess) { 1471 builder = mCamera.createReprocessCaptureRequest(inputResults[i]); 1472 numReprocessCaptures++; 1473 } else { 1474 builder = mCamera.createCaptureRequest(CAPTURE_TEMPLATE); 1475 } 1476 builder.addTarget(outputs[i]); 1477 CaptureRequest request = builder.build(); 1478 assertTrue("Capture request reprocess type " + request.isReprocess() + " is wrong.", 1479 request.isReprocess() == isReprocess); 1480 1481 captureRequests.add(request); 1482 } 1483 1484 if (captureRequests.size() == 1) { 1485 mSession.capture(captureRequests.get(0), captureCallback, mHandler); 1486 } else { 1487 mSession.captureBurst(captureRequests, captureCallback, mHandler); 1488 } 1489 1490 TotalCaptureResult[] results; 1491 if (numReprocessCaptures == 0 || numReprocessCaptures == outputs.length) { 1492 results = new TotalCaptureResult[outputs.length]; 1493 // If the requests are not mixed, they should come in order. 1494 for (int i = 0; i < results.length; i++){ 1495 results[i] = captureCallback.getTotalCaptureResultForRequest( 1496 captureRequests.get(i), CAPTURE_TIMEOUT_FRAMES); 1497 } 1498 } else { 1499 // If the requests are mixed, they may not come in order. 1500 results = captureCallback.getTotalCaptureResultsForRequests( 1501 captureRequests, CAPTURE_TIMEOUT_FRAMES * captureRequests.size()); 1502 } 1503 1504 // make sure all input surfaces are released. 1505 for (int i = 0; i < numReprocessCaptures; i++) { 1506 mImageWriterListener.waitForImageReleased(CAPTURE_TIMEOUT_MS); 1507 } 1508 1509 return results; 1510 } 1511 getMaxSize(int format, StaticMetadata.StreamDirection direction)1512 private Size getMaxSize(int format, StaticMetadata.StreamDirection direction) { 1513 Size[] sizes = mStaticInfo.getAvailableSizesForFormatChecked(format, direction); 1514 return getAscendingOrderSizes(Arrays.asList(sizes), /*ascending*/false).get(0); 1515 } 1516 isYuvReprocessSupported(String cameraId)1517 private boolean isYuvReprocessSupported(String cameraId) throws Exception { 1518 return isReprocessSupported(cameraId, ImageFormat.YUV_420_888); 1519 } 1520 isOpaqueReprocessSupported(String cameraId)1521 private boolean isOpaqueReprocessSupported(String cameraId) throws Exception { 1522 return isReprocessSupported(cameraId, ImageFormat.PRIVATE); 1523 } 1524 dumpImage(Image image, String name)1525 private void dumpImage(Image image, String name) { 1526 String filename = mDebugFileNameBase + name; 1527 switch(image.getFormat()) { 1528 case ImageFormat.JPEG: 1529 filename += ".jpg"; 1530 break; 1531 case ImageFormat.HEIC: 1532 filename += ".heic"; 1533 break; 1534 case ImageFormat.NV16: 1535 case ImageFormat.NV21: 1536 case ImageFormat.YUV_420_888: 1537 filename += ".yuv"; 1538 break; 1539 default: 1540 filename += "." + image.getFormat(); 1541 break; 1542 } 1543 1544 Log.d(TAG, "dumping an image to " + filename); 1545 dumpFile(filename , getDataFromImage(image)); 1546 } 1547 1548 /** 1549 * A class that holds an Image and a TotalCaptureResult. 1550 */ 1551 private static class ImageResultHolder { 1552 private final Image mImage; 1553 private final TotalCaptureResult mResult; 1554 ImageResultHolder(Image image, TotalCaptureResult result)1555 public ImageResultHolder(Image image, TotalCaptureResult result) { 1556 mImage = image; 1557 mResult = result; 1558 } 1559 getImage()1560 public Image getImage() { 1561 return mImage; 1562 } 1563 getTotalCaptureResult()1564 public TotalCaptureResult getTotalCaptureResult() { 1565 return mResult; 1566 } 1567 } 1568 } 1569