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