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