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