1 /* 2 * Copyright (C) 2014 The Android Open Source Project Licensed under the Apache 3 * License, Version 2.0 (the "License"); you may not use this file except in 4 * compliance with the License. You may obtain a copy of the License at 5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law 6 * or agreed to in writing, software distributed under the License is 7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 8 * KIND, either express or implied. See the License for the specific language 9 * governing permissions and limitations under the License. 10 */ 11 12 package android.hardware.camera2.cts; 13 14 import static android.hardware.camera2.cts.CameraTestUtils.*; 15 import static com.android.ex.camera2.blocking.BlockingSessionCallback.*; 16 17 import android.cts.util.MediaUtils; 18 import android.graphics.ImageFormat; 19 import android.hardware.camera2.CameraCharacteristics; 20 import android.hardware.camera2.CameraCaptureSession; 21 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; 22 import android.hardware.camera2.CameraDevice; 23 import android.hardware.camera2.CaptureRequest; 24 import android.hardware.camera2.CaptureResult; 25 import android.hardware.camera2.params.StreamConfigurationMap; 26 import android.util.Size; 27 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; 28 import android.media.CamcorderProfile; 29 import android.media.MediaCodec; 30 import android.media.MediaCodecInfo; 31 import android.media.MediaCodecInfo.CodecCapabilities; 32 import android.media.MediaCodecInfo.CodecProfileLevel; 33 import android.media.Image; 34 import android.media.ImageReader; 35 import android.media.MediaCodecList; 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.view.Surface; 45 46 import com.android.ex.camera2.blocking.BlockingSessionCallback; 47 48 import junit.framework.AssertionFailedError; 49 50 import java.io.File; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.List; 54 import java.util.HashMap; 55 56 /** 57 * CameraDevice video recording use case tests by using MediaRecorder and 58 * MediaCodec. 59 */ 60 @LargeTest 61 public class RecordingTest extends Camera2SurfaceViewTestCase { 62 private static final String TAG = "RecordingTest"; 63 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 64 private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG); 65 private static final int RECORDING_DURATION_MS = 3000; 66 private static final float DURATION_MARGIN = 0.2f; 67 private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0; 68 private static final int BIT_RATE_1080P = 16000000; 69 private static final int BIT_RATE_MIN = 64000; 70 private static final int BIT_RATE_MAX = 40000000; 71 private static final int VIDEO_FRAME_RATE = 30; 72 private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath(); 73 private static final int[] mCamcorderProfileList = { 74 CamcorderProfile.QUALITY_HIGH, 75 CamcorderProfile.QUALITY_2160P, 76 CamcorderProfile.QUALITY_1080P, 77 CamcorderProfile.QUALITY_720P, 78 CamcorderProfile.QUALITY_480P, 79 CamcorderProfile.QUALITY_CIF, 80 CamcorderProfile.QUALITY_QCIF, 81 CamcorderProfile.QUALITY_QVGA, 82 CamcorderProfile.QUALITY_LOW, 83 }; 84 private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5; 85 private static final int BURST_VIDEO_SNAPSHOT_NUM = 3; 86 private static final int SLOWMO_SLOW_FACTOR = 4; 87 private static final int MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED = 4; 88 private List<Size> mSupportedVideoSizes; 89 private Surface mRecordingSurface; 90 private Surface mPersistentSurface; 91 private MediaRecorder mMediaRecorder; 92 private String mOutMediaFileName; 93 private int mVideoFrameRate; 94 private Size mVideoSize; 95 private long mRecordingStartTime; 96 97 @Override setUp()98 protected void setUp() throws Exception { 99 super.setUp(); 100 } 101 102 @Override tearDown()103 protected void tearDown() throws Exception { 104 super.tearDown(); 105 } 106 doBasicRecording()107 private void doBasicRecording() throws Exception { 108 for (int i = 0; i < mCameraIds.length; i++) { 109 try { 110 Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]); 111 // Re-use the MediaRecorder object for the same camera device. 112 mMediaRecorder = new MediaRecorder(); 113 openDevice(mCameraIds[i]); 114 if (!mStaticInfo.isColorOutputSupported()) { 115 Log.i(TAG, "Camera " + mCameraIds[i] + 116 " does not support color outputs, skipping"); 117 continue; 118 } 119 initSupportedVideoSize(mCameraIds[i]); 120 121 basicRecordingTestByCamera(mCamcorderProfileList); 122 } finally { 123 closeDevice(); 124 releaseRecorder(); 125 } 126 } 127 } 128 129 /** 130 * <p> 131 * Test basic camera recording. 132 * </p> 133 * <p> 134 * This test covers the typical basic use case of camera recording. 135 * MediaRecorder is used to record the audio and video, CamcorderProfile is 136 * used to configure the MediaRecorder. It goes through the pre-defined 137 * CamcorderProfile list, test each profile configuration and validate the 138 * recorded video. Preview is set to the video size. 139 * </p> 140 */ testBasicRecording()141 public void testBasicRecording() throws Exception { 142 doBasicRecording(); 143 } 144 145 /** 146 * <p> 147 * Test basic camera recording from a persistent input surface. 148 * </p> 149 * <p> 150 * This test is similar to testBasicRecording except that MediaRecorder records 151 * from a persistent input surface that's used across multiple recording sessions. 152 * </p> 153 */ testRecordingFromPersistentSurface()154 public void testRecordingFromPersistentSurface() throws Exception { 155 if (!MediaUtils.checkCodecForDomain(true /* encoder */, "video")) { 156 return; // skipped 157 } 158 mPersistentSurface = MediaCodec.createPersistentInputSurface(); 159 assertNotNull("Failed to create persistent input surface!", mPersistentSurface); 160 161 try { 162 doBasicRecording(); 163 } finally { 164 mPersistentSurface.release(); 165 mPersistentSurface = null; 166 } 167 } 168 169 /** 170 * <p> 171 * Test camera recording for all supported sizes by using MediaRecorder. 172 * </p> 173 * <p> 174 * This test covers camera recording for all supported sizes by camera. MediaRecorder 175 * is used to encode the video. Preview is set to the video size. Recorded videos are 176 * validated according to the recording configuration. 177 * </p> 178 */ testSupportedVideoSizes()179 public void testSupportedVideoSizes() throws Exception { 180 for (int i = 0; i < mCameraIds.length; i++) { 181 try { 182 Log.i(TAG, "Testing supported video size recording for camera " + mCameraIds[i]); 183 // Re-use the MediaRecorder object for the same camera device. 184 mMediaRecorder = new MediaRecorder(); 185 openDevice(mCameraIds[i]); 186 if (!mStaticInfo.isColorOutputSupported()) { 187 Log.i(TAG, "Camera " + mCameraIds[i] + 188 " does not support color outputs, skipping"); 189 continue; 190 } 191 initSupportedVideoSize(mCameraIds[i]); 192 193 recordingSizeTestByCamera(); 194 } finally { 195 closeDevice(); 196 releaseRecorder(); 197 } 198 } 199 } 200 201 /** 202 * Test different start/stop orders of Camera and Recorder. 203 * 204 * <p>The recording should be working fine for any kind of start/stop orders.</p> 205 */ testCameraRecorderOrdering()206 public void testCameraRecorderOrdering() { 207 // TODO: need implement 208 } 209 210 /** 211 * <p> 212 * Test camera recording for all supported sizes by using MediaCodec. 213 * </p> 214 * <p> 215 * This test covers video only recording for all supported sizes (camera and 216 * encoder). MediaCodec is used to encode the video. The recorded videos are 217 * validated according to the recording configuration. 218 * </p> 219 */ testMediaCodecRecording()220 public void testMediaCodecRecording() throws Exception { 221 // TODO. Need implement. 222 } 223 224 /** 225 * <p> 226 * Test video snapshot for each camera. 227 * </p> 228 * <p> 229 * This test covers video snapshot typical use case. The MediaRecorder is used to record the 230 * video for each available video size. The largest still capture size is selected to 231 * capture the JPEG image. The still capture images are validated according to the capture 232 * configuration. The timestamp of capture result before and after video snapshot is also 233 * checked to make sure no frame drop caused by video snapshot. 234 * </p> 235 */ testVideoSnapshot()236 public void testVideoSnapshot() throws Exception { 237 videoSnapshotHelper(/*burstTest*/false); 238 } 239 240 /** 241 * <p> 242 * Test burst video snapshot for each camera. 243 * </p> 244 * <p> 245 * This test covers burst video snapshot capture. The MediaRecorder is used to record the 246 * video for each available video size. The largest still capture size is selected to 247 * capture the JPEG image. {@value #BURST_VIDEO_SNAPSHOT_NUM} video snapshot requests will be 248 * sent during the test. The still capture images are validated according to the capture 249 * configuration. 250 * </p> 251 */ testBurstVideoSnapshot()252 public void testBurstVideoSnapshot() throws Exception { 253 videoSnapshotHelper(/*burstTest*/true); 254 } 255 256 /** 257 * Test timelapse recording, where capture rate is slower than video (playback) frame rate. 258 */ testTimelapseRecording()259 public void testTimelapseRecording() throws Exception { 260 // TODO. Need implement. 261 } 262 testSlowMotionRecording()263 public void testSlowMotionRecording() throws Exception { 264 slowMotionRecording(); 265 } 266 testConstrainedHighSpeedRecording()267 public void testConstrainedHighSpeedRecording() throws Exception { 268 constrainedHighSpeedRecording(); 269 } 270 271 /** 272 * <p> 273 * Test recording framerate accuracy when switching from low FPS to high FPS. 274 * </p> 275 * <p> 276 * This test first record a video with profile of lowest framerate then record a video with 277 * profile of highest framerate. Make sure that the video framerate are still accurate. 278 * </p> 279 */ testRecordingFramerateLowToHigh()280 public void testRecordingFramerateLowToHigh() throws Exception { 281 for (int i = 0; i < mCameraIds.length; i++) { 282 try { 283 Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]); 284 // Re-use the MediaRecorder object for the same camera device. 285 mMediaRecorder = new MediaRecorder(); 286 openDevice(mCameraIds[i]); 287 if (!mStaticInfo.isColorOutputSupported()) { 288 Log.i(TAG, "Camera " + mCameraIds[i] + 289 " does not support color outputs, skipping"); 290 continue; 291 } 292 initSupportedVideoSize(mCameraIds[i]); 293 294 int minFpsProfileId = -1, minFps = 1000; 295 int maxFpsProfileId = -1, maxFps = 0; 296 int cameraId = Integer.valueOf(mCamera.getId()); 297 298 for (int profileId : mCamcorderProfileList) { 299 if (!CamcorderProfile.hasProfile(cameraId, profileId)) { 300 continue; 301 } 302 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId); 303 if (profile.videoFrameRate < minFps) { 304 minFpsProfileId = profileId; 305 minFps = profile.videoFrameRate; 306 } 307 if (profile.videoFrameRate > maxFps) { 308 maxFpsProfileId = profileId; 309 maxFps = profile.videoFrameRate; 310 } 311 } 312 313 int camcorderProfileList[] = new int[] {minFpsProfileId, maxFpsProfileId}; 314 basicRecordingTestByCamera(camcorderProfileList); 315 } finally { 316 closeDevice(); 317 releaseRecorder(); 318 } 319 } 320 } 321 322 /** 323 * Test slow motion recording where capture rate (camera output) is different with 324 * video (playback) frame rate for each camera if high speed recording is supported 325 * by both camera and encoder. 326 * 327 * <p> 328 * Normal recording use cases make the capture rate (camera output frame 329 * rate) the same as the video (playback) frame rate. This guarantees that 330 * the motions in the scene play at the normal speed. If the capture rate is 331 * faster than video frame rate, for a given time duration, more number of 332 * frames are captured than it can be played in the same time duration. This 333 * generates "slow motion" effect during playback. 334 * </p> 335 */ slowMotionRecording()336 private void slowMotionRecording() throws Exception { 337 for (String id : mCameraIds) { 338 try { 339 Log.i(TAG, "Testing slow motion recording for camera " + id); 340 // Re-use the MediaRecorder object for the same camera device. 341 mMediaRecorder = new MediaRecorder(); 342 openDevice(id); 343 if (!mStaticInfo.isColorOutputSupported()) { 344 Log.i(TAG, "Camera " + id + 345 " does not support color outputs, skipping"); 346 continue; 347 } 348 if (!mStaticInfo.isHighSpeedVideoSupported()) { 349 continue; 350 } 351 352 StreamConfigurationMap config = 353 mStaticInfo.getValueFromKeyNonNull( 354 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 355 Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes(); 356 for (Size size : highSpeedVideoSizes) { 357 Range<Integer> fpsRange = getHighestHighSpeedFixedFpsRangeForSize(config, size); 358 mCollector.expectNotNull("Unable to find the fixed frame rate fps range for " + 359 "size " + size, fpsRange); 360 if (fpsRange == null) { 361 continue; 362 } 363 364 int captureRate = fpsRange.getLower(); 365 int videoFramerate = captureRate / SLOWMO_SLOW_FACTOR; 366 // Skip the test if the highest recording FPS supported by CamcorderProfile 367 if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) { 368 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps" 369 + " is not supported by CamcorderProfile"); 370 continue; 371 } 372 373 mOutMediaFileName = VIDEO_FILE_PATH + "/test_slowMo_video.mp4"; 374 if (DEBUG_DUMP) { 375 mOutMediaFileName = VIDEO_FILE_PATH + "/test_slowMo_video_" + id + "_" 376 + size.toString() + ".mp4"; 377 } 378 379 prepareRecording(size, videoFramerate, captureRate); 380 381 // prepare preview surface by using video size. 382 updatePreviewSurfaceWithVideo(size, captureRate); 383 384 // Start recording 385 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 386 startSlowMotionRecording(/*useMediaRecorder*/true, videoFramerate, captureRate, 387 fpsRange, resultListener, /*useHighSpeedSession*/false); 388 389 // Record certain duration. 390 SystemClock.sleep(RECORDING_DURATION_MS); 391 392 // Stop recording and preview 393 stopRecording(/*useMediaRecorder*/true); 394 // Convert number of frames camera produced into the duration in unit of ms. 395 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f / 396 videoFramerate); 397 398 // Validation. 399 validateRecording(size, durationMs); 400 } 401 402 } finally { 403 closeDevice(); 404 releaseRecorder(); 405 } 406 } 407 } 408 constrainedHighSpeedRecording()409 private void constrainedHighSpeedRecording() throws Exception { 410 for (String id : mCameraIds) { 411 try { 412 Log.i(TAG, "Testing constrained high speed recording for camera " + id); 413 // Re-use the MediaRecorder object for the same camera device. 414 mMediaRecorder = new MediaRecorder(); 415 openDevice(id); 416 417 if (!mStaticInfo.isConstrainedHighSpeedVideoSupported()) { 418 continue; 419 } 420 421 StreamConfigurationMap config = 422 mStaticInfo.getValueFromKeyNonNull( 423 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 424 Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes(); 425 for (Size size : highSpeedVideoSizes) { 426 List<Range<Integer>> fixedFpsRanges = 427 getHighSpeedFixedFpsRangeForSize(config, size); 428 mCollector.expectTrue("Unable to find the fixed frame rate fps range for " + 429 "size " + size, fixedFpsRanges.size() > 0); 430 // Test recording for each FPS range 431 for (Range<Integer> fpsRange : fixedFpsRanges) { 432 int captureRate = fpsRange.getLower(); 433 final int VIDEO_FRAME_RATE = 30; 434 // Skip the test if the highest recording FPS supported by CamcorderProfile 435 if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) { 436 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps" 437 + " is not supported by CamcorderProfile"); 438 continue; 439 } 440 441 mOutMediaFileName = VIDEO_FILE_PATH + "/test_cslowMo_video_" + captureRate + 442 "fps_" + id + "_" + size.toString() + ".mp4"; 443 444 prepareRecording(size, VIDEO_FRAME_RATE, captureRate); 445 446 // prepare preview surface by using video size. 447 updatePreviewSurfaceWithVideo(size, captureRate); 448 449 // Start recording 450 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 451 startSlowMotionRecording(/*useMediaRecorder*/true, VIDEO_FRAME_RATE, 452 captureRate, fpsRange, resultListener, 453 /*useHighSpeedSession*/true); 454 455 // Record certain duration. 456 SystemClock.sleep(RECORDING_DURATION_MS); 457 458 // Stop recording and preview 459 stopRecording(/*useMediaRecorder*/true); 460 // Convert number of frames camera produced into the duration in unit of ms. 461 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f / 462 VIDEO_FRAME_RATE); 463 464 // Validation. 465 validateRecording(size, durationMs); 466 } 467 } 468 469 } finally { 470 closeDevice(); 471 releaseRecorder(); 472 } 473 } 474 } 475 476 /** 477 * Get high speed FPS from CamcorderProfiles for a given size. 478 * 479 * @param size The size used to search the CamcorderProfiles for the FPS. 480 * @return high speed video FPS, 0 if the given size is not supported by the CamcorderProfiles. 481 */ getFpsFromHighSpeedProfileForSize(Size size)482 private int getFpsFromHighSpeedProfileForSize(Size size) { 483 for (int quality = CamcorderProfile.QUALITY_HIGH_SPEED_480P; 484 quality <= CamcorderProfile.QUALITY_HIGH_SPEED_2160P; quality++) { 485 if (CamcorderProfile.hasProfile(quality)) { 486 CamcorderProfile profile = CamcorderProfile.get(quality); 487 if (size.equals(new Size(profile.videoFrameWidth, profile.videoFrameHeight))){ 488 return profile.videoFrameRate; 489 } 490 } 491 } 492 493 return 0; 494 } 495 getHighestHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, Size size)496 private Range<Integer> getHighestHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, 497 Size size) { 498 Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size); 499 Range<Integer> maxRange = availableFpsRanges[0]; 500 boolean foundRange = false; 501 for (Range<Integer> range : availableFpsRanges) { 502 if (range.getLower().equals(range.getUpper()) && range.getLower() >= maxRange.getLower()) { 503 foundRange = true; 504 maxRange = range; 505 } 506 } 507 508 if (!foundRange) { 509 return null; 510 } 511 return maxRange; 512 } 513 getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, Size size)514 private List<Range<Integer>> getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, 515 Size size) { 516 Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size); 517 List<Range<Integer>> fixedRanges = new ArrayList<Range<Integer>>(); 518 for (Range<Integer> range : availableFpsRanges) { 519 if (range.getLower().equals(range.getUpper())) { 520 fixedRanges.add(range); 521 } 522 } 523 return fixedRanges; 524 } 525 startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate, int captureRate, Range<Integer> fpsRange, CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession)526 private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate, 527 int captureRate, Range<Integer> fpsRange, 528 CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession) throws Exception { 529 List<Surface> outputSurfaces = new ArrayList<Surface>(2); 530 assertTrue("Both preview and recording surfaces should be valid", 531 mPreviewSurface.isValid() && mRecordingSurface.isValid()); 532 outputSurfaces.add(mPreviewSurface); 533 outputSurfaces.add(mRecordingSurface); 534 // Video snapshot surface 535 if (mReaderSurface != null) { 536 outputSurfaces.add(mReaderSurface); 537 } 538 mSessionListener = new BlockingSessionCallback(); 539 mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession, 540 mSessionListener, mHandler); 541 542 // Create slow motion request list 543 List<CaptureRequest> slowMoRequests = null; 544 if (useHighSpeedSession) { 545 CaptureRequest.Builder requestBuilder = 546 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 547 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 548 requestBuilder.addTarget(mPreviewSurface); 549 requestBuilder.addTarget(mRecordingSurface); 550 slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession). 551 createHighSpeedRequestList(requestBuilder.build()); 552 } else { 553 CaptureRequest.Builder recordingRequestBuilder = 554 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 555 recordingRequestBuilder.set(CaptureRequest.CONTROL_MODE, 556 CaptureRequest.CONTROL_MODE_USE_SCENE_MODE); 557 recordingRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, 558 CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO); 559 560 CaptureRequest.Builder recordingOnlyBuilder = 561 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 562 recordingOnlyBuilder.set(CaptureRequest.CONTROL_MODE, 563 CaptureRequest.CONTROL_MODE_USE_SCENE_MODE); 564 recordingOnlyBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, 565 CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO); 566 int slowMotionFactor = captureRate / videoFrameRate; 567 568 // Make sure camera output frame rate is set to correct value. 569 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 570 recordingRequestBuilder.addTarget(mRecordingSurface); 571 recordingRequestBuilder.addTarget(mPreviewSurface); 572 recordingOnlyBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 573 recordingOnlyBuilder.addTarget(mRecordingSurface); 574 575 slowMoRequests = new ArrayList<CaptureRequest>(); 576 slowMoRequests.add(recordingRequestBuilder.build());// Preview + recording. 577 578 for (int i = 0; i < slowMotionFactor - 1; i++) { 579 slowMoRequests.add(recordingOnlyBuilder.build()); // Recording only. 580 } 581 } 582 583 mSession.setRepeatingBurst(slowMoRequests, listener, mHandler); 584 585 if (useMediaRecorder) { 586 mMediaRecorder.start(); 587 } else { 588 // TODO: need implement MediaCodec path. 589 } 590 591 } 592 593 /** 594 * Test camera recording by using each available CamcorderProfile for a 595 * given camera. preview size is set to the video size. 596 */ basicRecordingTestByCamera(int[] camcorderProfileList)597 private void basicRecordingTestByCamera(int[] camcorderProfileList) throws Exception { 598 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 599 List<Range<Integer> > fpsRanges = Arrays.asList( 600 mStaticInfo.getAeAvailableTargetFpsRangesChecked()); 601 int cameraId = Integer.valueOf(mCamera.getId()); 602 for (int profileId : camcorderProfileList) { 603 if (!CamcorderProfile.hasProfile(cameraId, profileId) || 604 allowedUnsupported(cameraId, profileId)) { 605 continue; 606 } 607 608 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId); 609 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 610 Range<Integer> fpsRange = new Range(profile.videoFrameRate, profile.videoFrameRate); 611 if (mStaticInfo.isHardwareLevelLegacy() && 612 (videoSz.getWidth() > maxPreviewSize.getWidth() || 613 videoSz.getHeight() > maxPreviewSize.getHeight())) { 614 // Skip. Legacy mode can only do recording up to max preview size 615 continue; 616 } 617 assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId + 618 " must be one of the camera device supported video size!", 619 mSupportedVideoSizes.contains(videoSz)); 620 assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId + 621 ") must be one of the camera device available FPS range!", 622 fpsRanges.contains(fpsRange)); 623 624 if (VERBOSE) { 625 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString()); 626 } 627 628 // Configure preview and recording surfaces. 629 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4"; 630 if (DEBUG_DUMP) { 631 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_" 632 + videoSz.toString() + ".mp4"; 633 } 634 635 prepareRecordingWithProfile(profile); 636 637 // prepare preview surface by using video size. 638 updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate); 639 640 // Start recording 641 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 642 startRecording(/* useMediaRecorder */true, resultListener); 643 644 // Record certain duration. 645 SystemClock.sleep(RECORDING_DURATION_MS); 646 647 // Stop recording and preview 648 stopRecording(/* useMediaRecorder */true); 649 // Convert number of frames camera produced into the duration in unit of ms. 650 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f / 651 profile.videoFrameRate); 652 653 if (VERBOSE) { 654 Log.v(TAG, "video frame rate: " + profile.videoFrameRate + 655 ", num of frames produced: " + resultListener.getTotalNumFrames()); 656 } 657 658 // Validation. 659 validateRecording(videoSz, durationMs); 660 } 661 } 662 663 /** 664 * Test camera recording for each supported video size by camera, preview 665 * size is set to the video size. 666 */ recordingSizeTestByCamera()667 private void recordingSizeTestByCamera() throws Exception { 668 for (Size sz : mSupportedVideoSizes) { 669 if (!isSupported(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE)) { 670 continue; 671 } 672 673 if (VERBOSE) { 674 Log.v(TAG, "Testing camera recording with video size " + sz.toString()); 675 } 676 677 // Configure preview and recording surfaces. 678 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4"; 679 if (DEBUG_DUMP) { 680 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + mCamera.getId() + "_" 681 + sz.toString() + ".mp4"; 682 } 683 684 // Use AVC and AAC a/v compression format. 685 prepareRecording(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE); 686 687 // prepare preview surface by using video size. 688 updatePreviewSurfaceWithVideo(sz, VIDEO_FRAME_RATE); 689 690 // Start recording 691 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 692 startRecording(/* useMediaRecorder */true, resultListener); 693 694 // Record certain duration. 695 SystemClock.sleep(RECORDING_DURATION_MS); 696 697 // Stop recording and preview 698 stopRecording(/* useMediaRecorder */true); 699 // Convert number of frames camera produced into the duration in unit of ms. 700 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f / 701 VIDEO_FRAME_RATE); 702 703 // Validation. 704 validateRecording(sz, durationMs); 705 } 706 } 707 708 /** 709 * Initialize the supported video sizes. 710 */ initSupportedVideoSize(String cameraId)711 private void initSupportedVideoSize(String cameraId) throws Exception { 712 Size maxVideoSize = SIZE_BOUND_1080P; 713 if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) { 714 maxVideoSize = SIZE_BOUND_2160P; 715 } 716 mSupportedVideoSizes = 717 getSupportedVideoSizes(cameraId, mCameraManager, maxVideoSize); 718 } 719 720 /** 721 * Simple wrapper to wrap normal/burst video snapshot tests 722 */ videoSnapshotHelper(boolean burstTest)723 private void videoSnapshotHelper(boolean burstTest) throws Exception { 724 for (String id : mCameraIds) { 725 try { 726 Log.i(TAG, "Testing video snapshot for camera " + id); 727 // Re-use the MediaRecorder object for the same camera device. 728 mMediaRecorder = new MediaRecorder(); 729 730 openDevice(id); 731 732 if (!mStaticInfo.isColorOutputSupported()) { 733 Log.i(TAG, "Camera " + id + 734 " does not support color outputs, skipping"); 735 continue; 736 } 737 738 initSupportedVideoSize(id); 739 740 videoSnapshotTestByCamera(burstTest); 741 } finally { 742 closeDevice(); 743 releaseRecorder(); 744 } 745 } 746 } 747 748 /** 749 * Returns {@code true} if the {@link CamcorderProfile} ID is allowed to be unsupported. 750 * 751 * <p>This only allows unsupported profiles when using the LEGACY mode of the Camera API.</p> 752 * 753 * @param profileId a {@link CamcorderProfile} ID to check. 754 * @return {@code true} if supported. 755 */ allowedUnsupported(int cameraId, int profileId)756 private boolean allowedUnsupported(int cameraId, int profileId) { 757 if (!mStaticInfo.isHardwareLevelLegacy()) { 758 return false; 759 } 760 761 switch(profileId) { 762 case CamcorderProfile.QUALITY_2160P: 763 case CamcorderProfile.QUALITY_1080P: 764 case CamcorderProfile.QUALITY_HIGH: 765 return !CamcorderProfile.hasProfile(cameraId, profileId) || 766 CamcorderProfile.get(cameraId, profileId).videoFrameWidth >= 1080; 767 } 768 return false; 769 } 770 771 /** 772 * Test video snapshot for each available CamcorderProfile for a given camera. 773 * 774 * <p> 775 * Preview size is set to the video size. For the burst test, frame drop and jittering 776 * is not checked. 777 * </p> 778 * 779 * @param burstTest Perform burst capture or single capture. For burst capture 780 * {@value #BURST_VIDEO_SNAPSHOT_NUM} capture requests will be sent. 781 */ videoSnapshotTestByCamera(boolean burstTest)782 private void videoSnapshotTestByCamera(boolean burstTest) 783 throws Exception { 784 final int NUM_SINGLE_SHOT_TEST = 5; 785 final int FRAMEDROP_TOLERANCE = 8; 786 final int FRAME_SIZE_15M = 15000000; 787 final float FRAME_DROP_TOLERENCE_FACTOR = 1.5f; 788 int kFrameDrop_Tolerence = FRAMEDROP_TOLERANCE; 789 790 for (int profileId : mCamcorderProfileList) { 791 int cameraId = Integer.valueOf(mCamera.getId()); 792 if (!CamcorderProfile.hasProfile(cameraId, profileId) || 793 allowedUnsupported(cameraId, profileId)) { 794 continue; 795 } 796 797 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId); 798 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 799 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 800 801 if (mStaticInfo.isHardwareLevelLegacy() && 802 (videoSz.getWidth() > maxPreviewSize.getWidth() || 803 videoSz.getHeight() > maxPreviewSize.getHeight())) { 804 // Skip. Legacy mode can only do recording up to max preview size 805 continue; 806 } 807 808 if (!mSupportedVideoSizes.contains(videoSz)) { 809 mCollector.addMessage("Video size " + videoSz.toString() + " for profile ID " + 810 profileId + " must be one of the camera device supported video size!"); 811 continue; 812 } 813 814 // For LEGACY, find closest supported smaller or equal JPEG size to the current video 815 // size; if no size is smaller than the video, pick the smallest JPEG size. The assert 816 // for video size above guarantees that for LIMITED or FULL, we select videoSz here. 817 // Also check for minFrameDuration here to make sure jpeg stream won't slow down 818 // video capture 819 Size videoSnapshotSz = mOrderedStillSizes.get(mOrderedStillSizes.size() - 1); 820 // Allow a bit tolerance so we don't fail for a few nano seconds of difference 821 final float FRAME_DURATION_TOLERANCE = 0.01f; 822 long videoFrameDuration = (long) (1e9 / profile.videoFrameRate * 823 (1.0 + FRAME_DURATION_TOLERANCE)); 824 HashMap<Size, Long> minFrameDurationMap = mStaticInfo. 825 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG); 826 for (int i = mOrderedStillSizes.size() - 2; i >= 0; i--) { 827 Size candidateSize = mOrderedStillSizes.get(i); 828 if (mStaticInfo.isHardwareLevelLegacy()) { 829 // Legacy level doesn't report min frame duration 830 if (candidateSize.getWidth() <= videoSz.getWidth() && 831 candidateSize.getHeight() <= videoSz.getHeight()) { 832 videoSnapshotSz = candidateSize; 833 } 834 } else { 835 Long jpegFrameDuration = minFrameDurationMap.get(candidateSize); 836 assertTrue("Cannot find minimum frame duration for jpeg size " + candidateSize, 837 jpegFrameDuration != null); 838 if (candidateSize.getWidth() <= videoSz.getWidth() && 839 candidateSize.getHeight() <= videoSz.getHeight() && 840 jpegFrameDuration <= videoFrameDuration) { 841 videoSnapshotSz = candidateSize; 842 } 843 } 844 } 845 846 /** 847 * Only test full res snapshot when below conditions are all true. 848 * 1. Camera is a FULL device 849 * 2. video size is up to max preview size, which will be bounded by 1080p. 850 * 3. Full resolution jpeg stream can keep up to video stream speed. 851 * When full res jpeg stream cannot keep up to video stream speed, search 852 * the largest jpeg size that can susptain video speed instead. 853 */ 854 if (mStaticInfo.isHardwareLevelFull() && 855 videoSz.getWidth() <= maxPreviewSize.getWidth() && 856 videoSz.getHeight() <= maxPreviewSize.getHeight()) { 857 for (Size jpegSize : mOrderedStillSizes) { 858 Long jpegFrameDuration = minFrameDurationMap.get(jpegSize); 859 assertTrue("Cannot find minimum frame duration for jpeg size " + jpegSize, 860 jpegFrameDuration != null); 861 if (jpegFrameDuration <= videoFrameDuration) { 862 videoSnapshotSz = jpegSize; 863 break; 864 } 865 if (jpegSize.equals(videoSz)) { 866 throw new AssertionFailedError( 867 "Cannot find adequate video snapshot size for video size" + 868 videoSz); 869 } 870 } 871 } 872 873 Log.i(TAG, "Testing video snapshot size " + videoSnapshotSz + 874 " for video size " + videoSz); 875 if (videoSnapshotSz.getWidth() * videoSnapshotSz.getHeight() > FRAME_SIZE_15M) 876 kFrameDrop_Tolerence = (int)(FRAMEDROP_TOLERANCE * FRAME_DROP_TOLERENCE_FACTOR); 877 878 createImageReader( 879 videoSnapshotSz, ImageFormat.JPEG, 880 MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null); 881 882 if (VERBOSE) { 883 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString()); 884 } 885 886 // Configure preview and recording surfaces. 887 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4"; 888 if (DEBUG_DUMP) { 889 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_" 890 + videoSz.toString() + ".mp4"; 891 } 892 893 int numTestIterations = burstTest ? 1 : NUM_SINGLE_SHOT_TEST; 894 int totalDroppedFrames = 0; 895 896 for (int numTested = 0; numTested < numTestIterations; numTested++) { 897 prepareRecordingWithProfile(profile); 898 899 // prepare video snapshot 900 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 901 SimpleImageReaderListener imageListener = new SimpleImageReaderListener(); 902 CaptureRequest.Builder videoSnapshotRequestBuilder = 903 mCamera.createCaptureRequest((mStaticInfo.isHardwareLevelLegacy()) ? 904 CameraDevice.TEMPLATE_RECORD : 905 CameraDevice.TEMPLATE_VIDEO_SNAPSHOT); 906 907 // prepare preview surface by using video size. 908 updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate); 909 910 prepareVideoSnapshot(videoSnapshotRequestBuilder, imageListener); 911 CaptureRequest request = videoSnapshotRequestBuilder.build(); 912 913 // Start recording 914 startRecording(/* useMediaRecorder */true, resultListener); 915 long startTime = SystemClock.elapsedRealtime(); 916 917 // Record certain duration. 918 SystemClock.sleep(RECORDING_DURATION_MS / 2); 919 920 // take video snapshot 921 if (burstTest) { 922 List<CaptureRequest> requests = 923 new ArrayList<CaptureRequest>(BURST_VIDEO_SNAPSHOT_NUM); 924 for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) { 925 requests.add(request); 926 } 927 mSession.captureBurst(requests, resultListener, mHandler); 928 } else { 929 mSession.capture(request, resultListener, mHandler); 930 } 931 932 // make sure recording is still going after video snapshot 933 SystemClock.sleep(RECORDING_DURATION_MS / 2); 934 935 // Stop recording and preview 936 int durationMs = stopRecording(/* useMediaRecorder */true); 937 // For non-burst test, use number of frames to also double check video frame rate. 938 // Burst video snapshot is allowed to cause frame rate drop, so do not use number 939 // of frames to estimate duration 940 if (!burstTest) { 941 durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f / 942 profile.videoFrameRate); 943 } 944 945 // Validation recorded video 946 validateRecording(videoSz, durationMs); 947 948 if (burstTest) { 949 for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) { 950 Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS); 951 validateVideoSnapshotCapture(image, videoSnapshotSz); 952 image.close(); 953 } 954 } else { 955 // validate video snapshot image 956 Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS); 957 validateVideoSnapshotCapture(image, videoSnapshotSz); 958 959 // validate if there is framedrop around video snapshot 960 totalDroppedFrames += validateFrameDropAroundVideoSnapshot( 961 resultListener, image.getTimestamp()); 962 963 //TODO: validate jittering. Should move to PTS 964 //validateJittering(resultListener); 965 966 image.close(); 967 } 968 } 969 970 if (!burstTest) { 971 Log.w(TAG, String.format("Camera %d Video size %s: Number of dropped frames " + 972 "detected in %d trials is %d frames.", cameraId, videoSz.toString(), 973 numTestIterations, totalDroppedFrames)); 974 mCollector.expectLessOrEqual( 975 String.format( 976 "Camera %d Video size %s: Number of dropped frames %d must not" 977 + " be larger than %d", 978 cameraId, videoSz.toString(), totalDroppedFrames, 979 kFrameDrop_Tolerence), 980 kFrameDrop_Tolerence, totalDroppedFrames); 981 } 982 closeImageReader(); 983 } 984 } 985 986 /** 987 * Configure video snapshot request according to the still capture size 988 */ prepareVideoSnapshot( CaptureRequest.Builder requestBuilder, ImageReader.OnImageAvailableListener imageListener)989 private void prepareVideoSnapshot( 990 CaptureRequest.Builder requestBuilder, 991 ImageReader.OnImageAvailableListener imageListener) 992 throws Exception { 993 mReader.setOnImageAvailableListener(imageListener, mHandler); 994 assertNotNull("Recording surface must be non-null!", mRecordingSurface); 995 requestBuilder.addTarget(mRecordingSurface); 996 assertNotNull("Preview surface must be non-null!", mPreviewSurface); 997 requestBuilder.addTarget(mPreviewSurface); 998 assertNotNull("Reader surface must be non-null!", mReaderSurface); 999 requestBuilder.addTarget(mReaderSurface); 1000 } 1001 1002 /** 1003 * Update preview size with video size. 1004 * 1005 * <p>Preview size will be capped with max preview size.</p> 1006 * 1007 * @param videoSize The video size used for preview. 1008 * @param videoFrameRate The video frame rate 1009 * 1010 */ updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate)1011 private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) { 1012 if (mOrderedPreviewSizes == null) { 1013 throw new IllegalStateException("supported preview size list is not initialized yet"); 1014 } 1015 final float FRAME_DURATION_TOLERANCE = 0.01f; 1016 long videoFrameDuration = (long) (1e9 / videoFrameRate * 1017 (1.0 + FRAME_DURATION_TOLERANCE)); 1018 HashMap<Size, Long> minFrameDurationMap = mStaticInfo. 1019 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE); 1020 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 1021 Size previewSize = null; 1022 if (videoSize.getWidth() > maxPreviewSize.getWidth() || 1023 videoSize.getHeight() > maxPreviewSize.getHeight()) { 1024 for (Size s : mOrderedPreviewSizes) { 1025 Long frameDuration = minFrameDurationMap.get(s); 1026 if (mStaticInfo.isHardwareLevelLegacy()) { 1027 // Legacy doesn't report min frame duration 1028 frameDuration = new Long(0); 1029 } 1030 assertTrue("Cannot find minimum frame duration for private size" + s, 1031 frameDuration != null); 1032 if (frameDuration <= videoFrameDuration && 1033 s.getWidth() <= videoSize.getWidth() && 1034 s.getHeight() <= videoSize.getHeight()) { 1035 Log.w(TAG, "Overwrite preview size from " + videoSize.toString() + 1036 " to " + s.toString()); 1037 previewSize = s; 1038 break; 1039 // If all preview size doesn't work then we fallback to video size 1040 } 1041 } 1042 } 1043 if (previewSize == null) { 1044 previewSize = videoSize; 1045 } 1046 updatePreviewSurface(previewSize); 1047 } 1048 1049 /** 1050 * Configure MediaRecorder recording session with CamcorderProfile, prepare 1051 * the recording surface. 1052 */ prepareRecordingWithProfile(CamcorderProfile profile)1053 private void prepareRecordingWithProfile(CamcorderProfile profile) 1054 throws Exception { 1055 // Prepare MediaRecorder. 1056 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 1057 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 1058 mMediaRecorder.setProfile(profile); 1059 mMediaRecorder.setOutputFile(mOutMediaFileName); 1060 if (mPersistentSurface != null) { 1061 mMediaRecorder.setInputSurface(mPersistentSurface); 1062 mRecordingSurface = mPersistentSurface; 1063 } 1064 mMediaRecorder.prepare(); 1065 if (mPersistentSurface == null) { 1066 mRecordingSurface = mMediaRecorder.getSurface(); 1067 } 1068 assertNotNull("Recording surface must be non-null!", mRecordingSurface); 1069 mVideoFrameRate = profile.videoFrameRate; 1070 mVideoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 1071 } 1072 1073 /** 1074 * Configure MediaRecorder recording session with CamcorderProfile, prepare 1075 * the recording surface. Use AVC for video compression, AAC for audio compression. 1076 * Both are required for android devices by android CDD. 1077 */ prepareRecording(Size sz, int videoFrameRate, int captureRate)1078 private void prepareRecording(Size sz, int videoFrameRate, int captureRate) 1079 throws Exception { 1080 // Prepare MediaRecorder. 1081 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 1082 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 1083 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 1084 mMediaRecorder.setOutputFile(mOutMediaFileName); 1085 mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz)); 1086 mMediaRecorder.setVideoFrameRate(videoFrameRate); 1087 mMediaRecorder.setCaptureRate(captureRate); 1088 mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight()); 1089 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 1090 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 1091 if (mPersistentSurface != null) { 1092 mMediaRecorder.setInputSurface(mPersistentSurface); 1093 mRecordingSurface = mPersistentSurface; 1094 } 1095 mMediaRecorder.prepare(); 1096 if (mPersistentSurface == null) { 1097 mRecordingSurface = mMediaRecorder.getSurface(); 1098 } 1099 assertNotNull("Recording surface must be non-null!", mRecordingSurface); 1100 mVideoFrameRate = videoFrameRate; 1101 mVideoSize = sz; 1102 } 1103 startRecording(boolean useMediaRecorder, CameraCaptureSession.CaptureCallback listener)1104 private void startRecording(boolean useMediaRecorder, 1105 CameraCaptureSession.CaptureCallback listener) throws Exception { 1106 List<Surface> outputSurfaces = new ArrayList<Surface>(2); 1107 assertTrue("Both preview and recording surfaces should be valid", 1108 mPreviewSurface.isValid() && mRecordingSurface.isValid()); 1109 outputSurfaces.add(mPreviewSurface); 1110 outputSurfaces.add(mRecordingSurface); 1111 // Video snapshot surface 1112 if (mReaderSurface != null) { 1113 outputSurfaces.add(mReaderSurface); 1114 } 1115 mSessionListener = new BlockingSessionCallback(); 1116 mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler); 1117 1118 CaptureRequest.Builder recordingRequestBuilder = 1119 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 1120 // Make sure camera output frame rate is set to correct value. 1121 Range<Integer> fpsRange = Range.create(mVideoFrameRate, mVideoFrameRate); 1122 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 1123 recordingRequestBuilder.addTarget(mRecordingSurface); 1124 recordingRequestBuilder.addTarget(mPreviewSurface); 1125 mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler); 1126 1127 if (useMediaRecorder) { 1128 mMediaRecorder.start(); 1129 } else { 1130 // TODO: need implement MediaCodec path. 1131 } 1132 mRecordingStartTime = SystemClock.elapsedRealtime(); 1133 } 1134 startRecording(boolean useMediaRecorder)1135 private void startRecording(boolean useMediaRecorder) throws Exception { 1136 startRecording(useMediaRecorder, null); 1137 } 1138 stopCameraStreaming()1139 private void stopCameraStreaming() throws Exception { 1140 if (VERBOSE) { 1141 Log.v(TAG, "Stopping camera streaming and waiting for idle"); 1142 } 1143 // Stop repeating, wait for captures to complete, and disconnect from 1144 // surfaces 1145 mSession.close(); 1146 mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS); 1147 } 1148 1149 // Stop recording and return the estimated video duration in milliseconds. stopRecording(boolean useMediaRecorder)1150 private int stopRecording(boolean useMediaRecorder) throws Exception { 1151 long stopRecordingTime = SystemClock.elapsedRealtime(); 1152 if (useMediaRecorder) { 1153 stopCameraStreaming(); 1154 1155 mMediaRecorder.stop(); 1156 // Can reuse the MediaRecorder object after reset. 1157 mMediaRecorder.reset(); 1158 } else { 1159 // TODO: need implement MediaCodec path. 1160 } 1161 if (mPersistentSurface == null && mRecordingSurface != null) { 1162 mRecordingSurface.release(); 1163 mRecordingSurface = null; 1164 } 1165 return (int) (stopRecordingTime - mRecordingStartTime); 1166 } 1167 releaseRecorder()1168 private void releaseRecorder() { 1169 if (mMediaRecorder != null) { 1170 mMediaRecorder.release(); 1171 mMediaRecorder = null; 1172 } 1173 } 1174 validateRecording(Size sz, int expectedDurationMs)1175 private void validateRecording(Size sz, int expectedDurationMs) throws Exception { 1176 File outFile = new File(mOutMediaFileName); 1177 assertTrue("No video is recorded", outFile.exists()); 1178 1179 MediaExtractor extractor = new MediaExtractor(); 1180 try { 1181 extractor.setDataSource(mOutMediaFileName); 1182 long durationUs = 0; 1183 int width = -1, height = -1; 1184 int numTracks = extractor.getTrackCount(); 1185 final String VIDEO_MIME_TYPE = "video"; 1186 for (int i = 0; i < numTracks; i++) { 1187 MediaFormat format = extractor.getTrackFormat(i); 1188 String mime = format.getString(MediaFormat.KEY_MIME); 1189 if (mime.contains(VIDEO_MIME_TYPE)) { 1190 Log.i(TAG, "video format is: " + format.toString()); 1191 durationUs = format.getLong(MediaFormat.KEY_DURATION); 1192 width = format.getInteger(MediaFormat.KEY_WIDTH); 1193 height = format.getInteger(MediaFormat.KEY_HEIGHT); 1194 break; 1195 } 1196 } 1197 Size videoSz = new Size(width, height); 1198 assertTrue("Video size doesn't match, expected " + sz.toString() + 1199 " got " + videoSz.toString(), videoSz.equals(sz)); 1200 int duration = (int) (durationUs / 1000); 1201 if (VERBOSE) { 1202 Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms", 1203 duration, expectedDurationMs)); 1204 } 1205 1206 // TODO: Don't skip this for video snapshot 1207 if (!mStaticInfo.isHardwareLevelLegacy()) { 1208 assertTrue(String.format( 1209 "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.", 1210 mCamera.getId(), duration, expectedDurationMs), 1211 Math.abs(duration - expectedDurationMs) < 1212 DURATION_MARGIN * expectedDurationMs); 1213 } 1214 } finally { 1215 extractor.release(); 1216 if (!DEBUG_DUMP) { 1217 outFile.delete(); 1218 } 1219 } 1220 } 1221 1222 /** 1223 * Validate video snapshot capture image object sanity and test. 1224 * 1225 * <p> Check for size, format and jpeg decoding</p> 1226 * 1227 * @param image The JPEG image to be verified. 1228 * @param size The JPEG capture size to be verified against. 1229 */ 1230 private void validateVideoSnapshotCapture(Image image, Size size) { 1231 CameraTestUtils.validateImage(image, size.getWidth(), size.getHeight(), 1232 ImageFormat.JPEG, /*filePath*/null); 1233 } 1234 1235 /** 1236 * Validate if video snapshot causes frame drop. 1237 * Here frame drop is defined as frame duration >= 2 * expected frame duration. 1238 * Return the estimated number of frames dropped during video snapshot 1239 */ 1240 private int validateFrameDropAroundVideoSnapshot( 1241 SimpleCaptureCallback resultListener, long imageTimeStamp) { 1242 double expectedDurationMs = 1000.0 / mVideoFrameRate; 1243 CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 1244 long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP); 1245 while (!resultListener.hasMoreResults()) { 1246 CaptureResult currentResult = 1247 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 1248 long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP); 1249 if (currentTS == imageTimeStamp) { 1250 // validate the timestamp before and after, then return 1251 CaptureResult nextResult = 1252 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 1253 long nextTS = getValueNotNull(nextResult, CaptureResult.SENSOR_TIMESTAMP); 1254 double durationMs = (currentTS - prevTS) / 1000000.0; 1255 int totalFramesDropped = 0; 1256 1257 // Snapshots in legacy mode pause the preview briefly. Skip the duration 1258 // requirements for legacy mode unless this is fixed. 1259 if (!mStaticInfo.isHardwareLevelLegacy()) { 1260 mCollector.expectTrue( 1261 String.format( 1262 "Video %dx%d Frame drop detected before video snapshot: " + 1263 "duration %.2fms (expected %.2fms)", 1264 mVideoSize.getWidth(), mVideoSize.getHeight(), 1265 durationMs, expectedDurationMs 1266 ), 1267 durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED) 1268 ); 1269 // Log a warning is there is any frame drop detected. 1270 if (durationMs >= expectedDurationMs * 2) { 1271 Log.w(TAG, String.format( 1272 "Video %dx%d Frame drop detected before video snapshot: " + 1273 "duration %.2fms (expected %.2fms)", 1274 mVideoSize.getWidth(), mVideoSize.getHeight(), 1275 durationMs, expectedDurationMs 1276 )); 1277 } 1278 1279 durationMs = (nextTS - currentTS) / 1000000.0; 1280 mCollector.expectTrue( 1281 String.format( 1282 "Video %dx%d Frame drop detected after video snapshot: " + 1283 "duration %.2fms (expected %.2fms)", 1284 mVideoSize.getWidth(), mVideoSize.getHeight(), 1285 durationMs, expectedDurationMs 1286 ), 1287 durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED) 1288 ); 1289 // Log a warning is there is any frame drop detected. 1290 if (durationMs >= expectedDurationMs * 2) { 1291 Log.w(TAG, String.format( 1292 "Video %dx%d Frame drop detected after video snapshot: " + 1293 "duration %fms (expected %fms)", 1294 mVideoSize.getWidth(), mVideoSize.getHeight(), 1295 durationMs, expectedDurationMs 1296 )); 1297 } 1298 1299 double totalDurationMs = (nextTS - prevTS) / 1000000.0; 1300 // Minus 2 for the expected 2 frames interval 1301 totalFramesDropped = (int) (totalDurationMs / expectedDurationMs) - 2; 1302 if (totalFramesDropped < 0) { 1303 Log.w(TAG, "totalFrameDropped is " + totalFramesDropped + 1304 ". Video frame rate might be too fast."); 1305 } 1306 totalFramesDropped = Math.max(0, totalFramesDropped); 1307 } 1308 return totalFramesDropped; 1309 } 1310 prevTS = currentTS; 1311 } 1312 throw new AssertionFailedError( 1313 "Video snapshot timestamp does not match any of capture results!"); 1314 } 1315 1316 /** 1317 * Validate frame jittering from the input simple listener's buffered results 1318 */ 1319 private void validateJittering(SimpleCaptureCallback resultListener) { 1320 double expectedDurationMs = 1000.0 / mVideoFrameRate; 1321 CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 1322 long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP); 1323 while (!resultListener.hasMoreResults()) { 1324 CaptureResult currentResult = 1325 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 1326 long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP); 1327 double durationMs = (currentTS - prevTS) / 1000000.0; 1328 double durationError = Math.abs(durationMs - expectedDurationMs); 1329 long frameNumber = currentResult.getFrameNumber(); 1330 mCollector.expectTrue( 1331 String.format( 1332 "Resolution %dx%d Frame %d: jittering (%.2fms) exceeds bound [%.2fms,%.2fms]", 1333 mVideoSize.getWidth(), mVideoSize.getHeight(), 1334 frameNumber, durationMs, 1335 expectedDurationMs - FRAME_DURATION_ERROR_TOLERANCE_MS, 1336 expectedDurationMs + FRAME_DURATION_ERROR_TOLERANCE_MS), 1337 durationError <= FRAME_DURATION_ERROR_TOLERANCE_MS); 1338 prevTS = currentTS; 1339 } 1340 } 1341 1342 /** 1343 * Calculate a video bit rate based on the size. The bit rate is scaled 1344 * based on ratio of video size to 1080p size. 1345 */ 1346 private int getVideoBitRate(Size sz) { 1347 int rate = BIT_RATE_1080P; 1348 float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080); 1349 rate = (int)(rate * scaleFactor); 1350 1351 // Clamp to the MIN, MAX range. 1352 return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate)); 1353 } 1354 1355 /** 1356 * Check if the encoder and camera are able to support this size and frame rate. 1357 * Assume the video compression format is AVC. 1358 */ 1359 private boolean isSupported(Size sz, int captureRate, int encodingRate) throws Exception { 1360 // Check camera capability. 1361 if (!isSupportedByCamera(sz, captureRate)) { 1362 return false; 1363 } 1364 1365 // Check encode capability. 1366 if (!isSupportedByAVCEncoder(sz, encodingRate)){ 1367 return false; 1368 } 1369 1370 if(VERBOSE) { 1371 Log.v(TAG, "Both encoder and camera support " + sz.toString() + "@" + encodingRate + "@" 1372 + getVideoBitRate(sz) / 1000 + "Kbps"); 1373 } 1374 1375 return true; 1376 } 1377 1378 private boolean isSupportedByCamera(Size sz, int frameRate) { 1379 // Check if camera can support this sz and frame rate combination. 1380 StreamConfigurationMap config = mStaticInfo. 1381 getValueFromKeyNonNull(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1382 1383 long minDuration = config.getOutputMinFrameDuration(MediaRecorder.class, sz); 1384 if (minDuration == 0) { 1385 return false; 1386 } 1387 1388 int maxFrameRate = (int) (1e9f / minDuration); 1389 return maxFrameRate >= frameRate; 1390 } 1391 1392 /** 1393 * Check if encoder can support this size and frame rate combination by querying 1394 * MediaCodec capability. Check is based on size and frame rate. Ignore the bit rate 1395 * as the bit rates targeted in this test are well below the bit rate max value specified 1396 * by AVC specification for certain level. 1397 */ 1398 private static boolean isSupportedByAVCEncoder(Size sz, int frameRate) { 1399 MediaFormat format = MediaFormat.createVideoFormat( 1400 MediaFormat.MIMETYPE_VIDEO_AVC, sz.getWidth(), sz.getHeight()); 1401 format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); 1402 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 1403 return mcl.findEncoderForFormat(format) != null; 1404 } 1405 } 1406