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