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.CameraMetadata;
27 import android.hardware.camera2.cts.helpers.StaticMetadata;
28 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
29 import android.hardware.camera2.params.Capability;
30 import android.hardware.camera2.params.StreamConfigurationMap;
31 import android.media.Image;
32 import android.util.Log;
33 import android.util.Range;
34 import android.util.Size;
35 
36 import java.util.List;
37 import java.util.ArrayList;
38 
39 import org.junit.runner.RunWith;
40 import org.junit.runners.Parameterized;
41 import org.junit.Test;
42 
43 @RunWith(Parameterized.class)
44 public class BurstCaptureTest extends Camera2SurfaceViewTestCase {
45     private static final String TAG = "BurstCaptureTest";
46     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
47     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
48 
49     /**
50      * Test YUV burst capture with full-AUTO control.
51      * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
52      */
53     @Test
testYuvBurst()54     public void testYuvBurst() throws Exception {
55         final int YUV_BURST_SIZE = 100;
56         testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/,
57                 false/*testStillBokeh*/);
58     }
59 
60     /**
61      * Test JPEG burst capture with full-AUTO control.
62      *
63      * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
64      * Compared to testYuvBurst, this test uses STILL_CAPTURE intent, and exercises path where
65      * enableZsl is enabled.
66      */
67     @Test
testJpegBurst()68     public void testJpegBurst() throws Exception {
69         final int JPEG_BURST_SIZE = 10;
70         testBurst(ImageFormat.JPEG, JPEG_BURST_SIZE, false/*checkFrameRate*/,
71                 false/*testStillBokeh*/);
72     }
73 
74     /**
75      * Test YUV burst capture with full-AUTO control and STILL_CAPTURE bokeh mode.
76      * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
77      */
78     @Test
testYuvBurstWithStillBokeh()79     public void testYuvBurstWithStillBokeh() throws Exception {
80         final int YUV_BURST_SIZE = 100;
81         testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/,
82                 true/*testStillBokeh*/);
83     }
84 
testBurst(int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh)85     private void testBurst(int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh)
86             throws Exception {
87         String[] cameraIdsUnderTest = getCameraIdsUnderTest();
88         for (int i = 0; i < cameraIdsUnderTest.length; i++) {
89             try {
90                 String id = cameraIdsUnderTest[i];
91 
92                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
93                 if (!staticInfo.isColorOutputSupported()) {
94                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
95                 }
96                 if (!staticInfo.isAeLockSupported() || !staticInfo.isAwbLockSupported()) {
97                     Log.i(TAG, "AE/AWB lock is not supported in camera " + id +
98                             ". Skip the test");
99                     continue;
100                 }
101 
102                 if (staticInfo.isHardwareLevelLegacy()) {
103                     Log.i(TAG, "Legacy camera doesn't report min frame duration" +
104                             ". Skip the test");
105                     continue;
106                 }
107 
108                 Capability[] extendedSceneModeCaps =
109                         staticInfo.getAvailableExtendedSceneModeCapsChecked();
110                 boolean supportStillBokeh = false;
111                 for (Capability cap : extendedSceneModeCaps) {
112                     if (cap.getMode() ==
113                             CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE) {
114                         supportStillBokeh = true;
115                         break;
116                     }
117                 }
118                 if (testStillBokeh && !supportStillBokeh) {
119                     Log.v(TAG, "Device doesn't support STILL_CAPTURE bokeh. Skip the test");
120                     continue;
121                 }
122 
123                 openDevice(id);
124                 burstTestByCamera(id, fmt, burstSize, checkFrameRate, testStillBokeh);
125             } finally {
126                 closeDevice();
127                 closeImageReader();
128             }
129         }
130     }
131 
burstTestByCamera(String cameraId, int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh)132     private void burstTestByCamera(String cameraId, int fmt, int burstSize,
133             boolean checkFrameRate, boolean testStillBokeh) throws Exception {
134         // Parameters
135         final int MAX_CONVERGENCE_FRAMES = 150; // 5 sec at 30fps
136         final long MAX_PREVIEW_RESULT_TIMEOUT_MS = 2000;
137         final float FRAME_DURATION_MARGIN_FRACTION = 0.1f;
138 
139         // Find a good preview size (bound to 1080p)
140         final Size previewSize = mOrderedPreviewSizes.get(0);
141 
142         // Get maximum size for fmt
143         final Size stillSize = getSortedSizesForFormat(
144                 cameraId, mCameraManager, fmt, /*bound*/null).get(0);
145 
146         // Find max pipeline depth and sync latency
147         final int maxPipelineDepth = mStaticInfo.getCharacteristics().get(
148             CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH);
149         final int maxSyncLatency = mStaticInfo.getCharacteristics().get(
150             CameraCharacteristics.SYNC_MAX_LATENCY);
151 
152         // Find minimum frame duration for full-res resolution
153         StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
154             CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
155         final long minStillFrameDuration =
156                 config.getOutputMinFrameDuration(fmt, stillSize);
157 
158 
159         Range<Integer> targetRange = getSuitableFpsRangeForDuration(cameraId, minStillFrameDuration);
160 
161         Log.i(TAG, String.format("Selected frame rate range %d - %d for YUV burst",
162                         targetRange.getLower(), targetRange.getUpper()));
163 
164         // Check if READ_SENSOR_SETTINGS is supported
165         final boolean checkSensorSettings = mStaticInfo.isCapabilitySupported(
166             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS);
167 
168         // Configure basic preview and burst settings
169 
170         CaptureRequest.Builder previewBuilder =
171             mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
172         int burstTemplate = (fmt == ImageFormat.JPEG) ?
173                 CameraDevice.TEMPLATE_STILL_CAPTURE : CameraDevice.TEMPLATE_PREVIEW;
174         CaptureRequest.Builder burstBuilder = mCamera.createCaptureRequest(burstTemplate);
175         Boolean enableZsl = burstBuilder.get(CaptureRequest.CONTROL_ENABLE_ZSL);
176         boolean zslStillEnabled = enableZsl != null && enableZsl &&
177                 burstTemplate == CameraDevice.TEMPLATE_STILL_CAPTURE;
178 
179         previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
180                 targetRange);
181         burstBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
182                 targetRange);
183         burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
184         burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
185         if (testStillBokeh) {
186             previewBuilder.set(CaptureRequest.CONTROL_EXTENDED_SCENE_MODE,
187                     CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE);
188             burstBuilder.set(CaptureRequest.CONTROL_EXTENDED_SCENE_MODE,
189                     CameraMetadata.CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE);
190         }
191 
192         // Create session and start up preview
193 
194         SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
195         SimpleCaptureCallback burstResultListener = new SimpleCaptureCallback();
196         ImageDropperListener imageDropper = new ImageDropperListener();
197 
198         prepareCaptureAndStartPreview(
199             previewBuilder, burstBuilder,
200             previewSize, stillSize,
201             fmt, resultListener,
202             /*maxNumImages*/ 3, imageDropper);
203 
204         // Create burst
205 
206         List<CaptureRequest> burst = new ArrayList<>();
207         for (int i = 0; i < burstSize; i++) {
208             burst.add(burstBuilder.build());
209         }
210 
211         // Converge AE/AWB
212 
213         int frameCount = 0;
214         while (true) {
215             CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
216             int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
217             int awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
218 
219             if (DEBUG) {
220                 Log.d(TAG, "aeState: " + aeState + ". awbState: " + awbState);
221             }
222 
223             if ((aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED ||
224                     aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) &&
225                     awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED) {
226                 break;
227             }
228             frameCount++;
229             assertTrue(String.format("Cam %s: Can not converge AE and AWB within %d frames",
230                     cameraId, MAX_CONVERGENCE_FRAMES),
231                 frameCount < MAX_CONVERGENCE_FRAMES);
232         }
233 
234         // Lock AF if there's a focuser
235 
236         if (mStaticInfo.hasFocuser()) {
237             previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
238                 CaptureRequest.CONTROL_AF_TRIGGER_START);
239             mSession.capture(previewBuilder.build(), resultListener, mHandler);
240             previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
241                 CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
242 
243             frameCount = 0;
244             while (true) {
245                 CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
246                 int afState = result.get(CaptureResult.CONTROL_AF_STATE);
247 
248                 if (DEBUG) {
249                     Log.d(TAG, "afState: " + afState);
250                 }
251 
252                 if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
253                     afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
254                     break;
255                 }
256                 frameCount++;
257                 assertTrue(String.format("Cam %s: Cannot lock AF within %d frames", cameraId,
258                         MAX_CONVERGENCE_FRAMES),
259                     frameCount < MAX_CONVERGENCE_FRAMES);
260             }
261         }
262 
263         // Lock AE/AWB
264 
265         previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
266         previewBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
267 
268         CaptureRequest lockedRequest = previewBuilder.build();
269         mSession.setRepeatingRequest(lockedRequest, resultListener, mHandler);
270 
271         // Wait for first result with locking
272         resultListener.drain();
273         // Add 1 extra frame to wait due to earlier repeating request could introduce
274         // 1 more frame delay.
275         CaptureResult lockedResult =
276                 resultListener.getCaptureResultForRequest(lockedRequest, maxPipelineDepth + 1);
277 
278         int pipelineDepth = lockedResult.get(CaptureResult.REQUEST_PIPELINE_DEPTH);
279 
280         // Then start waiting on results to get the first result that should be synced
281         // up, and also fire the burst as soon as possible
282 
283         if (maxSyncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL) {
284             // The locked result we have is already synchronized so start the burst
285             mSession.captureBurst(burst, burstResultListener, mHandler);
286         } else {
287             // Need to get a synchronized result, and may need to start burst later to
288             // be synchronized correctly
289 
290             boolean burstSent = false;
291 
292             // Calculate how many requests we need to still send down to camera before we
293             // know the settings have settled for the burst
294 
295             int numFramesWaited = maxSyncLatency;
296             if (numFramesWaited == CameraCharacteristics.SYNC_MAX_LATENCY_UNKNOWN) {
297                 numFramesWaited = NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY;
298             }
299 
300             int requestsNeededToSync = numFramesWaited - pipelineDepth;
301             for (int i = 0; i < numFramesWaited; i++) {
302                 if (!burstSent && requestsNeededToSync <= 0) {
303                     mSession.captureBurst(burst, burstResultListener, mHandler);
304                     burstSent = true;
305                 }
306                 lockedResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
307                 requestsNeededToSync--;
308             }
309 
310             assertTrue("Cam " + cameraId + ": Burst failed to fire!", burstSent);
311         }
312 
313         // Read in locked settings if supported
314 
315         long burstExposure = 0;
316         long burstFrameDuration = 0;
317         int burstSensitivity = 0;
318         if (checkSensorSettings) {
319             burstExposure = lockedResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
320             burstFrameDuration = lockedResult.get(CaptureResult.SENSOR_FRAME_DURATION);
321             burstSensitivity = lockedResult.get(CaptureResult.SENSOR_SENSITIVITY);
322 
323             assertTrue(String.format("Cam %s: Frame duration %d ns too short compared to " +
324                     "exposure time %d ns", cameraId, burstFrameDuration, burstExposure),
325                 burstFrameDuration >= burstExposure);
326 
327             assertTrue(String.format("Cam %s: Exposure time is not valid: %d",
328                     cameraId, burstExposure),
329                 burstExposure > 0);
330             assertTrue(String.format("Cam %s: Frame duration is not valid: %d",
331                     cameraId, burstFrameDuration),
332                 burstFrameDuration > 0);
333             assertTrue(String.format("Cam %s: Sensitivity is not valid: %d",
334                     cameraId, burstSensitivity),
335                 burstSensitivity > 0);
336         }
337 
338         // Process burst results
339         int burstIndex = 0;
340         CaptureResult burstResult =
341                 burstResultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
342         long prevTimestamp = -1;
343         final long frameDurationBound = (long)
344                 (minStillFrameDuration * (1 + FRAME_DURATION_MARGIN_FRACTION) );
345 
346         long burstStartTimestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP);
347         long burstEndTimeStamp = 0;
348 
349         List<Long> frameDurations = new ArrayList<>();
350 
351         while(true) {
352             // Verify the result
353             assertTrue("Cam " + cameraId + ": Result doesn't match expected request",
354                     burstResult.getRequest() == burst.get(burstIndex));
355 
356             // Verify locked settings
357             if (checkSensorSettings) {
358                 long exposure = burstResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
359                 int sensitivity = burstResult.get(CaptureResult.SENSOR_SENSITIVITY);
360                 assertTrue("Cam " + cameraId + ": Exposure not locked!",
361                     exposure == burstExposure);
362                 assertTrue("Cam " + cameraId + ": Sensitivity not locked!",
363                     sensitivity == burstSensitivity);
364             }
365 
366             // Collect inter-frame durations
367             long timestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP);
368             if (prevTimestamp != -1) {
369                 long frameDuration = timestamp - prevTimestamp;
370                 frameDurations.add(frameDuration);
371                 if (DEBUG) {
372                     Log.i(TAG, String.format("Frame %03d    Duration %.2f ms", burstIndex,
373                             frameDuration/1e6));
374                 }
375             }
376             prevTimestamp = timestamp;
377 
378             // Get next result
379             burstIndex++;
380             if (burstIndex == burstSize) {
381                 burstEndTimeStamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP);
382                 break;
383             }
384             burstResult = burstResultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
385         }
386 
387         // Verify no preview frames interleaved in burst results
388         while (true) {
389             CaptureResult previewResult =
390                     resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
391             long previewTimestamp = previewResult.get(CaptureResult.SENSOR_TIMESTAMP);
392             if (!zslStillEnabled && previewTimestamp >= burstStartTimestamp
393                     && previewTimestamp <= burstEndTimeStamp) {
394                 fail("Preview frame is interleaved with burst frames! Preview timestamp:" +
395                         previewTimestamp + ", burst [" + burstStartTimestamp + ", " +
396                         burstEndTimeStamp + "]");
397             } else if (previewTimestamp > burstEndTimeStamp) {
398                 break;
399             }
400         }
401 
402         // Verify inter-frame durations
403         if (checkFrameRate) {
404             long meanFrameSum = 0;
405             for (Long duration : frameDurations) {
406                 meanFrameSum += duration;
407             }
408             float meanFrameDuration = (float) meanFrameSum / frameDurations.size();
409 
410             float stddevSum = 0;
411             for (Long duration : frameDurations) {
412                 stddevSum += (duration - meanFrameDuration) * (duration - meanFrameDuration);
413             }
414             float stddevFrameDuration = (float)
415                     Math.sqrt(1.f / (frameDurations.size() - 1 ) * stddevSum);
416 
417             Log.i(TAG, String.format("Cam %s: Burst frame duration mean: %.1f, stddev: %.1f",
418                     cameraId, meanFrameDuration, stddevFrameDuration));
419 
420             assertTrue(
421                 String.format("Cam %s: Burst frame duration mean %.1f ns is larger than " +
422                     "acceptable, expecting below %d ns, allowing below %d", cameraId,
423                     meanFrameDuration, minStillFrameDuration, frameDurationBound),
424                 meanFrameDuration <= frameDurationBound);
425 
426             // Calculate upper 97.5% bound (assuming durations are normally distributed...)
427             float limit95FrameDuration = meanFrameDuration + 2 * stddevFrameDuration;
428 
429             // Don't enforce this yet, but warn
430             if (limit95FrameDuration > frameDurationBound) {
431                 Log.w(TAG,
432                     String.format("Cam %s: Standard deviation is too large compared to limit: " +
433                         "mean: %.1f ms, stddev: %.1f ms: 95%% bound: %f ms", cameraId,
434                         meanFrameDuration/1e6, stddevFrameDuration/1e6,
435                         limit95FrameDuration/1e6));
436             }
437         }
438     }
439 }
440