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