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.view.Surface;
23 import android.hardware.camera2.CameraCaptureSession;
24 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
25 import android.hardware.camera2.CameraDevice;
26 import android.hardware.camera2.CaptureFailure;
27 import android.hardware.camera2.CaptureRequest;
28 import android.hardware.camera2.CaptureResult;
29 import android.hardware.camera2.TotalCaptureResult;
30 import android.util.Size;
31 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
32 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
33 import android.util.Log;
34 import android.util.Pair;
35 import android.util.Range;
36 
37 import org.mockito.ArgumentCaptor;
38 import org.mockito.ArgumentMatcher;
39 
40 import static org.mockito.Mockito.*;
41 
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 
46 /**
47  * CameraDevice preview test by using SurfaceView.
48  */
49 public class SurfaceViewPreviewTest extends Camera2SurfaceViewTestCase {
50     private static final String TAG = "SurfaceViewPreviewTest";
51     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
52     private static final int FRAME_TIMEOUT_MS = 1000;
53     private static final int NUM_FRAMES_VERIFIED = 30;
54     private static final int NUM_TEST_PATTERN_FRAMES_VERIFIED = 60;
55     private static final float FRAME_DURATION_ERROR_MARGIN = 0.005f; // 0.5 percent error margin.
56 
57     @Override
setUp()58     protected void setUp() throws Exception {
59         super.setUp();
60     }
61 
62     @Override
tearDown()63     protected void tearDown() throws Exception {
64         super.tearDown();
65     }
66 
67     /**
68      * Test all supported preview sizes for each camera device.
69      * <p>
70      * For the first  {@link #NUM_FRAMES_VERIFIED}  of capture results,
71      * the {@link CaptureCallback} callback availability and the capture timestamp
72      * (monotonically increasing) ordering are verified.
73      * </p>
74      */
testCameraPreview()75     public void testCameraPreview() throws Exception {
76         for (int i = 0; i < mCameraIds.length; i++) {
77             try {
78                 Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
79                 openDevice(mCameraIds[i]);
80                 if (!mStaticInfo.isColorOutputSupported()) {
81                     Log.i(TAG, "Camera " + mCameraIds[i] +
82                             " does not support color outputs, skipping");
83                     continue;
84                 }
85                 previewTestByCamera();
86             } finally {
87                 closeDevice();
88             }
89         }
90     }
91 
92     /**
93      * Basic test pattern mode preview.
94      * <p>
95      * Only test the test pattern preview and capture result, the image buffer
96      * is not validated.
97      * </p>
98      */
testBasicTestPatternPreview()99     public void testBasicTestPatternPreview() throws Exception{
100         for (int i = 0; i < mCameraIds.length; i++) {
101             try {
102                 Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
103                 openDevice(mCameraIds[i]);
104                 if (!mStaticInfo.isColorOutputSupported()) {
105                     Log.i(TAG, "Camera " + mCameraIds[i] +
106                             " does not support color outputs, skipping");
107                     continue;
108                 }
109                 previewTestPatternTestByCamera();
110             } finally {
111                 closeDevice();
112             }
113         }
114     }
115 
116     /**
117      * Test {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE} for preview, validate the preview
118      * frame duration and exposure time.
119      */
testPreviewFpsRange()120     public void testPreviewFpsRange() throws Exception {
121         for (String id : mCameraIds) {
122             try {
123                 openDevice(id);
124                 if (!mStaticInfo.isColorOutputSupported()) {
125                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
126                     continue;
127                 }
128                 previewFpsRangeTestByCamera();
129             } finally {
130                 closeDevice();
131             }
132         }
133     }
134 
135     /**
136      * Test to verify the {@link CameraCaptureSession#prepare} method works correctly, and has the
137      * expected effects on performance.
138      *
139      * - Ensure that prepare() results in onSurfacePrepared() being invoked
140      * - Ensure that prepare() does not cause preview glitches while operating
141      * - Ensure that starting to use a newly-prepared output does not cause additional
142      *   preview glitches to occur
143      */
testPreparePerformance()144     public void testPreparePerformance() throws Throwable {
145         for (int i = 0; i < mCameraIds.length; i++) {
146             try {
147                 openDevice(mCameraIds[i]);
148                 if (!mStaticInfo.isColorOutputSupported()) {
149                     Log.i(TAG, "Camera " + mCameraIds[i] +
150                             " does not support color outputs, skipping");
151                     continue;
152                 }
153                 preparePerformanceTestByCamera(mCameraIds[i]);
154             }
155             finally {
156                 closeDevice();
157             }
158         }
159     }
160 
preparePerformanceTestByCamera(String cameraId)161     private void preparePerformanceTestByCamera(String cameraId) throws Exception {
162         final int MAX_IMAGES_TO_PREPARE = 10;
163         final int UNKNOWN_LATENCY_RESULT_WAIT = 5;
164         final int MAX_RESULTS_TO_WAIT = 10;
165         final int FRAMES_FOR_AVERAGING = 100;
166         final int PREPARE_TIMEOUT_MS = 10000; // 10 s
167         final float PREPARE_FRAME_RATE_BOUNDS = 0.05f; // fraction allowed difference
168         final float PREPARE_PEAK_RATE_BOUNDS = 0.5f; // fraction allowed difference
169 
170         Size maxYuvSize = getSupportedPreviewSizes(cameraId, mCameraManager, null).get(0);
171         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
172 
173         // Don't need image data, just drop it right away to minimize overhead
174         ImageDropperListener imageListener = new ImageDropperListener();
175 
176         SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
177 
178         CaptureRequest.Builder previewRequest =
179                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
180 
181         // Configure outputs and session
182 
183         updatePreviewSurface(maxPreviewSize);
184 
185         createImageReader(maxYuvSize, ImageFormat.YUV_420_888, MAX_IMAGES_TO_PREPARE, imageListener);
186 
187         List<Surface> outputSurfaces = new ArrayList<Surface>();
188         outputSurfaces.add(mPreviewSurface);
189         outputSurfaces.add(mReaderSurface);
190 
191         CameraCaptureSession.StateCallback mockSessionListener =
192                 mock(CameraCaptureSession.StateCallback.class);
193 
194         mSession = configureCameraSession(mCamera, outputSurfaces, mockSessionListener, mHandler);
195 
196         previewRequest.addTarget(mPreviewSurface);
197         Range<Integer> maxFpsTarget = mStaticInfo.getAeMaxTargetFpsRange();
198         previewRequest.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, maxFpsTarget);
199 
200         mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
201 
202         // Converge AE
203         waitForAeStable(resultListener, UNKNOWN_LATENCY_RESULT_WAIT);
204 
205         if (mStaticInfo.isAeLockSupported()) {
206             // Lock AE if possible to improve stability
207             previewRequest.set(CaptureRequest.CONTROL_AE_LOCK, true);
208             mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
209             if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
210                 // Legacy mode doesn't output AE state
211                 waitForResultValue(resultListener, CaptureResult.CONTROL_AE_STATE,
212                         CaptureResult.CONTROL_AE_STATE_LOCKED, MAX_RESULTS_TO_WAIT);
213             }
214         }
215 
216         // Measure frame rate for a bit
217         Pair<Long, Long> frameDurationStats =
218                 measureMeanFrameInterval(resultListener, FRAMES_FOR_AVERAGING, /*prevTimestamp*/ 0);
219 
220         Log.i(TAG, String.format("Frame interval avg during normal preview: %f ms, peak %f ms",
221                         frameDurationStats.first / 1e6, frameDurationStats.second / 1e6));
222 
223         // Drain results, do prepare
224         resultListener.drain();
225 
226         mSession.prepare(mReaderSurface);
227 
228         verify(mockSessionListener,
229                 timeout(PREPARE_TIMEOUT_MS).times(1)).
230                 onSurfacePrepared(eq(mSession), eq(mReaderSurface));
231 
232         // Calculate frame rate during prepare
233 
234         int resultsReceived = (int) resultListener.getTotalNumFrames();
235         if (resultsReceived > 2) {
236             // Only verify frame rate if there are a couple of results
237             Pair<Long, Long> whilePreparingFrameDurationStats =
238                     measureMeanFrameInterval(resultListener, resultsReceived, /*prevTimestamp*/ 0);
239 
240             Log.i(TAG, String.format("Frame interval during prepare avg: %f ms, peak %f ms",
241                             whilePreparingFrameDurationStats.first / 1e6,
242                             whilePreparingFrameDurationStats.second / 1e6));
243 
244             if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
245                 mCollector.expectTrue(
246                     String.format("Camera %s: Preview peak frame interval affected by prepare " +
247                             "call: preview avg frame duration: %f ms, peak during prepare: %f ms",
248                             cameraId,
249                             frameDurationStats.first / 1e6,
250                             whilePreparingFrameDurationStats.second / 1e6),
251                     (whilePreparingFrameDurationStats.second <=
252                             frameDurationStats.first * (1 + PREPARE_PEAK_RATE_BOUNDS)));
253                 mCollector.expectTrue(
254                     String.format("Camera %s: Preview average frame interval affected by prepare " +
255                             "call: preview avg frame duration: %f ms, during prepare: %f ms",
256                             cameraId,
257                             frameDurationStats.first / 1e6,
258                             whilePreparingFrameDurationStats.first / 1e6),
259                     (whilePreparingFrameDurationStats.first <=
260                             frameDurationStats.first * (1 + PREPARE_FRAME_RATE_BOUNDS)));
261             }
262         }
263 
264         resultListener.drain();
265 
266         // Get at least one more preview result without prepared target
267         CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
268         long prevTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
269 
270         // Now use the prepared stream and ensure there are no hiccups from using it
271         previewRequest.addTarget(mReaderSurface);
272 
273         mSession.setRepeatingRequest(previewRequest.build(), resultListener, mHandler);
274 
275         Pair<Long, Long> preparedFrameDurationStats =
276                 measureMeanFrameInterval(resultListener, MAX_IMAGES_TO_PREPARE*2, prevTimestamp);
277 
278         Log.i(TAG, String.format("Frame interval with prepared stream added avg: %f ms, peak %f ms",
279                         preparedFrameDurationStats.first / 1e6,
280                         preparedFrameDurationStats.second / 1e6));
281 
282         if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
283             mCollector.expectTrue(
284                 String.format("Camera %s: Preview peak frame interval affected by use of new " +
285                         " stream: preview avg frame duration: %f ms, peak with new stream: %f ms",
286                         cameraId,
287                         frameDurationStats.first / 1e6, preparedFrameDurationStats.second / 1e6),
288                 (preparedFrameDurationStats.second <=
289                         frameDurationStats.first * (1 + PREPARE_PEAK_RATE_BOUNDS)));
290             mCollector.expectTrue(
291                 String.format("Camera %s: Preview average frame interval affected by use of new " +
292                         "stream: preview avg frame duration: %f ms, with new stream: %f ms",
293                         cameraId,
294                         frameDurationStats.first / 1e6, preparedFrameDurationStats.first / 1e6),
295                 (preparedFrameDurationStats.first <=
296                         frameDurationStats.first * (1 + PREPARE_FRAME_RATE_BOUNDS)));
297         }
298     }
299 
300     /**
301      * Measure the inter-frame interval based on SENSOR_TIMESTAMP for frameCount frames from the
302      * provided capture listener.  If prevTimestamp is positive, it is used for the first interval
303      * calculation; otherwise, the first result is used to establish the starting time.
304      *
305      * Returns the mean interval in the first pair entry, and the largest interval in the second
306      * pair entry
307      */
measureMeanFrameInterval(SimpleCaptureCallback resultListener, int frameCount, long prevTimestamp)308     Pair<Long, Long> measureMeanFrameInterval(SimpleCaptureCallback resultListener, int frameCount,
309             long prevTimestamp) throws Exception {
310         long summedIntervals = 0;
311         long maxInterval = 0;
312         int measurementCount = frameCount - ((prevTimestamp > 0) ? 0 : 1);
313 
314         for (int i = 0; i < frameCount; i++) {
315             CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
316             long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
317             if (prevTimestamp > 0) {
318                 long interval = timestamp - prevTimestamp;
319                 if (interval > maxInterval) maxInterval = interval;
320                 summedIntervals += interval;
321             }
322             prevTimestamp = timestamp;
323         }
324         return new Pair<Long, Long>(summedIntervals / measurementCount, maxInterval);
325     }
326 
327 
328     /**
329      * Test preview fps range for all supported ranges. The exposure time are frame duration are
330      * validated.
331      */
previewFpsRangeTestByCamera()332     private void previewFpsRangeTestByCamera() throws Exception {
333         final int FPS_RANGE_SIZE = 2;
334         Size maxPreviewSz = mOrderedPreviewSizes.get(0);
335         Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
336         boolean antiBandingOffIsSupported = mStaticInfo.isAntiBandingOffModeSupported();
337         Range<Integer> fpsRange;
338         CaptureRequest.Builder requestBuilder =
339                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
340         SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
341         startPreview(requestBuilder, maxPreviewSz, resultListener);
342 
343         for (int i = 0; i < fpsRanges.length; i += 1) {
344             fpsRange = fpsRanges[i];
345 
346             requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
347             // Turn off auto antibanding to avoid exposure time and frame duration interference
348             // from antibanding algorithm.
349             if (antiBandingOffIsSupported) {
350                 requestBuilder.set(CaptureRequest.CONTROL_AE_ANTIBANDING_MODE,
351                         CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_OFF);
352             } else {
353                 // The device doesn't implement the OFF mode, test continues. It need make sure
354                 // that the antibanding algorithm doesn't interfere with the fps range control.
355                 Log.i(TAG, "OFF antibanding mode is not supported, the camera device output must" +
356                         " satisfy the specified fps range regardless of its current antibanding" +
357                         " mode");
358             }
359 
360             resultListener = new SimpleCaptureCallback();
361             mSession.setRepeatingRequest(requestBuilder.build(), resultListener, mHandler);
362 
363             verifyPreviewTargetFpsRange(resultListener, NUM_FRAMES_VERIFIED, fpsRange,
364                     maxPreviewSz);
365         }
366 
367         stopPreview();
368     }
369 
verifyPreviewTargetFpsRange(SimpleCaptureCallback resultListener, int numFramesVerified, Range<Integer> fpsRange, Size previewSz)370     private void verifyPreviewTargetFpsRange(SimpleCaptureCallback resultListener,
371             int numFramesVerified, Range<Integer> fpsRange, Size previewSz) {
372         CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
373         List<Integer> capabilities = mStaticInfo.getAvailableCapabilitiesChecked();
374 
375         if (capabilities.contains(CaptureRequest.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
376             long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
377             long[] frameDurationRange =
378                     new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
379             mCollector.expectInRange(
380                     "Frame duration must be in the range of " + Arrays.toString(frameDurationRange),
381                     frameDuration, (long) (frameDurationRange[0] * (1 - FRAME_DURATION_ERROR_MARGIN)),
382                     (long) (frameDurationRange[1] * (1 + FRAME_DURATION_ERROR_MARGIN)));
383             long expTime = getValueNotNull(result, CaptureResult.SENSOR_EXPOSURE_TIME);
384             mCollector.expectTrue(String.format("Exposure time %d must be no larger than frame"
385                     + "duration %d", expTime, frameDuration), expTime <= frameDuration);
386 
387             Long minFrameDuration = mMinPreviewFrameDurationMap.get(previewSz);
388             boolean findDuration = mCollector.expectTrue("Unable to find minFrameDuration for size "
389                     + previewSz.toString(), minFrameDuration != null);
390             if (findDuration) {
391                 mCollector.expectTrue("Frame duration " + frameDuration + " must be no smaller than"
392                         + " minFrameDuration " + minFrameDuration, frameDuration >= minFrameDuration);
393             }
394         } else {
395             Log.i(TAG, "verifyPreviewTargetFpsRange - MANUAL_SENSOR control is not supported," +
396                     " skipping duration and exposure time check.");
397         }
398     }
399 
400     /**
401      * Test all supported preview sizes for a camera device
402      *
403      * @throws Exception
404      */
previewTestByCamera()405     private void previewTestByCamera() throws Exception {
406         List<Size> previewSizes = getSupportedPreviewSizes(
407                 mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
408 
409         for (final Size sz : previewSizes) {
410             if (VERBOSE) {
411                 Log.v(TAG, "Testing camera preview size: " + sz.toString());
412             }
413 
414             // TODO: vary the different settings like crop region to cover more cases.
415             CaptureRequest.Builder requestBuilder =
416                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
417             CaptureCallback mockCaptureCallback =
418                     mock(CameraCaptureSession.CaptureCallback.class);
419 
420             startPreview(requestBuilder, sz, mockCaptureCallback);
421             verifyCaptureResults(mSession, mockCaptureCallback, NUM_FRAMES_VERIFIED,
422                     NUM_FRAMES_VERIFIED * FRAME_TIMEOUT_MS);
423             stopPreview();
424         }
425     }
426 
previewTestPatternTestByCamera()427     private void previewTestPatternTestByCamera() throws Exception {
428         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
429         int[] testPatternModes = mStaticInfo.getAvailableTestPatternModesChecked();
430         CaptureRequest.Builder requestBuilder =
431                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
432         CaptureCallback mockCaptureCallback;
433 
434         final int[] TEST_PATTERN_DATA = {0, 0xFFFFFFFF, 0xFFFFFFFF, 0}; // G:100%, RB:0.
435         for (int mode : testPatternModes) {
436             if (VERBOSE) {
437                 Log.v(TAG, "Test pattern mode: " + mode);
438             }
439             requestBuilder.set(CaptureRequest.SENSOR_TEST_PATTERN_MODE, mode);
440             if (mode == CaptureRequest.SENSOR_TEST_PATTERN_MODE_SOLID_COLOR) {
441                 // Assign color pattern to SENSOR_TEST_PATTERN_MODE_DATA
442                 requestBuilder.set(CaptureRequest.SENSOR_TEST_PATTERN_DATA, TEST_PATTERN_DATA);
443             }
444             mockCaptureCallback = mock(CaptureCallback.class);
445             startPreview(requestBuilder, maxPreviewSize, mockCaptureCallback);
446             verifyCaptureResults(mSession, mockCaptureCallback, NUM_TEST_PATTERN_FRAMES_VERIFIED,
447                     NUM_TEST_PATTERN_FRAMES_VERIFIED * FRAME_TIMEOUT_MS);
448         }
449 
450         stopPreview();
451     }
452 
453     private class IsCaptureResultValid extends ArgumentMatcher<TotalCaptureResult> {
454         @Override
matches(Object obj)455         public boolean matches(Object obj) {
456             TotalCaptureResult result = (TotalCaptureResult)obj;
457             Long timeStamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
458             if (timeStamp != null && timeStamp.longValue() > 0L) {
459                 return true;
460             }
461             return false;
462         }
463     }
464 
verifyCaptureResults( CameraCaptureSession session, CaptureCallback mockListener, int expectResultCount, int timeOutMs)465     private void verifyCaptureResults(
466             CameraCaptureSession session,
467             CaptureCallback mockListener,
468             int expectResultCount,
469             int timeOutMs) {
470         // Should receive expected number of onCaptureStarted callbacks.
471         ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
472         ArgumentCaptor<Long> frameNumbers = ArgumentCaptor.forClass(Long.class);
473         verify(mockListener,
474                 timeout(timeOutMs).atLeast(expectResultCount))
475                         .onCaptureStarted(
476                                 eq(session),
477                                 isA(CaptureRequest.class),
478                                 timestamps.capture(),
479                                 frameNumbers.capture());
480 
481         // Validate timestamps: all timestamps should be larger than 0 and monotonically increase.
482         long timestamp = 0;
483         for (Long nextTimestamp : timestamps.getAllValues()) {
484             assertNotNull("Next timestamp is null!", nextTimestamp);
485             assertTrue("Captures are out of order", timestamp < nextTimestamp);
486             timestamp = nextTimestamp;
487         }
488 
489         // Validate framenumbers: all framenumbers should be consecutive and positive
490         long frameNumber = -1;
491         for (Long nextFrameNumber : frameNumbers.getAllValues()) {
492             assertNotNull("Next frame number is null!", nextFrameNumber);
493             assertTrue("Captures are out of order",
494                     (frameNumber == -1) || (frameNumber + 1 == nextFrameNumber));
495             frameNumber = nextFrameNumber;
496         }
497 
498         // Should receive expected number of capture results.
499         verify(mockListener,
500                 timeout(timeOutMs).atLeast(expectResultCount))
501                         .onCaptureCompleted(
502                                 eq(session),
503                                 isA(CaptureRequest.class),
504                                 argThat(new IsCaptureResultValid()));
505 
506         // Should not receive any capture failed callbacks.
507         verify(mockListener, never())
508                         .onCaptureFailed(
509                                 eq(session),
510                                 isA(CaptureRequest.class),
511                                 isA(CaptureFailure.class));
512     }
513 
514 }
515