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