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