Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 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.hardware.camera2.cts.CameraTestUtils.*;
     20 
     21 import android.graphics.ImageFormat;
     22 import android.hardware.camera2.CameraDevice;
     23 import android.hardware.camera2.CaptureRequest;
     24 import android.hardware.camera2.CaptureResult;
     25 import android.hardware.camera2.CameraCharacteristics;
     26 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
     27 import android.hardware.camera2.params.StreamConfigurationMap;
     28 import android.media.Image;
     29 import android.util.Log;
     30 import android.util.Range;
     31 import android.util.Size;
     32 
     33 import java.util.List;
     34 import java.util.ArrayList;
     35 import java.util.Arrays;
     36 
     37 public class BurstCaptureTest extends Camera2SurfaceViewTestCase {
     38     private static final String TAG = "BurstCaptureTest";
     39     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     40     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     41 
     42     /**
     43      * Test YUV burst capture with full-AUTO control.
     44      * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
     45      */
     46     public void testYuvBurst() throws Exception {
     47         for (int i = 0; i < mCameraIds.length; i++) {
     48             try {
     49                 String id = mCameraIds[i];
     50                 Log.i(TAG, "Testing YUV Burst for camera " + id);
     51                 openDevice(id);
     52 
     53                 if (!mStaticInfo.isColorOutputSupported()) {
     54                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
     55                 }
     56                 if (!mStaticInfo.isAeLockSupported() || !mStaticInfo.isAwbLockSupported()) {
     57                     Log.i(TAG, "AE/AWB lock is not supported in camera " + id +
     58                             ". Skip the test");
     59                     continue;
     60                 }
     61 
     62                 if (mStaticInfo.isHardwareLevelLegacy()) {
     63                     Log.i(TAG, "Legacy camera doesn't report min frame duration" +
     64                             ". Skip the test");
     65                     continue;
     66                 }
     67 
     68                 yuvBurstTestByCamera(id);
     69             } finally {
     70                 closeDevice();
     71                 closeImageReader();
     72             }
     73         }
     74     }
     75 
     76     private void yuvBurstTestByCamera(String cameraId) throws Exception {
     77         // Parameters
     78         final int MAX_CONVERGENCE_FRAMES = 150; // 5 sec at 30fps
     79         final long MAX_PREVIEW_RESULT_TIMEOUT_MS = 1000;
     80         final int BURST_SIZE = 100;
     81         final float FRAME_DURATION_MARGIN_FRACTION = 0.1f;
     82 
     83         // Find a good preview size (bound to 1080p)
     84         final Size previewSize = mOrderedPreviewSizes.get(0);
     85 
     86         // Get maximum YUV_420_888 size
     87         final Size stillSize = getSortedSizesForFormat(
     88                 cameraId, mCameraManager, ImageFormat.YUV_420_888, /*bound*/null).get(0);
     89 
     90         // Find max pipeline depth and sync latency
     91         final int maxPipelineDepth = mStaticInfo.getCharacteristics().get(
     92             CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH);
     93         final int maxSyncLatency = mStaticInfo.getCharacteristics().get(
     94             CameraCharacteristics.SYNC_MAX_LATENCY);
     95 
     96         // Find minimum frame duration for full-res YUV_420_888
     97         StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
     98             CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
     99         final long minStillFrameDuration =
    100                 config.getOutputMinFrameDuration(ImageFormat.YUV_420_888, stillSize);
    101 
    102         // Add 0.05 here so Fps like 29.99 evaluated to 30
    103         int minBurstFps = (int) Math.floor(1e9 / minStillFrameDuration + 0.05f);
    104         boolean foundConstantMaxYUVRange = false;
    105         boolean foundYUVStreamingRange = false;
    106 
    107         // Find suitable target FPS range - as high as possible that covers the max YUV rate
    108         // Also verify that there's a good preview rate as well
    109         List<Range<Integer> > fpsRanges = Arrays.asList(
    110                 mStaticInfo.getAeAvailableTargetFpsRangesChecked());
    111         Range<Integer> targetRange = null;
    112         for (Range<Integer> fpsRange : fpsRanges) {
    113             if (fpsRange.getLower() == minBurstFps && fpsRange.getUpper() == minBurstFps) {
    114                 foundConstantMaxYUVRange = true;
    115                 targetRange = fpsRange;
    116             }
    117             if (fpsRange.getLower() <= 15 && fpsRange.getUpper() == minBurstFps) {
    118                 foundYUVStreamingRange = true;
    119             }
    120         }
    121 
    122         assertTrue(String.format("Cam %s: Target FPS range of (%d, %d) must be supported",
    123                 cameraId, minBurstFps, minBurstFps), foundConstantMaxYUVRange);
    124         assertTrue(String.format(
    125                 "Cam %s: Target FPS range of (x, %d) where x <= 15 must be supported",
    126                 cameraId, minBurstFps), foundYUVStreamingRange);
    127 
    128         Log.i(TAG, String.format("Selected frame rate range %d - %d for YUV burst",
    129                         targetRange.getLower(), targetRange.getUpper()));
    130 
    131         // Check if READ_SENSOR_SETTINGS is supported
    132         final boolean checkSensorSettings = mStaticInfo.isCapabilitySupported(
    133             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS);
    134 
    135         // Configure basic preview and burst settings
    136 
    137         CaptureRequest.Builder previewBuilder =
    138             mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    139         CaptureRequest.Builder burstBuilder =
    140             mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    141 
    142         previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
    143                 targetRange);
    144         burstBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
    145                 targetRange);
    146         burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
    147         burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
    148 
    149         // Create session and start up preview
    150 
    151         SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
    152         ImageDropperListener imageDropper = new ImageDropperListener();
    153 
    154         prepareCaptureAndStartPreview(
    155             previewBuilder, burstBuilder,
    156             previewSize, stillSize,
    157             ImageFormat.YUV_420_888, resultListener,
    158             /*maxNumImages*/ 3, imageDropper);
    159 
    160         // Create burst
    161 
    162         List<CaptureRequest> burst = new ArrayList<>();
    163         for (int i = 0; i < BURST_SIZE; i++) {
    164             burst.add(burstBuilder.build());
    165         }
    166 
    167         // Converge AE/AWB
    168 
    169         int frameCount = 0;
    170         while (true) {
    171             CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
    172             int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
    173             int awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
    174 
    175             if (DEBUG) {
    176                 Log.d(TAG, "aeState: " + aeState + ". awbState: " + awbState);
    177             }
    178 
    179             if ((aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED ||
    180                     aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) &&
    181                     awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED) {
    182                 break;
    183             }
    184             frameCount++;
    185             assertTrue(String.format("Cam %s: Can not converge AE and AWB within %d frames",
    186                     cameraId, MAX_CONVERGENCE_FRAMES),
    187                 frameCount < MAX_CONVERGENCE_FRAMES);
    188         }
    189 
    190         // Lock AF if there's a focuser
    191 
    192         if (mStaticInfo.hasFocuser()) {
    193             previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
    194                 CaptureRequest.CONTROL_AF_TRIGGER_START);
    195             mSession.capture(previewBuilder.build(), resultListener, mHandler);
    196             previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
    197                 CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
    198 
    199             frameCount = 0;
    200             while (true) {
    201                 CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
    202                 int afState = result.get(CaptureResult.CONTROL_AF_STATE);
    203 
    204                 if (DEBUG) {
    205                     Log.d(TAG, "afState: " + afState);
    206                 }
    207 
    208                 if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
    209                     afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
    210                     break;
    211                 }
    212                 frameCount++;
    213                 assertTrue(String.format("Cam %s: Cannot lock AF within %d frames", cameraId,
    214                         MAX_CONVERGENCE_FRAMES),
    215                     frameCount < MAX_CONVERGENCE_FRAMES);
    216             }
    217         }
    218 
    219         // Lock AE/AWB
    220 
    221         previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
    222         previewBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
    223 
    224         CaptureRequest lockedRequest = previewBuilder.build();
    225         mSession.setRepeatingRequest(lockedRequest, resultListener, mHandler);
    226 
    227         // Wait for first result with locking
    228         resultListener.drain();
    229         CaptureResult lockedResult =
    230                 resultListener.getCaptureResultForRequest(lockedRequest, maxPipelineDepth);
    231 
    232         int pipelineDepth = lockedResult.get(CaptureResult.REQUEST_PIPELINE_DEPTH);
    233 
    234         // Then start waiting on results to get the first result that should be synced
    235         // up, and also fire the burst as soon as possible
    236 
    237         if (maxSyncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL) {
    238             // The locked result we have is already synchronized so start the burst
    239             mSession.captureBurst(burst, resultListener, mHandler);
    240         } else {
    241             // Need to get a synchronized result, and may need to start burst later to
    242             // be synchronized correctly
    243 
    244             boolean burstSent = false;
    245 
    246             // Calculate how many requests we need to still send down to camera before we
    247             // know the settings have settled for the burst
    248 
    249             int numFramesWaited = maxSyncLatency;
    250             if (numFramesWaited == CameraCharacteristics.SYNC_MAX_LATENCY_UNKNOWN) {
    251                 numFramesWaited = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY;
    252             }
    253 
    254             int requestsNeededToSync = numFramesWaited - pipelineDepth;
    255             for (int i = 0; i < numFramesWaited; i++) {
    256                 if (!burstSent && requestsNeededToSync <= 0) {
    257                     mSession.captureBurst(burst, resultListener, mHandler);
    258                     burstSent = true;
    259                 }
    260                 lockedResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
    261                 requestsNeededToSync--;
    262             }
    263 
    264             assertTrue("Cam " + cameraId + ": Burst failed to fire!", burstSent);
    265         }
    266 
    267         // Read in locked settings if supported
    268 
    269         long burstExposure = 0;
    270         long burstFrameDuration = 0;
    271         int burstSensitivity = 0;
    272         if (checkSensorSettings) {
    273             burstExposure = lockedResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
    274             burstFrameDuration = lockedResult.get(CaptureResult.SENSOR_FRAME_DURATION);
    275             burstSensitivity = lockedResult.get(CaptureResult.SENSOR_SENSITIVITY);
    276 
    277             assertTrue(String.format("Cam %s: Frame duration %d ns too short compared to " +
    278                     "exposure time %d ns", cameraId, burstFrameDuration, burstExposure),
    279                 burstFrameDuration >= burstExposure);
    280 
    281             assertTrue(String.format("Cam %s: Exposure time is not valid: %d",
    282                     cameraId, burstExposure),
    283                 burstExposure > 0);
    284             assertTrue(String.format("Cam %s: Frame duration is not valid: %d",
    285                     cameraId, burstFrameDuration),
    286                 burstFrameDuration > 0);
    287             assertTrue(String.format("Cam %s: Sensitivity is not valid: %d",
    288                     cameraId, burstSensitivity),
    289                 burstSensitivity > 0);
    290         }
    291 
    292         // Process burst results
    293         int burstIndex = 0;
    294         CaptureResult burstResult =
    295                 resultListener.getCaptureResultForRequest(burst.get(burstIndex),
    296                     maxPipelineDepth + 1);
    297         long prevTimestamp = -1;
    298         final long frameDurationBound = (long)
    299                 (minStillFrameDuration * (1 + FRAME_DURATION_MARGIN_FRACTION) );
    300 
    301         List<Long> frameDurations = new ArrayList<>();
    302 
    303         while(true) {
    304             // Verify the result
    305             assertTrue("Cam " + cameraId + ": Result doesn't match expected request",
    306                     burstResult.getRequest() == burst.get(burstIndex));
    307 
    308             // Verify locked settings
    309             if (checkSensorSettings) {
    310                 long exposure = burstResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
    311                 int sensitivity = burstResult.get(CaptureResult.SENSOR_SENSITIVITY);
    312                 assertTrue("Cam " + cameraId + ": Exposure not locked!",
    313                     exposure == burstExposure);
    314                 assertTrue("Cam " + cameraId + ": Sensitivity not locked!",
    315                     sensitivity == burstSensitivity);
    316             }
    317 
    318             // Collect inter-frame durations
    319             long timestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP);
    320             if (prevTimestamp != -1) {
    321                 long frameDuration = timestamp - prevTimestamp;
    322                 frameDurations.add(frameDuration);
    323                 if (DEBUG) {
    324                     Log.i(TAG, String.format("Frame %03d    Duration %.2f ms", burstIndex,
    325                             frameDuration/1e6));
    326                 }
    327             }
    328             prevTimestamp = timestamp;
    329 
    330             // Get next result
    331             burstIndex++;
    332             if (burstIndex == BURST_SIZE) break;
    333             burstResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
    334         }
    335 
    336         // Verify inter-frame durations
    337 
    338         long meanFrameSum = 0;
    339         for (Long duration : frameDurations) {
    340             meanFrameSum += duration;
    341         }
    342         float meanFrameDuration = (float) meanFrameSum / frameDurations.size();
    343 
    344         float stddevSum = 0;
    345         for (Long duration : frameDurations) {
    346             stddevSum += (duration - meanFrameDuration) * (duration - meanFrameDuration);
    347         }
    348         float stddevFrameDuration = (float)
    349                 Math.sqrt(1.f / (frameDurations.size() - 1 ) * stddevSum);
    350 
    351         Log.i(TAG, String.format("Cam %s: Burst frame duration mean: %.1f, stddev: %.1f", cameraId,
    352                 meanFrameDuration, stddevFrameDuration));
    353 
    354         assertTrue(
    355             String.format("Cam %s: Burst frame duration mean %.1f ns is larger than acceptable, " +
    356                 "expecting below %d ns, allowing below %d", cameraId,
    357                 meanFrameDuration, minStillFrameDuration, frameDurationBound),
    358             meanFrameDuration <= frameDurationBound);
    359 
    360         // Calculate upper 97.5% bound (assuming durations are normally distributed...)
    361         float limit95FrameDuration = meanFrameDuration + 2 * stddevFrameDuration;
    362 
    363         // Don't enforce this yet, but warn
    364         if (limit95FrameDuration > frameDurationBound) {
    365             Log.w(TAG,
    366                 String.format("Cam %s: Standard deviation is too large compared to limit: " +
    367                     "mean: %.1f ms, stddev: %.1f ms: 95%% bound: %f ms", cameraId,
    368                     meanFrameDuration/1e6, stddevFrameDuration/1e6,
    369                     limit95FrameDuration/1e6));
    370         }
    371     }
    372 }
    373