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      */
testYuvBurst()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 
yuvBurstTestByCamera(String cameraId)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         // Find suitable target FPS range - as high as possible
103         List<Range<Integer> > fpsRanges = Arrays.asList(
104                 mStaticInfo.getAeAvailableTargetFpsRangesChecked());
105         Range<Integer> targetRange = mStaticInfo.getAeMaxTargetFpsRange();
106         // Add 0.05 here so Fps like 29.99 evaluated to 30
107         int minBurstFps = (int) Math.floor(1e9 / minStillFrameDuration + 0.05f);
108         boolean foundConstantMaxYUVRange = false;
109         boolean foundYUVStreamingRange = false;
110 
111         for (Range<Integer> fpsRange : fpsRanges) {
112             if (fpsRange.getLower() == minBurstFps && fpsRange.getUpper() == minBurstFps) {
113                 foundConstantMaxYUVRange = true;
114             }
115             if (fpsRange.getLower() <= 15 && fpsRange.getUpper() == minBurstFps) {
116                 foundYUVStreamingRange = true;
117             }
118         }
119 
120         assertTrue(String.format("Cam %s: Target FPS range of (%d, %d) must be supported",
121                 cameraId, minBurstFps, minBurstFps), foundConstantMaxYUVRange);
122         assertTrue(String.format(
123                 "Cam %s: Target FPS range of (x, %d) where x <= 15 must be supported",
124                 cameraId, minBurstFps), foundYUVStreamingRange);
125         assertTrue(String.format("Cam %s: No target FPS range found with minimum FPS above " +
126                         " 1/minFrameDuration (%d fps, duration %d ns) for full-resolution YUV",
127                         cameraId, minBurstFps, minStillFrameDuration),
128                 targetRange.getLower() >= minBurstFps);
129 
130         Log.i(TAG, String.format("Selected frame rate range %d - %d for YUV burst",
131                         targetRange.getLower(), targetRange.getUpper()));
132 
133         // Check if READ_SENSOR_SETTINGS is supported
134         final boolean checkSensorSettings = mStaticInfo.isCapabilitySupported(
135             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS);
136 
137         // Configure basic preview and burst settings
138 
139         CaptureRequest.Builder previewBuilder =
140             mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
141         CaptureRequest.Builder burstBuilder =
142             mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
143 
144         previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
145                 targetRange);
146         burstBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
147                 targetRange);
148         burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
149         burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
150 
151         // Create session and start up preview
152 
153         SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
154         ImageDropperListener imageDropper = new ImageDropperListener();
155 
156         prepareCaptureAndStartPreview(
157             previewBuilder, burstBuilder,
158             previewSize, stillSize,
159             ImageFormat.YUV_420_888, resultListener,
160             /*maxNumImages*/ 3, imageDropper);
161 
162         // Create burst
163 
164         List<CaptureRequest> burst = new ArrayList<>();
165         for (int i = 0; i < BURST_SIZE; i++) {
166             burst.add(burstBuilder.build());
167         }
168 
169         // Converge AE/AWB
170 
171         int frameCount = 0;
172         while (true) {
173             CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
174             int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
175             int awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
176 
177             if (DEBUG) {
178                 Log.d(TAG, "aeState: " + aeState + ". awbState: " + awbState);
179             }
180 
181             if ((aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED ||
182                     aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) &&
183                     awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED) {
184                 break;
185             }
186             frameCount++;
187             assertTrue(String.format("Cam %s: Can not converge AE and AWB within %d frames",
188                     cameraId, MAX_CONVERGENCE_FRAMES),
189                 frameCount < MAX_CONVERGENCE_FRAMES);
190         }
191 
192         // Lock AF if there's a focuser
193 
194         if (mStaticInfo.hasFocuser()) {
195             previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
196                 CaptureRequest.CONTROL_AF_TRIGGER_START);
197             mSession.capture(previewBuilder.build(), resultListener, mHandler);
198             previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
199                 CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
200 
201             frameCount = 0;
202             while (true) {
203                 CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
204                 int afState = result.get(CaptureResult.CONTROL_AF_STATE);
205 
206                 if (DEBUG) {
207                     Log.d(TAG, "afState: " + afState);
208                 }
209 
210                 if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
211                     afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
212                     break;
213                 }
214                 frameCount++;
215                 assertTrue(String.format("Cam %s: Cannot lock AF within %d frames", cameraId,
216                         MAX_CONVERGENCE_FRAMES),
217                     frameCount < MAX_CONVERGENCE_FRAMES);
218             }
219         }
220 
221         // Lock AE/AWB
222 
223         previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
224         previewBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
225 
226         CaptureRequest lockedRequest = previewBuilder.build();
227         mSession.setRepeatingRequest(lockedRequest, resultListener, mHandler);
228 
229         // Wait for first result with locking
230         resultListener.drain();
231         CaptureResult lockedResult =
232                 resultListener.getCaptureResultForRequest(lockedRequest, maxPipelineDepth);
233 
234         int pipelineDepth = lockedResult.get(CaptureResult.REQUEST_PIPELINE_DEPTH);
235 
236         // Then start waiting on results to get the first result that should be synced
237         // up, and also fire the burst as soon as possible
238 
239         if (maxSyncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL) {
240             // The locked result we have is already synchronized so start the burst
241             mSession.captureBurst(burst, resultListener, mHandler);
242         } else {
243             // Need to get a synchronized result, and may need to start burst later to
244             // be synchronized correctly
245 
246             boolean burstSent = false;
247 
248             // Calculate how many requests we need to still send down to camera before we
249             // know the settings have settled for the burst
250 
251             int numFramesWaited = maxSyncLatency;
252             if (numFramesWaited == CameraCharacteristics.SYNC_MAX_LATENCY_UNKNOWN) {
253                 numFramesWaited = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY;
254             }
255 
256             int requestsNeededToSync = numFramesWaited - pipelineDepth;
257             for (int i = 0; i < numFramesWaited; i++) {
258                 if (!burstSent && requestsNeededToSync <= 0) {
259                     mSession.captureBurst(burst, resultListener, mHandler);
260                     burstSent = true;
261                 }
262                 lockedResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
263                 requestsNeededToSync--;
264             }
265 
266             assertTrue("Cam " + cameraId + ": Burst failed to fire!", burstSent);
267         }
268 
269         // Read in locked settings if supported
270 
271         long burstExposure = 0;
272         long burstFrameDuration = 0;
273         int burstSensitivity = 0;
274         if (checkSensorSettings) {
275             burstExposure = lockedResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
276             burstFrameDuration = lockedResult.get(CaptureResult.SENSOR_FRAME_DURATION);
277             burstSensitivity = lockedResult.get(CaptureResult.SENSOR_SENSITIVITY);
278 
279             assertTrue(String.format("Cam %s: Frame duration %d ns too short compared to " +
280                     "exposure time %d ns", cameraId, burstFrameDuration, burstExposure),
281                 burstFrameDuration >= burstExposure);
282 
283             assertTrue(String.format("Cam %s: Exposure time is not valid: %d",
284                     cameraId, burstExposure),
285                 burstExposure > 0);
286             assertTrue(String.format("Cam %s: Frame duration is not valid: %d",
287                     cameraId, burstFrameDuration),
288                 burstFrameDuration > 0);
289             assertTrue(String.format("Cam %s: Sensitivity is not valid: %d",
290                     cameraId, burstSensitivity),
291                 burstSensitivity > 0);
292         }
293 
294         // Process burst results
295         int burstIndex = 0;
296         CaptureResult burstResult =
297                 resultListener.getCaptureResultForRequest(burst.get(burstIndex),
298                     maxPipelineDepth + 1);
299         long prevTimestamp = -1;
300         final long frameDurationBound = (long)
301                 (minStillFrameDuration * (1 + FRAME_DURATION_MARGIN_FRACTION) );
302 
303         List<Long> frameDurations = new ArrayList<>();
304 
305         while(true) {
306             // Verify the result
307             assertTrue("Cam " + cameraId + ": Result doesn't match expected request",
308                     burstResult.getRequest() == burst.get(burstIndex));
309 
310             // Verify locked settings
311             if (checkSensorSettings) {
312                 long exposure = burstResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
313                 int sensitivity = burstResult.get(CaptureResult.SENSOR_SENSITIVITY);
314                 assertTrue("Cam " + cameraId + ": Exposure not locked!",
315                     exposure == burstExposure);
316                 assertTrue("Cam " + cameraId + ": Sensitivity not locked!",
317                     sensitivity == burstSensitivity);
318             }
319 
320             // Collect inter-frame durations
321             long timestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP);
322             if (prevTimestamp != -1) {
323                 long frameDuration = timestamp - prevTimestamp;
324                 frameDurations.add(frameDuration);
325                 if (DEBUG) {
326                     Log.i(TAG, String.format("Frame %03d    Duration %.2f ms", burstIndex,
327                             frameDuration/1e6));
328                 }
329             }
330             prevTimestamp = timestamp;
331 
332             // Get next result
333             burstIndex++;
334             if (burstIndex == BURST_SIZE) break;
335             burstResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
336         }
337 
338         // Verify inter-frame durations
339 
340         long meanFrameSum = 0;
341         for (Long duration : frameDurations) {
342             meanFrameSum += duration;
343         }
344         float meanFrameDuration = (float) meanFrameSum / frameDurations.size();
345 
346         float stddevSum = 0;
347         for (Long duration : frameDurations) {
348             stddevSum += (duration - meanFrameDuration) * (duration - meanFrameDuration);
349         }
350         float stddevFrameDuration = (float)
351                 Math.sqrt(1.f / (frameDurations.size() - 1 ) * stddevSum);
352 
353         Log.i(TAG, String.format("Cam %s: Burst frame duration mean: %.1f, stddev: %.1f", cameraId,
354                 meanFrameDuration, stddevFrameDuration));
355 
356         assertTrue(
357             String.format("Cam %s: Burst frame duration mean %.1f ns is larger than acceptable, " +
358                 "expecting below %d ns, allowing below %d", cameraId,
359                 meanFrameDuration, minStillFrameDuration, frameDurationBound),
360             meanFrameDuration <= frameDurationBound);
361 
362         // Calculate upper 97.5% bound (assuming durations are normally distributed...)
363         float limit95FrameDuration = meanFrameDuration + 2 * stddevFrameDuration;
364 
365         // Don't enforce this yet, but warn
366         if (limit95FrameDuration > frameDurationBound) {
367             Log.w(TAG,
368                 String.format("Cam %s: Standard deviation is too large compared to limit: " +
369                     "mean: %.1f ms, stddev: %.1f ms: 95%% bound: %f ms", cameraId,
370                     meanFrameDuration/1e6, stddevFrameDuration/1e6,
371                     limit95FrameDuration/1e6));
372         }
373     }
374 }
375