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                 CaptureRequest request = videoSnapshotRequestBuilder.build();
663 
664                 // Start recording
665                 startRecording(/* useMediaRecorder */true, resultListener, /*useVideoStab*/false);
666                 long startTime = SystemClock.elapsedRealtime();
667 
668                 // Record certain duration.
669                 SystemClock.sleep(RECORDING_DURATION_MS / 2);
670 
671                 // take video snapshot
672                 if (burstTest) {
673                     List<CaptureRequest> requests =
674                             new ArrayList<CaptureRequest>(BURST_VIDEO_SNAPSHOT_NUM);
675                     for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
676                         requests.add(request);
677                     }
678                     mSession.captureBurst(requests, resultListener, mHandler);
679                 } else {
680                     mSession.capture(request, resultListener, mHandler);
681                 }
682 
683                 // make sure recording is still going after video snapshot
684                 SystemClock.sleep(RECORDING_DURATION_MS / 2);
685 
686                 // Stop recording and preview
687                 int durationMs = stopRecording(/* useMediaRecorder */true);
688                 // For non-burst test, use number of frames to also double check video frame rate.
689                 // Burst video snapshot is allowed to cause frame rate drop, so do not use number
690                 // of frames to estimate duration
691                 if (!burstTest) {
692                     durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
693                         profile.videoFrameRate);
694                 }
695 
696                 // Validation recorded video
697                 validateRecording(videoSz, durationMs);
698 
699                 if (burstTest) {
700                     for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
701                         Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
702                         validateVideoSnapshotCapture(image, videoSnapshotSz);
703                         image.close();
704                     }
705                 } else {
706                     // validate video snapshot image
707                     Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
708                     validateVideoSnapshotCapture(image, videoSnapshotSz);
709 
710                     // validate if there is framedrop around video snapshot
711                     totalDroppedFrames +=  validateFrameDropAroundVideoSnapshot(
712                             resultListener, image.getTimestamp());
713 
714                     //TODO: validate jittering. Should move to PTS
715                     //validateJittering(resultListener);
716 
717                     image.close();
718                 }
719             }
720 
721             if (!burstTest) {
722                 Log.w(TAG, String.format("Camera %d Video size %s: Number of dropped frames " +
723                         "detected in %d trials is %d frames.", cameraId, videoSz.toString(),
724                         numTestIterations, totalDroppedFrames));
725                 mCollector.expectLessOrEqual(
726                         String.format(
727                                 "Camera %d Video size %s: Number of dropped frames %d must not"
728                                 + " be larger than %d",
729                                 cameraId, videoSz.toString(), totalDroppedFrames,
730                                 kFrameDrop_Tolerence),
731                         kFrameDrop_Tolerence, totalDroppedFrames);
732             }
733             closeImageReader();
734         }
735     }
736 
737     /**
738      * Configure video snapshot request according to the still capture size
739      */
prepareVideoSnapshot( CaptureRequest.Builder requestBuilder, ImageReader.OnImageAvailableListener imageListener)740     private void prepareVideoSnapshot(
741             CaptureRequest.Builder requestBuilder,
742             ImageReader.OnImageAvailableListener imageListener)
743             throws Exception {
744         mReader.setOnImageAvailableListener(imageListener, mHandler);
745         assertNotNull("Recording surface must be non-null!", mRecordingSurface);
746         requestBuilder.addTarget(mRecordingSurface);
747         assertNotNull("Preview surface must be non-null!", mPreviewSurface);
748         requestBuilder.addTarget(mPreviewSurface);
749         assertNotNull("Reader surface must be non-null!", mReaderSurface);
750         requestBuilder.addTarget(mReaderSurface);
751     }
752 
753     /**
754      * Update preview size with video size.
755      *
756      * <p>Preview size will be capped with max preview size.</p>
757      *
758      * @param videoSize The video size used for preview.
759      * @param videoFrameRate The video frame rate
760      *
761      */
updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate)762     private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
763         if (mOrderedPreviewSizes == null) {
764             throw new IllegalStateException("supported preview size list is not initialized yet");
765         }
766         final float FRAME_DURATION_TOLERANCE = 0.01f;
767         long videoFrameDuration = (long) (1e9 / videoFrameRate *
768                 (1.0 + FRAME_DURATION_TOLERANCE));
769         HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
770                 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE);
771         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
772         Size previewSize = null;
773         if (videoSize.getWidth() > maxPreviewSize.getWidth() ||
774                 videoSize.getHeight() > maxPreviewSize.getHeight()) {
775             for (Size s : mOrderedPreviewSizes) {
776                 Long frameDuration = minFrameDurationMap.get(s);
777                 if (mStaticInfo.isHardwareLevelLegacy()) {
778                     // Legacy doesn't report min frame duration
779                     frameDuration = new Long(0);
780                 }
781                 assertTrue("Cannot find minimum frame duration for private size" + s,
782                         frameDuration != null);
783                 if (frameDuration <= videoFrameDuration &&
784                         s.getWidth() <= videoSize.getWidth() &&
785                         s.getHeight() <= videoSize.getHeight()) {
786                     Log.w(TAG, "Overwrite preview size from " + videoSize.toString() +
787                             " to " + s.toString());
788                     previewSize = s;
789                     break;
790                     // If all preview size doesn't work then we fallback to video size
791                 }
792             }
793         }
794         if (previewSize == null) {
795             previewSize = videoSize;
796         }
797         updatePreviewSurface(previewSize);
798     }
799 
800     /**
801      * Configure MediaRecorder recording session with CamcorderProfile, prepare
802      * the recording surface.
803      */
prepareRecordingWithProfile(CamcorderProfile profile)804     private void prepareRecordingWithProfile(CamcorderProfile profile)
805             throws Exception {
806         // Prepare MediaRecorder.
807         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
808         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
809         mMediaRecorder.setProfile(profile);
810         mMediaRecorder.setOutputFile(mOutMediaFileName);
811         if (mPersistentSurface != null) {
812             mMediaRecorder.setInputSurface(mPersistentSurface);
813             mRecordingSurface = mPersistentSurface;
814         }
815         mMediaRecorder.prepare();
816         if (mPersistentSurface == null) {
817             mRecordingSurface = mMediaRecorder.getSurface();
818         }
819         assertNotNull("Recording surface must be non-null!", mRecordingSurface);
820         mVideoFrameRate = profile.videoFrameRate;
821         mVideoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
822     }
823 
824     /**
825      * Configure MediaRecorder recording session with CamcorderProfile, prepare
826      * the recording surface. Use AVC for video compression, AAC for audio compression.
827      * Both are required for android devices by android CDD.
828      */
prepareRecording(Size sz, int videoFrameRate, int captureRate)829     private void prepareRecording(Size sz, int videoFrameRate, int captureRate)
830             throws Exception {
831         // Prepare MediaRecorder.
832         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
833         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
834         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
835         mMediaRecorder.setOutputFile(mOutMediaFileName);
836         mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz));
837         mMediaRecorder.setVideoFrameRate(videoFrameRate);
838         mMediaRecorder.setCaptureRate(captureRate);
839         mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight());
840         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
841         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
842         if (mPersistentSurface != null) {
843             mMediaRecorder.setInputSurface(mPersistentSurface);
844             mRecordingSurface = mPersistentSurface;
845         }
846         mMediaRecorder.prepare();
847         if (mPersistentSurface == null) {
848             mRecordingSurface = mMediaRecorder.getSurface();
849         }
850         assertNotNull("Recording surface must be non-null!", mRecordingSurface);
851         mVideoFrameRate = videoFrameRate;
852         mVideoSize = sz;
853     }
854 
startRecording(boolean useMediaRecorder, CameraCaptureSession.CaptureCallback listener, boolean useVideoStab)855     private void startRecording(boolean useMediaRecorder,
856             CameraCaptureSession.CaptureCallback listener, boolean useVideoStab) throws Exception {
857         if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) {
858             throw new IllegalArgumentException("Video stabilization is not supported");
859         }
860 
861         List<Surface> outputSurfaces = new ArrayList<Surface>(2);
862         assertTrue("Both preview and recording surfaces should be valid",
863                 mPreviewSurface.isValid() && mRecordingSurface.isValid());
864         outputSurfaces.add(mPreviewSurface);
865         outputSurfaces.add(mRecordingSurface);
866         // Video snapshot surface
867         if (mReaderSurface != null) {
868             outputSurfaces.add(mReaderSurface);
869         }
870         mSessionListener = new BlockingSessionCallback();
871         mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
872 
873         CaptureRequest.Builder recordingRequestBuilder =
874                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
875         // Make sure camera output frame rate is set to correct value.
876         Range<Integer> fpsRange = Range.create(mVideoFrameRate, mVideoFrameRate);
877         recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
878         if (useVideoStab) {
879             recordingRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE,
880                     CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE_ON);
881         }
882         recordingRequestBuilder.addTarget(mRecordingSurface);
883         recordingRequestBuilder.addTarget(mPreviewSurface);
884         mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler);
885 
886         if (useMediaRecorder) {
887             mMediaRecorder.start();
888         } else {
889             // TODO: need implement MediaCodec path.
890         }
891         mRecordingStartTime = SystemClock.elapsedRealtime();
892     }
893 
stopCameraStreaming()894     private void stopCameraStreaming() throws Exception {
895         if (VERBOSE) {
896             Log.v(TAG, "Stopping camera streaming and waiting for idle");
897         }
898         // Stop repeating, wait for captures to complete, and disconnect from
899         // surfaces
900         mSession.close();
901         mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
902     }
903 
904     // Stop recording and return the estimated video duration in milliseconds.
stopRecording(boolean useMediaRecorder)905     private int stopRecording(boolean useMediaRecorder) throws Exception {
906         long stopRecordingTime = SystemClock.elapsedRealtime();
907         if (useMediaRecorder) {
908             stopCameraStreaming();
909 
910             mMediaRecorder.stop();
911             // Can reuse the MediaRecorder object after reset.
912             mMediaRecorder.reset();
913         } else {
914             // TODO: need implement MediaCodec path.
915         }
916         if (mPersistentSurface == null && mRecordingSurface != null) {
917             mRecordingSurface.release();
918             mRecordingSurface = null;
919         }
920         return (int) (stopRecordingTime - mRecordingStartTime);
921     }
922 
releaseRecorder()923     private void releaseRecorder() {
924         if (mMediaRecorder != null) {
925             mMediaRecorder.release();
926             mMediaRecorder = null;
927         }
928     }
929 
validateRecording(Size sz, int expectedDurationMs)930     private void validateRecording(Size sz, int expectedDurationMs) throws Exception {
931         File outFile = new File(mOutMediaFileName);
932         assertTrue("No video is recorded", outFile.exists());
933 
934         MediaExtractor extractor = new MediaExtractor();
935         try {
936             extractor.setDataSource(mOutMediaFileName);
937             long durationUs = 0;
938             int width = -1, height = -1;
939             int numTracks = extractor.getTrackCount();
940             final String VIDEO_MIME_TYPE = "video";
941             for (int i = 0; i < numTracks; i++) {
942                 MediaFormat format = extractor.getTrackFormat(i);
943                 String mime = format.getString(MediaFormat.KEY_MIME);
944                 if (mime.contains(VIDEO_MIME_TYPE)) {
945                     Log.i(TAG, "video format is: " + format.toString());
946                     durationUs = format.getLong(MediaFormat.KEY_DURATION);
947                     width = format.getInteger(MediaFormat.KEY_WIDTH);
948                     height = format.getInteger(MediaFormat.KEY_HEIGHT);
949                     break;
950                 }
951             }
952             Size videoSz = new Size(width, height);
953             assertTrue("Video size doesn't match, expected " + sz.toString() +
954                     " got " + videoSz.toString(), videoSz.equals(sz));
955             int duration = (int) (durationUs / 1000);
956             if (VERBOSE) {
957                 Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms",
958                                          duration, expectedDurationMs));
959             }
960 
961             // TODO: Don't skip this for video snapshot
962             if (!mStaticInfo.isHardwareLevelLegacy()) {
963                 assertTrue(String.format(
964                         "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.",
965                         mCamera.getId(), duration, expectedDurationMs),
966                         Math.abs(duration - expectedDurationMs) <
967                         DURATION_MARGIN * expectedDurationMs);
968             }
969         } finally {
970             extractor.release();
971             if (!DEBUG_DUMP) {
972                 outFile.delete();
973             }
974         }
975     }
976 
977     /**
978      * Validate video snapshot capture image object sanity and test.
979      *
980      * <p> Check for size, format and jpeg decoding</p>
981      *
982      * @param image The JPEG image to be verified.
983      * @param size The JPEG capture size to be verified against.
984      */
985     private void validateVideoSnapshotCapture(Image image, Size size) {
986         CameraTestUtils.validateImage(image, size.getWidth(), size.getHeight(),
987                 ImageFormat.JPEG, /*filePath*/null);
988     }
989 
990     /**
991      * Validate if video snapshot causes frame drop.
992      * Here frame drop is defined as frame duration >= 2 * expected frame duration.
993      * Return the estimated number of frames dropped during video snapshot
994      */
995     private int validateFrameDropAroundVideoSnapshot(
996             SimpleCaptureCallback resultListener, long imageTimeStamp) {
997         double expectedDurationMs = 1000.0 / mVideoFrameRate;
998         CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
999         long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP);
1000         while (!resultListener.hasMoreResults()) {
1001             CaptureResult currentResult =
1002                     resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1003             long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP);
1004             if (currentTS == imageTimeStamp) {
1005                 // validate the timestamp before and after, then return
1006                 CaptureResult nextResult =
1007                         resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1008                 long nextTS = getValueNotNull(nextResult, CaptureResult.SENSOR_TIMESTAMP);
1009                 double durationMs = (currentTS - prevTS) / 1000000.0;
1010                 int totalFramesDropped = 0;
1011 
1012                 // Snapshots in legacy mode pause the preview briefly.  Skip the duration
1013                 // requirements for legacy mode unless this is fixed.
1014                 if (!mStaticInfo.isHardwareLevelLegacy()) {
1015                     mCollector.expectTrue(
1016                             String.format(
1017                                     "Video %dx%d Frame drop detected before video snapshot: " +
1018                                             "duration %.2fms (expected %.2fms)",
1019                                     mVideoSize.getWidth(), mVideoSize.getHeight(),
1020                                     durationMs, expectedDurationMs
1021                             ),
1022                             durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
1023                     );
1024                     // Log a warning is there is any frame drop detected.
1025                     if (durationMs >= expectedDurationMs * 2) {
1026                         Log.w(TAG, String.format(
1027                                 "Video %dx%d Frame drop detected before video snapshot: " +
1028                                         "duration %.2fms (expected %.2fms)",
1029                                 mVideoSize.getWidth(), mVideoSize.getHeight(),
1030                                 durationMs, expectedDurationMs
1031                         ));
1032                     }
1033 
1034                     durationMs = (nextTS - currentTS) / 1000000.0;
1035                     mCollector.expectTrue(
1036                             String.format(
1037                                     "Video %dx%d Frame drop detected after video snapshot: " +
1038                                             "duration %.2fms (expected %.2fms)",
1039                                     mVideoSize.getWidth(), mVideoSize.getHeight(),
1040                                     durationMs, expectedDurationMs
1041                             ),
1042                             durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
1043                     );
1044                     // Log a warning is there is any frame drop detected.
1045                     if (durationMs >= expectedDurationMs * 2) {
1046                         Log.w(TAG, String.format(
1047                                 "Video %dx%d Frame drop detected after video snapshot: " +
1048                                         "duration %fms (expected %fms)",
1049                                 mVideoSize.getWidth(), mVideoSize.getHeight(),
1050                                 durationMs, expectedDurationMs
1051                         ));
1052                     }
1053 
1054                     double totalDurationMs = (nextTS - prevTS) / 1000000.0;
1055                     // Minus 2 for the expected 2 frames interval
1056                     totalFramesDropped = (int) (totalDurationMs / expectedDurationMs) - 2;
1057                     if (totalFramesDropped < 0) {
1058                         Log.w(TAG, "totalFrameDropped is " + totalFramesDropped +
1059                                 ". Video frame rate might be too fast.");
1060                     }
1061                     totalFramesDropped = Math.max(0, totalFramesDropped);
1062                 }
1063                 return totalFramesDropped;
1064             }
1065             prevTS = currentTS;
1066         }
1067         throw new AssertionFailedError(
1068                 "Video snapshot timestamp does not match any of capture results!");
1069     }
1070 
1071     /**
1072      * Calculate a video bit rate based on the size. The bit rate is scaled
1073      * based on ratio of video size to 1080p size.
1074      */
1075     private int getVideoBitRate(Size sz) {
1076         int rate = BIT_RATE_1080P;
1077         float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080);
1078         rate = (int)(rate * scaleFactor);
1079 
1080         // Clamp to the MIN, MAX range.
1081         return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
1082     }
1083 }
1084