Camera2: Test mandatory reprocess configs

Add a test to verify reprocessable sessions can be created with
mandatory reprocess configs and used to submit reprocess
capture requests.

Bug: 20945169
Change-Id: I82362df1126de3c2c0f03b7d20ebdf80f69b43c3
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index afd71f5..6f2eaf9 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -739,6 +739,15 @@
         BlockingSessionCallback sessionListener = new BlockingSessionCallback(listener);
         camera.createReprocessableCaptureSession(inputConfiguration, outputSurfaces,
                 sessionListener, handler);
+
+        Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
+                                   BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
+        int state = sessionListener.getStateWaiter().waitForAnyOfStates(
+                Arrays.asList(sessionStates), SESSION_CONFIGURE_TIMEOUT_MS);
+
+        assertTrue("Creating a reprocessable session failed.",
+                state == BlockingSessionCallback.SESSION_READY);
+
         CameraCaptureSession session =
                 sessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
         assertTrue("Camera session should be a reprocessable session", session.isReprocessable());
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
index 3d5ceaa..d75550a 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -17,7 +17,7 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static android.hardware.camera2.cts.RobustnessTest.MaxOutputSizes.*;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.*;
 
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
@@ -27,16 +27,20 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.media.CamcorderProfile;
 import android.media.Image;
 import android.media.ImageReader;
+import android.media.ImageWriter;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
@@ -144,7 +148,7 @@
          * Each row of the table is a set of (format, max resolution) pairs, using the below consts
          */
 
