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