1 /*
2  * Copyright 2016 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 com.android.mediaframeworktest.stress;
18 
19 import com.android.ex.camera2.blocking.BlockingSessionCallback;
20 import com.android.mediaframeworktest.Camera2SurfaceViewTestCase;
21 import com.android.mediaframeworktest.helpers.CameraTestUtils;
22 
23 import junit.framework.AssertionFailedError;
24 
25 import android.graphics.ImageFormat;
26 import android.hardware.camera2.CameraCaptureSession;
27 import android.hardware.camera2.CameraCharacteristics;
28 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
29 import android.hardware.camera2.CameraDevice;
30 import android.hardware.camera2.CaptureRequest;
31 import android.hardware.camera2.CaptureResult;
32 import android.hardware.camera2.params.StreamConfigurationMap;
33 import android.media.CamcorderProfile;
34 import android.media.Image;
35 import android.media.ImageReader;
36 import android.media.MediaExtractor;
37 import android.media.MediaFormat;
38 import android.media.MediaRecorder;
39 import android.os.Environment;
40 import android.os.SystemClock;
41 import android.test.suitebuilder.annotation.LargeTest;
42 import android.util.Log;
43 import android.util.Range;
44 import android.util.Size;
45 import android.view.Surface;
46 
47 import java.io.File;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.HashMap;
51 import java.util.List;
52 
53 import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
54 import static com.android.mediaframeworktest.helpers.CameraTestUtils.CAPTURE_IMAGE_TIMEOUT_MS;
55 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS;
56 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SIZE_BOUND_1080P;
57 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SIZE_BOUND_2160P;
58 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
59 import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
60 import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
61 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedVideoSizes;
62 import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
63 
64 /**
65  * CameraDevice video recording use case tests by using MediaRecorder and
66  * MediaCodec.
67  *
68  * adb shell am instrument \
69  *    -e class com.android.mediaframeworktest.stress.Camera2RecordingTest#testBasicRecording \
70  *    -e iterations 10 \
71  *    -e waitIntervalMs 1000 \
72  *    -e resultToFile false \
73  *    -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
74  */
75 @LargeTest
76 public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
77     private static final String TAG = "RecordingTest";
78     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
79     private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG);
80     private static final int RECORDING_DURATION_MS = 3000;
81     private static final float DURATION_MARGIN = 0.2f;
82     private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
83     private static final int BIT_RATE_1080P = 16000000;
84     private static final int BIT_RATE_MIN = 64000;
85     private static final int BIT_RATE_MAX = 40000000;
86     private static final int VIDEO_FRAME_RATE = 30;
87     private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
88     private static final int[] mCamcorderProfileList = {
89             CamcorderProfile.QUALITY_HIGH,
90             CamcorderProfile.QUALITY_2160P,
91             CamcorderProfile.QUALITY_1080P,
92             CamcorderProfile.QUALITY_720P,
93             CamcorderProfile.QUALITY_480P,
94             CamcorderProfile.QUALITY_CIF,
95             CamcorderProfile.QUALITY_QCIF,
96             CamcorderProfile.QUALITY_QVGA,
97             CamcorderProfile.QUALITY_LOW,
98     };
99     private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5;
100     private static final int BURST_VIDEO_SNAPSHOT_NUM = 3;
101     private static final int SLOWMO_SLOW_FACTOR = 4;
102     private static final int MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED = 4;
103     private List<Size> mSupportedVideoSizes;
104     private Surface mRecordingSurface;
105     private Surface mPersistentSurface;
106     private MediaRecorder mMediaRecorder;
107     private String mOutMediaFileName;
108     private int mVideoFrameRate;
109     private Size mVideoSize;
110     private long mRecordingStartTime;
111 
112     @Override
setUp()113     protected void setUp() throws Exception {
114         super.setUp();
115     }
116 
117     @Override
tearDown()118     protected void tearDown() throws Exception {
119         super.tearDown();
120     }
121 
doBasicRecording(boolean useVideoStab)122     private void doBasicRecording(boolean useVideoStab) throws Exception {
123         for (int i = 0; i < mCameraIds.length; i++) {
124             try {
125                 Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
126                 // Re-use the MediaRecorder object for the same camera device.
127                 mMediaRecorder = new MediaRecorder();
128                 openDevice(mCameraIds[i]);
129                 if (!mStaticInfo.isColorOutputSupported()) {
130                     Log.i(TAG, "Camera " + mCameraIds[i] +
131                             " does not support color outputs, skipping");
132                     continue;
133                 }
134 
135                 if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) {
136                     Log.i(TAG, "Camera " + mCameraIds[i] +
137                             " does not support video stabilization, skipping the stabilization"
138                             + " test");
139                     continue;
140                 }
141 
142                 initSupportedVideoSize(mCameraIds[i]);
143 
144                 // Test iteration starts...
145                 for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
146                     Log.v(TAG, String.format("Recording video: %d/%d", iteration + 1,
147                             getIterationCount()));
148                     basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab);
149                     getResultPrinter().printStatus(getIterationCount(), iteration + 1,
150                             mCameraIds[i]);
151                     Thread.sleep(getTestWaitIntervalMs());
152                 }
153             } finally {
154                 closeDevice();
155                 releaseRecorder();
156             }
157         }
158     }
159 
160     /**
161      * <p>
162      * Test basic camera recording.
163      * </p>
164      * <p>
165      * This test covers the typical basic use case of camera recording.
166      * MediaRecorder is used to record the audio and video, CamcorderProfile is
167      * used to configure the MediaRecorder. It goes through the pre-defined
168      * CamcorderProfile list, test each profile configuration and validate the
169      * recorded video. Preview is set to the video size.
170      * </p>
171      */
testBasicRecording()172     public void testBasicRecording() throws Exception {
173         doBasicRecording(/*useVideoStab*/false);
174     }
175 
176     /**
177      * <p>
178      * Test video snapshot for each camera.
179      * </p>
180      * <p>
181      * This test covers video snapshot typical use case. The MediaRecorder is used to record the
182      * video for each available video size. The largest still capture size is selected to
183      * capture the JPEG image. The still capture images are validated according to the capture
184      * configuration. The timestamp of capture result before and after video snapshot is also
185      * checked to make sure no frame drop caused by video snapshot.
186      * </p>
187      */
testVideoSnapshot()188     public void testVideoSnapshot() throws Exception {
189         videoSnapshotHelper(/*burstTest*/false);
190     }
191 
testConstrainedHighSpeedRecording()192     public void testConstrainedHighSpeedRecording() throws Exception {
193         constrainedHighSpeedRecording();
194     }
195 
constrainedHighSpeedRecording()196     private void constrainedHighSpeedRecording() throws Exception {
197         for (String id : mCameraIds) {
198             try {
199                 Log.i(TAG, "Testing constrained high speed recording for camera " + id);
200                 // Re-use the MediaRecorder object for the same camera device.
201                 mMediaRecorder = new MediaRecorder();
202                 openDevice(id);
203 
204                 if (!mStaticInfo.isConstrainedHighSpeedVideoSupported()) {
205                     Log.i(TAG, "Camera " + id + " doesn't support high speed recording, skipping.");
206                     continue;
207                 }
208 
209                 // Test iteration starts...
210                 for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
211                     Log.v(TAG, String.format("Constrained high speed recording: %d/%d",
212                             iteration + 1, getIterationCount()));
213 
214                     StreamConfigurationMap config =
215                             mStaticInfo.getValueFromKeyNonNull(
216                                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
217                     Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes();
218                     for (Size size : highSpeedVideoSizes) {
219                         List<Range<Integer>> fixedFpsRanges =
220                                 getHighSpeedFixedFpsRangeForSize(config, size);
221                         mCollector.expectTrue("Unable to find the fixed frame rate fps range for " +
222                                 "size " + size, fixedFpsRanges.size() > 0);
223                         // Test recording for each FPS range
224                         for (Range<Integer> fpsRange : fixedFpsRanges) {
225                             int captureRate = fpsRange.getLower();
226                             final int VIDEO_FRAME_RATE = 30;
227                             // Skip the test if the highest recording FPS supported by CamcorderProfile
228                             if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) {
229                                 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps"
230                                         + " is not supported by CamcorderProfile");
231                                 continue;
232                             }
233 
234                             mOutMediaFileName = VIDEO_FILE_PATH + "/test_cslowMo_video_" + captureRate +
235                                     "fps_" + id + "_" + size.toString() + ".mp4";
236 
237                             prepareRecording(size, VIDEO_FRAME_RATE, captureRate);
238 
239                             // prepare preview surface by using video size.
240                             updatePreviewSurfaceWithVideo(size, captureRate);
241 
242                             // Start recording
243                             SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
244                             startSlowMotionRecording(/*useMediaRecorder*/true, VIDEO_FRAME_RATE,
245                                     captureRate, fpsRange, resultListener,
246                                     /*useHighSpeedSession*/true);
247 
248                             // Record certain duration.
249                             SystemClock.sleep(RECORDING_DURATION_MS);
250 
251                             // Stop recording and preview
252                             stopRecording(/*useMediaRecorder*/true);
253                             // Convert number of frames camera produced into the duration in unit of ms.
254                             int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
255                                             VIDEO_FRAME_RATE);
256 
257                             // Validation.
258                             validateRecording(size, durationMs);
259                         }
260 
261                     getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
262                     Thread.sleep(getTestWaitIntervalMs());
263                     }
264                 }
265 
266             } finally {
267                 closeDevice();
268                 releaseRecorder();
269             }
270         }
271     }
272 
273     /**
274      * Get high speed FPS from CamcorderProfiles for a given size.
275      *
276      * @param size The size used to search the CamcorderProfiles for the FPS.
277      * @return high speed video FPS, 0 if the given size is not supported by the CamcorderProfiles.
278      */
getFpsFromHighSpeedProfileForSize(Size size)279     private int getFpsFromHighSpeedProfileForSize(Size size) {
280         for (int quality = CamcorderProfile.QUALITY_HIGH_SPEED_480P;
281                 quality <= CamcorderProfile.QUALITY_HIGH_SPEED_2160P; quality++) {
282             if (CamcorderProfile.hasProfile(quality)) {
283                 CamcorderProfile profile = CamcorderProfile.get(quality);
284                 if (size.equals(new Size(profile.videoFrameWidth, profile.videoFrameHeight))){
285                     return profile.videoFrameRate;
286                 }
287             }
288         }
289 
290         return 0;
291     }
292 
getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, Size size)293     private List<Range<Integer>> getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config,
294             Size size) {
295         Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size);
296         List<Range<Integer>> fixedRanges = new ArrayList<Range<Integer>>();
297         for (Range<Integer> range : availableFpsRanges) {
298             if (range.getLower().equals(range.getUpper())) {
299                 fixedRanges.add(range);
300             }
301         }
302         return fixedRanges;
303     }
304 
startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate, int captureRate, Range<Integer> fpsRange, CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession)305     private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate,
306             int captureRate, Range<Integer> fpsRange,
307             CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession) throws Exception {
308         List<Surface> outputSurfaces = new ArrayList<Surface>(2);
309         assertTrue("Both preview and recording surfaces should be valid",
310                 mPreviewSurface.isValid() && mRecordingSurface.isValid());
311         outputSurfaces.add(mPreviewSurface);
312         outputSurfaces.add(mRecordingSurface);
313         // Video snapshot surface
314         if (mReaderSurface != null) {
315             outputSurfaces.add(mReaderSurface);
316         }
317         mSessionListener = new BlockingSessionCallback();
318         mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
319                 mSessionListener, mHandler);
320 
321         // Create slow motion request list
322         List<CaptureRequest> slowMoRequests = null;
323         if (useHighSpeedSession) {
324             CaptureRequest.Builder requestBuilder =
325                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
326             requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
327             requestBuilder.addTarget(mPreviewSurface);
328             requestBuilder.addTarget(mRecordingSurface);
329             slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession).
330                     createHighSpeedRequestList(requestBuilder.build());
331         } else {
332             CaptureRequest.Builder recordingRequestBuilder =
333                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
334             recordingRequestBuilder.set(CaptureRequest.CONTROL_MODE,
335                     CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
336             recordingRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,
337                     CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO);
338 
339             CaptureRequest.Builder recordingOnlyBuilder =
340                     mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
341             recordingOnlyBuilder.set(CaptureRequest.CONTROL_MODE,
342                     CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
343             recordingOnlyBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,
344                     CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO);
345             int slowMotionFactor = captureRate / videoFrameRate;
346 
347             // Make sure camera output frame rate is set to correct value.
348             recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
349             recordingRequestBuilder.addTarget(mRecordingSurface);
350             recordingRequestBuilder.addTarget(mPreviewSurface);
351             recordingOnlyBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
352             recordingOnlyBuilder.addTarget(mRecordingSurface);
353 
354             slowMoRequests = new ArrayList<CaptureRequest>();
355             slowMoRequests.add(recordingRequestBuilder.build());// Preview + recording.
356 
357             for (int i = 0; i < slowMotionFactor - 1; i++) {
358                 slowMoRequests.add(recordingOnlyBuilder.build()); // Recording only.
359             }
360         }
361 
362         mSession.setRepeatingBurst(slowMoRequests, listener, mHandler);
363 
364         if (useMediaRecorder) {
365             mMediaRecorder.start();
366         } else {
367             // TODO: need implement MediaCodec path.
368         }
369 
370     }
371 
372     /**
373      * Test camera recording by using each available CamcorderProfile for a
374      * given camera. preview size is set to the video size.
375      */
basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab)376     private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab)
377             throws Exception {
378         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
379         List<Range<Integer> > fpsRanges = Arrays.asList(
380                 mStaticInfo.getAeAvailableTargetFpsRangesChecked());
381         int cameraId = Integer.parseInt(mCamera.getId());
382         int maxVideoFrameRate = -1;
383         for (int profileId : camcorderProfileList) {
384             if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
385                     allowedUnsupported(cameraId, profileId)) {
386                 continue;
387             }
388 
389             CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
390             Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
391             Range<Integer> fpsRange = new Range(profile.videoFrameRate, profile.videoFrameRate);
392             if (maxVideoFrameRate < profile.videoFrameRate) {
393                 maxVideoFrameRate = profile.videoFrameRate;
394             }
395             if (mStaticInfo.isHardwareLevelLegacy() &&
396                     (videoSz.getWidth() > maxPreviewSize.getWidth() ||
397                      videoSz.getHeight() > maxPreviewSize.getHeight())) {
398                 // Skip. Legacy mode can only do recording up to max preview size
399                 continue;
400             }
401             assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId +
402                             " must be one of the camera device supported video size!",
403                             mSupportedVideoSizes.contains(videoSz));
404             assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId +
405                     ") must be one of the camera device available FPS range!",
406                     fpsRanges.contains(fpsRange));
407 
408             if (VERBOSE) {
409                 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
410             }
411 
412             // Configure preview and recording surfaces.
413             mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
414             if (DEBUG_DUMP) {
415                 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
416                         + videoSz.toString() + ".mp4";
417             }
418 
419             prepareRecordingWithProfile(profile);
420 
421             // prepare preview surface by using video size.
422             updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
423 
424             // Start recording
425             SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
426             startRecording(/* useMediaRecorder */true, resultListener, useVideoStab);
427 
428             // Record certain duration.
429             SystemClock.sleep(RECORDING_DURATION_MS);
430 
431             // Stop recording and preview
432             stopRecording(/* useMediaRecorder */true);
433             // Convert number of frames camera produced into the duration in unit of ms.
434             int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
435                             profile.videoFrameRate);
436 
437             if (VERBOSE) {
438                 Log.v(TAG, "video frame rate: " + profile.videoFrameRate +
439                                 ", num of frames produced: " + resultListener.getTotalNumFrames());
440             }
441 
442             // Validation.
443             validateRecording(videoSz, durationMs);
444         }
445         if (maxVideoFrameRate != -1) {
446             // At least one CamcorderProfile is present, check FPS
447             assertTrue("At least one CamcorderProfile must support >= 24 FPS",
448                     maxVideoFrameRate >= 24);
449         }
450     }
451 
452     /**
453      * Initialize the supported video sizes.
454      */
initSupportedVideoSize(String cameraId)455     private void initSupportedVideoSize(String cameraId)  throws Exception {
456         Size maxVideoSize = SIZE_BOUND_1080P;
457         if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) {
458             maxVideoSize = SIZE_BOUND_2160P;
459         }
460         mSupportedVideoSizes =
461                 getSupportedVideoSizes(cameraId, mCameraManager, maxVideoSize);
462     }
463 
464     /**
465      * Simple wrapper to wrap normal/burst video snapshot tests
466      */
videoSnapshotHelper(boolean burstTest)467     private void videoSnapshotHelper(boolean burstTest) throws Exception {
468             for (String id : mCameraIds) {
469                 try {
470                     Log.i(TAG, "Testing video snapshot for camera " + id);
471                     // Re-use the MediaRecorder object for the same camera device.
472                     mMediaRecorder = new MediaRecorder();
473 
474                     openDevice(id);
475 
476                     if (!mStaticInfo.isColorOutputSupported()) {
477                         Log.i(TAG, "Camera " + id +
478                                 " does not support color outputs, skipping");
479                         continue;
480                     }
481 
482                     initSupportedVideoSize(id);
483 
484                     // Test iteration starts...
485                     for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
486                         Log.v(TAG, String.format("Video snapshot: %d/%d", iteration + 1,
487                                 getIterationCount()));
488                         videoSnapshotTestByCamera(burstTest);
489                         getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
490                         Thread.sleep(getTestWaitIntervalMs());
491                     }
492                 } finally {
493                     closeDevice();
494                     releaseRecorder();
495                 }
496             }
497     }
498 
499     /**
500      * Returns {@code true} if the {@link CamcorderProfile} ID is allowed to be unsupported.
501      *
502      * <p>This only allows unsupported profiles when using the LEGACY mode of the Camera API.</p>
503      *
504      * @param profileId a {@link CamcorderProfile} ID to check.
505      * @return {@code true} if supported.
506      */
allowedUnsupported(int cameraId, int profileId)507     private boolean allowedUnsupported(int cameraId, int profileId) {
508         if (!mStaticInfo.isHardwareLevelLegacy()) {
509             return false;
510         }
511 
512         switch(profileId) {
513             case CamcorderProfile.QUALITY_2160P:
514             case CamcorderProfile.QUALITY_1080P:
515             case CamcorderProfile.QUALITY_HIGH:
516                 return !CamcorderProfile.hasProfile(cameraId, profileId) ||
517                         CamcorderProfile.get(cameraId, profileId).videoFrameWidth >= 1080;
518         }
519         return false;
520     }
521 
522     /**
523      * Test video snapshot for each  available CamcorderProfile for a given camera.
524      *
525      * <p>
526      * Preview size is set to the video size. For the burst test, frame drop and jittering
527      * is not checked.
528      * </p>
529      *
530      * @param burstTest Perform burst capture or single capture. For burst capture
531      *                  {@value #BURST_VIDEO_SNAPSHOT_NUM} capture requests will be sent.
532      */
videoSnapshotTestByCamera(boolean burstTest)533     private void videoSnapshotTestByCamera(boolean burstTest)
534             throws Exception {
535         final int NUM_SINGLE_SHOT_TEST = 5;
536         final int FRAMEDROP_TOLERANCE = 8;
537         final int FRAME_SIZE_15M = 15000000;
538         final float FRAME_DROP_TOLERENCE_FACTOR = 1.5f;
539         int kFrameDrop_Tolerence = FRAMEDROP_TOLERANCE;
540 
541         for (int profileId : mCamcorderProfileList) {
542             int cameraId = Integer.parseInt(mCamera.getId());
543             if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
544                     allowedUnsupported(cameraId, profileId)) {
545                 continue;
546             }
547 
548             CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
549             Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
550             Size maxPreviewSize = mOrderedPreviewSizes.get(0);
551 
552             if (mStaticInfo.isHardwareLevelLegacy() &&
553                     (videoSz.getWidth() > maxPreviewSize.getWidth() ||
554                      videoSz.getHeight() > maxPreviewSize.getHeight())) {
555                 // Skip. Legacy mode can only do recording up to max preview size
556                 continue;
557             }
558 
559             if (!mSupportedVideoSizes.contains(videoSz)) {
560                 mCollector.addMessage("Video size " + videoSz.toString() + " for profile ID " +
561                         profileId + " must be one of the camera device supported video size!");
562                 continue;
563             }
564 
565             // For LEGACY, find closest supported smaller or equal JPEG size to the current video
566             // size; if no size is smaller than the video, pick the smallest JPEG size.  The assert
567             // for video size above guarantees that for LIMITED or FULL, we select videoSz here.
568             // Also check for minFrameDuration here to make sure jpeg stream won't slow down
569             // video capture
570             Size videoSnapshotSz = mOrderedStillSizes.get(mOrderedStillSizes.size() - 1);
571             // Allow a bit tolerance so we don't fail for a few nano seconds of difference
572             final float FRAME_DURATION_TOLERANCE = 0.01f;
573             long videoFrameDuration = (long) (1e9 / profile.videoFrameRate *
574                     (1.0 + FRAME_DURATION_TOLERANCE));
575             HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
576                     getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG);
577             for (int i = mOrderedStillSizes.size() - 2; i >= 0; i--) {
578                 Size candidateSize = mOrderedStillSizes.get(i);
579                 if (mStaticInfo.isHardwareLevelLegacy()) {
580                     // Legacy level doesn't report min frame duration
581                     if (candidateSize.getWidth() <= videoSz.getWidth() &&
582                             candidateSize.getHeight() <= videoSz.getHeight()) {
583                         videoSnapshotSz = candidateSize;
584                     }
585                 } else {
586                     Long jpegFrameDuration = minFrameDurationMap.get(candidateSize);
587                     assertTrue("Cannot find minimum frame duration for jpeg size " + candidateSize,
588                             jpegFrameDuration != null);
589                     if (candidateSize.getWidth() <= videoSz.getWidth() &&
590                             candidateSize.getHeight() <= videoSz.getHeight() &&
591                             jpegFrameDuration <= videoFrameDuration) {
592                         videoSnapshotSz = candidateSize;
593                     }
594                 }
595             }
596 
597             /**
598              * Only test full res snapshot when below conditions are all true.
599              * 1. Camera is a FULL device
600              * 2. video size is up to max preview size, which will be bounded by 1080p.
601              * 3. Full resolution jpeg stream can keep up to video stream speed.
602              *    When full res jpeg stream cannot keep up to video stream speed, search
603              *    the largest jpeg size that can susptain video speed instead.
604              */
605             if (mStaticInfo.isHardwareLevelFull() &&
606                     videoSz.getWidth() <= maxPreviewSize.getWidth() &&
607                     videoSz.getHeight() <= maxPreviewSize.getHeight()) {
608                 for (Size jpegSize : mOrderedStillSizes) {
609                     Long jpegFrameDuration = minFrameDurationMap.get(jpegSize);
610                     assertTrue("Cannot find minimum frame duration for jpeg size " + jpegSize,
611                             jpegFrameDuration != null);
612                     if (jpegFrameDuration <= videoFrameDuration) {
613                         videoSnapshotSz = jpegSize;
614                         break;
615                     }
616                     if (jpegSize.equals(videoSz)) {
617                         throw new AssertionFailedError(
618                                 "Cannot find adequate video snapshot size for video size" +
619                                         videoSz);
620                     }
621                 }
622             }
623 
624             Log.i(TAG, "Testing video snapshot size " + videoSnapshotSz +
625                     " for video size " + videoSz);
626             if (videoSnapshotSz.getWidth() * videoSnapshotSz.getHeight() > FRAME_SIZE_15M)
627                 kFrameDrop_Tolerence = (int)(FRAMEDROP_TOLERANCE * FRAME_DROP_TOLERENCE_FACTOR);
628 
629             createImageReader(
630                     videoSnapshotSz, ImageFormat.JPEG,
631                     MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null);
632 
633             if (VERBOSE) {
634                 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
635             }
636 
637             // Configure preview and recording surfaces.
638             mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
639             if (DEBUG_DUMP) {
640                 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
641                         + videoSz.toString() + ".mp4";
642             }
643 
644             int numTestIterations = burstTest ? 1 : NUM_SINGLE_SHOT_TEST;
645             int totalDroppedFrames = 0;
646 
647             for (int numTested = 0; numTested < numTestIterations; numTested++) {
648                 prepareRecordingWithProfile(profile);
649 
650                 // prepare video snapshot
651                 SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
652                 SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
653                 CaptureRequest.Builder videoSnapshotRequestBuilder =
654                         mCamera.createCaptureRequest((mStaticInfo.isHardwareLevelLegacy()) ?
655                                 CameraDevice.TEMPLATE_RECORD :
656                                 CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
657 
658                 // prepare preview surface by using video size.
659                 updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
660 
661                 prepareVideoSnapshot(videoSnapshotRequestBuilder, imageListener);
662                 Range<Integer> fpsRange = Range.create(profile.videoFrameRate,
663                         profile.videoFrameRate);
664                 videoSnapshotRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
665                         fpsRange);
666                 CaptureRequest request = videoSnapshotRequestBuilder.build();
667 
668                 // Start recording
669                 startRecording(/* useMediaRecorder */true, resultListener, /*useVideoStab*/false);
670                 long startTime = SystemClock.elapsedRealtime();
671 
672                 // Record certain duration.
673                 SystemClock.sleep(RECORDING_DURATION_MS / 2);
674 
675                 // take video snapshot
676                 if (burstTest) {
677                     List<CaptureRequest> requests =
678                             new ArrayList<CaptureRequest>(BURST_VIDEO_SNAPSHOT_NUM);
679                     for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
680                         requests.add(request);
681                     }
682                     mSession.captureBurst(requests, resultListener, mHandler);
683                 } else {
684                     mSession.capture(request, resultListener, mHandler);
685                 }
686 
687                 // make sure recording is still going after video snapshot
688                 SystemClock.sleep(RECORDING_DURATION_MS / 2);
689 
690                 // Stop recording and preview
691                 int durationMs = stopRecording(/* useMediaRecorder */true);
692                 // For non-burst test, use number of frames to also double check video frame rate.
693                 // Burst video snapshot is allowed to cause frame rate drop, so do not use number
694                 // of frames to estimate duration
695                 if (!burstTest) {
696                     durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
697                         profile.videoFrameRate);
698                 }
699 
700                 // Validation recorded video
701                 validateRecording(videoSz, durationMs);
702 
703                 if (burstTest) {
704                     for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
705                         Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
706                         validateVideoSnapshotCapture(image, videoSnapshotSz);
707                         image.close();
708                     }
709                 } else {
710                     // validate video snapshot image
711                     Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
712                     validateVideoSnapshotCapture(image, videoSnapshotSz);
713 
714                     // validate if there is framedrop around video snapshot
715                     totalDroppedFrames +=  validateFrameDropAroundVideoSnapshot(
716                             resultListener, image.getTimestamp());
717 
718                     //TODO: validate jittering. Should move to PTS
719                     //validateJittering(resultListener);
720 
721                     image.close();
722                 }
723             }
724 
725             if (!burstTest) {
726                 Log.w(TAG, String.format("Camera %d Video size %s: Number of dropped frames " +
727                         "detected in %d trials is %d frames.", cameraId, videoSz.toString(),
728                         numTestIterations, totalDroppedFrames));
729                 mCollector.expectLessOrEqual(
730                         String.format(
731                                 "Camera %d Video size %s: Number of dropped frames %d must not"
732                                 + " be larger than %d",
733                                 cameraId, videoSz.toString(), totalDroppedFrames,
734                                 kFrameDrop_Tolerence),
735                         kFrameDrop_Tolerence, totalDroppedFrames);
736             }
737             closeImageReader();
738         }
739     }
740 
741     /**
742      * Configure video snapshot request according to the still capture size
743      */
prepareVideoSnapshot( CaptureRequest.Builder requestBuilder, ImageReader.OnImageAvailableListener imageListener)744     private void prepareVideoSnapshot(
745             CaptureRequest.Builder requestBuilder,
746             ImageReader.OnImageAvailableListener imageListener)
747             throws Exception {
748         mReader.setOnImageAvailableListener(imageListener, mHandler);
749         assertNotNull("Recording surface must be non-null!", mRecordingSurface);
750         requestBuilder.addTarget(mRecordingSurface);
751         assertNotNull("Preview surface must be non-null!", mPreviewSurface);
752         requestBuilder.addTarget(mPreviewSurface);
753         assertNotNull("Reader surface must be non-null!", mReaderSurface);
754         requestBuilder.addTarget(mReaderSurface);
755     }
756 
757     /**
758      * Update preview size with video size.
759      *
760      * <p>Preview size will be capped with max preview size.</p>
761      *
762      * @param videoSize The video size used for preview.
763      * @param videoFrameRate The video frame rate
764      *
765      */
updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate)766     private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
767         if (mOrderedPreviewSizes == null) {
768             throw new IllegalStateException("supported preview size list is not initialized yet");
769         }
770         final float FRAME_DURATION_TOLERANCE = 0.01f;
771         long videoFrameDuration = (long) (1e9 / videoFrameRate *
772                 (1.0 + FRAME_DURATION_TOLERANCE));
773         HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
774                 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE);
775         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
776         Size previewSize = null;
777         if (videoSize.getWidth() > maxPreviewSize.getWidth() ||
778                 videoSize.getHeight() > maxPreviewSize.getHeight()) {
779             for (Size s : mOrderedPreviewSizes) {
780                 Long frameDuration = minFrameDurationMap.get(s);
781                 if (mStaticInfo.isHardwareLevelLegacy()) {
782                     // Legacy doesn't report min frame duration
783                     frameDuration = new Long(0);
784                 }
785                 assertTrue("Cannot find minimum frame duration for private size" + s,
786                         frameDuration != null);
787                 if (frameDuration <= videoFrameDuration &&
788                         s.getWidth() <= videoSize.getWidth() &&
789                         s.getHeight() <= videoSize.getHeight()) {
790                     Log.w(TAG, "Overwrite preview size from " + videoSize.toString() +
791                             " to " + s.toString());
792                     previewSize = s;
793                     break;
794                     // If all preview size doesn't work then we fallback to video size
795                 }
796             }
797         }
798         if (previewSize == null) {
799             previewSize = videoSize;
800         }
801         updatePreviewSurface(previewSize);
802     }
803 
804     /**
805      * Configure MediaRecorder recording session with CamcorderProfile, prepare
806      * the recording surface.
807      */
prepareRecordingWithProfile(CamcorderProfile profile)808     private void prepareRecordingWithProfile(CamcorderProfile profile)
809             throws Exception {
810         // Prepare MediaRecorder.
811         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
812         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
813         mMediaRecorder.setProfile(profile);
814         mMediaRecorder.setOutputFile(mOutMediaFileName);
815         if (mPersistentSurface != null) {
816             mMediaRecorder.setInputSurface(mPersistentSurface);
817             mRecordingSurface = mPersistentSurface;
818         }
819         mMediaRecorder.prepare();
820         if (mPersistentSurface == null) {
821             mRecordingSurface = mMediaRecorder.getSurface();
822         }
823         assertNotNull("Recording surface must be non-null!", mRecordingSurface);
824         mVideoFrameRate = profile.videoFrameRate;
825         mVideoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
826     }
827 
828     /**
829      * Configure MediaRecorder recording session with CamcorderProfile, prepare
830      * the recording surface. Use AVC for video compression, AAC for audio compression.
831      * Both are required for android devices by android CDD.
832      */
prepareRecording(Size sz, int videoFrameRate, int captureRate)833     private void prepareRecording(Size sz, int videoFrameRate, int captureRate)
834             throws Exception {
835         // Prepare MediaRecorder.
836         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
837         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
838         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
839         mMediaRecorder.setOutputFile(mOutMediaFileName);
840         mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz));
841         mMediaRecorder.setVideoFrameRate(videoFrameRate);
842         mMediaRecorder.setCaptureRate(captureRate);
843         mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight());
844         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
845         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
846         if (mPersistentSurface != null) {
847             mMediaRecorder.setInputSurface(mPersistentSurface);
848             mRecordingSurface = mPersistentSurface;
849         }
850         mMediaRecorder.prepare();
851         if (mPersistentSurface == null) {
852             mRecordingSurface = mMediaRecorder.getSurface();
853         }
854         assertNotNull("Recording surface must be non-null!", mRecordingSurface);
855         mVideoFrameRate = videoFrameRate;
856         mVideoSize = sz;
857     }
858 
startRecording(boolean useMediaRecorder, CameraCaptureSession.CaptureCallback listener, boolean useVideoStab)859     private void startRecording(boolean useMediaRecorder,
860             CameraCaptureSession.CaptureCallback listener, boolean useVideoStab) throws Exception {
861         if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) {
862             throw new IllegalArgumentException("Video stabilization is not supported");
863         }
864 
865         List<Surface> outputSurfaces = new ArrayList<Surface>(2);
866         assertTrue("Both preview and recording surfaces should be valid",
867                 mPreviewSurface.isValid() && mRecordingSurface.isValid());
868         outputSurfaces.add(mPreviewSurface);
869         outputSurfaces.add(mRecordingSurface);
870         // Video snapshot surface
871         if (mReaderSurface != null) {
872             outputSurfaces.add(mReaderSurface);
873         }
874         mSessionListener = new BlockingSessionCallback();
875         mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
876 
877         CaptureRequest.Builder recordingRequestBuilder =
878                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
879         // Make sure camera output frame rate is set to correct value.
880         Range<Integer> fpsRange = Range.create(mVideoFrameRate, mVideoFrameRate);
881         recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
882         if (useVideoStab) {
883             recordingRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
884                     CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
885         }
886         recordingRequestBuilder.addTarget(mRecordingSurface);
887         recordingRequestBuilder.addTarget(mPreviewSurface);
888         mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler);
889 
890         if (useMediaRecorder) {
891             mMediaRecorder.start();
892         } else {
893             // TODO: need implement MediaCodec path.
894         }
895         mRecordingStartTime = SystemClock.elapsedRealtime();
896     }
897 
stopCameraStreaming()898     private void stopCameraStreaming() throws Exception {
899         if (VERBOSE) {
900             Log.v(TAG, "Stopping camera streaming and waiting for idle");
901         }
902         // Stop repeating, wait for captures to complete, and disconnect from
903         // surfaces
904         mSession.close();
905         mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
906     }
907 
908     // Stop recording and return the estimated video duration in milliseconds.
stopRecording(boolean useMediaRecorder)909     private int stopRecording(boolean useMediaRecorder) throws Exception {
910         long stopRecordingTime = SystemClock.elapsedRealtime();
911         if (useMediaRecorder) {
912             stopCameraStreaming();
913 
914             mMediaRecorder.stop();
915             // Can reuse the MediaRecorder object after reset.
916             mMediaRecorder.reset();
917         } else {
918             // TODO: need implement MediaCodec path.
919         }
920         if (mPersistentSurface == null && mRecordingSurface != null) {
921             mRecordingSurface.release();
922             mRecordingSurface = null;
923         }
924         return (int) (stopRecordingTime - mRecordingStartTime);
925     }
926 
releaseRecorder()927     private void releaseRecorder() {
928         if (mMediaRecorder != null) {
929             mMediaRecorder.release();
930             mMediaRecorder = null;
931         }
932     }
933 
validateRecording(Size sz, int expectedDurationMs)934     private void validateRecording(Size sz, int expectedDurationMs) throws Exception {
935         File outFile = new File(mOutMediaFileName);
936         assertTrue("No video is recorded", outFile.exists());
937 
938         MediaExtractor extractor = new MediaExtractor();
939         try {
940             extractor.setDataSource(mOutMediaFileName);
941             long durationUs = 0;
942             int width = -1, height = -1;
943             int numTracks = extractor.getTrackCount();
944             final String VIDEO_MIME_TYPE = "video";
945             for (int i = 0; i < numTracks; i++) {
946                 MediaFormat format = extractor.getTrackFormat(i);
947                 String mime = format.getString(MediaFormat.KEY_MIME);
948                 if (mime.contains(VIDEO_MIME_TYPE)) {
949                     Log.i(TAG, "video format is: " + format.toString());
950                     durationUs = format.getLong(MediaFormat.KEY_DURATION);
951                     width = format.getInteger(MediaFormat.KEY_WIDTH);
952                     height = format.getInteger(MediaFormat.KEY_HEIGHT);
953                     break;
954                 }
955             }
956             Size videoSz = new Size(width, height);
957             assertTrue("Video size doesn't match, expected " + sz.toString() +
958                     " got " + videoSz.toString(), videoSz.equals(sz));
959             int duration = (int) (durationUs / 1000);
960             if (VERBOSE) {
961                 Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms",
962                                          duration, expectedDurationMs));
963             }
964 
965             // TODO: Don't skip this for video snapshot
966             if (!mStaticInfo.isHardwareLevelLegacy()) {
967                 assertTrue(String.format(
968                         "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.",
969                         mCamera.getId(), duration, expectedDurationMs),
970                         Math.abs(duration - expectedDurationMs) <
971                         DURATION_MARGIN * expectedDurationMs);
972             }
973         } finally {
974             extractor.release();
975             if (!DEBUG_DUMP) {
976                 outFile.delete();
977             }
978         }
979     }
980 
981     /**
982      * Validate video snapshot capture image object sanity and test.
983      *
984      * <p> Check for size, format and jpeg decoding</p>
985      *
986      * @param image The JPEG image to be verified.
987      * @param size The JPEG capture size to be verified against.
988      */
989     private void validateVideoSnapshotCapture(Image image, Size size) {
990         CameraTestUtils.validateImage(image, size.getWidth(), size.getHeight(),
991                 ImageFormat.JPEG, /*filePath*/null);
992     }
993 
994     /**
995      * Validate if video snapshot causes frame drop.
996      * Here frame drop is defined as frame duration >= 2 * expected frame duration.
997      * Return the estimated number of frames dropped during video snapshot
998      */
999     private int validateFrameDropAroundVideoSnapshot(
1000             SimpleCaptureCallback resultListener, long imageTimeStamp) {
1001         double expectedDurationMs = 1000.0 / mVideoFrameRate;
1002         CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1003         long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP);
1004         while (!resultListener.hasMoreResults()) {
1005             CaptureResult currentResult =
1006                     resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1007             long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP);
1008             if (currentTS == imageTimeStamp) {
1009                 // validate the timestamp before and after, then return
1010                 CaptureResult nextResult =
1011                         resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1012                 long nextTS = getValueNotNull(nextResult, CaptureResult.SENSOR_TIMESTAMP);
1013                 double durationMs = (currentTS - prevTS) / 1000000.0;
1014                 int totalFramesDropped = 0;
1015 
1016                 // Snapshots in legacy mode pause the preview briefly.  Skip the duration
1017                 // requirements for legacy mode unless this is fixed.
1018                 if (!mStaticInfo.isHardwareLevelLegacy()) {
1019                     mCollector.expectTrue(
1020                             String.format(
1021                                     "Video %dx%d Frame drop detected before video snapshot: " +
1022                                             "duration %.2fms (expected %.2fms)",
1023                                     mVideoSize.getWidth(), mVideoSize.getHeight(),
1024                                     durationMs, expectedDurationMs
1025                             ),
1026                             durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
1027                     );
1028                     // Log a warning is there is any frame drop detected.
1029                     if (durationMs >= expectedDurationMs * 2) {
1030                         Log.w(TAG, String.format(
1031                                 "Video %dx%d Frame drop detected before video snapshot: " +
1032                                         "duration %.2fms (expected %.2fms)",
1033                                 mVideoSize.getWidth(), mVideoSize.getHeight(),
1034                                 durationMs, expectedDurationMs
1035                         ));
1036                     }
1037 
1038                     durationMs = (nextTS - currentTS) / 1000000.0;
1039                     mCollector.expectTrue(
1040                             String.format(
1041                                     "Video %dx%d Frame drop detected after video snapshot: " +
1042                                             "duration %.2fms (expected %.2fms)",
1043                                     mVideoSize.getWidth(), mVideoSize.getHeight(),
1044                                     durationMs, expectedDurationMs
1045                             ),
1046                             durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
1047                     );
1048                     // Log a warning is there is any frame drop detected.
1049                     if (durationMs >= expectedDurationMs * 2) {
1050                         Log.w(TAG, String.format(
1051                                 "Video %dx%d Frame drop detected after video snapshot: " +
1052                                         "duration %fms (expected %fms)",
1053                                 mVideoSize.getWidth(), mVideoSize.getHeight(),
1054                                 durationMs, expectedDurationMs
1055                         ));
1056                     }
1057 
1058                     double totalDurationMs = (nextTS - prevTS) / 1000000.0;
1059                     // Minus 2 for the expected 2 frames interval
1060                     totalFramesDropped = (int) (totalDurationMs / expectedDurationMs) - 2;
1061                     if (totalFramesDropped < 0) {
1062                         Log.w(TAG, "totalFrameDropped is " + totalFramesDropped +
1063                                 ". Video frame rate might be too fast.");
1064                     }
1065                     totalFramesDropped = Math.max(0, totalFramesDropped);
1066                 }
1067                 return totalFramesDropped;
1068             }
1069             prevTS = currentTS;
1070         }
1071         throw new AssertionFailedError(
1072                 "Video snapshot timestamp does not match any of capture results!");
1073     }
1074 
1075     /**
1076      * Calculate a video bit rate based on the size. The bit rate is scaled
1077      * based on ratio of video size to 1080p size.
1078      */
1079     private int getVideoBitRate(Size sz) {
1080         int rate = BIT_RATE_1080P;
1081         float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080);
1082         rate = (int)(rate * scaleFactor);
1083 
1084         // Clamp to the MIN, MAX range.
1085         return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
1086     }
1087 }
1088