-        // Enum values are defined in MaxOutputSizes
+        // Enum values are defined in MaxStreamSizes
         final int[][] LEGACY_COMBINATIONS = {
             {PRIV, MAXIMUM}, // Simple preview, GPU video processing, or no-preview video recording
             {JPEG, MAXIMUM}, // No-viewfinder still image capture
@@ -191,29 +195,7 @@
         final int[][][] TABLES =
             { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, BURST_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS };
 
-        // Sanity check the tables
-        int tableIdx = 0;
-        for (int[][] table : TABLES) {
-            int rowIdx = 0;
-            for (int[] row : table) {
-                assertTrue(String.format("Odd number of entries for table %d row %d: %s ",
-                                tableIdx, rowIdx, Arrays.toString(row)),
-                        (row.length % 2) == 0);
-                for (int i = 0; i < row.length; i += 2) {
-                    int format = row[i];
-                    int maxSize = row[i + 1];
-                    assertTrue(String.format("table %d row %d index %d format not valid: %d",
-                                    tableIdx, rowIdx, i, format),
-                            format == PRIV || format == JPEG || format == YUV || format == RAW);
-                    assertTrue(String.format("table %d row %d index %d max size not valid: %d",
-                                    tableIdx, rowIdx, i + 1, maxSize),
-                            maxSize == PREVIEW || maxSize == RECORD ||
-                            maxSize == MAXIMUM || maxSize == VGA);
-                }
-                rowIdx++;
-            }
-            tableIdx++;
-        }
+        sanityCheckConfigurationTables(TABLES);
 
         for (String id : mCameraIds) {
 
@@ -221,7 +203,7 @@
 
             CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(id);
 
-            MaxOutputSizes maxSizes = new MaxOutputSizes(cc, id);
+            MaxStreamSizes maxSizes = new MaxStreamSizes(cc, id);
 
             final StaticMetadata staticInfo = new StaticMetadata(cc);
             String streamConfigurationMapString =
@@ -276,11 +258,133 @@
     }
 
     /**
+     * Test for making sure the required reprocess input/output combinations for each hardware
+     * level and capability work as expected.
+     */
+    public void testMandatoryReprocessConfigurations() throws Exception {
+
+        /**
+         * For each stream combination, verify that
+         *    1. A reprocessable session can be created using the stream combination.
+         *    2. Reprocess capture requests targeting YUV and JPEG outputs are successful.
+         */
+        final int[][] LIMITED_COMBINATIONS = {
+            // Input        Outputs
+            {PRIV, MAXIMUM, JPEG, MAXIMUM},
+            {YUV , MAXIMUM, JPEG, MAXIMUM},
+            {PRIV, MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM},
+            {YUV , MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM},
+            {PRIV, MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM},
+            {YUV , MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM},
+            {PRIV, MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+            {YUV,  MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+        };
+
+        final int[][] FULL_COMBINATIONS = {
+            // Input        Outputs
+            {YUV , MAXIMUM, PRIV, PREVIEW},
+            {YUV , MAXIMUM, YUV , PREVIEW},
+            {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , RECORD},
+            {YUV , MAXIMUM, PRIV, PREVIEW, YUV , RECORD},
+            {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , MAXIMUM},
+            {PRIV, MAXIMUM, YUV , PREVIEW, YUV , MAXIMUM},
+            {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , RECORD , JPEG, RECORD},
+            {YUV , MAXIMUM, PRIV, PREVIEW, YUV , RECORD , JPEG, RECORD},
+            {PRIV, MAXIMUM, YUV , PREVIEW, PRIV, PREVIEW, YUV , MAXIMUM},
+            {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+            {YUV , MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, JPEG, MAXIMUM},
+        };
+
+        final int[][] RAW_COMBINATIONS = {
+            // Input        Outputs
+            {PRIV, MAXIMUM, YUV , PREVIEW, RAW , MAXIMUM},
+            {YUV , MAXIMUM, YUV , PREVIEW, RAW , MAXIMUM},
+            {PRIV, MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+            {YUV , MAXIMUM, PRIV, PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+            {PRIV, MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+            {YUV , MAXIMUM, YUV , PREVIEW, YUV , PREVIEW, RAW , MAXIMUM},
+            {PRIV, MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+            {YUV , MAXIMUM, PRIV, PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+            {PRIV, MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+            {YUV , MAXIMUM, YUV , PREVIEW, JPEG, MAXIMUM, RAW , MAXIMUM},
+        };
+
+        final int[][][] TABLES =
+                { LIMITED_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS };
+
+        sanityCheckConfigurationTables(TABLES);
+
+        for (String id : mCameraIds) {
+            CameraCharacteristics cc = mCameraManager.getCameraCharacteristics(id);
+            MaxStreamSizes maxSizes = new MaxStreamSizes(cc, id);
+            StaticMetadata staticInfo = new StaticMetadata(cc);
+
+            // Skip the test for legacy devices.
+            if (staticInfo.isHardwareLevelLegacy()) {
+                continue;
+            }
+
+            openDevice(id);
+
+            try {
+                for (int[] config : LIMITED_COMBINATIONS) {
+                    testReprocessStreamCombination(id, config, maxSizes, staticInfo);
+                }
+
+                // Check FULL devices
+                if (staticInfo.isHardwareLevelFull()) {
+                    for (int[] config : FULL_COMBINATIONS) {
+                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
+                    }
+                }
+
+                // Check devices with RAW capability.
+                if (staticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+                    for (int[] config : RAW_COMBINATIONS) {
+                        testReprocessStreamCombination(id, config, maxSizes, staticInfo);
+                    }
+                }
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    /**
+     * Sanity check the configuration tables.
+     */
+    private void sanityCheckConfigurationTables(final int[][][] tables) throws Exception {
+        int tableIdx = 0;
+        for (int[][] table : tables) {
+            int rowIdx = 0;
+            for (int[] row : table) {
+                assertTrue(String.format("Odd number of entries for table %d row %d: %s ",
+                                tableIdx, rowIdx, Arrays.toString(row)),
+                        (row.length % 2) == 0);
+                for (int i = 0; i < row.length; i += 2) {
+                    int format = row[i];
+                    int maxSize = row[i + 1];
+                    assertTrue(String.format("table %d row %d index %d format not valid: %d",
+                                    tableIdx, rowIdx, i, format),
+                            format == PRIV || format == JPEG || format == YUV || format == RAW);
+                    assertTrue(String.format("table %d row %d index %d max size not valid: %d",
+                                    tableIdx, rowIdx, i + 1, maxSize),
+                            maxSize == PREVIEW || maxSize == RECORD ||
+                            maxSize == MAXIMUM || maxSize == VGA);
+                }
+                rowIdx++;
+            }
+            tableIdx++;
+        }
+    }
+
+    /**
      * Simple holder for resolutions to use for different camera outputs and size limits.
      */
-    static class MaxOutputSizes {
+    static class MaxStreamSizes {
         // Format shorthands
-        static final int PRIV = -1;
+        static final int PRIV = ImageFormat.PRIVATE;
         static final int JPEG = ImageFormat.JPEG;
         static final int YUV  = ImageFormat.YUV_420_888;
         static final int RAW  = ImageFormat.RAW_SENSOR;
@@ -292,7 +396,7 @@
         static final int VGA = 3;
         static final int RESOLUTION_COUNT = 4;
 
-        public MaxOutputSizes(CameraCharacteristics cc, String cameraId) {
+        public MaxStreamSizes(CameraCharacteristics cc, String cameraId) {
             StreamConfigurationMap configs =
                     cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
             Size[] privSizes = configs.getOutputSizes(SurfaceTexture.class);
@@ -314,6 +418,11 @@
             maxYuvSizes[MAXIMUM] = CameraTestUtils.getMaxSize(yuvSizes);
             maxJpegSizes[MAXIMUM] = CameraTestUtils.getMaxSize(jpegSizes);
 
+            maxInputPrivSize = CameraTestUtils.getMaxSize(
+                    configs.getInputSizes(ImageFormat.PRIVATE));
+            maxInputYuvSize = CameraTestUtils.getMaxSize(
+                    configs.getInputSizes(ImageFormat.YUV_420_888));
+
             // Must always be supported, add unconditionally
             final Size vgaSize = new Size(640, 480);
             maxPrivSizes[VGA] = vgaSize;
@@ -325,118 +434,252 @@
         public final Size[] maxJpegSizes = new Size[RESOLUTION_COUNT];
         public final Size[] maxYuvSizes = new Size[RESOLUTION_COUNT];
         public final Size maxRawSize;
+        // TODO: support non maximum reprocess input.
+        public final Size maxInputPrivSize;
+        public final Size maxInputYuvSize;
 
         static public String configToString(int[] config) {
             StringBuilder b = new StringBuilder("{ ");
             for (int i = 0; i < config.length; i += 2) {
                 int format = config[i];
                 int sizeLimit = config[i + 1];
-                switch (format) {
-                    case PRIV:
-                        b.append("[PRIV, ");
-                        break;
-                    case JPEG:
-                        b.append("[JPEG, ");
-                        break;
-                    case YUV:
-                        b.append("[YUV, ");
-                        break;
-                    case RAW:
-                        b.append("[RAW, ");
-                        break;
-                    default:
-                        b.append("[UNK, ");
-                        break;
-                }
-                switch (sizeLimit) {
-                    case PREVIEW:
-                        b.append("PREVIEW] ");
-                        break;
-                    case RECORD:
-                        b.append("RECORD] ");
-                        break;
-                    case MAXIMUM:
-                        b.append("MAXIMUM] ");
-                        break;
-                    case VGA:
-                        b.append("VGA] ");
-                        break;
-                    default:
-                        b.append("UNK] ");
-                        break;
-                }
+
+                appendFormatSize(b, format, sizeLimit);
+                b.append(" ");
             }
             b.append("}");
             return b.toString();
         }
+
+        static public String reprocessConfigToString(int[] reprocessConfig) {
+            // reprocessConfig[0..1] is the input configuration
+            StringBuilder b = new StringBuilder("Input: ");
+            appendFormatSize(b, reprocessConfig[0], reprocessConfig[1]);
+
+            // reprocessConfig[0..1] is also output configuration to be captured as reprocess input.
+            b.append(", Outputs: { ");
+            for (int i = 0; i < reprocessConfig.length; i += 2) {
+                int format = reprocessConfig[i];
+                int sizeLimit = reprocessConfig[i + 1];
+
+                appendFormatSize(b, format, sizeLimit);
+                b.append(" ");
+            }
+            b.append("}");
+            return b.toString();
+        }
+
+        static private void appendFormatSize(StringBuilder b, int format, int Size) {
+            switch (format) {
+                case PRIV:
+                    b.append("[PRIV, ");
+                    break;
+                case JPEG:
+                    b.append("[JPEG, ");
+                    break;
+                case YUV:
+                    b.append("[YUV, ");
+                    break;
+                case RAW:
+                    b.append("[RAW, ");
+                    break;
+                default:
+                    b.append("[UNK, ");
+                    break;
+            }
+
+            switch (Size) {
+                case PREVIEW:
+                    b.append("PREVIEW]");
+                    break;
+                case RECORD:
+                    b.append("RECORD]");
+                    break;
+                case MAXIMUM:
+                    b.append("MAXIMUM]");
+                    break;
+                case VGA:
+                    b.append("VGA]");
+                    break;
+                default:
+                    b.append("UNK]");
+                    break;
+            }
+        }
     }
 
-    private void testOutputCombination(String cameraId, int[] config, MaxOutputSizes maxSizes)
+    /**
+     * Return an InputConfiguration for a given reprocess configuration.
+     */
+    private InputConfiguration getInputConfig(int[] reprocessConfig, MaxStreamSizes maxSizes) {
+        int format;
+        Size size;
+
+        if (reprocessConfig[1] != MAXIMUM) {
+            throw new IllegalArgumentException("Test only supports MAXIMUM input");
+        }
+
+        switch (reprocessConfig[0]) {
+            case PRIV:
+                format = ImageFormat.PRIVATE;
+                size = maxSizes.maxInputPrivSize;
+                break;
+            case YUV:
+                format = ImageFormat.YUV_420_888;
+                size = maxSizes.maxInputYuvSize;
+                break;
+            default:
+                throw new IllegalArgumentException("Input format not supported: " +
+                        reprocessConfig[0]);
+        }
+
+        return new InputConfiguration(size.getWidth(), size.getHeight(), format);
+    }
+
+    private void testReprocessStreamCombination(String cameraId, int[] reprocessConfig,
+            MaxStreamSizes maxSizes, StaticMetadata staticInfo) throws Exception {
+
+        Log.i(TAG, String.format("Testing Camera %s, reprocess config: %s", cameraId,
+                MaxStreamSizes.reprocessConfigToString(reprocessConfig)));
+
+        final int TIMEOUT_FOR_RESULT_MS = 3000;
+        final int NUM_REPROCESS_CAPTURES = 3;
+
+        List<SurfaceTexture> privTargets = new ArrayList<>();
+        List<ImageReader> jpegTargets = new ArrayList<>();
+        List<ImageReader> yuvTargets = new ArrayList<>();
+        List<ImageReader> rawTargets = new ArrayList<>();
+        List<Surface> outputSurfaces = new ArrayList<>();
+        ImageReader inputReader = null;
+        ImageWriter inputWriter = null;
+        SimpleImageReaderListener inputReaderListener = new SimpleImageReaderListener();
+        SimpleCaptureCallback inputCaptureListener = new SimpleCaptureCallback();
+        SimpleCaptureCallback reprocessOutputCaptureListener = new SimpleCaptureCallback();
+
+        boolean supportYuvReprocess = staticInfo.isCapabilitySupported(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING);
+        boolean supportOpaqueReprocess = staticInfo.isCapabilitySupported(
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
+
+        try {
+            // reprocessConfig[0:1] is input
+            InputConfiguration inputConfig = getInputConfig(
+                    Arrays.copyOfRange(reprocessConfig, 0, 2), maxSizes);
+
+            // Skip the configuration if the format is not supported for reprocessing.
+            if ((inputConfig.getFormat() == ImageFormat.YUV_420_888 && !supportYuvReprocess) ||
+                    (inputConfig.getFormat() == ImageFormat.PRIVATE && !supportOpaqueReprocess)) {
+                return;
+            }
+
+            inputReader = ImageReader.newInstance(inputConfig.getWidth(), inputConfig.getHeight(),
+                    inputConfig.getFormat(), NUM_REPROCESS_CAPTURES);
+            inputReader.setOnImageAvailableListener(inputReaderListener, mHandler);
+            outputSurfaces.add(inputReader.getSurface());
+
+            // reprocessConfig[2..] are additional outputs
+            setupConfigurationTargets(
+                    Arrays.copyOfRange(reprocessConfig, 2, reprocessConfig.length),
+                    maxSizes, privTargets, jpegTargets, yuvTargets, rawTargets, outputSurfaces,
+                    NUM_REPROCESS_CAPTURES);
+
+            // Verify we can create a reprocessable session with the input and all outputs.
+            BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+            CameraCaptureSession session = configureReprocessableCameraSession(mCamera,
+                    inputConfig, outputSurfaces, sessionListener, mHandler);
+            inputWriter = ImageWriter.newInstance(session.getInputSurface(),
+                    NUM_REPROCESS_CAPTURES);
+
+            // Prepare a request for reprocess input
+            CaptureRequest.Builder builder = mCamera.createCaptureRequest(
+                    CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+            builder.addTarget(inputReader.getSurface());
+
+            for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+                session.capture(builder.build(), inputCaptureListener, mHandler);
+            }
+
+            List<CaptureRequest> reprocessRequests = new ArrayList<>();
+            int numReprocessOutputs = 0;
+
+            for (int i = 0; i < NUM_REPROCESS_CAPTURES; i++) {
+                TotalCaptureResult result = inputCaptureListener.getTotalCaptureResult(
+                        TIMEOUT_FOR_RESULT_MS);
+                builder =  mCamera.createReprocessCaptureRequest(result);
+                inputWriter.queueInputImage(inputReaderListener.getImage(TIMEOUT_FOR_RESULT_MS));
+
+                // Test mandatory YUV and JPEG reprocess outputs.
+                for (ImageReader reader : jpegTargets) {
+                    builder.addTarget(reader.getSurface());
+                    numReprocessOutputs++;
+                }
+
+                for (ImageReader reader : yuvTargets) {
+                    builder.addTarget(reader.getSurface());
+                    numReprocessOutputs++;
+                }
+
+                reprocessRequests.add(builder.build());
+            }
+
+            session.captureBurst(reprocessRequests, reprocessOutputCaptureListener, mHandler);
+
+            for (int i = 0; i < numReprocessOutputs; i++) {
+                TotalCaptureResult result = reprocessOutputCaptureListener.getTotalCaptureResult(
+                        TIMEOUT_FOR_RESULT_MS);
+            }
+        } catch (Throwable e) {
+            mCollector.addMessage(String.format("Reprocess stream combination %s failed due to: %s",
+                    MaxStreamSizes.reprocessConfigToString(reprocessConfig), e.getMessage()));
+        } finally {
+            inputReaderListener.drain();
+            reprocessOutputCaptureListener.drain();
+
+            for (SurfaceTexture target : privTargets) {
+                target.release();
+            }
+
+            for (ImageReader target : jpegTargets) {
+                target.close();
+            }
+
+            for (ImageReader target : yuvTargets) {
+                target.close();
+            }
+
+            for (ImageReader target : rawTargets) {
+                target.close();
+            }
+
+            if (inputReader != null) {
+                inputReader.close();
+            }
+
+            if (inputWriter != null) {
+                inputWriter.close();
+            }
+        }
+    }
+
+    private void testOutputCombination(String cameraId, int[] config, MaxStreamSizes maxSizes)
             throws Exception {
 
         Log.i(TAG, String.format("Testing Camera %s, config %s",
-                        cameraId, MaxOutputSizes.configToString(config)));
+                        cameraId, MaxStreamSizes.configToString(config)));
 
         // Timeout is relaxed by 500ms for LEGACY devices to reduce false positive rate in CTS
         final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 1500 : 1000;
         final int MIN_RESULT_COUNT = 3;
 
-        ImageDropperListener imageDropperListener = new ImageDropperListener();
         // Set up outputs
-        List<Object> outputTargets = new ArrayList<>();
-        List<Surface> outputSurfaces = new ArrayList<>();
+        List<Surface> outputSurfaces = new ArrayList<Surface>();
         List<SurfaceTexture> privTargets = new ArrayList<SurfaceTexture>();
         List<ImageReader> jpegTargets = new ArrayList<ImageReader>();
         List<ImageReader> yuvTargets = new ArrayList<ImageReader>();
         List<ImageReader> rawTargets = new ArrayList<ImageReader>();
-        for (int i = 0; i < config.length; i += 2) {
-            int format = config[i];
-            int sizeLimit = config[i + 1];
 
-            switch (format) {
-                case PRIV: {
-                    Size targetSize = maxSizes.maxPrivSizes[sizeLimit];
-                    SurfaceTexture target = new SurfaceTexture(/*random int*/1);
-                    target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
-                    outputTargets.add(target);
-                    outputSurfaces.add(new Surface(target));
-                    privTargets.add(target);
-                    break;
-                }
-                case JPEG: {
-                    Size targetSize = maxSizes.maxJpegSizes[sizeLimit];
-                    ImageReader target = ImageReader.newInstance(
-                        targetSize.getWidth(), targetSize.getHeight(), JPEG, MIN_RESULT_COUNT);
-                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
-                    outputTargets.add(target);
-                    outputSurfaces.add(target.getSurface());
-                    jpegTargets.add(target);
-                    break;
-                }
-                case YUV: {
-                    Size targetSize = maxSizes.maxYuvSizes[sizeLimit];
-                    ImageReader target = ImageReader.newInstance(
-                        targetSize.getWidth(), targetSize.getHeight(), YUV, MIN_RESULT_COUNT);
-                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
-                    outputTargets.add(target);
-                    outputSurfaces.add(target.getSurface());
-                    yuvTargets.add(target);
-                    break;
-                }
-                case RAW: {
-                    Size targetSize = maxSizes.maxRawSize;
-                    ImageReader target = ImageReader.newInstance(
-                        targetSize.getWidth(), targetSize.getHeight(), RAW, MIN_RESULT_COUNT);
-                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
-                    outputTargets.add(target);
-                    outputSurfaces.add(target.getSurface());
-                    rawTargets.add(target);
-                    break;
-                }
-                default:
-                    fail("Unknown output format " + format);
-            }
-        }
+        setupConfigurationTargets(config, maxSizes, privTargets, jpegTargets, yuvTargets,
+                rawTargets, outputSurfaces, MIN_RESULT_COUNT);
 
         boolean haveSession = false;
         try {
@@ -469,17 +712,17 @@
 
         } catch (Throwable e) {
             mCollector.addMessage(String.format("Output combination %s failed due to: %s",
-                    MaxOutputSizes.configToString(config), e.getMessage()));
+                    MaxStreamSizes.configToString(config), e.getMessage()));
         }
         if (haveSession) {
             try {
                 Log.i(TAG, String.format("Done with camera %s, config %s, closing session",
-                                cameraId, MaxOutputSizes.configToString(config)));
+                                cameraId, MaxStreamSizes.configToString(config)));
                 stopCapture(/*fast*/false);
             } catch (Throwable e) {
                 mCollector.addMessage(
                     String.format("Closing down for output combination %s failed due to: %s",
-                            MaxOutputSizes.configToString(config), e.getMessage()));
+                            MaxStreamSizes.configToString(config), e.getMessage()));
             }
         }
 
@@ -497,6 +740,59 @@
         }
     }
 
+    private void setupConfigurationTargets(int[] outputConfigs, MaxStreamSizes maxSizes,
+            List<SurfaceTexture> privTargets, List<ImageReader> jpegTargets,
+            List<ImageReader> yuvTargets, List<ImageReader> rawTargets,
+            List<Surface> outputSurfaces, int numBuffers) {
+
+        ImageDropperListener imageDropperListener = new ImageDropperListener();
+
+        for (int i = 0; i < outputConfigs.length; i += 2) {
+            int format = outputConfigs[i];
+            int sizeLimit = outputConfigs[i + 1];
+
+            switch (format) {
+                case PRIV: {
+                    Size targetSize = maxSizes.maxPrivSizes[sizeLimit];
+                    SurfaceTexture target = new SurfaceTexture(/*random int*/1);
+                    target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
+                    outputSurfaces.add(new Surface(target));
+                    privTargets.add(target);
+                    break;
+                }
+                case JPEG: {
+                    Size targetSize = maxSizes.maxJpegSizes[sizeLimit];
+                    ImageReader target = ImageReader.newInstance(
+                        targetSize.getWidth(), targetSize.getHeight(), JPEG, numBuffers);
+                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                    outputSurfaces.add(target.getSurface());
+                    jpegTargets.add(target);
+                    break;
+                }
+                case YUV: {
+                    Size targetSize = maxSizes.maxYuvSizes[sizeLimit];
+                    ImageReader target = ImageReader.newInstance(
+                        targetSize.getWidth(), targetSize.getHeight(), YUV, numBuffers);
+                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                    outputSurfaces.add(target.getSurface());
+                    yuvTargets.add(target);
+                    break;
+                }
+                case RAW: {
+                    Size targetSize = maxSizes.maxRawSize;
+                    ImageReader target = ImageReader.newInstance(
+                        targetSize.getWidth(), targetSize.getHeight(), RAW, numBuffers);
+                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                    outputSurfaces.add(target.getSurface());
+                    rawTargets.add(target);
+                    break;
+                }
+                default:
+                    fail("Unknown output format " + format);
+            }
+        }
+    }
+
     private static Size getMaxRecordingSize(String cameraId) {
         int id = Integer.valueOf(cameraId);