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 
17 package com.android.mediaframeworktest.stress;
18 
19 import com.android.ex.camera2.blocking.BlockingSessionCallback;
20 import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
21 import com.android.mediaframeworktest.helpers.CameraTestUtils;
22 import com.android.mediaframeworktest.helpers.StaticMetadata;
23 
24 import android.graphics.ImageFormat;
25 import android.hardware.camera2.CameraDevice;
26 import android.hardware.camera2.CaptureFailure;
27 import android.hardware.camera2.CaptureRequest;
28 import android.hardware.camera2.CaptureResult;
29 import android.hardware.camera2.TotalCaptureResult;
30 import android.hardware.camera2.params.InputConfiguration;
31 import android.media.Image;
32 import android.media.ImageReader;
33 import android.media.ImageWriter;
34 import android.util.Log;
35 import android.util.Size;
36 import android.view.Surface;
37 
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.List;
41 
42 import static com.android.mediaframeworktest.helpers.CameraTestUtils.EXIF_TEST_DATA;
43 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS;
44 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
45 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
46 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageWriterListener;
47 import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureReprocessableCameraSession;
48 import static com.android.mediaframeworktest.helpers.CameraTestUtils.dumpFile;
49 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getAscendingOrderSizes;
50 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getDataFromImage;
51 import static com.android.mediaframeworktest.helpers.CameraTestUtils.makeImageReader;
52 import static com.android.mediaframeworktest.helpers.CameraTestUtils.setJpegKeys;
53 import 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  */
66 public 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      */
testBasicYuvToJpegReprocessing()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      */
testBasicOpaqueToJpegReprocessing()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      */
testReprocessingSizeFormatWithPreview()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      */
testMixedBurstReprocessing()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      */
testBasicReprocessing(String cameraId, int inputFormat, int reprocessOutputFormat)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      */
testReprocessingMaxSizes(String cameraId, int inputFormat, int reprocessOutputFormat, Size previewSize, CaptureTestCase captureTestCase)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      */
testReprocessingAllCombinations(String cameraId, Size previewSize, CaptureTestCase captureTestCase)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      */
testReprocessMixedBurst(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, int numBurst)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      */
testReprocessBurst(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, int numBurst)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      */
testReprocess(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, Size previewSize, int numReprocessCaptures)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      */
testReprocessAbort(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat)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      */
testReprocessTimestamps(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat)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      */
testReprocessJpegExif(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize)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      */
testReprocessRequestKeys(String cameraId, Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat)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      */
setupImageReaders(Size inputSize, int inputFormat, Size reprocessOutputSize, int reprocessOutputFormat, int maxImages)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      */
closeImageReaders()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      */
getReprocessOutputImageReader()856     private ImageReader getReprocessOutputImageReader() {
857         if (mShareOneImageReader) {
858             return mFirstImageReader;
859         } else {
860             return mSecondImageReader;
861         }
862     }
863 
getReprocessOutputImageReaderListener()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      */
setupReprocessableSession(Surface previewSurface, int numImageWriterImages)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      */
closeReprossibleSession()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      */
doReprocessCapture()934     private ImageResultHolder doReprocessCapture() throws Exception {
935         return doReprocessBurstCapture(/*numBurst*/1)[0];
936     }
937 
938     /**
939      * Do a burst of reprocess captures.
940      */
doReprocessBurstCapture(int numBurst)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      */
doMixedReprocessBurstCapture(boolean[] isReprocessCaptures)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      */
startPreview(Surface previewSurface)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      */
submitCaptureRequest(Surface output, TotalCaptureResult inputResult)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      */
submitMixedCaptureBurstRequest(Surface[] outputs, TotalCaptureResult[] inputResults)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 
getMaxSize(int format, StaticMetadata.StreamDirection direction)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 
isYuvReprocessSupported(String cameraId)1114     private boolean isYuvReprocessSupported(String cameraId) throws Exception {
1115         return isReprocessSupported(cameraId, ImageFormat.YUV_420_888);
1116     }
1117 
isOpaqueReprocessSupported(String cameraId)1118     private boolean isOpaqueReprocessSupported(String cameraId) throws Exception {
1119         return isReprocessSupported(cameraId, ImageFormat.PRIVATE);
1120     }
1121 
dumpImage(Image image, String name)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 
ImageResultHolder(Image image, TotalCaptureResult result)1149         public ImageResultHolder(Image image, TotalCaptureResult result) {
1150             mImage = image;
1151             mResult = result;
1152         }
1153 
getImage()1154         public Image getImage() {
1155             return mImage;
1156         }
1157 
getTotalCaptureResult()1158         public TotalCaptureResult getTotalCaptureResult() {
1159             return mResult;
1160         }
1161     }
1162 }
1163