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