Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright 2014 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.graphics.ImageFormat.YUV_420_888;
     20 import static android.hardware.camera2.cts.helpers.Preconditions.*;
     21 import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
     22 import static android.hardware.camera2.cts.CameraTestUtils.*;
     23 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
     24 
     25 import android.content.Context;
     26 import android.graphics.ImageFormat;
     27 import android.graphics.RectF;
     28 import android.hardware.camera2.CameraAccessException;
     29 import android.hardware.camera2.CameraCaptureSession;
     30 import android.hardware.camera2.CameraCharacteristics;
     31 import android.hardware.camera2.CameraDevice;
     32 import android.hardware.camera2.CameraManager;
     33 import android.hardware.camera2.CameraMetadata;
     34 import android.hardware.camera2.CaptureRequest;
     35 import android.hardware.camera2.CaptureResult;
     36 import android.hardware.camera2.TotalCaptureResult;
     37 import android.hardware.camera2.params.ColorSpaceTransform;
     38 import android.hardware.camera2.params.RggbChannelVector;
     39 import android.hardware.camera2.params.StreamConfigurationMap;
     40 import android.util.Size;
     41 import android.hardware.camera2.cts.helpers.MaybeNull;
     42 import android.hardware.camera2.cts.helpers.StaticMetadata;
     43 import android.hardware.camera2.cts.rs.RenderScriptSingleton;
     44 import android.hardware.camera2.cts.rs.ScriptGraph;
     45 import android.hardware.camera2.cts.rs.ScriptYuvCrop;
     46 import android.hardware.camera2.cts.rs.ScriptYuvMeans1d;
     47 import android.hardware.camera2.cts.rs.ScriptYuvMeans2dTo1d;
     48 import android.hardware.camera2.cts.rs.ScriptYuvToRgb;
     49 import android.os.Handler;
     50 import android.os.HandlerThread;
     51 import android.platform.test.annotations.AppModeFull;
     52 import android.renderscript.Allocation;
     53 import android.renderscript.Script.LaunchOptions;
     54 import android.test.AndroidTestCase;
     55 import android.util.Log;
     56 import android.util.Rational;
     57 import android.view.Surface;
     58 
     59 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
     60 import com.android.ex.camera2.blocking.BlockingStateCallback;
     61 import com.android.ex.camera2.blocking.BlockingSessionCallback;
     62 
     63 import java.util.ArrayList;
     64 import java.util.Arrays;
     65 import java.util.List;
     66 
     67 /**
     68  * Suite of tests for camera2 -> RenderScript APIs.
     69  *
     70  * <p>It uses CameraDevice as producer, camera sends the data to the surface provided by
     71  * Allocation. Only the below format is tested:</p>
     72  *
     73  * <p>YUV_420_888: flexible YUV420, it is a mandatory format for camera.</p>
     74  */
     75 @AppModeFull
     76 public class AllocationTest extends AndroidTestCase {
     77     private static final String TAG = "AllocationTest";
     78     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     79 
     80     private CameraManager mCameraManager;
     81     private CameraDevice mCamera;
     82     private CameraCaptureSession mSession;
     83     private BlockingStateCallback mCameraListener;
     84     private BlockingSessionCallback mSessionListener;
     85 
     86     private String[] mCameraIds;
     87 
     88     private Handler mHandler;
     89     private HandlerThread mHandlerThread;
     90 
     91     private CameraIterable mCameraIterable;
     92     private SizeIterable mSizeIterable;
     93     private ResultIterable mResultIterable;
     94 
     95     @Override
     96     public synchronized void setContext(Context context) {
     97         super.setContext(context);
     98         mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
     99         assertNotNull("Can't connect to camera manager!", mCameraManager);
    100     }
    101 
    102     @Override
    103     protected void setUp() throws Exception {
    104         super.setUp();
    105         mCameraIds = mCameraManager.getCameraIdList();
    106         mHandlerThread = new HandlerThread("AllocationTest");
    107         mHandlerThread.start();
    108         mHandler = new Handler(mHandlerThread.getLooper());
    109         mCameraListener = new BlockingStateCallback();
    110 
    111         mCameraIterable = new CameraIterable();
    112         mSizeIterable = new SizeIterable();
    113         mResultIterable = new ResultIterable();
    114 
    115         RenderScriptSingleton.setContext(getContext());
    116     }
    117 
    118     @Override
    119     protected void tearDown() throws Exception {
    120         MaybeNull.close(mCamera);
    121         RenderScriptSingleton.clearContext();
    122         mHandlerThread.quitSafely();
    123         mHandler = null;
    124         super.tearDown();
    125     }
    126 
    127     /**
    128      * Update the request with a default manual request template.
    129      *
    130      * @param request A builder for a CaptureRequest
    131      * @param sensitivity ISO gain units (e.g. 100)
    132      * @param expTimeNs Exposure time in nanoseconds
    133      */
    134     private static void setManualCaptureRequest(CaptureRequest.Builder request, int sensitivity,
    135             long expTimeNs) {
    136         final Rational ONE = new Rational(1, 1);
    137         final Rational ZERO = new Rational(0, 1);
    138 
    139         if (VERBOSE) {
    140             Log.v(TAG, String.format("Create manual capture request, sensitivity = %d, expTime = %f",
    141                     sensitivity, expTimeNs / (1000.0 * 1000)));
    142         }
    143 
    144         request.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
    145         request.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
    146         request.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF);
    147         request.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
    148         request.set(CaptureRequest.CONTROL_EFFECT_MODE, CaptureRequest.CONTROL_EFFECT_MODE_OFF);
    149         request.set(CaptureRequest.SENSOR_FRAME_DURATION, 0L);
    150         request.set(CaptureRequest.SENSOR_SENSITIVITY, sensitivity);
    151         request.set(CaptureRequest.SENSOR_EXPOSURE_TIME, expTimeNs);
    152         request.set(CaptureRequest.COLOR_CORRECTION_MODE,
    153                 CaptureRequest.COLOR_CORRECTION_MODE_TRANSFORM_MATRIX);
    154 
    155         // Identity transform
    156         request.set(CaptureRequest.COLOR_CORRECTION_TRANSFORM,
    157             new ColorSpaceTransform(new Rational[] {
    158                 ONE, ZERO, ZERO,
    159                 ZERO, ONE, ZERO,
    160                 ZERO, ZERO, ONE
    161             }));
    162 
    163         // Identity gains
    164         request.set(CaptureRequest.COLOR_CORRECTION_GAINS,
    165                 new RggbChannelVector(1.0f, 1.0f, 1.0f, 1.0f ));
    166         request.set(CaptureRequest.TONEMAP_MODE, CaptureRequest.TONEMAP_MODE_FAST);
    167     }
    168 
    169     /**
    170      * Calculate the absolute crop window from a {@link Size},
    171      * and configure {@link LaunchOptions} for it.
    172      */
    173     // TODO: split patch crop window and the application against a particular size into 2 classes
    174     public static class Patch {
    175         /**
    176          * Create a new {@link Patch} from relative crop coordinates.
    177          *
    178          * <p>All float values must be normalized coordinates between [0, 1].</p>
    179          *
    180          * @param size Size of the original rectangle that is being cropped.
    181          * @param xNorm The X coordinate defining the left side of the rectangle (in [0, 1]).
    182          * @param yNorm The Y coordinate defining the top side of the rectangle (in [0, 1]).
    183          * @param wNorm The width of the crop rectangle (normalized between [0, 1]).
    184          * @param hNorm The height of the crop rectangle (normalized between [0, 1]).
    185          *
    186          * @throws NullPointerException if size was {@code null}.
    187          * @throws AssertionError if any of the normalized coordinates were out of range
    188          */
    189         public Patch(Size size, float xNorm, float yNorm, float wNorm, float hNorm) {
    190             checkNotNull("size", size);
    191 
    192             assertInRange(xNorm, 0.0f, 1.0f);
    193             assertInRange(yNorm, 0.0f, 1.0f);
    194             assertInRange(wNorm, 0.0f, 1.0f);
    195             assertInRange(hNorm, 0.0f, 1.0f);
    196 
    197             wFull = size.getWidth();
    198             hFull = size.getWidth();
    199 
    200             xTile = (int)Math.ceil(xNorm * wFull);
    201             yTile = (int)Math.ceil(yNorm * hFull);
    202 
    203             wTile = (int)Math.ceil(wNorm * wFull);
    204             hTile = (int)Math.ceil(hNorm * hFull);
    205 
    206             mSourceSize = size;
    207         }
    208 
    209         /**
    210          * Get the original size used to create this {@link Patch}.
    211          *
    212          * @return source size
    213          */
    214         public Size getSourceSize() {
    215             return mSourceSize;
    216         }
    217 
    218         /**
    219          * Get the cropped size after applying the normalized crop window.
    220          *
    221          * @return cropped size
    222          */
    223         public Size getSize() {
    224             return new Size(wFull, hFull);
    225         }
    226 
    227         /**
    228          * Get the {@link LaunchOptions} that can be used with a {@link android.renderscript.Script}
    229          * to apply a kernel over a subset of an {@link Allocation}.
    230          *
    231          * @return launch options
    232          */
    233         public LaunchOptions getLaunchOptions() {
    234             return (new LaunchOptions())
    235                     .setX(xTile, xTile + wTile)
    236                     .setY(yTile, yTile + hTile);
    237         }
    238 
    239         /**
    240          * Get the cropped width after applying the normalized crop window.
    241          *
    242          * @return cropped width
    243          */
    244         public int getWidth() {
    245             return wTile;
    246         }
    247 
    248         /**
    249          * Get the cropped height after applying the normalized crop window.
    250          *
    251          * @return cropped height
    252          */
    253         public int getHeight() {
    254             return hTile;
    255         }
    256 
    257         /**
    258          * Convert to a {@link RectF} where each corner is represented by a
    259          * normalized coordinate in between [0.0, 1.0] inclusive.
    260          *
    261          * @return a new rectangle
    262          */
    263         public RectF toRectF() {
    264             return new RectF(
    265                     xTile * 1.0f / wFull,
    266                     yTile * 1.0f / hFull,
    267                     (xTile + wTile) * 1.0f / wFull,
    268                     (yTile + hTile) * 1.0f / hFull);
    269         }
    270 
    271         private final Size mSourceSize;
    272         private final int wFull;
    273         private final int hFull;
    274         private final int xTile;
    275         private final int yTile;
    276         private final int wTile;
    277         private final int hTile;
    278     }
    279 
    280     /**
    281      * Convert a single YUV pixel (3 byte elements) to an RGB pixel.
    282      *
    283      * <p>The color channels must be in the following order:
    284      * <ul><li>Y - 0th channel
    285      * <li>U - 1st channel
    286      * <li>V - 2nd channel
    287      * </ul></p>
    288      *
    289      * <p>Each channel has data in the range 0-255.</p>
    290      *
    291      * <p>Output data is a 3-element pixel with each channel in the range of [0,1].
    292      * Each channel is saturated to avoid over/underflow.</p>
    293      *
    294      * <p>The conversion is done using JFIF File Interchange Format's "Conversion to and from RGB":
    295      * <ul>
    296      * <li>R = Y + 1.042 (Cr - 128)
    297      * <li>G = Y - 0.34414 (Cb - 128) - 0.71414 (Cr - 128)
    298      * <li>B = Y + 1.772 (Cb - 128)
    299      * </ul>
    300      *
    301      * Where Cr and Cb are aliases of V and U respectively.
    302      * </p>
    303      *
    304      * @param yuvData An array of a YUV pixel (at least 3 bytes large)
    305      *
    306      * @return an RGB888 pixel with each channel in the range of [0,1]
    307      */
    308     private static float[] convertPixelYuvToRgb(byte[] yuvData) {
    309         final int CHANNELS = 3; // yuv
    310         final float COLOR_RANGE = 255f;
    311 
    312         assertTrue("YUV pixel must be at least 3 bytes large", CHANNELS <= yuvData.length);
    313 
    314         float[] rgb = new float[CHANNELS];
    315 
    316         float y = yuvData[0] & 0xFF;  // Y channel
    317         float cb = yuvData[1] & 0xFF; // U channel
    318         float cr = yuvData[2] & 0xFF; // V channel
    319 
    320         // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
    321         float r = y + 1.402f * (cr - 128);
    322         float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
    323         float b = y + 1.772f * (cb - 128);
    324 
    325         // normalize [0,255] -> [0,1]
    326         rgb[0] = r / COLOR_RANGE;
    327         rgb[1] = g / COLOR_RANGE;
    328         rgb[2] = b / COLOR_RANGE;
    329 
    330         // Clamp to range [0,1]
    331         for (int i = 0; i < CHANNELS; ++i) {
    332             rgb[i] = Math.max(0.0f, Math.min(1.0f, rgb[i]));
    333         }
    334 
    335         if (VERBOSE) {
    336             Log.v(TAG, String.format("RGB calculated (r,g,b) = (%f, %f, %f)", rgb[0], rgb[1],
    337                     rgb[2]));
    338         }
    339 
    340         return rgb;
    341     }
    342 
    343     /**
    344      * Configure the camera with the target surface;
    345      * create a capture request builder with {@code cameraTarget} as the sole surface target.
    346      *
    347      * <p>Outputs are configured with the new surface targets, and this function blocks until
    348      * the camera has finished configuring.</p>
    349      *
    350      * <p>The capture request is created from the {@link CameraDevice#TEMPLATE_PREVIEW} template.
    351      * No other keys are set.
    352      * </p>
    353      */
    354     private CaptureRequest.Builder configureAndCreateRequestForSurface(Surface cameraTarget)
    355             throws CameraAccessException {
    356         List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/1);
    357         assertNotNull("Failed to get Surface", cameraTarget);
    358         outputSurfaces.add(cameraTarget);
    359 
    360         mSessionListener = new BlockingSessionCallback();
    361         mCamera.createCaptureSession(outputSurfaces, mSessionListener, mHandler);
    362         mSession = mSessionListener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
    363         CaptureRequest.Builder captureBuilder =
    364                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    365         assertNotNull("Fail to create captureRequest", captureBuilder);
    366         captureBuilder.addTarget(cameraTarget);
    367 
    368         if (VERBOSE) Log.v(TAG, "configureAndCreateRequestForSurface - done");
    369 
    370         return captureBuilder;
    371     }
    372 
    373     /**
    374      * Submit a single request to the camera, block until the buffer is available.
    375      *
    376      * <p>Upon return from this function, script has been executed against the latest buffer.
    377      * </p>
    378      */
    379     private void captureSingleShotAndExecute(CaptureRequest request, ScriptGraph graph)
    380             throws CameraAccessException {
    381         checkNotNull("request", request);
    382         checkNotNull("graph", graph);
    383 
    384         long exposureTimeNs = -1;
    385         int controlMode = -1;
    386         int aeMode = -1;
    387         if (request.get(CaptureRequest.CONTROL_MODE) != null) {
    388             controlMode = request.get(CaptureRequest.CONTROL_MODE);
    389         }
    390         if (request.get(CaptureRequest.CONTROL_AE_MODE) != null) {
    391             aeMode = request.get(CaptureRequest.CONTROL_AE_MODE);
    392         }
    393         if ((request.get(CaptureRequest.SENSOR_EXPOSURE_TIME) != null) &&
    394                 ((controlMode == CaptureRequest.CONTROL_MODE_OFF) ||
    395                  (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF))) {
    396             exposureTimeNs = request.get(CaptureRequest.SENSOR_EXPOSURE_TIME);
    397         }
    398         mSession.capture(request, new CameraCaptureSession.CaptureCallback() {
    399             @Override
    400             public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
    401                     TotalCaptureResult result) {
    402                 if (VERBOSE) Log.v(TAG, "Capture completed");
    403             }
    404         }, mHandler);
    405 
    406         if (VERBOSE) Log.v(TAG, "Waiting for single shot buffer");
    407         if (exposureTimeNs > 0) {
    408             graph.advanceInputWaiting(
    409                     java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(exposureTimeNs));
    410         } else {
    411             graph.advanceInputWaiting();
    412         }
    413         if (VERBOSE) Log.v(TAG, "Got the buffer");
    414         graph.execute();
    415     }
    416 
    417     private void stopCapture() throws CameraAccessException {
    418         if (VERBOSE) Log.v(TAG, "Stopping capture and waiting for idle");
    419         // Stop repeat, wait for captures to complete, and disconnect from surfaces
    420         mSession.close();
    421         mSessionListener.getStateWaiter().waitForState(BlockingSessionCallback.SESSION_CLOSED,
    422                 SESSION_CLOSE_TIMEOUT_MS);
    423         mSession = null;
    424         mSessionListener = null;
    425     }
    426 
    427     /**
    428      * Extremely dumb validator. Makes sure there is at least one non-zero RGB pixel value.
    429      */
    430     private void validateInputOutputNotZeroes(ScriptGraph scriptGraph, Size size) {
    431         final int BPP = 8; // bits per pixel
    432 
    433         int width = size.getWidth();
    434         int height = size.getHeight();
    435         /**
    436          * Check the input allocation is sane.
    437          * - Byte size matches what we expect.
    438          * - The input is not all zeroes.
    439          */
    440 
    441         // Check that input data was updated first. If it wasn't, the rest of the test will fail.
    442         byte[] data = scriptGraph.getInputData();
    443         assertArrayNotAllZeroes("Input allocation data was not updated", data);
    444 
    445         // Minimal required size to represent YUV 4:2:0 image
    446         int packedSize =
    447                 width * height * ImageFormat.getBitsPerPixel(YUV_420_888) / BPP;
    448         if (VERBOSE) Log.v(TAG, "Expected image size = " + packedSize);
    449         int actualSize = data.length;
    450         // Actual size may be larger due to strides or planes being non-contiguous
    451         assertTrue(
    452                 String.format(
    453                         "YUV 420 packed size (%d) should be at least as large as the actual size " +
    454                         "(%d)", packedSize, actualSize), packedSize <= actualSize);
    455         /**
    456          * Check the output allocation by converting to RGBA.
    457          * - Byte size matches what we expect
    458          * - The output is not all zeroes
    459          */
    460         final int RGBA_CHANNELS = 4;
    461 
    462         int actualSizeOut = scriptGraph.getOutputAllocation().getBytesSize();
    463         int packedSizeOut = width * height * RGBA_CHANNELS;
    464 
    465         byte[] dataOut = scriptGraph.getOutputData();
    466         assertEquals("RGB mismatched byte[] and expected size",
    467                 packedSizeOut, dataOut.length);
    468 
    469         if (VERBOSE) {
    470             Log.v(TAG, "checkAllocationByConvertingToRgba - RGB data size " + dataOut.length);
    471         }
    472 
    473         assertArrayNotAllZeroes("RGBA data was not updated", dataOut);
    474         // RGBA8888 stride should be equal to the width
    475         assertEquals("RGBA 8888 mismatched byte[] and expected size", packedSizeOut, actualSizeOut);
    476 
    477         if (VERBOSE) Log.v(TAG, "validating Buffer , size = " + actualSize);
    478     }
    479 
    480     public void testAllocationFromCameraFlexibleYuv() throws Exception {
    481 
    482         /** number of frame (for streaming requests) to be verified. */
    483         final int NUM_FRAME_VERIFIED = 1;
    484 
    485         mCameraIterable.forEachCamera(new CameraBlock() {
    486             @Override
    487             public void run(CameraDevice camera) throws CameraAccessException {
    488 
    489                 // Iterate over each size in the camera
    490                 mSizeIterable.forEachSize(YUV_420_888, new SizeBlock() {
    491                     @Override
    492                     public void run(final Size size) throws CameraAccessException {
    493                         // Create a script graph that converts YUV to RGB
    494                         try (ScriptGraph scriptGraph = ScriptGraph.create()
    495                                 .configureInputWithSurface(size, YUV_420_888)
    496                                 .chainScript(ScriptYuvToRgb.class)
    497                                 .buildGraph()) {
    498 
    499                             if (VERBOSE) Log.v(TAG, "Prepared ScriptYuvToRgb for size " + size);
    500 
    501                             // Run the graph against camera input and validate we get some input
    502                             CaptureRequest request =
    503                                     configureAndCreateRequestForSurface(scriptGraph.getInputSurface()).build();
    504 
    505                             // Block until we get 1 result, then iterate over the result
    506                             mResultIterable.forEachResultRepeating(
    507                                     request, NUM_FRAME_VERIFIED, new ResultBlock() {
    508                                 @Override
    509                                 public void run(CaptureResult result) throws CameraAccessException {
    510                                     scriptGraph.advanceInputWaiting();
    511                                     scriptGraph.execute();
    512                                     validateInputOutputNotZeroes(scriptGraph, size);
    513                                     scriptGraph.advanceInputAndDrop();
    514                                 }
    515                             });
    516 
    517                             stopCapture();
    518                             if (VERBOSE) Log.v(TAG, "Cleanup Renderscript cache");
    519                             scriptGraph.close();
    520                             RenderScriptSingleton.clearContext();
    521                             RenderScriptSingleton.setContext(getContext());
    522                         }
    523                     }
    524                 });
    525             }
    526         });
    527     }
    528 
    529     /**
    530      * Take two shots and ensure per-frame-control with exposure/gain is working correctly.
    531      *
    532      * <p>Takes a shot with very low ISO and exposure time. Expect it to be black.</p>
    533      *
    534      * <p>Take a shot with very high ISO and exposure time. Expect it to be white.</p>
    535      *
    536      * @throws Exception
    537      */
    538     public void testBlackWhite() throws CameraAccessException {
    539 
    540         /** low iso + low exposure (first shot) */
    541         final float THRESHOLD_LOW = 0.025f;
    542         /** high iso + high exposure (second shot) */
    543         final float THRESHOLD_HIGH = 0.975f;
    544 
    545         mCameraIterable.forEachCamera(/*fullHwLevel*/false, new CameraBlock() {
    546             @Override
    547             public void run(CameraDevice camera) throws CameraAccessException {
    548                 final StaticMetadata staticInfo =
    549                         new StaticMetadata(mCameraManager.getCameraCharacteristics(camera.getId()));
    550 
    551                 // This test requires PFC and manual sensor control
    552                 if (!staticInfo.isCapabilitySupported(
    553                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR) ||
    554                         !staticInfo.isPerFrameControlSupported()) {
    555                     return;
    556                 }
    557 
    558                 final Size maxSize = getMaxSize(
    559                         getSupportedSizeForFormat(YUV_420_888, camera.getId(), mCameraManager));
    560 
    561                 try (ScriptGraph scriptGraph = createGraphForYuvCroppedMeans(maxSize)) {
    562 
    563                     CaptureRequest.Builder req =
    564                             configureAndCreateRequestForSurface(scriptGraph.getInputSurface());
    565 
    566                     // Take a shot with very low ISO and exposure time. Expect it to be black.
    567                     int minimumSensitivity = staticInfo.getSensitivityMinimumOrDefault();
    568                     long minimumExposure = staticInfo.getExposureMinimumOrDefault();
    569                     setManualCaptureRequest(req, minimumSensitivity, minimumExposure);
    570 
    571                     CaptureRequest lowIsoExposureShot = req.build();
    572                     captureSingleShotAndExecute(lowIsoExposureShot, scriptGraph);
    573 
    574                     float[] blackMeans = convertPixelYuvToRgb(scriptGraph.getOutputData());
    575 
    576                     // Take a shot with very high ISO and exposure time. Expect it to be white.
    577                     int maximumSensitivity = staticInfo.getSensitivityMaximumOrDefault();
    578                     long maximumExposure = staticInfo.getExposureMaximumOrDefault();
    579                     setManualCaptureRequest(req, maximumSensitivity, maximumExposure);
    580 
    581                     CaptureRequest highIsoExposureShot = req.build();
    582                     captureSingleShotAndExecute(highIsoExposureShot, scriptGraph);
    583 
    584                     float[] whiteMeans = convertPixelYuvToRgb(scriptGraph.getOutputData());
    585 
    586                     // Low iso + low exposure (first shot), just check and log the error.
    587                     for (int i = 0; i < blackMeans.length; ++i) {
    588                         if (blackMeans[i] >= THRESHOLD_LOW) {
    589                             Log.e(TAG,
    590                                     String.format("Black means too high: (%s should be greater"
    591                                             + " than %s; item index %d in %s)", blackMeans[i],
    592                                             THRESHOLD_LOW, i,
    593                                             Arrays.toString(blackMeans)));
    594                         }
    595                     }
    596 
    597                     // High iso + high exposure (second shot), just check and log the error
    598                     for (int i = 0; i < whiteMeans.length; ++i) {
    599                         if (whiteMeans[i] <= THRESHOLD_HIGH) {
    600                             Log.e(TAG,
    601                                     String.format("White means too low: (%s should be less than"
    602                                             + " %s; item index %d in %s)", whiteMeans[i],
    603                                             THRESHOLD_HIGH, i,
    604                                             Arrays.toString(whiteMeans)));
    605                         }
    606                     }
    607                 }
    608             }
    609         });
    610     }
    611 
    612     /**
    613      * Test that the android.sensitivity.parameter is applied.
    614      */
    615     public void testParamSensitivity() throws CameraAccessException {
    616         final float THRESHOLD_MAX_MIN_DIFF = 0.3f;
    617         final float THRESHOLD_MAX_MIN_RATIO = 2.0f;
    618         final int NUM_STEPS = 5;
    619         final long EXPOSURE_TIME_NS = 2000000; // 2 ms
    620         final int RGB_CHANNELS = 3;
    621 
    622         mCameraIterable.forEachCamera(/*fullHwLevel*/false, new CameraBlock() {
    623 
    624 
    625             @Override
    626             public void run(CameraDevice camera) throws CameraAccessException {
    627                 final StaticMetadata staticInfo =
    628                         new StaticMetadata(mCameraManager.getCameraCharacteristics(camera.getId()));
    629                 // This test requires PFC and manual sensor control
    630                 if (!staticInfo.isCapabilitySupported(
    631                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR) ||
    632                         !staticInfo.isPerFrameControlSupported()) {
    633                     return;
    634                 }
    635 
    636                 final List<float[]> rgbMeans = new ArrayList<float[]>();
    637                 final Size maxSize = getMaxSize(
    638                         getSupportedSizeForFormat(YUV_420_888, camera.getId(), mCameraManager));
    639 
    640                 final int sensitivityMin = staticInfo.getSensitivityMinimumOrDefault();
    641                 final int sensitivityMax = staticInfo.getSensitivityMaximumOrDefault();
    642 
    643                 // List each sensitivity from min-max in NUM_STEPS increments
    644                 int[] sensitivities = new int[NUM_STEPS];
    645                 for (int i = 0; i < NUM_STEPS; ++i) {
    646                     int delta = (sensitivityMax - sensitivityMin) / (NUM_STEPS - 1);
    647                     sensitivities[i] = sensitivityMin + delta * i;
    648                 }
    649 
    650                 try (ScriptGraph scriptGraph = createGraphForYuvCroppedMeans(maxSize)) {
    651 
    652                     CaptureRequest.Builder req =
    653                             configureAndCreateRequestForSurface(scriptGraph.getInputSurface());
    654 
    655                     // Take burst shots with increasing sensitivity one after other.
    656                     for (int i = 0; i < NUM_STEPS; ++i) {
    657                         setManualCaptureRequest(req, sensitivities[i], EXPOSURE_TIME_NS);
    658                         captureSingleShotAndExecute(req.build(), scriptGraph);
    659                         float[] means = convertPixelYuvToRgb(scriptGraph.getOutputData());
    660                         rgbMeans.add(means);
    661 
    662                         if (VERBOSE) {
    663                             Log.v(TAG, "testParamSensitivity - captured image " + i +
    664                                     " with RGB means: " + Arrays.toString(means));
    665                         }
    666                     }
    667 
    668                     // Test that every consecutive image gets brighter.
    669                     for (int i = 0; i < rgbMeans.size() - 1; ++i) {
    670                         float[] curMeans = rgbMeans.get(i);
    671                         float[] nextMeans = rgbMeans.get(i+1);
    672 
    673                         float[] left = curMeans;
    674                         float[] right = nextMeans;
    675                         String leftString = Arrays.toString(left);
    676                         String rightString = Arrays.toString(right);
    677 
    678                         String msgHeader =
    679                                 String.format("Shot with sensitivity %d should not have higher " +
    680                                 "average means than shot with sensitivity %d",
    681                                 sensitivities[i], sensitivities[i+1]);
    682                         for (int m = 0; m < left.length; ++m) {
    683                             String msg = String.format(
    684                                     "%s: (%s should be less than or equal to %s; item index %d;"
    685                                     + " left = %s; right = %s)",
    686                                     msgHeader, left[m], right[m], m, leftString, rightString);
    687                             if (left[m] > right[m]) {
    688                                 Log.e(TAG, msg);
    689                             }
    690                         }
    691                     }
    692 
    693                     // Test the min-max diff and ratios are within expected thresholds
    694                     float[] lastMeans = rgbMeans.get(NUM_STEPS - 1);
    695                     float[] firstMeans = rgbMeans.get(/*location*/0);
    696                     for (int i = 0; i < RGB_CHANNELS; ++i) {
    697                         if (lastMeans[i] - firstMeans[i] <= THRESHOLD_MAX_MIN_DIFF) {
    698                             Log.w(TAG, String.format("Sensitivity max-min diff too small"
    699                                     + "(max=%f, min=%f)", lastMeans[i], firstMeans[i]));
    700                         }
    701                         if (lastMeans[i] / firstMeans[i] <= THRESHOLD_MAX_MIN_RATIO) {
    702                             Log.w(TAG, String.format("Sensitivity max-min ratio too small"
    703                                     + "(max=%f, min=%f)", lastMeans[i], firstMeans[i]));
    704                         }
    705                     }
    706                 }
    707             }
    708         });
    709 
    710     }
    711 
    712     /**
    713      * Common script graph for manual-capture based tests that determine the average pixel
    714      * values of a cropped sub-region.
    715      *
    716      * <p>Processing chain:
    717      *
    718      * <pre>
    719      * input:  YUV_420_888 surface
    720      * output: mean YUV value of a central section of the image,
    721      *         YUV 4:4:4 encoded as U8_3
    722      * steps:
    723      *      1) crop [0.45,0.45] - [0.55, 0.55]
    724      *      2) average columns
    725      *      3) average rows
    726      * </pre>
    727      * </p>
    728      */
    729     private static ScriptGraph createGraphForYuvCroppedMeans(final Size size) {
    730         ScriptGraph scriptGraph = ScriptGraph.create()
    731                 .configureInputWithSurface(size, YUV_420_888)
    732                 .configureScript(ScriptYuvCrop.class)
    733                     .set(ScriptYuvCrop.CROP_WINDOW,
    734                             new Patch(size, /*x*/0.45f, /*y*/0.45f, /*w*/0.1f, /*h*/0.1f).toRectF())
    735                     .buildScript()
    736                 .chainScript(ScriptYuvMeans2dTo1d.class)
    737                 .chainScript(ScriptYuvMeans1d.class)
    738                 // TODO: Make a script for YUV 444 -> RGB 888 conversion
    739                 .buildGraph();
    740         return scriptGraph;
    741     }
    742 
    743     /*
    744      * TODO: Refactor below code into separate classes and to not depend on AllocationTest
    745      * inner variables.
    746      *
    747      * TODO: add javadocs to below methods
    748      *
    749      * TODO: Figure out if there's some elegant way to compose these forEaches together, so that
    750      * the callers don't have to do a ton of nesting
    751      */
    752 
    753     interface CameraBlock {
    754         void run(CameraDevice camera) throws CameraAccessException;
    755     }
    756 
    757     class CameraIterable {
    758         public void forEachCamera(CameraBlock runnable)
    759                 throws CameraAccessException {
    760             forEachCamera(/*fullHwLevel*/false, runnable);
    761         }
    762 
    763         public void forEachCamera(boolean fullHwLevel, CameraBlock runnable)
    764                 throws CameraAccessException {
    765             assertNotNull("No camera manager", mCameraManager);
    766             assertNotNull("No camera IDs", mCameraIds);
    767 
    768             for (int i = 0; i < mCameraIds.length; i++) {
    769                 // Don't execute the runnable against non-FULL cameras if FULL is required
    770                 CameraCharacteristics properties =
    771                         mCameraManager.getCameraCharacteristics(mCameraIds[i]);
    772                 StaticMetadata staticInfo = new StaticMetadata(properties);
    773                 if (fullHwLevel && !staticInfo.isHardwareLevelAtLeastFull()) {
    774                     Log.i(TAG, String.format(
    775                             "Skipping this test for camera %s, needs FULL hw level",
    776                             mCameraIds[i]));
    777                     continue;
    778                 }
    779                 if (!staticInfo.isColorOutputSupported()) {
    780                     Log.i(TAG, String.format(
    781                         "Skipping this test for camera %s, does not support regular outputs",
    782                         mCameraIds[i]));
    783                     continue;
    784                 }
    785                 // Open camera and execute test
    786                 Log.i(TAG, "Testing Camera " + mCameraIds[i]);
    787                 try {
    788                     openDevice(mCameraIds[i]);
    789 
    790                     runnable.run(mCamera);
    791                 } finally {
    792                     closeDevice(mCameraIds[i]);
    793                 }
    794             }
    795         }
    796 
    797         private void openDevice(String cameraId) {
    798             if (mCamera != null) {
    799                 throw new IllegalStateException("Already have open camera device");
    800             }
    801             try {
    802                 mCamera = openCamera(
    803                     mCameraManager, cameraId, mCameraListener, mHandler);
    804             } catch (CameraAccessException e) {
    805                 fail("Fail to open camera synchronously, " + Log.getStackTraceString(e));
    806             } catch (BlockingOpenException e) {
    807                 fail("Fail to open camera asynchronously, " + Log.getStackTraceString(e));
    808             }
    809             mCameraListener.waitForState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
    810         }
    811 
    812         private void closeDevice(String cameraId) {
    813             if (mCamera != null) {
    814                 mCamera.close();
    815                 mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
    816                 mCamera = null;
    817             }
    818         }
    819     }
    820 
    821     interface SizeBlock {
    822         void run(Size size) throws CameraAccessException;
    823     }
    824 
    825     class SizeIterable {
    826         public void forEachSize(int format, SizeBlock runnable) throws CameraAccessException {
    827             assertNotNull("No camera opened", mCamera);
    828             assertNotNull("No camera manager", mCameraManager);
    829 
    830             CameraCharacteristics properties =
    831                     mCameraManager.getCameraCharacteristics(mCamera.getId());
    832 
    833             assertNotNull("Can't get camera properties!", properties);
    834 
    835             StreamConfigurationMap config =
    836                     properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    837             int[] availableOutputFormats = config.getOutputFormats();
    838             assertArrayNotEmpty(availableOutputFormats,
    839                     "availableOutputFormats should not be empty");
    840             Arrays.sort(availableOutputFormats);
    841             assertTrue("Can't find the format " + format + " in supported formats " +
    842                     Arrays.toString(availableOutputFormats),
    843                     Arrays.binarySearch(availableOutputFormats, format) >= 0);
    844 
    845             Size[] availableSizes = getSupportedSizeForFormat(format, mCamera.getId(),
    846                     mCameraManager);
    847             assertArrayNotEmpty(availableSizes, "availableSizes should not be empty");
    848 
    849             for (Size size : availableSizes) {
    850 
    851                 if (VERBOSE) {
    852                     Log.v(TAG, "Testing size " + size.toString() +
    853                             " for camera " + mCamera.getId());
    854                 }
    855                 runnable.run(size);
    856             }
    857         }
    858     }
    859 
    860     interface ResultBlock {
    861         void run(CaptureResult result) throws CameraAccessException;
    862     }
    863 
    864     class ResultIterable {
    865         public void forEachResultOnce(CaptureRequest request, ResultBlock block)
    866                 throws CameraAccessException {
    867             forEachResult(request, /*count*/1, /*repeating*/false, block);
    868         }
    869 
    870         public void forEachResultRepeating(CaptureRequest request, int count, ResultBlock block)
    871                 throws CameraAccessException {
    872             forEachResult(request, count, /*repeating*/true, block);
    873         }
    874 
    875         public void forEachResult(CaptureRequest request, int count, boolean repeating,
    876                 ResultBlock block) throws CameraAccessException {
    877 
    878             // TODO: start capture, i.e. configureOutputs
    879 
    880             SimpleCaptureCallback listener = new SimpleCaptureCallback();
    881 
    882             if (!repeating) {
    883                 for (int i = 0; i < count; ++i) {
    884                     mSession.capture(request, listener, mHandler);
    885                 }
    886             } else {
    887                 mSession.setRepeatingRequest(request, listener, mHandler);
    888             }
    889 
    890             // Assume that the device is already IDLE.
    891             mSessionListener.getStateWaiter().waitForState(BlockingSessionCallback.SESSION_ACTIVE,
    892                     CAMERA_ACTIVE_TIMEOUT_MS);
    893 
    894             for (int i = 0; i < count; ++i) {
    895                 if (VERBOSE) {
    896                     Log.v(TAG, String.format("Testing with result %d of %d for camera %s",
    897                             i, count, mCamera.getId()));
    898                 }
    899 
    900                 CaptureResult result = listener.getCaptureResult(CAPTURE_RESULT_TIMEOUT_MS);
    901                 block.run(result);
    902             }
    903 
    904             if (repeating) {
    905                 mSession.stopRepeating();
    906                 mSessionListener.getStateWaiter().waitForState(
    907                     BlockingSessionCallback.SESSION_READY, CAMERA_IDLE_TIMEOUT_MS);
    908             }
    909 
    910             // TODO: Make a Configure decorator or some such for configureOutputs
    911         }
    912     }
    913 }
    914