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 android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10; 16 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10; 17 import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus; 18 19 import static com.android.ex.camera2.blocking.BlockingSessionCallback.*; 20 21 import android.graphics.ImageFormat; 22 import android.graphics.SurfaceTexture; 23 import android.hardware.camera2.CameraCharacteristics; 24 import android.hardware.camera2.CameraCaptureSession; 25 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession; 26 import android.hardware.camera2.CameraDevice; 27 import android.hardware.camera2.CaptureRequest; 28 import android.hardware.camera2.CaptureResult; 29 import android.hardware.camera2.cts.helpers.StaticMetadata; 30 import android.hardware.camera2.params.DynamicRangeProfiles; 31 import android.hardware.camera2.params.OutputConfiguration; 32 import android.hardware.camera2.params.SessionConfiguration; 33 import android.hardware.camera2.params.StreamConfigurationMap; 34 import android.hardware.HardwareBuffer; 35 import android.media.MediaMuxer; 36 import android.util.Size; 37 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase; 38 import android.media.CamcorderProfile; 39 import android.media.EncoderProfiles; 40 import android.media.MediaCodec; 41 import android.media.MediaCodecInfo.CodecCapabilities; 42 import android.media.Image; 43 import android.media.ImageReader; 44 import android.media.ImageWriter; 45 import android.media.MediaCodecList; 46 import android.media.MediaExtractor; 47 import android.media.MediaFormat; 48 import android.media.MediaRecorder; 49 import android.os.Handler; 50 import android.os.HandlerThread; 51 import android.os.SystemClock; 52 import android.test.suitebuilder.annotation.LargeTest; 53 import android.util.Log; 54 import android.util.Range; 55 import android.view.Surface; 56 57 import com.android.compatibility.common.util.MediaUtils; 58 import com.android.ex.camera2.blocking.BlockingSessionCallback; 59 60 import junit.framework.AssertionFailedError; 61 62 import org.junit.runners.Parameterized; 63 import org.junit.runner.RunWith; 64 import org.junit.Test; 65 66 import static org.mockito.Matchers.eq; 67 import static org.mockito.Mockito.mock; 68 import static org.mockito.Mockito.timeout; 69 import static org.mockito.Mockito.verify; 70 71 import java.io.File; 72 import java.util.ArrayList; 73 import java.util.Arrays; 74 import java.util.Collections; 75 import java.util.List; 76 import java.util.Objects; 77 import java.util.HashMap; 78 import java.util.Optional; 79 import java.util.Set; 80 81 /** 82 * CameraDevice video recording use case tests by using MediaRecorder and 83 * MediaCodec. 84 */ 85 @LargeTest 86 @RunWith(Parameterized.class) 87 public class RecordingTest extends Camera2SurfaceViewTestCase { 88 private static final String TAG = "RecordingTest"; 89 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 90 private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG); 91 private static final int RECORDING_DURATION_MS = 3000; 92 private static final int PREVIEW_DURATION_MS = 3000; 93 private static final float DURATION_MARGIN = 0.2f; 94 private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0; 95 private static final float FRAMEDURATION_MARGIN = 0.2f; 96 private static final float VID_SNPSHT_FRMDRP_RATE_TOLERANCE = 10.0f; 97 private static final float FRMDRP_RATE_TOLERANCE = 5.0f; 98 private static final int BIT_RATE_1080P = 16000000; 99 private static final int BIT_RATE_MIN = 64000; 100 private static final int BIT_RATE_MAX = 40000000; 101 private static final int VIDEO_FRAME_RATE = 30; 102 private static final int[] mCamcorderProfileList = { 103 CamcorderProfile.QUALITY_HIGH, 104 CamcorderProfile.QUALITY_2160P, 105 CamcorderProfile.QUALITY_1080P, 106 CamcorderProfile.QUALITY_720P, 107 CamcorderProfile.QUALITY_480P, 108 CamcorderProfile.QUALITY_CIF, 109 CamcorderProfile.QUALITY_QCIF, 110 CamcorderProfile.QUALITY_QVGA, 111 CamcorderProfile.QUALITY_LOW, 112 }; 113 114 private static final int[] mTenBitCodecProfileList = { 115 HEVCProfileMain10, 116 HEVCProfileMain10HDR10, 117 HEVCProfileMain10HDR10Plus, 118 //todo(b/215396395): DolbyVision 119 }; 120 private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5; 121 private static final int BURST_VIDEO_SNAPSHOT_NUM = 3; 122 private static final int SLOWMO_SLOW_FACTOR = 4; 123 private static final int MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED = 4; 124 private List<Size> mSupportedVideoSizes; 125 private Surface mRecordingSurface; 126 private Surface mPersistentSurface; 127 private MediaRecorder mMediaRecorder; 128 private String mOutMediaFileName; 129 private int mVideoFrameRate; 130 private Size mVideoSize; 131 private long mRecordingStartTime; 132 133 private Surface mIntermediateSurface; 134 private ImageReader mIntermediateReader; 135 private ImageWriter mIntermediateWriter; 136 private ImageWriterQueuer mQueuer; 137 private HandlerThread mIntermediateThread; 138 private Handler mIntermediateHandler; 139 140 @Override setUp()141 public void setUp() throws Exception { 142 super.setUp(); 143 } 144 145 @Override tearDown()146 public void tearDown() throws Exception { 147 super.tearDown(); 148 } 149 doBasicRecording(boolean useVideoStab)150 private void doBasicRecording(boolean useVideoStab) throws Exception { 151 doBasicRecording(useVideoStab, false); 152 } 153 doBasicRecording(boolean useVideoStab, boolean useIntermediateSurface)154 private void doBasicRecording(boolean useVideoStab, boolean useIntermediateSurface) 155 throws Exception { 156 doBasicRecording(useVideoStab, useIntermediateSurface, false); 157 } 158 doBasicRecording(boolean useVideoStab, boolean useIntermediateSurface, boolean useEncoderProfiles)159 private void doBasicRecording(boolean useVideoStab, boolean useIntermediateSurface, 160 boolean useEncoderProfiles) throws Exception { 161 for (int i = 0; i < mCameraIdsUnderTest.length; i++) { 162 try { 163 Log.i(TAG, "Testing basic recording for camera " + mCameraIdsUnderTest[i]); 164 StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]); 165 if (!staticInfo.isColorOutputSupported()) { 166 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 167 " does not support color outputs, skipping"); 168 continue; 169 } 170 171 // External camera doesn't support CamcorderProfile recording 172 if (staticInfo.isExternalCamera()) { 173 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 174 " does not support CamcorderProfile, skipping"); 175 continue; 176 } 177 178 if (!staticInfo.isVideoStabilizationSupported() && useVideoStab) { 179 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 180 " does not support video stabilization, skipping the stabilization" 181 + " test"); 182 continue; 183 } 184 185 // Re-use the MediaRecorder object for the same camera device. 186 mMediaRecorder = new MediaRecorder(); 187 openDevice(mCameraIdsUnderTest[i]); 188 initSupportedVideoSize(mCameraIdsUnderTest[i]); 189 190 basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab, 191 useIntermediateSurface, useEncoderProfiles); 192 } finally { 193 closeDevice(); 194 releaseRecorder(); 195 } 196 } 197 } 198 199 /** 200 * <p> 201 * Test basic video stabilization camera recording. 202 * </p> 203 * <p> 204 * This test covers the typical basic use case of camera recording with video 205 * stabilization is enabled, if video stabilization is supported. 206 * MediaRecorder is used to record the audio and video, CamcorderProfile is 207 * used to configure the MediaRecorder. It goes through the pre-defined 208 * CamcorderProfile list, test each profile configuration and validate the 209 * recorded video. Preview is set to the video size. 210 * </p> 211 */ 212 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testBasicVideoStabilizationRecording()213 public void testBasicVideoStabilizationRecording() throws Exception { 214 doBasicRecording(/*useVideoStab*/true); 215 } 216 217 /** 218 * <p> 219 * Test basic camera recording. 220 * </p> 221 * <p> 222 * This test covers the typical basic use case of camera recording. 223 * MediaRecorder is used to record the audio and video, CamcorderProfile is 224 * used to configure the MediaRecorder. It goes through the pre-defined 225 * CamcorderProfile list, test each profile configuration and validate the 226 * recorded video. Preview is set to the video size. 227 * </p> 228 */ 229 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testBasicRecording()230 public void testBasicRecording() throws Exception { 231 doBasicRecording(/*useVideoStab*/false); 232 } 233 234 /** 235 * <p> 236 * Test camera recording with intermediate surface. 237 * </p> 238 * <p> 239 * This test is similar to testBasicRecording with a tweak where an intermediate 240 * surface is setup between camera and MediaRecorder, giving application a chance 241 * to decide whether to send a frame to recorder or not. 242 * </p> 243 */ 244 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testIntermediateSurfaceRecording()245 public void testIntermediateSurfaceRecording() throws Exception { 246 doBasicRecording(/*useVideoStab*/false, /*useIntermediateSurface*/true); 247 } 248 249 /** 250 * <p> 251 * Test basic camera recording using encoder profiles. 252 * </p> 253 * <p> 254 * This test covers the typical basic use case of camera recording. 255 * MediaRecorder is used to record the audio and video, 256 * EncoderProfiles are used to configure the MediaRecorder. It 257 * goes through the pre-defined CamcorderProfile list, test each 258 * encoder profile combination and validate the recorded video. 259 * Preview is set to the video size. 260 * </p> 261 */ 262 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testBasicEncoderProfilesRecording()263 public void testBasicEncoderProfilesRecording() throws Exception { 264 doBasicRecording(/*useVideoStab*/false, /*useIntermediateSurface*/false, 265 /*useEncoderProfiles*/true); 266 } 267 268 /** 269 * <p> 270 * Test basic camera recording from a persistent input surface. 271 * </p> 272 * <p> 273 * This test is similar to testBasicRecording except that MediaRecorder records 274 * from a persistent input surface that's used across multiple recording sessions. 275 * </p> 276 */ 277 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testRecordingFromPersistentSurface()278 public void testRecordingFromPersistentSurface() throws Exception { 279 if (!MediaUtils.checkCodecForDomain(true /* encoder */, "video")) { 280 return; // skipped 281 } 282 mPersistentSurface = MediaCodec.createPersistentInputSurface(); 283 assertNotNull("Failed to create persistent input surface!", mPersistentSurface); 284 285 try { 286 doBasicRecording(/*useVideoStab*/false); 287 } finally { 288 mPersistentSurface.release(); 289 mPersistentSurface = null; 290 } 291 } 292 293 /** 294 * <p> 295 * Test camera recording for all supported sizes by using MediaRecorder. 296 * </p> 297 * <p> 298 * This test covers camera recording for all supported sizes by camera. MediaRecorder 299 * is used to encode the video. Preview is set to the video size. Recorded videos are 300 * validated according to the recording configuration. 301 * </p> 302 */ 303 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testSupportedVideoSizes()304 public void testSupportedVideoSizes() throws Exception { 305 for (int i = 0; i < mCameraIdsUnderTest.length; i++) { 306 try { 307 Log.i(TAG, "Testing supported video size recording for camera " + mCameraIdsUnderTest[i]); 308 StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]); 309 if (!staticInfo.isColorOutputSupported()) { 310 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 311 " does not support color outputs, skipping"); 312 continue; 313 } 314 if (staticInfo.isExternalCamera()) { 315 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 316 " does not support CamcorderProfile, skipping"); 317 continue; 318 } 319 // Re-use the MediaRecorder object for the same camera device. 320 mMediaRecorder = new MediaRecorder(); 321 openDevice(mCameraIdsUnderTest[i]); 322 323 initSupportedVideoSize(mCameraIdsUnderTest[i]); 324 325 recordingSizeTestByCamera(); 326 } finally { 327 closeDevice(); 328 releaseRecorder(); 329 } 330 } 331 } 332 333 /** 334 * Test different start/stop orders of Camera and Recorder. 335 * 336 * <p>The recording should be working fine for any kind of start/stop orders.</p> 337 */ 338 @Test testCameraRecorderOrdering()339 public void testCameraRecorderOrdering() { 340 // TODO: need implement 341 } 342 343 /** 344 * <p> 345 * Test camera recording for all supported sizes by using MediaCodec. 346 * </p> 347 * <p> 348 * This test covers video only recording for all supported sizes (camera and 349 * encoder). MediaCodec is used to encode the video. The recorded videos are 350 * validated according to the recording configuration. 351 * </p> 352 */ 353 @Test testMediaCodecRecording()354 public void testMediaCodecRecording() throws Exception { 355 // TODO. Need implement. 356 } 357 358 private class MediaCodecListener extends MediaCodec.Callback { 359 private final MediaMuxer mMediaMuxer; 360 private final Object mCondition; 361 private int mTrackId = -1; 362 private boolean mEndOfStream = false; 363 MediaCodecListener(MediaMuxer mediaMuxer, Object condition)364 private MediaCodecListener(MediaMuxer mediaMuxer, Object condition) { 365 mMediaMuxer = mediaMuxer; 366 mCondition = condition; 367 } 368 369 @Override onInputBufferAvailable(MediaCodec codec, int index)370 public void onInputBufferAvailable(MediaCodec codec, int index) { 371 fail("Unexpected input buffer available callback!"); 372 } 373 374 @Override onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info)375 public void onOutputBufferAvailable(MediaCodec codec, int index, 376 MediaCodec.BufferInfo info) { 377 synchronized (mCondition) { 378 if (mTrackId < 0) { 379 return; 380 } 381 mMediaMuxer.writeSampleData(mTrackId, codec.getOutputBuffer(index), info); 382 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 383 MediaCodec.BUFFER_FLAG_END_OF_STREAM) { 384 mEndOfStream = true; 385 mCondition.notifyAll(); 386 } 387 388 if (!mEndOfStream) { 389 codec.releaseOutputBuffer(index, false); 390 } 391 } 392 } 393 394 @Override onError(MediaCodec codec, MediaCodec.CodecException e)395 public void onError(MediaCodec codec, MediaCodec.CodecException e) { 396 fail("Codec error: " + e.getDiagnosticInfo()); 397 } 398 399 @Override onOutputFormatChanged(MediaCodec codec, MediaFormat format)400 public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { 401 synchronized (mCondition) { 402 mTrackId = mMediaMuxer.addTrack(format); 403 mMediaMuxer.start(); 404 } 405 } 406 } 407 getDynamicRangeProfile(int codecProfile, StaticMetadata staticMeta)408 private static long getDynamicRangeProfile(int codecProfile, StaticMetadata staticMeta) { 409 if (!staticMeta.isCapabilitySupported( 410 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) { 411 return DynamicRangeProfiles.PUBLIC_MAX; 412 } 413 414 DynamicRangeProfiles profiles = staticMeta.getCharacteristics().get( 415 CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES); 416 Set<Long> availableProfiles = profiles.getSupportedProfiles(); 417 switch (codecProfile) { 418 case HEVCProfileMain10: 419 return availableProfiles.contains(DynamicRangeProfiles.HLG10) ? 420 DynamicRangeProfiles.HLG10 : DynamicRangeProfiles.PUBLIC_MAX; 421 case HEVCProfileMain10HDR10: 422 return availableProfiles.contains(DynamicRangeProfiles.HDR10) ? 423 DynamicRangeProfiles.HDR10 : DynamicRangeProfiles.PUBLIC_MAX; 424 case HEVCProfileMain10HDR10Plus: 425 return availableProfiles.contains(DynamicRangeProfiles.HDR10_PLUS) ? 426 DynamicRangeProfiles.HDR10_PLUS : DynamicRangeProfiles.PUBLIC_MAX; 427 //todo(b/215396395): DolbyVision 428 default: 429 return DynamicRangeProfiles.PUBLIC_MAX; 430 } 431 } 432 getTransferFunction(int codecProfile)433 private static int getTransferFunction(int codecProfile) { 434 switch (codecProfile) { 435 case HEVCProfileMain10: 436 return MediaFormat.COLOR_TRANSFER_HLG; 437 case HEVCProfileMain10HDR10: 438 case HEVCProfileMain10HDR10Plus: 439 //todo(b/215396395): DolbyVision 440 return MediaFormat.COLOR_TRANSFER_ST2084; 441 default: 442 return MediaFormat.COLOR_TRANSFER_SDR_VIDEO; 443 } 444 } 445 446 /** 447 * <p> 448 * Test basic camera 10-bit recording. 449 * </p> 450 * <p> 451 * This test covers the typical basic use case of camera recording. 452 * MediaCodec is used to record 10-bit video, CamcorderProfile and codec profiles 453 * are used to configure the MediaCodec. It goes through the pre-defined 454 * CamcorderProfile and 10-bit codec profile lists, tests each configuration and 455 * validates the recorded video. Preview is set to the video size. 456 * </p> 457 */ 458 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testBasic10BitRecording()459 public void testBasic10BitRecording() throws Exception { 460 for (int i = 0; i < mCameraIdsUnderTest.length; i++) { 461 try { 462 Log.i(TAG, "Testing 10-bit recording " + mCameraIdsUnderTest[i]); 463 StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]); 464 if (!staticInfo.isColorOutputSupported()) { 465 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 466 " does not support color outputs, skipping"); 467 continue; 468 } 469 if (staticInfo.isExternalCamera()) { 470 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 471 " does not support CamcorderProfile, skipping"); 472 continue; 473 } 474 475 int cameraId; 476 try { 477 cameraId = Integer.valueOf(mCameraIdsUnderTest[i]); 478 } catch (NumberFormatException e) { 479 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " cannot be parsed" 480 + " to an integer camera id, skipping"); 481 continue; 482 } 483 484 for (int camcorderProfile : mCamcorderProfileList) { 485 if (!CamcorderProfile.hasProfile(cameraId, camcorderProfile)) { 486 continue; 487 } 488 489 for (int codecProfile : mTenBitCodecProfileList) { 490 CamcorderProfile profile = CamcorderProfile.get(cameraId, camcorderProfile); 491 492 Size videoSize = new Size(profile.videoFrameWidth, 493 profile.videoFrameHeight); 494 MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 495 MediaFormat format = MediaFormat.createVideoFormat( 496 MediaFormat.MIMETYPE_VIDEO_HEVC, videoSize.getWidth(), 497 videoSize.getHeight()); 498 format.setInteger(MediaFormat.KEY_PROFILE, codecProfile); 499 format.setInteger(MediaFormat.KEY_BIT_RATE, profile.videoBitRate); 500 format.setInteger(MediaFormat.KEY_FRAME_RATE, profile.videoFrameRate); 501 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 502 CodecCapabilities.COLOR_FormatSurface); 503 format.setInteger(MediaFormat.KEY_COLOR_STANDARD, 504 MediaFormat.COLOR_STANDARD_BT2020); 505 format.setInteger(MediaFormat.KEY_COLOR_RANGE, 506 MediaFormat.COLOR_RANGE_FULL); 507 format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, 508 getTransferFunction(codecProfile)); 509 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); 510 511 String codecName = list.findEncoderForFormat(format); 512 if (codecName == null) { 513 continue; 514 } 515 516 long dynamicRangeProfile = getDynamicRangeProfile(codecProfile, staticInfo); 517 if (dynamicRangeProfile == DynamicRangeProfiles.PUBLIC_MAX) { 518 continue; 519 } 520 521 MediaCodec mediaCodec = null; 522 mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4"; 523 MediaMuxer muxer = new MediaMuxer(mOutMediaFileName, 524 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 525 SimpleCaptureCallback captureCallback = new SimpleCaptureCallback(); 526 try { 527 mediaCodec = MediaCodec.createByCodecName(codecName); 528 assertNotNull(mediaCodec); 529 530 openDevice(mCameraIdsUnderTest[i]); 531 532 mediaCodec.configure(format, null, null, 533 MediaCodec.CONFIGURE_FLAG_ENCODE); 534 Object condition = new Object(); 535 mediaCodec.setCallback(new MediaCodecListener(muxer, condition), 536 mHandler); 537 538 updatePreviewSurfaceWithVideo(videoSize, profile.videoFrameRate); 539 540 Surface recordingSurface = mediaCodec.createInputSurface(); 541 assertNotNull(recordingSurface); 542 543 List<Surface> outputSurfaces = new ArrayList<Surface>(2); 544 assertTrue("Both preview and recording surfaces should be valid", 545 mPreviewSurface.isValid() && recordingSurface.isValid()); 546 547 outputSurfaces.add(mPreviewSurface); 548 outputSurfaces.add(recordingSurface); 549 550 CameraCaptureSession.StateCallback mockCallback = mock( 551 CameraCaptureSession.StateCallback.class); 552 BlockingSessionCallback sessionListener = 553 new BlockingSessionCallback(mockCallback); 554 555 CaptureRequest.Builder recordingRequestBuilder = 556 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 557 recordingRequestBuilder.addTarget(recordingSurface); 558 recordingRequestBuilder.addTarget(mPreviewSurface); 559 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 560 new Range<>(profile.videoFrameRate, profile.videoFrameRate)); 561 CaptureRequest recordingRequest = recordingRequestBuilder.build(); 562 563 List<OutputConfiguration> outConfigurations = 564 new ArrayList<>(outputSurfaces.size()); 565 for (Surface s : outputSurfaces) { 566 OutputConfiguration config = new OutputConfiguration(s); 567 config.setDynamicRangeProfile(dynamicRangeProfile); 568 outConfigurations.add(config); 569 } 570 571 SessionConfiguration sessionConfig = new SessionConfiguration( 572 SessionConfiguration.SESSION_REGULAR, outConfigurations, 573 new HandlerExecutor(mHandler), sessionListener); 574 mCamera.createCaptureSession(sessionConfig); 575 576 CameraCaptureSession session = sessionListener.waitAndGetSession( 577 SESSION_CONFIGURE_TIMEOUT_MS); 578 579 mediaCodec.start(); 580 session.setRepeatingRequest(recordingRequest, captureCallback, 581 mHandler); 582 583 SystemClock.sleep(RECORDING_DURATION_MS); 584 585 session.stopRepeating(); 586 session.close(); 587 verify(mockCallback, timeout(SESSION_CLOSE_TIMEOUT_MS). 588 times(1)).onClosed(eq(session)); 589 590 mediaCodec.signalEndOfInputStream(); 591 synchronized (condition) { 592 condition.wait(SESSION_CLOSE_TIMEOUT_MS); 593 } 594 595 mediaCodec.stop(); 596 muxer.stop(); 597 598 } finally { 599 if (mediaCodec != null) { 600 mediaCodec.release(); 601 } 602 if (muxer != null) { 603 muxer.release(); 604 } 605 } 606 607 // Validation. 608 float frameDurationMinMs = 1000.0f / profile.videoFrameRate; 609 float durationMinMs = 610 captureCallback.getTotalNumFrames() * frameDurationMinMs; 611 float durationMaxMs = durationMinMs; 612 float frameDurationMaxMs = 0.f; 613 614 validateRecording(videoSize, durationMinMs, durationMaxMs, 615 frameDurationMinMs, frameDurationMaxMs, 616 FRMDRP_RATE_TOLERANCE); 617 } 618 } 619 } finally { 620 closeDevice(); 621 } 622 } 623 } 624 625 /** 626 * <p> 627 * Test video snapshot for each camera. 628 * </p> 629 * <p> 630 * This test covers video snapshot typical use case. The MediaRecorder is used to record the 631 * video for each available video size. The largest still capture size is selected to 632 * capture the JPEG image. The still capture images are validated according to the capture 633 * configuration. The timestamp of capture result before and after video snapshot is also 634 * checked to make sure no frame drop caused by video snapshot. 635 * </p> 636 */ 637 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testVideoSnapshot()638 public void testVideoSnapshot() throws Exception { 639 videoSnapshotHelper(/*burstTest*/false); 640 } 641 642 /** 643 * <p> 644 * Test burst video snapshot for each camera. 645 * </p> 646 * <p> 647 * This test covers burst video snapshot capture. The MediaRecorder is used to record the 648 * video for each available video size. The largest still capture size is selected to 649 * capture the JPEG image. {@value #BURST_VIDEO_SNAPSHOT_NUM} video snapshot requests will be 650 * sent during the test. The still capture images are validated according to the capture 651 * configuration. 652 * </p> 653 */ 654 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testBurstVideoSnapshot()655 public void testBurstVideoSnapshot() throws Exception { 656 videoSnapshotHelper(/*burstTest*/true); 657 } 658 659 /** 660 * Test timelapse recording, where capture rate is slower than video (playback) frame rate. 661 */ 662 @Test testTimelapseRecording()663 public void testTimelapseRecording() throws Exception { 664 // TODO. Need implement. 665 } 666 667 @Test testSlowMotionRecording()668 public void testSlowMotionRecording() throws Exception { 669 slowMotionRecording(); 670 } 671 672 @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests testConstrainedHighSpeedRecording()673 public void testConstrainedHighSpeedRecording() throws Exception { 674 constrainedHighSpeedRecording(); 675 } 676 677 @Test testAbandonedHighSpeedRequest()678 public void testAbandonedHighSpeedRequest() throws Exception { 679 for (String id : mCameraIdsUnderTest) { 680 try { 681 Log.i(TAG, "Testing bad suface for createHighSpeedRequestList for camera " + id); 682 StaticMetadata staticInfo = mAllStaticInfo.get(id); 683 if (!staticInfo.isColorOutputSupported()) { 684 Log.i(TAG, "Camera " + id + 685 " does not support color outputs, skipping"); 686 continue; 687 } 688 if (!staticInfo.isConstrainedHighSpeedVideoSupported()) { 689 Log.i(TAG, "Camera " + id + 690 " does not support constrained high speed video, skipping"); 691 continue; 692 } 693 694 // Re-use the MediaRecorder object for the same camera device. 695 mMediaRecorder = new MediaRecorder(); 696 openDevice(id); 697 698 StreamConfigurationMap config = 699 mStaticInfo.getValueFromKeyNonNull( 700 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 701 Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes(); 702 Size size = highSpeedVideoSizes[0]; 703 Range<Integer> fpsRange = getHighestHighSpeedFixedFpsRangeForSize(config, size); 704 mCollector.expectNotNull("Unable to find the fixed frame rate fps range for " + 705 "size " + size, fpsRange); 706 if (fpsRange == null) { 707 continue; 708 } 709 710 int captureRate = fpsRange.getLower(); 711 int videoFramerate = captureRate / SLOWMO_SLOW_FACTOR; 712 // Skip the test if the highest recording FPS supported by CamcorderProfile 713 if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) { 714 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps" 715 + " is not supported by CamcorderProfile"); 716 continue; 717 } 718 719 mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4"; 720 prepareRecording(size, videoFramerate, captureRate); 721 updatePreviewSurfaceWithVideo(size, captureRate); 722 723 List<Surface> outputSurfaces = new ArrayList<Surface>(2); 724 assertTrue("Both preview and recording surfaces should be valid", 725 mPreviewSurface.isValid() && mRecordingSurface.isValid()); 726 727 outputSurfaces.add(mPreviewSurface); 728 outputSurfaces.add(mRecordingSurface); 729 730 mSessionListener = new BlockingSessionCallback(); 731 mSession = configureCameraSession(mCamera, outputSurfaces, /*highSpeed*/true, 732 mSessionListener, mHandler); 733 734 CaptureRequest.Builder requestBuilder = 735 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 736 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 737 requestBuilder.addTarget(mPreviewSurface); 738 requestBuilder.addTarget(mRecordingSurface); 739 740 // 1. Test abandoned MediaRecorder 741 releaseRecorder(); 742 try { 743 List<CaptureRequest> slowMoRequests = 744 ((CameraConstrainedHighSpeedCaptureSession) mSession). 745 createHighSpeedRequestList(requestBuilder.build()); 746 fail("Create high speed request on abandoned surface must fail!"); 747 } catch (IllegalArgumentException e) { 748 Log.i(TAG, "Release recording surface test passed"); 749 // expected 750 } 751 752 // 2. Test abandoned preview surface 753 mMediaRecorder = new MediaRecorder(); 754 SurfaceTexture preview = new SurfaceTexture(/*random int*/ 1); 755 Surface previewSurface = new Surface(preview); 756 preview.setDefaultBufferSize(size.getWidth(), size.getHeight()); 757 758 outputSurfaces = new ArrayList<Surface>(); 759 outputSurfaces.add(previewSurface); 760 761 prepareRecording(size, videoFramerate, captureRate); 762 updatePreviewSurfaceWithVideo(size, captureRate); 763 764 mSession = configureCameraSession(mCamera, outputSurfaces, /*highSpeed*/true, 765 mSessionListener, mHandler); 766 767 requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 768 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 769 requestBuilder.addTarget(previewSurface); 770 771 // Abandon preview surface. 772 previewSurface.release(); 773 774 try { 775 List<CaptureRequest> slowMoRequests = 776 ((CameraConstrainedHighSpeedCaptureSession) mSession). 777 createHighSpeedRequestList(requestBuilder.build()); 778 fail("Create high speed request on abandoned preview surface must fail!"); 779 } catch (IllegalArgumentException e) { 780 Log.i(TAG, "Release preview surface test passed"); 781 // expected 782 } 783 } finally { 784 closeDevice(); 785 releaseRecorder(); 786 } 787 } 788 } 789 790 /** 791 * <p> 792 * Test recording framerate accuracy when switching from low FPS to high FPS. 793 * </p> 794 * <p> 795 * This test first record a video with profile of lowest framerate then record a video with 796 * profile of highest framerate. Make sure that the video framerate are still accurate. 797 * </p> 798 */ 799 @Test testRecordingFramerateLowToHigh()800 public void testRecordingFramerateLowToHigh() throws Exception { 801 for (int i = 0; i < mCameraIdsUnderTest.length; i++) { 802 try { 803 Log.i(TAG, "Testing recording framerate low to high for camera " + mCameraIdsUnderTest[i]); 804 StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]); 805 if (!staticInfo.isColorOutputSupported()) { 806 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 807 " does not support color outputs, skipping"); 808 continue; 809 } 810 if (staticInfo.isExternalCamera()) { 811 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 812 " does not support CamcorderProfile, skipping"); 813 continue; 814 } 815 // Re-use the MediaRecorder object for the same camera device. 816 mMediaRecorder = new MediaRecorder(); 817 openDevice(mCameraIdsUnderTest[i]); 818 819 initSupportedVideoSize(mCameraIdsUnderTest[i]); 820 821 int minFpsProfileId = -1, minFps = 1000; 822 int maxFpsProfileId = -1, maxFps = 0; 823 int cameraId = Integer.valueOf(mCamera.getId()); 824 825 for (int profileId : mCamcorderProfileList) { 826 if (!CamcorderProfile.hasProfile(cameraId, profileId)) { 827 continue; 828 } 829 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId); 830 if (profile.videoFrameRate < minFps) { 831 minFpsProfileId = profileId; 832 minFps = profile.videoFrameRate; 833 } 834 if (profile.videoFrameRate > maxFps) { 835 maxFpsProfileId = profileId; 836 maxFps = profile.videoFrameRate; 837 } 838 } 839 840 int camcorderProfileList[] = new int[] {minFpsProfileId, maxFpsProfileId}; 841 basicRecordingTestByCamera(camcorderProfileList, /*useVideoStab*/false); 842 } finally { 843 closeDevice(); 844 releaseRecorder(); 845 } 846 } 847 } 848 849 /** 850 * <p> 851 * Test preview and video surfaces sharing the same camera stream. 852 * </p> 853 */ 854 @Test testVideoPreviewSurfaceSharing()855 public void testVideoPreviewSurfaceSharing() throws Exception { 856 for (int i = 0; i < mCameraIdsUnderTest.length; i++) { 857 try { 858 StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]); 859 if (staticInfo.isHardwareLevelLegacy()) { 860 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " is legacy, skipping"); 861 continue; 862 } 863 if (!staticInfo.isColorOutputSupported()) { 864 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 865 " does not support color outputs, skipping"); 866 continue; 867 } 868 if (staticInfo.isExternalCamera()) { 869 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 870 " does not support CamcorderProfile, skipping"); 871 continue; 872 } 873 // Re-use the MediaRecorder object for the same camera device. 874 mMediaRecorder = new MediaRecorder(); 875 openDevice(mCameraIdsUnderTest[i]); 876 877 initSupportedVideoSize(mCameraIdsUnderTest[i]); 878 879 videoPreviewSurfaceSharingTestByCamera(); 880 } finally { 881 closeDevice(); 882 releaseRecorder(); 883 } 884 } 885 } 886 887 /** 888 * <p> 889 * Test recording with same recording surface and different preview surfaces. 890 * </p> 891 * <p> 892 * This test maintains persistent video surface while changing preview surface. 893 * This exercises format/dataspace override behavior of the camera device. 894 * </p> 895 */ 896 @Test testRecordingWithDifferentPreviewSizes()897 public void testRecordingWithDifferentPreviewSizes() throws Exception { 898 if (!MediaUtils.checkCodecForDomain(true /* encoder */, "video")) { 899 return; // skipped 900 } 901 mPersistentSurface = MediaCodec.createPersistentInputSurface(); 902 assertNotNull("Failed to create persistent input surface!", mPersistentSurface); 903 904 try { 905 doRecordingWithDifferentPreviewSizes(); 906 } finally { 907 mPersistentSurface.release(); 908 mPersistentSurface = null; 909 } 910 } 911 doRecordingWithDifferentPreviewSizes()912 public void doRecordingWithDifferentPreviewSizes() throws Exception { 913 for (int i = 0; i < mCameraIdsUnderTest.length; i++) { 914 try { 915 Log.i(TAG, "Testing recording with different preview sizes for camera " + 916 mCameraIdsUnderTest[i]); 917 StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]); 918 if (!staticInfo.isColorOutputSupported()) { 919 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 920 " does not support color outputs, skipping"); 921 continue; 922 } 923 if (staticInfo.isExternalCamera()) { 924 Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + 925 " does not support CamcorderProfile, skipping"); 926 continue; 927 } 928 // Re-use the MediaRecorder object for the same camera device. 929 mMediaRecorder = new MediaRecorder(); 930 openDevice(mCameraIdsUnderTest[i]); 931 932 initSupportedVideoSize(mCameraIdsUnderTest[i]); 933 934 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 935 List<Range<Integer> > fpsRanges = Arrays.asList( 936 mStaticInfo.getAeAvailableTargetFpsRangesChecked()); 937 int cameraId = Integer.valueOf(mCamera.getId()); 938 int maxVideoFrameRate = -1; 939 for (int profileId : mCamcorderProfileList) { 940 if (!CamcorderProfile.hasProfile(cameraId, profileId)) { 941 continue; 942 } 943 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId); 944 945 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 946 Range<Integer> fpsRange = new Range( 947 profile.videoFrameRate, profile.videoFrameRate); 948 if (maxVideoFrameRate < profile.videoFrameRate) { 949 maxVideoFrameRate = profile.videoFrameRate; 950 } 951 952 if (allowedUnsupported(cameraId, profileId)) { 953 continue; 954 } 955 956 if (mStaticInfo.isHardwareLevelLegacy() && 957 (videoSz.getWidth() > maxPreviewSize.getWidth() || 958 videoSz.getHeight() > maxPreviewSize.getHeight())) { 959 // Skip. Legacy mode can only do recording up to max preview size 960 continue; 961 } 962 assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId + 963 " must be one of the camera device supported video size!", 964 mSupportedVideoSizes.contains(videoSz)); 965 assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId + 966 ") must be one of the camera device available FPS range!", 967 fpsRanges.contains(fpsRange)); 968 969 // Configure preview and recording surfaces. 970 mOutMediaFileName = mDebugFileNameBase + "/test_video_surface_reconfig.mp4"; 971 972 // prepare preview surface by using video size. 973 List<Size> previewSizes = getPreviewSizesForVideo(videoSz, 974 profile.videoFrameRate); 975 if (previewSizes.size() <= 1) { 976 continue; 977 } 978 979 // 1. Do video recording using largest compatbile preview sizes 980 prepareRecordingWithProfile(profile); 981 updatePreviewSurface(previewSizes.get(0)); 982 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 983 startRecording( 984 /* useMediaRecorder */true, resultListener, 985 /*useVideoStab*/false, fpsRange, false); 986 SystemClock.sleep(RECORDING_DURATION_MS); 987 stopRecording(/* useMediaRecorder */true, /* useIntermediateSurface */false, 988 /* stopStreaming */false); 989 990 // 2. Reconfigure with the same recording surface, but switch to a smaller 991 // preview size. 992 prepareRecordingWithProfile(profile); 993 updatePreviewSurface(previewSizes.get(1)); 994 SimpleCaptureCallback resultListener2 = new SimpleCaptureCallback(); 995 startRecording( 996 /* useMediaRecorder */true, resultListener2, 997 /*useVideoStab*/false, fpsRange, false); 998 SystemClock.sleep(RECORDING_DURATION_MS); 999 stopRecording(/* useMediaRecorder */true); 1000 break; 1001 } 1002 } finally { 1003 closeDevice(); 1004 releaseRecorder(); 1005 } 1006 } 1007 } 1008 1009 /** 1010 * Test camera preview and video surface sharing for maximum supported size. 1011 */ videoPreviewSurfaceSharingTestByCamera()1012 private void videoPreviewSurfaceSharingTestByCamera() throws Exception { 1013 for (Size sz : mOrderedPreviewSizes) { 1014 if (!isSupported(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE)) { 1015 continue; 1016 } 1017 1018 if (VERBOSE) { 1019 Log.v(TAG, "Testing camera recording with video size " + sz.toString()); 1020 } 1021 1022 // Configure preview and recording surfaces. 1023 mOutMediaFileName = mDebugFileNameBase + "/test_video_share.mp4"; 1024 if (DEBUG_DUMP) { 1025 mOutMediaFileName = mDebugFileNameBase + "/test_video_share_" + mCamera.getId() + 1026 "_" + sz.toString() + ".mp4"; 1027 } 1028 1029 // Allow external camera to use variable fps range 1030 Range<Integer> fpsRange = null; 1031 if (mStaticInfo.isExternalCamera()) { 1032 Range<Integer>[] availableFpsRange = 1033 mStaticInfo.getAeAvailableTargetFpsRangesChecked(); 1034 1035 boolean foundRange = false; 1036 int minFps = 0; 1037 for (int i = 0; i < availableFpsRange.length; i += 1) { 1038 if (minFps < availableFpsRange[i].getLower() 1039 && VIDEO_FRAME_RATE == availableFpsRange[i].getUpper()) { 1040 minFps = availableFpsRange[i].getLower(); 1041 foundRange = true; 1042 } 1043 } 1044 assertTrue("Cannot find FPS range for maxFps " + VIDEO_FRAME_RATE, foundRange); 1045 fpsRange = Range.create(minFps, VIDEO_FRAME_RATE); 1046 } 1047 1048 // Use AVC and AAC a/v compression format. 1049 prepareRecording(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE); 1050 1051 // prepare preview surface by using video size. 1052 updatePreviewSurfaceWithVideo(sz, VIDEO_FRAME_RATE); 1053 1054 // Start recording 1055 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 1056 if (!startSharedRecording(/* useMediaRecorder */true, resultListener, 1057 /*useVideoStab*/false, fpsRange)) { 1058 mMediaRecorder.reset(); 1059 continue; 1060 } 1061 1062 // Record certain duration. 1063 SystemClock.sleep(RECORDING_DURATION_MS); 1064 1065 // Stop recording and preview 1066 stopRecording(/* useMediaRecorder */true); 1067 // Convert number of frames camera produced into the duration in unit of ms. 1068 float frameDurationMinMs = 1000.0f / VIDEO_FRAME_RATE; 1069 float durationMinMs = resultListener.getTotalNumFrames() * frameDurationMinMs; 1070 float durationMaxMs = durationMinMs; 1071 float frameDurationMaxMs = 0.f; 1072 if (fpsRange != null) { 1073 frameDurationMaxMs = 1000.0f / fpsRange.getLower(); 1074 durationMaxMs = resultListener.getTotalNumFrames() * frameDurationMaxMs; 1075 } 1076 1077 // Validation. 1078 validateRecording(sz, durationMinMs, durationMaxMs, 1079 frameDurationMinMs, frameDurationMaxMs, 1080 FRMDRP_RATE_TOLERANCE); 1081 1082 break; 1083 } 1084 } 1085 1086 /** 1087 * Test slow motion recording where capture rate (camera output) is different with 1088 * video (playback) frame rate for each camera if high speed recording is supported 1089 * by both camera and encoder. 1090 * 1091 * <p> 1092 * Normal recording use cases make the capture rate (camera output frame 1093 * rate) the same as the video (playback) frame rate. This guarantees that 1094 * the motions in the scene play at the normal speed. If the capture rate is 1095 * faster than video frame rate, for a given time duration, more number of 1096 * frames are captured than it can be played in the same time duration. This 1097 * generates "slow motion" effect during playback. 1098 * </p> 1099 */ slowMotionRecording()1100 private void slowMotionRecording() throws Exception { 1101 for (String id : mCameraIdsUnderTest) { 1102 try { 1103 Log.i(TAG, "Testing slow motion recording for camera " + id); 1104 StaticMetadata staticInfo = mAllStaticInfo.get(id); 1105 if (!staticInfo.isColorOutputSupported()) { 1106 Log.i(TAG, "Camera " + id + 1107 " does not support color outputs, skipping"); 1108 continue; 1109 } 1110 if (!staticInfo.isHighSpeedVideoSupported()) { 1111 continue; 1112 } 1113 1114 // Re-use the MediaRecorder object for the same camera device. 1115 mMediaRecorder = new MediaRecorder(); 1116 openDevice(id); 1117 1118 StreamConfigurationMap config = 1119 mStaticInfo.getValueFromKeyNonNull( 1120 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1121 Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes(); 1122 for (Size size : highSpeedVideoSizes) { 1123 Range<Integer> fpsRange = getHighestHighSpeedFixedFpsRangeForSize(config, size); 1124 mCollector.expectNotNull("Unable to find the fixed frame rate fps range for " + 1125 "size " + size, fpsRange); 1126 if (fpsRange == null) { 1127 continue; 1128 } 1129 1130 int captureRate = fpsRange.getLower(); 1131 int videoFramerate = captureRate / SLOWMO_SLOW_FACTOR; 1132 // Skip the test if the highest recording FPS supported by CamcorderProfile 1133 if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) { 1134 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps" 1135 + " is not supported by CamcorderProfile"); 1136 continue; 1137 } 1138 1139 mOutMediaFileName = mDebugFileNameBase + "/test_slowMo_video.mp4"; 1140 if (DEBUG_DUMP) { 1141 mOutMediaFileName = mDebugFileNameBase + "/test_slowMo_video_" + id + "_" 1142 + size.toString() + ".mp4"; 1143 } 1144 1145 prepareRecording(size, videoFramerate, captureRate); 1146 1147 // prepare preview surface by using video size. 1148 updatePreviewSurfaceWithVideo(size, captureRate); 1149 1150 // Start recording 1151 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 1152 startSlowMotionRecording(/*useMediaRecorder*/true, videoFramerate, captureRate, 1153 fpsRange, resultListener, /*useHighSpeedSession*/false); 1154 1155 // Record certain duration. 1156 SystemClock.sleep(RECORDING_DURATION_MS); 1157 1158 // Stop recording and preview 1159 stopRecording(/*useMediaRecorder*/true); 1160 // Convert number of frames camera produced into the duration in unit of ms. 1161 float frameDurationMs = 1000.0f / videoFramerate; 1162 float durationMs = resultListener.getTotalNumFrames() * frameDurationMs; 1163 1164 // Validation. 1165 validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE); 1166 } 1167 1168 } finally { 1169 closeDevice(); 1170 releaseRecorder(); 1171 } 1172 } 1173 } 1174 constrainedHighSpeedRecording()1175 private void constrainedHighSpeedRecording() throws Exception { 1176 for (String id : mCameraIdsUnderTest) { 1177 try { 1178 Log.i(TAG, "Testing constrained high speed recording for camera " + id); 1179 1180 if (!mAllStaticInfo.get(id).isConstrainedHighSpeedVideoSupported()) { 1181 Log.i(TAG, "Camera " + id + " doesn't support high speed recording, skipping."); 1182 continue; 1183 } 1184 1185 // Re-use the MediaRecorder object for the same camera device. 1186 mMediaRecorder = new MediaRecorder(); 1187 openDevice(id); 1188 1189 StreamConfigurationMap config = 1190 mStaticInfo.getValueFromKeyNonNull( 1191 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 1192 Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes(); 1193 Log.v(TAG, "highSpeedVideoSizes:" + Arrays.toString(highSpeedVideoSizes)); 1194 int previewFrameRate = Integer.MAX_VALUE; 1195 for (Size size : highSpeedVideoSizes) { 1196 List<Range<Integer>> fixedFpsRanges = 1197 getHighSpeedFixedFpsRangeForSize(config, size); 1198 Range<Integer>[] highSpeedFpsRangesForSize = 1199 config.getHighSpeedVideoFpsRangesFor(size); 1200 1201 Log.v(TAG, "highSpeedFpsRangesForSize for size - " + size + " : " + 1202 Arrays.toString(highSpeedFpsRangesForSize)); 1203 // Map to store max_fps and preview fps for each video size 1204 HashMap<Integer, Integer> previewRateMap = new HashMap(); 1205 for (Range<Integer> r : highSpeedFpsRangesForSize ) { 1206 if (!Objects.equals(r.getLower(), r.getUpper())) { 1207 if (previewRateMap.containsKey(r.getUpper())) { 1208 Log.w(TAG, "previewFps for max_fps already exists."); 1209 } else { 1210 previewRateMap.put(r.getUpper(), r.getLower()); 1211 } 1212 } 1213 } 1214 1215 mCollector.expectTrue("Unable to find the fixed frame rate fps range for " + 1216 "size " + size, fixedFpsRanges.size() > 0); 1217 // Test recording for each FPS range 1218 for (Range<Integer> fpsRange : fixedFpsRanges) { 1219 int captureRate = fpsRange.getLower(); 1220 previewFrameRate = previewRateMap.get(captureRate); 1221 Log.v(TAG, "previewFrameRate: " + previewFrameRate + " captureRate: " + 1222 captureRate); 1223 1224 Range<Integer> previewfpsRange = 1225 new Range<Integer>(previewFrameRate, captureRate); 1226 1227 // Skip the test if the highest recording FPS supported by CamcorderProfile 1228 if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) { 1229 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps" 1230 + " is not supported by CamcorderProfile"); 1231 continue; 1232 } 1233 1234 SimpleCaptureCallback previewResultListener = new SimpleCaptureCallback(); 1235 1236 // prepare preview surface by using video size. 1237 updatePreviewSurfaceWithVideo(size, captureRate); 1238 1239 startConstrainedPreview(previewfpsRange, previewResultListener); 1240 1241 mOutMediaFileName = mDebugFileNameBase + "/test_cslowMo_video_" + 1242 captureRate + "fps_" + id + "_" + size.toString() + ".mp4"; 1243 1244 // b/239101664 It appears that video frame rates higher than 30 fps may not 1245 // trigger slow motion recording consistently. 1246 int videoFrameRate = previewFrameRate > VIDEO_FRAME_RATE ? 1247 VIDEO_FRAME_RATE : previewFrameRate; 1248 Log.v(TAG, "videoFrameRate:" + videoFrameRate); 1249 1250 int cameraId = Integer.valueOf(mCamera.getId()); 1251 int videoEncoder = MediaRecorder.VideoEncoder.H264; 1252 for (int profileId : mCamcorderProfileList) { 1253 if (CamcorderProfile.hasProfile(cameraId, profileId)) { 1254 CamcorderProfile profile = 1255 CamcorderProfile.get(cameraId, profileId); 1256 1257 if (profile.videoFrameHeight == size.getHeight() && 1258 profile.videoFrameWidth == size.getWidth() && 1259 profile.videoFrameRate == videoFrameRate) { 1260 videoEncoder = profile.videoCodec; 1261 // Since mCamcorderProfileList is a list representing different 1262 // resolutions, we can break when a profile with the same 1263 // dimensions as size is found 1264 break; 1265 } 1266 } 1267 } 1268 1269 prepareRecording(size, videoFrameRate, captureRate, videoEncoder); 1270 1271 SystemClock.sleep(PREVIEW_DURATION_MS); 1272 1273 stopCameraStreaming(); 1274 1275 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 1276 // Start recording 1277 startSlowMotionRecording(/*useMediaRecorder*/true, videoFrameRate, 1278 captureRate, fpsRange, resultListener, 1279 /*useHighSpeedSession*/true); 1280 1281 // Record certain duration. 1282 SystemClock.sleep(RECORDING_DURATION_MS); 1283 1284 // Stop recording and preview 1285 stopRecording(/*useMediaRecorder*/true); 1286 1287 startConstrainedPreview(previewfpsRange, previewResultListener); 1288 1289 // Convert number of frames camera produced into the duration in unit of ms. 1290 float frameDurationMs = 1000.0f / videoFrameRate; 1291 float durationMs = resultListener.getTotalNumFrames() * frameDurationMs; 1292 1293 // Validation. 1294 validateRecording(size, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE); 1295 1296 SystemClock.sleep(PREVIEW_DURATION_MS); 1297 1298 stopCameraStreaming(); 1299 } 1300 } 1301 } catch (NumberFormatException e) { 1302 fail("Cannot convert cameraId " + mCamera.getId() + " to int"); 1303 } finally { 1304 closeDevice(); 1305 releaseRecorder(); 1306 } 1307 } 1308 } 1309 1310 /** 1311 * Get high speed FPS from CamcorderProfiles for a given size. 1312 * 1313 * @param size The size used to search the CamcorderProfiles for the FPS. 1314 * @return high speed video FPS, 0 if the given size is not supported by the CamcorderProfiles. 1315 */ getFpsFromHighSpeedProfileForSize(Size size)1316 private int getFpsFromHighSpeedProfileForSize(Size size) { 1317 for (int quality = CamcorderProfile.QUALITY_HIGH_SPEED_480P; 1318 quality <= CamcorderProfile.QUALITY_HIGH_SPEED_2160P; quality++) { 1319 if (CamcorderProfile.hasProfile(quality)) { 1320 CamcorderProfile profile = CamcorderProfile.get(quality); 1321 if (size.equals(new Size(profile.videoFrameWidth, profile.videoFrameHeight))){ 1322 return profile.videoFrameRate; 1323 } 1324 } 1325 } 1326 1327 return 0; 1328 } 1329 getHighestHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, Size size)1330 private Range<Integer> getHighestHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, 1331 Size size) { 1332 Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size); 1333 Range<Integer> maxRange = availableFpsRanges[0]; 1334 boolean foundRange = false; 1335 for (Range<Integer> range : availableFpsRanges) { 1336 if (range.getLower().equals(range.getUpper()) && range.getLower() >= maxRange.getLower()) { 1337 foundRange = true; 1338 maxRange = range; 1339 } 1340 } 1341 1342 if (!foundRange) { 1343 return null; 1344 } 1345 return maxRange; 1346 } 1347 getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, Size size)1348 private List<Range<Integer>> getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config, 1349 Size size) { 1350 Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size); 1351 List<Range<Integer>> fixedRanges = new ArrayList<Range<Integer>>(); 1352 for (Range<Integer> range : availableFpsRanges) { 1353 if (range.getLower().equals(range.getUpper())) { 1354 fixedRanges.add(range); 1355 } 1356 } 1357 return fixedRanges; 1358 } 1359 startConstrainedPreview(Range<Integer> fpsRange, CameraCaptureSession.CaptureCallback listener)1360 private void startConstrainedPreview(Range<Integer> fpsRange, 1361 CameraCaptureSession.CaptureCallback listener) throws Exception { 1362 List<Surface> outputSurfaces = new ArrayList<Surface>(1); 1363 assertTrue("Preview surface should be valid", mPreviewSurface.isValid()); 1364 outputSurfaces.add(mPreviewSurface); 1365 mSessionListener = new BlockingSessionCallback(); 1366 1367 List<CaptureRequest> slowMoRequests = null; 1368 CaptureRequest.Builder requestBuilder = 1369 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 1370 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 1371 requestBuilder.addTarget(mPreviewSurface); 1372 CaptureRequest initialRequest = requestBuilder.build(); 1373 CameraTestUtils.checkSessionConfigurationWithSurfaces(mCamera, mHandler, 1374 outputSurfaces, /*inputConfig*/ null, SessionConfiguration.SESSION_HIGH_SPEED, 1375 /*defaultSupport*/ true, "Constrained session configuration query failed"); 1376 mSession = buildConstrainedCameraSession(mCamera, outputSurfaces, mSessionListener, 1377 mHandler, initialRequest); 1378 slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession). 1379 createHighSpeedRequestList(initialRequest); 1380 1381 mSession.setRepeatingBurst(slowMoRequests, listener, mHandler); 1382 } 1383 startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate, int captureRate, Range<Integer> fpsRange, SimpleCaptureCallback listener, boolean useHighSpeedSession)1384 private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate, 1385 int captureRate, Range<Integer> fpsRange, 1386 SimpleCaptureCallback listener, boolean useHighSpeedSession) 1387 throws Exception { 1388 List<Surface> outputSurfaces = new ArrayList<Surface>(2); 1389 assertTrue("Both preview and recording surfaces should be valid", 1390 mPreviewSurface.isValid() && mRecordingSurface.isValid()); 1391 outputSurfaces.add(mPreviewSurface); 1392 outputSurfaces.add(mRecordingSurface); 1393 // Video snapshot surface 1394 if (mReaderSurface != null) { 1395 outputSurfaces.add(mReaderSurface); 1396 } 1397 mSessionListener = new BlockingSessionCallback(); 1398 1399 // Create slow motion request list 1400 List<CaptureRequest> slowMoRequests = null; 1401 if (useHighSpeedSession) { 1402 CaptureRequest.Builder requestBuilder = 1403 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 1404 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 1405 requestBuilder.addTarget(mPreviewSurface); 1406 requestBuilder.addTarget(mRecordingSurface); 1407 CaptureRequest initialRequest = requestBuilder.build(); 1408 mSession = buildConstrainedCameraSession(mCamera, outputSurfaces, mSessionListener, 1409 mHandler, initialRequest); 1410 slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession). 1411 createHighSpeedRequestList(initialRequest); 1412 } else { 1413 CaptureRequest.Builder recordingRequestBuilder = 1414 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 1415 recordingRequestBuilder.set(CaptureRequest.CONTROL_MODE, 1416 CaptureRequest.CONTROL_MODE_USE_SCENE_MODE); 1417 recordingRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, 1418 CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO); 1419 1420 CaptureRequest.Builder recordingOnlyBuilder = 1421 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 1422 recordingOnlyBuilder.set(CaptureRequest.CONTROL_MODE, 1423 CaptureRequest.CONTROL_MODE_USE_SCENE_MODE); 1424 recordingOnlyBuilder.set(CaptureRequest.CONTROL_SCENE_MODE, 1425 CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO); 1426 int slowMotionFactor = captureRate / videoFrameRate; 1427 1428 // Make sure camera output frame rate is set to correct value. 1429 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 1430 recordingRequestBuilder.addTarget(mRecordingSurface); 1431 recordingRequestBuilder.addTarget(mPreviewSurface); 1432 recordingOnlyBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 1433 recordingOnlyBuilder.addTarget(mRecordingSurface); 1434 1435 CaptureRequest initialRequest = recordingRequestBuilder.build(); 1436 mSession = configureCameraSessionWithParameters(mCamera, outputSurfaces, 1437 mSessionListener, mHandler, initialRequest); 1438 1439 slowMoRequests = new ArrayList<CaptureRequest>(); 1440 slowMoRequests.add(initialRequest);// Preview + recording. 1441 1442 for (int i = 0; i < slowMotionFactor - 1; i++) { 1443 slowMoRequests.add(recordingOnlyBuilder.build()); // Recording only. 1444 } 1445 } 1446 1447 mSession.setRepeatingBurst(slowMoRequests, listener, mHandler); 1448 1449 if (useMediaRecorder) { 1450 // Wait for the first capture start before starting mediaRecorder 1451 listener.getCaptureStartTimestamps(1); 1452 mMediaRecorder.start(); 1453 } else { 1454 // TODO: need implement MediaCodec path. 1455 } 1456 1457 } 1458 basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab)1459 private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab) 1460 throws Exception { 1461 basicRecordingTestByCamera(camcorderProfileList, useVideoStab, false); 1462 } 1463 basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab, boolean useIntermediateSurface)1464 private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab, 1465 boolean useIntermediateSurface) throws Exception { 1466 basicRecordingTestByCamera(camcorderProfileList, useVideoStab, 1467 useIntermediateSurface, false); 1468 } 1469 1470 /** 1471 * Test camera recording by using each available CamcorderProfile for a 1472 * given camera. preview size is set to the video size. 1473 */ basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab, boolean useIntermediateSurface, boolean useEncoderProfiles)1474 private void basicRecordingTestByCamera(int[] camcorderProfileList, boolean useVideoStab, 1475 boolean useIntermediateSurface, boolean useEncoderProfiles) throws Exception { 1476 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 1477 List<Range<Integer> > fpsRanges = Arrays.asList( 1478 mStaticInfo.getAeAvailableTargetFpsRangesChecked()); 1479 int cameraId = Integer.valueOf(mCamera.getId()); 1480 int maxVideoFrameRate = -1; 1481 1482 // only validate recording for non-perf measurement runs 1483 boolean validateRecording = !isPerfMeasure(); 1484 for (int profileId : camcorderProfileList) { 1485 if (!CamcorderProfile.hasProfile(cameraId, profileId)) { 1486 continue; 1487 } 1488 1489 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId); 1490 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 1491 1492 Range<Integer> fpsRange = new Range(profile.videoFrameRate, profile.videoFrameRate); 1493 if (maxVideoFrameRate < profile.videoFrameRate) { 1494 maxVideoFrameRate = profile.videoFrameRate; 1495 } 1496 1497 if (allowedUnsupported(cameraId, profileId)) { 1498 continue; 1499 } 1500 1501 if (mStaticInfo.isHardwareLevelLegacy() && 1502 (videoSz.getWidth() > maxPreviewSize.getWidth() || 1503 videoSz.getHeight() > maxPreviewSize.getHeight())) { 1504 // Skip. Legacy mode can only do recording up to max preview size 1505 continue; 1506 } 1507 assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId + 1508 " must be one of the camera device supported video size!", 1509 mSupportedVideoSizes.contains(videoSz)); 1510 assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId + 1511 ") must be one of the camera device available FPS range!", 1512 fpsRanges.contains(fpsRange)); 1513 1514 1515 if (useEncoderProfiles) { 1516 // Iterate through all video-audio codec combination 1517 EncoderProfiles profiles = CamcorderProfile.getAll(mCamera.getId(), profileId); 1518 for (EncoderProfiles.VideoProfile videoProfile : profiles.getVideoProfiles()) { 1519 boolean hasAudioProfile = false; 1520 for (EncoderProfiles.AudioProfile audioProfile : profiles.getAudioProfiles()) { 1521 hasAudioProfile = true; 1522 doBasicRecordingByProfile(profiles, videoProfile, audioProfile, 1523 useVideoStab, useIntermediateSurface, validateRecording); 1524 // Only measure the default video profile of the largest video 1525 // recording size when measuring perf 1526 if (isPerfMeasure()) { 1527 break; 1528 } 1529 } 1530 // Timelapse profiles do not have audio track 1531 if (!hasAudioProfile) { 1532 doBasicRecordingByProfile(profiles, videoProfile, /* audioProfile */null, 1533 useVideoStab, useIntermediateSurface, validateRecording); 1534 } 1535 } 1536 } else { 1537 doBasicRecordingByProfile( 1538 profile, useVideoStab, useIntermediateSurface, validateRecording); 1539 } 1540 1541 if (isPerfMeasure()) { 1542 // Only measure the largest video recording size when measuring perf 1543 break; 1544 } 1545 } 1546 if (maxVideoFrameRate != -1) { 1547 // At least one CamcorderProfile is present, check FPS 1548 assertTrue("At least one CamcorderProfile must support >= 24 FPS", 1549 maxVideoFrameRate >= 24); 1550 } 1551 } 1552 doBasicRecordingByProfile( CamcorderProfile profile, boolean userVideoStab, boolean useIntermediateSurface, boolean validate)1553 private void doBasicRecordingByProfile( 1554 CamcorderProfile profile, boolean userVideoStab, 1555 boolean useIntermediateSurface, boolean validate) throws Exception { 1556 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 1557 int frameRate = profile.videoFrameRate; 1558 1559 if (VERBOSE) { 1560 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString()); 1561 } 1562 1563 // Configure preview and recording surfaces. 1564 mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4"; 1565 if (DEBUG_DUMP) { 1566 mOutMediaFileName = mDebugFileNameBase + "/test_video_" + mCamera.getId() + "_" 1567 + videoSz.toString() + ".mp4"; 1568 } 1569 1570 setupMediaRecorder(profile); 1571 completeBasicRecording(videoSz, frameRate, userVideoStab, useIntermediateSurface, validate); 1572 } 1573 doBasicRecordingByProfile( EncoderProfiles profiles, EncoderProfiles.VideoProfile videoProfile, EncoderProfiles.AudioProfile audioProfile, boolean userVideoStab, boolean useIntermediateSurface, boolean validate)1574 private void doBasicRecordingByProfile( 1575 EncoderProfiles profiles, 1576 EncoderProfiles.VideoProfile videoProfile, EncoderProfiles.AudioProfile audioProfile, 1577 boolean userVideoStab, boolean useIntermediateSurface, boolean validate) 1578 throws Exception { 1579 Size videoSz = new Size(videoProfile.getWidth(), videoProfile.getHeight()); 1580 int frameRate = videoProfile.getFrameRate(); 1581 1582 if (VERBOSE) { 1583 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString() + 1584 ", video codec " + videoProfile.getMediaType() + ", and audio codec " + 1585 (audioProfile == null ? "(null)" : audioProfile.getMediaType())); 1586 } 1587 1588 // Configure preview and recording surfaces. 1589 mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4"; 1590 if (DEBUG_DUMP) { 1591 mOutMediaFileName = mDebugFileNameBase + "/test_video_" + mCamera.getId() + "_" 1592 + videoSz.toString() + "_" + videoProfile.getCodec(); 1593 if (audioProfile != null) { 1594 mOutMediaFileName += "_" + audioProfile.getCodec(); 1595 } 1596 mOutMediaFileName += ".mp4"; 1597 } 1598 1599 setupMediaRecorder(profiles, videoProfile, audioProfile); 1600 completeBasicRecording(videoSz, frameRate, userVideoStab, useIntermediateSurface, validate); 1601 } 1602 completeBasicRecording( Size videoSz, int frameRate, boolean useVideoStab, boolean useIntermediateSurface, boolean validate)1603 private void completeBasicRecording( 1604 Size videoSz, int frameRate, boolean useVideoStab, 1605 boolean useIntermediateSurface, boolean validate) throws Exception { 1606 prepareRecording(useIntermediateSurface); 1607 1608 // prepare preview surface by using video size. 1609 updatePreviewSurfaceWithVideo(videoSz, frameRate); 1610 1611 // Start recording 1612 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 1613 startRecording(/* useMediaRecorder */true, resultListener, useVideoStab, 1614 useIntermediateSurface); 1615 1616 // Record certain duration. 1617 SystemClock.sleep(RECORDING_DURATION_MS); 1618 1619 // Stop recording and preview 1620 stopRecording(/* useMediaRecorder */true, useIntermediateSurface, 1621 /* stopCameraStreaming */true); 1622 // Convert number of frames camera produced into the duration in unit of ms. 1623 float frameDurationMs = 1000.0f / frameRate; 1624 float durationMs = 0.f; 1625 if (useIntermediateSurface) { 1626 durationMs = mQueuer.getQueuedCount() * frameDurationMs; 1627 } else { 1628 durationMs = resultListener.getTotalNumFrames() * frameDurationMs; 1629 } 1630 1631 if (VERBOSE) { 1632 Log.v(TAG, "video frame rate: " + frameRate + 1633 ", num of frames produced: " + resultListener.getTotalNumFrames()); 1634 } 1635 1636 if (validate) { 1637 validateRecording(videoSz, durationMs, frameDurationMs, FRMDRP_RATE_TOLERANCE); 1638 } 1639 } 1640 1641 /** 1642 * Test camera recording for each supported video size by camera, preview 1643 * size is set to the video size. 1644 */ recordingSizeTestByCamera()1645 private void recordingSizeTestByCamera() throws Exception { 1646 for (Size sz : mSupportedVideoSizes) { 1647 if (!isSupported(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE)) { 1648 continue; 1649 } 1650 1651 if (VERBOSE) { 1652 Log.v(TAG, "Testing camera recording with video size " + sz.toString()); 1653 } 1654 1655 // Configure preview and recording surfaces. 1656 mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4"; 1657 if (DEBUG_DUMP) { 1658 mOutMediaFileName = mDebugFileNameBase + "/test_video_" + mCamera.getId() + "_" 1659 + sz.toString() + ".mp4"; 1660 } 1661 1662 // Allow external camera to use variable fps range 1663 Range<Integer> fpsRange = null; 1664 if (mStaticInfo.isExternalCamera()) { 1665 Range<Integer>[] availableFpsRange = 1666 mStaticInfo.getAeAvailableTargetFpsRangesChecked(); 1667 1668 boolean foundRange = false; 1669 int minFps = 0; 1670 for (int i = 0; i < availableFpsRange.length; i += 1) { 1671 if (minFps < availableFpsRange[i].getLower() 1672 && VIDEO_FRAME_RATE == availableFpsRange[i].getUpper()) { 1673 minFps = availableFpsRange[i].getLower(); 1674 foundRange = true; 1675 } 1676 } 1677 assertTrue("Cannot find FPS range for maxFps " + VIDEO_FRAME_RATE, foundRange); 1678 fpsRange = Range.create(minFps, VIDEO_FRAME_RATE); 1679 } 1680 1681 // Use AVC and AAC a/v compression format. 1682 prepareRecording(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE); 1683 1684 // prepare preview surface by using video size. 1685 updatePreviewSurfaceWithVideo(sz, VIDEO_FRAME_RATE); 1686 1687 // Start recording 1688 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 1689 startRecording( 1690 /* useMediaRecorder */true, resultListener, 1691 /*useVideoStab*/false, fpsRange, false); 1692 1693 // Record certain duration. 1694 SystemClock.sleep(RECORDING_DURATION_MS); 1695 1696 // Stop recording and preview 1697 stopRecording(/* useMediaRecorder */true); 1698 // Convert number of frames camera produced into the duration in unit of ms. 1699 float frameDurationMinMs = 1000.0f / VIDEO_FRAME_RATE; 1700 float durationMinMs = resultListener.getTotalNumFrames() * frameDurationMinMs; 1701 float durationMaxMs = durationMinMs; 1702 float frameDurationMaxMs = 0.f; 1703 if (fpsRange != null) { 1704 frameDurationMaxMs = 1000.0f / fpsRange.getLower(); 1705 durationMaxMs = resultListener.getTotalNumFrames() * frameDurationMaxMs; 1706 } 1707 1708 // Validation. 1709 validateRecording(sz, durationMinMs, durationMaxMs, 1710 frameDurationMinMs, frameDurationMaxMs, 1711 FRMDRP_RATE_TOLERANCE); 1712 } 1713 } 1714 1715 /** 1716 * Initialize the supported video sizes. 1717 */ initSupportedVideoSize(String cameraId)1718 private void initSupportedVideoSize(String cameraId) throws Exception { 1719 int id = Integer.valueOf(cameraId); 1720 Size maxVideoSize = SIZE_BOUND_720P; 1721 if (CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_2160P)) { 1722 maxVideoSize = SIZE_BOUND_2160P; 1723 } else if (CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_QHD)) { 1724 maxVideoSize = SIZE_BOUND_QHD; 1725 } else if (CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_2K)) { 1726 maxVideoSize = SIZE_BOUND_2K; 1727 } else if (CamcorderProfile.hasProfile(id, CamcorderProfile.QUALITY_1080P)) { 1728 maxVideoSize = SIZE_BOUND_1080P; 1729 } 1730 1731 mSupportedVideoSizes = 1732 getSupportedVideoSizes(cameraId, mCameraManager, maxVideoSize); 1733 } 1734 1735 /** 1736 * Simple wrapper to wrap normal/burst video snapshot tests 1737 */ videoSnapshotHelper(boolean burstTest)1738 private void videoSnapshotHelper(boolean burstTest) throws Exception { 1739 for (String id : mCameraIdsUnderTest) { 1740 try { 1741 Log.i(TAG, "Testing video snapshot for camera " + id); 1742 1743 StaticMetadata staticInfo = mAllStaticInfo.get(id); 1744 if (!staticInfo.isColorOutputSupported()) { 1745 Log.i(TAG, "Camera " + id + 1746 " does not support color outputs, skipping"); 1747 continue; 1748 } 1749 1750 if (staticInfo.isExternalCamera()) { 1751 Log.i(TAG, "Camera " + id + 1752 " does not support CamcorderProfile, skipping"); 1753 continue; 1754 } 1755 1756 // Re-use the MediaRecorder object for the same camera device. 1757 mMediaRecorder = new MediaRecorder(); 1758 1759 openDevice(id); 1760 1761 initSupportedVideoSize(id); 1762 1763 videoSnapshotTestByCamera(burstTest); 1764 } finally { 1765 closeDevice(); 1766 releaseRecorder(); 1767 } 1768 } 1769 } 1770 1771 /** 1772 * Returns {@code true} if the {@link CamcorderProfile} ID is allowed to be unsupported. 1773 * 1774 * <p>This only allows unsupported profiles when using the LEGACY mode of the Camera API.</p> 1775 * 1776 * @param profileId a {@link CamcorderProfile} ID to check. 1777 * @return {@code true} if supported. 1778 */ allowedUnsupported(int cameraId, int profileId)1779 private boolean allowedUnsupported(int cameraId, int profileId) { 1780 if (!mStaticInfo.isHardwareLevelLegacy()) { 1781 return false; 1782 } 1783 1784 switch(profileId) { 1785 case CamcorderProfile.QUALITY_2160P: 1786 case CamcorderProfile.QUALITY_1080P: 1787 case CamcorderProfile.QUALITY_HIGH: 1788 return !CamcorderProfile.hasProfile(cameraId, profileId) || 1789 CamcorderProfile.get(cameraId, profileId).videoFrameWidth >= 1080; 1790 } 1791 return false; 1792 } 1793 1794 /** 1795 * Test video snapshot for each available CamcorderProfile for a given camera. 1796 * 1797 * <p> 1798 * Preview size is set to the video size. For the burst test, frame drop and jittering 1799 * is not checked. 1800 * </p> 1801 * 1802 * @param burstTest Perform burst capture or single capture. For burst capture 1803 * {@value #BURST_VIDEO_SNAPSHOT_NUM} capture requests will be sent. 1804 */ videoSnapshotTestByCamera(boolean burstTest)1805 private void videoSnapshotTestByCamera(boolean burstTest) 1806 throws Exception { 1807 final int NUM_SINGLE_SHOT_TEST = 5; 1808 final int FRAMEDROP_TOLERANCE = 8; 1809 final int FRAME_SIZE_15M = 15000000; 1810 final float FRAME_DROP_TOLERENCE_FACTOR = 1.5f; 1811 int kFrameDrop_Tolerence = FRAMEDROP_TOLERANCE; 1812 1813 for (int profileId : mCamcorderProfileList) { 1814 int cameraId = Integer.valueOf(mCamera.getId()); 1815 if (!CamcorderProfile.hasProfile(cameraId, profileId) || 1816 allowedUnsupported(cameraId, profileId)) { 1817 continue; 1818 } 1819 1820 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId); 1821 Size QCIF = new Size(176, 144); 1822 Size FULL_HD = new Size(1920, 1080); 1823 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 1824 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 1825 1826 if (mStaticInfo.isHardwareLevelLegacy() && 1827 (videoSz.getWidth() > maxPreviewSize.getWidth() || 1828 videoSz.getHeight() > maxPreviewSize.getHeight())) { 1829 // Skip. Legacy mode can only do recording up to max preview size 1830 continue; 1831 } 1832 1833 if (!mSupportedVideoSizes.contains(videoSz)) { 1834 mCollector.addMessage("Video size " + videoSz.toString() + " for profile ID " + 1835 profileId + " must be one of the camera device supported video size!"); 1836 continue; 1837 } 1838 1839 // For LEGACY, find closest supported smaller or equal JPEG size to the current video 1840 // size; if no size is smaller than the video, pick the smallest JPEG size. The assert 1841 // for video size above guarantees that for LIMITED or FULL, we select videoSz here. 1842 // Also check for minFrameDuration here to make sure jpeg stream won't slow down 1843 // video capture 1844 Size videoSnapshotSz = mOrderedStillSizes.get(mOrderedStillSizes.size() - 1); 1845 // Allow a bit tolerance so we don't fail for a few nano seconds of difference 1846 final float FRAME_DURATION_TOLERANCE = 0.01f; 1847 long videoFrameDuration = (long) (1e9 / profile.videoFrameRate * 1848 (1.0 + FRAME_DURATION_TOLERANCE)); 1849 HashMap<Size, Long> minFrameDurationMap = mStaticInfo. 1850 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG); 1851 for (int i = mOrderedStillSizes.size() - 2; i >= 0; i--) { 1852 Size candidateSize = mOrderedStillSizes.get(i); 1853 if (mStaticInfo.isHardwareLevelLegacy()) { 1854 // Legacy level doesn't report min frame duration 1855 if (candidateSize.getWidth() <= videoSz.getWidth() && 1856 candidateSize.getHeight() <= videoSz.getHeight()) { 1857 videoSnapshotSz = candidateSize; 1858 } 1859 } else { 1860 Long jpegFrameDuration = minFrameDurationMap.get(candidateSize); 1861 assertTrue("Cannot find minimum frame duration for jpeg size " + candidateSize, 1862 jpegFrameDuration != null); 1863 if (candidateSize.getWidth() <= videoSz.getWidth() && 1864 candidateSize.getHeight() <= videoSz.getHeight() && 1865 jpegFrameDuration <= videoFrameDuration) { 1866 videoSnapshotSz = candidateSize; 1867 } 1868 } 1869 } 1870 Size defaultvideoSnapshotSz = videoSnapshotSz; 1871 1872 /** 1873 * Only test full res snapshot when below conditions are all true. 1874 * 1. Camera is at least a LIMITED device. 1875 * 2. video size is up to max preview size, which will be bounded by 1080p. 1876 * 3. Full resolution jpeg stream can keep up to video stream speed. 1877 * When full res jpeg stream cannot keep up to video stream speed, search 1878 * the largest jpeg size that can susptain video speed instead. 1879 */ 1880 if (mStaticInfo.isHardwareLevelAtLeastLimited() && 1881 videoSz.getWidth() <= maxPreviewSize.getWidth() && 1882 videoSz.getHeight() <= maxPreviewSize.getHeight()) { 1883 for (Size jpegSize : mOrderedStillSizes) { 1884 Long jpegFrameDuration = minFrameDurationMap.get(jpegSize); 1885 assertTrue("Cannot find minimum frame duration for jpeg size " + jpegSize, 1886 jpegFrameDuration != null); 1887 if (jpegFrameDuration <= videoFrameDuration) { 1888 videoSnapshotSz = jpegSize; 1889 break; 1890 } 1891 if (jpegSize.equals(videoSz)) { 1892 throw new AssertionFailedError( 1893 "Cannot find adequate video snapshot size for video size" + 1894 videoSz); 1895 } 1896 } 1897 } 1898 1899 if (videoSnapshotSz.getWidth() * videoSnapshotSz.getHeight() > FRAME_SIZE_15M) 1900 kFrameDrop_Tolerence = (int)(FRAMEDROP_TOLERANCE * FRAME_DROP_TOLERENCE_FACTOR); 1901 1902 createImageReader( 1903 videoSnapshotSz, ImageFormat.JPEG, 1904 MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null); 1905 1906 // Full or better devices should support whatever video snapshot size calculated above. 1907 // Limited devices may only be able to support the default one. 1908 if (mStaticInfo.isHardwareLevelLimited()) { 1909 List<Surface> outputs = new ArrayList<Surface>(); 1910 outputs.add(mPreviewSurface); 1911 outputs.add(mRecordingSurface); 1912 outputs.add(mReaderSurface); 1913 boolean isSupported = isStreamConfigurationSupported( 1914 mCamera, outputs, mSessionListener, mHandler); 1915 if (!isSupported) { 1916 videoSnapshotSz = defaultvideoSnapshotSz; 1917 createImageReader( 1918 videoSnapshotSz, ImageFormat.JPEG, 1919 MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null); 1920 } 1921 } 1922 1923 if (videoSz.equals(QCIF) && 1924 ((videoSnapshotSz.getWidth() > FULL_HD.getWidth()) || 1925 (videoSnapshotSz.getHeight() > FULL_HD.getHeight()))) { 1926 List<Surface> outputs = new ArrayList<Surface>(); 1927 outputs.add(mPreviewSurface); 1928 outputs.add(mRecordingSurface); 1929 outputs.add(mReaderSurface); 1930 boolean isSupported = isStreamConfigurationSupported( 1931 mCamera, outputs, mSessionListener, mHandler); 1932 if (!isSupported) { 1933 videoSnapshotSz = defaultvideoSnapshotSz; 1934 createImageReader( 1935 videoSnapshotSz, ImageFormat.JPEG, 1936 MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null); 1937 } 1938 } 1939 1940 Log.i(TAG, "Testing video snapshot size " + videoSnapshotSz + 1941 " for video size " + videoSz); 1942 1943 if (VERBOSE) { 1944 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString()); 1945 } 1946 1947 // Configure preview and recording surfaces. 1948 mOutMediaFileName = mDebugFileNameBase + "/test_video.mp4"; 1949 if (DEBUG_DUMP) { 1950 mOutMediaFileName = mDebugFileNameBase + "/test_video_" + cameraId + "_" 1951 + videoSz.toString() + ".mp4"; 1952 } 1953 1954 int numTestIterations = burstTest ? 1 : NUM_SINGLE_SHOT_TEST; 1955 int totalDroppedFrames = 0; 1956 1957 for (int numTested = 0; numTested < numTestIterations; numTested++) { 1958 prepareRecordingWithProfile(profile); 1959 1960 // prepare video snapshot 1961 SimpleCaptureCallback resultListener = new SimpleCaptureCallback(); 1962 SimpleImageReaderListener imageListener = new SimpleImageReaderListener(); 1963 CaptureRequest.Builder videoSnapshotRequestBuilder = 1964 mCamera.createCaptureRequest((mStaticInfo.isHardwareLevelLegacy()) ? 1965 CameraDevice.TEMPLATE_RECORD : 1966 CameraDevice.TEMPLATE_VIDEO_SNAPSHOT); 1967 1968 // prepare preview surface by using video size. 1969 updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate); 1970 1971 prepareVideoSnapshot(videoSnapshotRequestBuilder, imageListener); 1972 Range<Integer> fpsRange = Range.create(profile.videoFrameRate, 1973 profile.videoFrameRate); 1974 videoSnapshotRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, 1975 fpsRange); 1976 boolean videoStabilizationSupported = mStaticInfo.isVideoStabilizationSupported(); 1977 if (videoStabilizationSupported) { 1978 videoSnapshotRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 1979 mStaticInfo.getChosenVideoStabilizationMode()); 1980 } 1981 CaptureRequest request = videoSnapshotRequestBuilder.build(); 1982 1983 // Start recording 1984 startRecording(/* useMediaRecorder */true, resultListener, 1985 /*useVideoStab*/videoStabilizationSupported); 1986 long startTime = SystemClock.elapsedRealtime(); 1987 1988 // Record certain duration. 1989 SystemClock.sleep(RECORDING_DURATION_MS / 2); 1990 1991 // take video snapshot 1992 if (burstTest) { 1993 List<CaptureRequest> requests = 1994 new ArrayList<CaptureRequest>(BURST_VIDEO_SNAPSHOT_NUM); 1995 for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) { 1996 requests.add(request); 1997 } 1998 mSession.captureBurst(requests, resultListener, mHandler); 1999 } else { 2000 mSession.capture(request, resultListener, mHandler); 2001 } 2002 2003 // make sure recording is still going after video snapshot 2004 SystemClock.sleep(RECORDING_DURATION_MS / 2); 2005 2006 // Stop recording and preview 2007 float durationMs = (float) stopRecording(/* useMediaRecorder */true); 2008 // For non-burst test, use number of frames to also double check video frame rate. 2009 // Burst video snapshot is allowed to cause frame rate drop, so do not use number 2010 // of frames to estimate duration 2011 if (!burstTest) { 2012 durationMs = resultListener.getTotalNumFrames() * 1000.0f / 2013 profile.videoFrameRate; 2014 } 2015 2016 float frameDurationMs = 1000.0f / profile.videoFrameRate; 2017 // Validation recorded video 2018 validateRecording(videoSz, durationMs, 2019 frameDurationMs, VID_SNPSHT_FRMDRP_RATE_TOLERANCE); 2020 2021 if (burstTest) { 2022 for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) { 2023 Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS); 2024 validateVideoSnapshotCapture(image, videoSnapshotSz); 2025 image.close(); 2026 } 2027 } else { 2028 // validate video snapshot image 2029 Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS); 2030 validateVideoSnapshotCapture(image, videoSnapshotSz); 2031 2032 // validate if there is framedrop around video snapshot 2033 totalDroppedFrames += validateFrameDropAroundVideoSnapshot( 2034 resultListener, image.getTimestamp()); 2035 2036 //TODO: validate jittering. Should move to PTS 2037 //validateJittering(resultListener); 2038 2039 image.close(); 2040 } 2041 } 2042 2043 if (!burstTest) { 2044 Log.w(TAG, String.format("Camera %d Video size %s: Number of dropped frames " + 2045 "detected in %d trials is %d frames.", cameraId, videoSz.toString(), 2046 numTestIterations, totalDroppedFrames)); 2047 mCollector.expectLessOrEqual( 2048 String.format( 2049 "Camera %d Video size %s: Number of dropped frames %d must not" 2050 + " be larger than %d", 2051 cameraId, videoSz.toString(), totalDroppedFrames, 2052 kFrameDrop_Tolerence), 2053 kFrameDrop_Tolerence, totalDroppedFrames); 2054 } 2055 closeImageReader(); 2056 } 2057 } 2058 2059 /** 2060 * Configure video snapshot request according to the still capture size 2061 */ prepareVideoSnapshot( CaptureRequest.Builder requestBuilder, ImageReader.OnImageAvailableListener imageListener)2062 private void prepareVideoSnapshot( 2063 CaptureRequest.Builder requestBuilder, 2064 ImageReader.OnImageAvailableListener imageListener) 2065 throws Exception { 2066 mReader.setOnImageAvailableListener(imageListener, mHandler); 2067 assertNotNull("Recording surface must be non-null!", mRecordingSurface); 2068 requestBuilder.addTarget(mRecordingSurface); 2069 assertNotNull("Preview surface must be non-null!", mPreviewSurface); 2070 requestBuilder.addTarget(mPreviewSurface); 2071 assertNotNull("Reader surface must be non-null!", mReaderSurface); 2072 requestBuilder.addTarget(mReaderSurface); 2073 } 2074 2075 /** 2076 * Find compatible preview sizes for video size and framerate. 2077 * 2078 * <p>Preview size will be capped with max preview size.</p> 2079 * 2080 * @param videoSize The video size used for preview. 2081 * @param videoFrameRate The video frame rate 2082 */ getPreviewSizesForVideo(Size videoSize, int videoFrameRate)2083 private List<Size> getPreviewSizesForVideo(Size videoSize, int videoFrameRate) { 2084 if (mOrderedPreviewSizes == null) { 2085 throw new IllegalStateException("supported preview size list is not initialized yet"); 2086 } 2087 final float FRAME_DURATION_TOLERANCE = 0.01f; 2088 long videoFrameDuration = (long) (1e9 / videoFrameRate * 2089 (1.0 + FRAME_DURATION_TOLERANCE)); 2090 HashMap<Size, Long> minFrameDurationMap = mStaticInfo. 2091 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE); 2092 Size maxPreviewSize = mOrderedPreviewSizes.get(0); 2093 ArrayList<Size> previewSizes = new ArrayList<>(); 2094 if (videoSize.getWidth() > maxPreviewSize.getWidth() || 2095 videoSize.getHeight() > maxPreviewSize.getHeight()) { 2096 for (Size s : mOrderedPreviewSizes) { 2097 Long frameDuration = minFrameDurationMap.get(s); 2098 if (mStaticInfo.isHardwareLevelLegacy()) { 2099 // Legacy doesn't report min frame duration 2100 frameDuration = new Long(0); 2101 } 2102 assertTrue("Cannot find minimum frame duration for private size" + s, 2103 frameDuration != null); 2104 if (frameDuration <= videoFrameDuration && 2105 s.getWidth() <= videoSize.getWidth() && 2106 s.getHeight() <= videoSize.getHeight()) { 2107 Log.v(TAG, "Add preview size " + s.toString() + " for video size " + 2108 videoSize.toString()); 2109 previewSizes.add(s); 2110 } 2111 } 2112 } 2113 2114 if (previewSizes.isEmpty()) { 2115 previewSizes.add(videoSize); 2116 } 2117 2118 return previewSizes; 2119 } 2120 2121 /** 2122 * Update preview size with video size. 2123 * 2124 * <p>Preview size will be capped with max preview size.</p> 2125 * 2126 * @param videoSize The video size used for preview. 2127 * @param videoFrameRate The video frame rate 2128 * 2129 */ updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate)2130 private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) { 2131 List<Size> previewSizes = getPreviewSizesForVideo(videoSize, videoFrameRate); 2132 updatePreviewSurface(previewSizes.get(0)); 2133 } 2134 prepareRecordingWithProfile(CamcorderProfile profile)2135 private void prepareRecordingWithProfile(CamcorderProfile profile) throws Exception { 2136 prepareRecordingWithProfile(profile, false); 2137 } 2138 2139 /** 2140 * Configure MediaRecorder recording session with CamcorderProfile, prepare 2141 * the recording surface. 2142 */ prepareRecordingWithProfile(CamcorderProfile profile, boolean useIntermediateSurface)2143 private void prepareRecordingWithProfile(CamcorderProfile profile, 2144 boolean useIntermediateSurface) throws Exception { 2145 // Prepare MediaRecorder. 2146 setupMediaRecorder(profile); 2147 prepareRecording(useIntermediateSurface); 2148 } 2149 setupMediaRecorder(CamcorderProfile profile)2150 private void setupMediaRecorder(CamcorderProfile profile) throws Exception { 2151 // Set-up MediaRecorder. 2152 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 2153 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 2154 mMediaRecorder.setProfile(profile); 2155 2156 mVideoFrameRate = profile.videoFrameRate; 2157 mVideoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight); 2158 } 2159 setupMediaRecorder( EncoderProfiles profiles, EncoderProfiles.VideoProfile videoProfile, EncoderProfiles.AudioProfile audioProfile)2160 private void setupMediaRecorder( 2161 EncoderProfiles profiles, 2162 EncoderProfiles.VideoProfile videoProfile, 2163 EncoderProfiles.AudioProfile audioProfile) throws Exception { 2164 // Set-up MediaRecorder. 2165 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 2166 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 2167 mMediaRecorder.setOutputFormat(profiles.getRecommendedFileFormat()); 2168 mMediaRecorder.setVideoProfile(videoProfile); 2169 if (audioProfile != null) { 2170 mMediaRecorder.setAudioProfile(audioProfile); 2171 } 2172 2173 mVideoFrameRate = videoProfile.getFrameRate(); 2174 mVideoSize = new Size(videoProfile.getWidth(), videoProfile.getHeight()); 2175 } 2176 prepareRecording(boolean useIntermediateSurface)2177 private void prepareRecording(boolean useIntermediateSurface) throws Exception { 2178 // Continue preparing MediaRecorder 2179 mMediaRecorder.setOutputFile(mOutMediaFileName); 2180 if (mPersistentSurface != null) { 2181 mMediaRecorder.setInputSurface(mPersistentSurface); 2182 mRecordingSurface = mPersistentSurface; 2183 } 2184 mMediaRecorder.prepare(); 2185 if (mPersistentSurface == null) { 2186 mRecordingSurface = mMediaRecorder.getSurface(); 2187 } 2188 assertNotNull("Recording surface must be non-null!", mRecordingSurface); 2189 2190 if (useIntermediateSurface) { 2191 Optional<Long> usage = getSurfaceUsage(mRecordingSurface); 2192 mIntermediateReader = ImageReader.newInstance( 2193 mVideoSize.getWidth(), mVideoSize.getHeight(), 2194 ImageFormat.PRIVATE, /*maxImages*/3, 2195 usage.orElse(HardwareBuffer.USAGE_VIDEO_ENCODE)); 2196 2197 mIntermediateSurface = mIntermediateReader.getSurface(); 2198 mIntermediateWriter = ImageWriter.newInstance(mRecordingSurface, /*maxImages*/3, 2199 ImageFormat.PRIVATE); 2200 mQueuer = new ImageWriterQueuer(mIntermediateWriter); 2201 2202 mIntermediateThread = new HandlerThread(TAG); 2203 mIntermediateThread.start(); 2204 mIntermediateHandler = new Handler(mIntermediateThread.getLooper()); 2205 mIntermediateReader.setOnImageAvailableListener(mQueuer, mIntermediateHandler); 2206 } 2207 } 2208 2209 /** 2210 * Configure MediaRecorder recording session with CamcorderProfile, prepare 2211 * the recording surface. Use AVC for video compression, AAC for audio compression. 2212 * Both are required for android devices by android CDD. 2213 */ prepareRecording(Size sz, int videoFrameRate, int captureRate)2214 private void prepareRecording(Size sz, int videoFrameRate, int captureRate) 2215 throws Exception { 2216 // Prepare MediaRecorder. 2217 prepareRecording(sz, videoFrameRate, captureRate, MediaRecorder.VideoEncoder.H264); 2218 } 2219 2220 /** 2221 * Configure MediaRecorder recording session with CamcorderProfile, prepare 2222 * the recording surface. Use AAC for audio compression as required for 2223 * android devices by android CDD. 2224 */ prepareRecording(Size sz, int videoFrameRate, int captureRate, int videoEncoder)2225 private void prepareRecording(Size sz, int videoFrameRate, int captureRate, 2226 int videoEncoder) throws Exception { 2227 // Prepare MediaRecorder. 2228 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 2229 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 2230 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 2231 mMediaRecorder.setOutputFile(mOutMediaFileName); 2232 mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz)); 2233 mMediaRecorder.setVideoFrameRate(videoFrameRate); 2234 mMediaRecorder.setCaptureRate(captureRate); 2235 mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight()); 2236 mMediaRecorder.setVideoEncoder(videoEncoder); 2237 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 2238 if (mPersistentSurface != null) { 2239 mMediaRecorder.setInputSurface(mPersistentSurface); 2240 mRecordingSurface = mPersistentSurface; 2241 } 2242 mMediaRecorder.prepare(); 2243 if (mPersistentSurface == null) { 2244 mRecordingSurface = mMediaRecorder.getSurface(); 2245 } 2246 assertNotNull("Recording surface must be non-null!", mRecordingSurface); 2247 mVideoFrameRate = videoFrameRate; 2248 mVideoSize = sz; 2249 } 2250 startRecording(boolean useMediaRecorder, SimpleCaptureCallback listener, boolean useVideoStab)2251 private void startRecording(boolean useMediaRecorder, 2252 SimpleCaptureCallback listener, boolean useVideoStab) throws Exception { 2253 startRecording(useMediaRecorder, listener, useVideoStab, /*variableFpsRange*/null, 2254 /*useIntermediateSurface*/false); 2255 } 2256 startRecording(boolean useMediaRecorder, SimpleCaptureCallback listener, boolean useVideoStab, boolean useIntermediateSurface)2257 private void startRecording(boolean useMediaRecorder, 2258 SimpleCaptureCallback listener, boolean useVideoStab, 2259 boolean useIntermediateSurface) throws Exception { 2260 startRecording(useMediaRecorder, listener, useVideoStab, /*variableFpsRange*/null, 2261 useIntermediateSurface); 2262 } 2263 startRecording(boolean useMediaRecorder, SimpleCaptureCallback listener, boolean useVideoStab, Range<Integer> variableFpsRange, boolean useIntermediateSurface)2264 private void startRecording(boolean useMediaRecorder, 2265 SimpleCaptureCallback listener, boolean useVideoStab, 2266 Range<Integer> variableFpsRange, boolean useIntermediateSurface) throws Exception { 2267 if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) { 2268 throw new IllegalArgumentException("Video stabilization is not supported"); 2269 } 2270 2271 List<Surface> outputSurfaces = new ArrayList<Surface>(2); 2272 assertTrue("Both preview and recording surfaces should be valid", 2273 mPreviewSurface.isValid() && mRecordingSurface.isValid()); 2274 outputSurfaces.add(mPreviewSurface); 2275 if (useIntermediateSurface) { 2276 outputSurfaces.add(mIntermediateSurface); 2277 } else { 2278 outputSurfaces.add(mRecordingSurface); 2279 } 2280 2281 // Video snapshot surface 2282 if (mReaderSurface != null) { 2283 outputSurfaces.add(mReaderSurface); 2284 } 2285 mSessionListener = new BlockingSessionCallback(); 2286 2287 CaptureRequest.Builder recordingRequestBuilder = 2288 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 2289 // Make sure camera output frame rate is set to correct value. 2290 Range<Integer> fpsRange = (variableFpsRange == null) ? 2291 Range.create(mVideoFrameRate, mVideoFrameRate) : variableFpsRange; 2292 2293 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 2294 if (useVideoStab) { 2295 recordingRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 2296 mStaticInfo.getChosenVideoStabilizationMode()); 2297 2298 } 2299 if (useIntermediateSurface) { 2300 recordingRequestBuilder.addTarget(mIntermediateSurface); 2301 if (mQueuer != null) { 2302 mQueuer.resetInvalidSurfaceFlag(); 2303 } 2304 } else { 2305 recordingRequestBuilder.addTarget(mRecordingSurface); 2306 } 2307 recordingRequestBuilder.addTarget(mPreviewSurface); 2308 CaptureRequest recordingRequest = recordingRequestBuilder.build(); 2309 mSession = configureCameraSessionWithParameters(mCamera, outputSurfaces, mSessionListener, 2310 mHandler, recordingRequest); 2311 mSession.setRepeatingRequest(recordingRequest, listener, mHandler); 2312 2313 if (useMediaRecorder) { 2314 // Wait for the first capture start before starting mediaRecorder 2315 listener.getCaptureStartTimestamps(1); 2316 mMediaRecorder.start(); 2317 } else { 2318 // TODO: need implement MediaCodec path. 2319 } 2320 mRecordingStartTime = SystemClock.elapsedRealtime(); 2321 } 2322 2323 /** 2324 * Start video recording with preview and video surfaces sharing the same 2325 * camera stream. 2326 * 2327 * @return true if success, false if sharing is not supported. 2328 */ startSharedRecording(boolean useMediaRecorder, SimpleCaptureCallback listener, boolean useVideoStab, Range<Integer> variableFpsRange)2329 private boolean startSharedRecording(boolean useMediaRecorder, 2330 SimpleCaptureCallback listener, boolean useVideoStab, 2331 Range<Integer> variableFpsRange) throws Exception { 2332 if (!mStaticInfo.isVideoStabilizationSupported() && useVideoStab) { 2333 throw new IllegalArgumentException("Video stabilization is not supported"); 2334 } 2335 2336 List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>(2); 2337 assertTrue("Both preview and recording surfaces should be valid", 2338 mPreviewSurface.isValid() && mRecordingSurface.isValid()); 2339 OutputConfiguration sharedConfig = new OutputConfiguration(mPreviewSurface); 2340 sharedConfig.enableSurfaceSharing(); 2341 sharedConfig.addSurface(mRecordingSurface); 2342 outputConfigs.add(sharedConfig); 2343 2344 CaptureRequest.Builder recordingRequestBuilder = 2345 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 2346 // Make sure camera output frame rate is set to correct value. 2347 Range<Integer> fpsRange = (variableFpsRange == null) ? 2348 Range.create(mVideoFrameRate, mVideoFrameRate) : variableFpsRange; 2349 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange); 2350 if (useVideoStab) { 2351 recordingRequestBuilder.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 2352 mStaticInfo.getChosenVideoStabilizationMode()); 2353 } 2354 CaptureRequest recordingRequest = recordingRequestBuilder.build(); 2355 2356 mSessionListener = new BlockingSessionCallback(); 2357 mSession = tryConfigureCameraSessionWithConfig(mCamera, outputConfigs, recordingRequest, 2358 mSessionListener, mHandler); 2359 2360 if (mSession == null) { 2361 Log.i(TAG, "Sharing between preview and video is not supported"); 2362 return false; 2363 } 2364 2365 recordingRequestBuilder.addTarget(mRecordingSurface); 2366 recordingRequestBuilder.addTarget(mPreviewSurface); 2367 mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler); 2368 2369 if (useMediaRecorder) { 2370 // Wait for the first capture start before starting mediaRecorder 2371 listener.getCaptureStartTimestamps(1); 2372 mMediaRecorder.start(); 2373 } else { 2374 // TODO: need implement MediaCodec path. 2375 } 2376 mRecordingStartTime = SystemClock.elapsedRealtime(); 2377 return true; 2378 } 2379 2380 stopCameraStreaming()2381 private void stopCameraStreaming() throws Exception { 2382 if (VERBOSE) { 2383 Log.v(TAG, "Stopping camera streaming and waiting for idle"); 2384 } 2385 // Stop repeating, wait for captures to complete, and disconnect from 2386 // surfaces 2387 mSession.close(); 2388 mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS); 2389 } 2390 stopRecording(boolean useMediaRecorder)2391 private int stopRecording(boolean useMediaRecorder) throws Exception { 2392 return stopRecording(useMediaRecorder, /*useIntermediateSurface*/false, 2393 /*stopStreaming*/true); 2394 } 2395 2396 // Stop recording and return the estimated video duration in milliseconds. stopRecording(boolean useMediaRecorder, boolean useIntermediateSurface, boolean stopStreaming)2397 private int stopRecording(boolean useMediaRecorder, boolean useIntermediateSurface, 2398 boolean stopStreaming) throws Exception { 2399 long stopRecordingTime = SystemClock.elapsedRealtime(); 2400 if (useMediaRecorder) { 2401 if (stopStreaming) { 2402 stopCameraStreaming(); 2403 } 2404 if (useIntermediateSurface) { 2405 mIntermediateReader.setOnImageAvailableListener(null, null); 2406 mQueuer.expectInvalidSurface(); 2407 } 2408 2409 mMediaRecorder.stop(); 2410 // Can reuse the MediaRecorder object after reset. 2411 mMediaRecorder.reset(); 2412 } else { 2413 // TODO: need implement MediaCodec path. 2414 } 2415 2416 if (useIntermediateSurface) { 2417 mIntermediateReader.close(); 2418 mQueuer.close(); 2419 mIntermediateWriter.close(); 2420 mIntermediateSurface.release(); 2421 mIntermediateReader = null; 2422 mIntermediateSurface = null; 2423 mIntermediateWriter = null; 2424 mIntermediateThread.quitSafely(); 2425 mIntermediateHandler = null; 2426 } 2427 2428 if (mPersistentSurface == null && mRecordingSurface != null) { 2429 mRecordingSurface.release(); 2430 mRecordingSurface = null; 2431 } 2432 return (int) (stopRecordingTime - mRecordingStartTime); 2433 } 2434 releaseRecorder()2435 private void releaseRecorder() { 2436 if (mMediaRecorder != null) { 2437 mMediaRecorder.release(); 2438 mMediaRecorder = null; 2439 } 2440 } 2441 validateRecording( Size sz, float expectedDurationMs, float expectedFrameDurationMs, float frameDropTolerance)2442 private void validateRecording( 2443 Size sz, float expectedDurationMs, float expectedFrameDurationMs, 2444 float frameDropTolerance) throws Exception { 2445 validateRecording(sz, 2446 expectedDurationMs, /*fixed FPS recording*/0.f, 2447 expectedFrameDurationMs, /*fixed FPS recording*/0.f, 2448 frameDropTolerance); 2449 } 2450 validateRecording( Size sz, float expectedDurationMinMs, float expectedDurationMaxMs, float expectedFrameDurationMinMs, float expectedFrameDurationMaxMs, float frameDropTolerance)2451 private void validateRecording( 2452 Size sz, 2453 float expectedDurationMinMs, // Min duration (maxFps) 2454 float expectedDurationMaxMs, // Max duration (minFps). 0.f for fixed fps recording 2455 float expectedFrameDurationMinMs, // maxFps 2456 float expectedFrameDurationMaxMs, // minFps. 0.f for fixed fps recording 2457 float frameDropTolerance) throws Exception { 2458 File outFile = new File(mOutMediaFileName); 2459 assertTrue("No video is recorded", outFile.exists()); 2460 float maxFrameDuration = expectedFrameDurationMinMs * (1.0f + FRAMEDURATION_MARGIN); 2461 if (expectedFrameDurationMaxMs > 0.f) { 2462 maxFrameDuration = expectedFrameDurationMaxMs * (1.0f + FRAMEDURATION_MARGIN); 2463 } 2464 2465 if (expectedDurationMaxMs == 0.f) { 2466 expectedDurationMaxMs = expectedDurationMinMs; 2467 } 2468 2469 MediaExtractor extractor = new MediaExtractor(); 2470 try { 2471 extractor.setDataSource(mOutMediaFileName); 2472 long durationUs = 0; 2473 int width = -1, height = -1; 2474 int numTracks = extractor.getTrackCount(); 2475 int selectedTrack = -1; 2476 final String VIDEO_MIME_TYPE = "video"; 2477 for (int i = 0; i < numTracks; i++) { 2478 MediaFormat format = extractor.getTrackFormat(i); 2479 String mime = format.getString(MediaFormat.KEY_MIME); 2480 if (mime.contains(VIDEO_MIME_TYPE)) { 2481 Log.i(TAG, "video format is: " + format.toString()); 2482 durationUs = format.getLong(MediaFormat.KEY_DURATION); 2483 width = format.getInteger(MediaFormat.KEY_WIDTH); 2484 height = format.getInteger(MediaFormat.KEY_HEIGHT); 2485 selectedTrack = i; 2486 extractor.selectTrack(i); 2487 break; 2488 } 2489 } 2490 if (selectedTrack < 0) { 2491 throw new AssertionFailedError( 2492 "Cannot find video track!"); 2493 } 2494 2495 Size videoSz = new Size(width, height); 2496 assertTrue("Video size doesn't match, expected " + sz.toString() + 2497 " got " + videoSz.toString(), videoSz.equals(sz)); 2498 float duration = (float) (durationUs / 1000); 2499 if (VERBOSE) { 2500 Log.v(TAG, String.format("Video duration: recorded %fms, expected [%f,%f]ms", 2501 duration, expectedDurationMinMs, expectedDurationMaxMs)); 2502 } 2503 2504 // Do rest of validation only for better-than-LEGACY devices 2505 if (mStaticInfo.isHardwareLevelLegacy()) return; 2506 2507 // TODO: Don't skip this one for video snapshot on LEGACY 2508 assertTrue(String.format( 2509 "Camera %s: Video duration doesn't match: recorded %fms, expected [%f,%f]ms.", 2510 mCamera.getId(), duration, 2511 expectedDurationMinMs * (1.f - DURATION_MARGIN), 2512 expectedDurationMaxMs * (1.f + DURATION_MARGIN)), 2513 duration > expectedDurationMinMs * (1.f - DURATION_MARGIN) && 2514 duration < expectedDurationMaxMs * (1.f + DURATION_MARGIN)); 2515 2516 // Check for framedrop 2517 long lastSampleUs = 0; 2518 int frameDropCount = 0; 2519 int expectedFrameCount = (int) (expectedDurationMinMs / expectedFrameDurationMinMs); 2520 ArrayList<Long> timestamps = new ArrayList<Long>(expectedFrameCount); 2521 while (true) { 2522 timestamps.add(extractor.getSampleTime()); 2523 if (!extractor.advance()) { 2524 break; 2525 } 2526 } 2527 Collections.sort(timestamps); 2528 long prevSampleUs = timestamps.get(0); 2529 for (int i = 1; i < timestamps.size(); i++) { 2530 long currentSampleUs = timestamps.get(i); 2531 float frameDurationMs = (float) (currentSampleUs - prevSampleUs) / 1000; 2532 if (frameDurationMs > maxFrameDuration) { 2533 Log.w(TAG, String.format( 2534 "Frame drop at %d: expectation %f, observed %f", 2535 i, expectedFrameDurationMinMs, frameDurationMs)); 2536 frameDropCount++; 2537 } 2538 prevSampleUs = currentSampleUs; 2539 } 2540 float frameDropRate = 100.f * frameDropCount / timestamps.size(); 2541 Log.i(TAG, String.format("Frame drop rate %d/%d (%f%%)", 2542 frameDropCount, timestamps.size(), frameDropRate)); 2543 assertTrue(String.format( 2544 "Camera %s: Video frame drop rate too high: %f%%, tolerance %f%%. " + 2545 "Video size: %s, expectedDuration [%f,%f], expectedFrameDuration %f, " + 2546 "frameDropCnt %d, frameCount %d", 2547 mCamera.getId(), frameDropRate, frameDropTolerance, 2548 sz.toString(), expectedDurationMinMs, expectedDurationMaxMs, 2549 expectedFrameDurationMinMs, frameDropCount, timestamps.size()), 2550 frameDropRate < frameDropTolerance); 2551 } finally { 2552 extractor.release(); 2553 if (!DEBUG_DUMP) { 2554 outFile.delete(); 2555 } 2556 } 2557 } 2558 2559 /** 2560 * Validate video snapshot capture image object validity and test. 2561 * 2562 * <p> Check for size, format and jpeg decoding</p> 2563 * 2564 * @param image The JPEG image to be verified. 2565 * @param size The JPEG capture size to be verified against. 2566 */ 2567 private void validateVideoSnapshotCapture(Image image, Size size) { 2568 CameraTestUtils.validateImage(image, size.getWidth(), size.getHeight(), 2569 ImageFormat.JPEG, /*filePath*/null); 2570 } 2571 2572 /** 2573 * Validate if video snapshot causes frame drop. 2574 * Here frame drop is defined as frame duration >= 2 * expected frame duration. 2575 * Return the estimated number of frames dropped during video snapshot 2576 */ 2577 private int validateFrameDropAroundVideoSnapshot( 2578 SimpleCaptureCallback resultListener, long imageTimeStamp) { 2579 double expectedDurationMs = 1000.0 / mVideoFrameRate; 2580 CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 2581 long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP); 2582 while (resultListener.hasMoreResults()) { 2583 CaptureResult currentResult = 2584 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 2585 long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP); 2586 if (currentTS == imageTimeStamp) { 2587 // validate the timestamp before and after, then return 2588 CaptureResult nextResult = 2589 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 2590 long nextTS = getValueNotNull(nextResult, CaptureResult.SENSOR_TIMESTAMP); 2591 double durationMs = (currentTS - prevTS) / 1000000.0; 2592 int totalFramesDropped = 0; 2593 2594 // Snapshots in legacy mode pause the preview briefly. Skip the duration 2595 // requirements for legacy mode unless this is fixed. 2596 if (!mStaticInfo.isHardwareLevelLegacy()) { 2597 mCollector.expectTrue( 2598 String.format( 2599 "Video %dx%d Frame drop detected before video snapshot: " + 2600 "duration %.2fms (expected %.2fms)", 2601 mVideoSize.getWidth(), mVideoSize.getHeight(), 2602 durationMs, expectedDurationMs 2603 ), 2604 durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED) 2605 ); 2606 // Log a warning is there is any frame drop detected. 2607 if (durationMs >= expectedDurationMs * 2) { 2608 Log.w(TAG, String.format( 2609 "Video %dx%d Frame drop detected before video snapshot: " + 2610 "duration %.2fms (expected %.2fms)", 2611 mVideoSize.getWidth(), mVideoSize.getHeight(), 2612 durationMs, expectedDurationMs 2613 )); 2614 } 2615 2616 durationMs = (nextTS - currentTS) / 1000000.0; 2617 mCollector.expectTrue( 2618 String.format( 2619 "Video %dx%d Frame drop detected after video snapshot: " + 2620 "duration %.2fms (expected %.2fms)", 2621 mVideoSize.getWidth(), mVideoSize.getHeight(), 2622 durationMs, expectedDurationMs 2623 ), 2624 durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED) 2625 ); 2626 // Log a warning is there is any frame drop detected. 2627 if (durationMs >= expectedDurationMs * 2) { 2628 Log.w(TAG, String.format( 2629 "Video %dx%d Frame drop detected after video snapshot: " + 2630 "duration %fms (expected %fms)", 2631 mVideoSize.getWidth(), mVideoSize.getHeight(), 2632 durationMs, expectedDurationMs 2633 )); 2634 } 2635 2636 double totalDurationMs = (nextTS - prevTS) / 1000000.0; 2637 // Minus 2 for the expected 2 frames interval 2638 totalFramesDropped = (int) (totalDurationMs / expectedDurationMs) - 2; 2639 if (totalFramesDropped < 0) { 2640 Log.w(TAG, "totalFrameDropped is " + totalFramesDropped + 2641 ". Video frame rate might be too fast."); 2642 } 2643 totalFramesDropped = Math.max(0, totalFramesDropped); 2644 } 2645 return totalFramesDropped; 2646 } 2647 prevTS = currentTS; 2648 } 2649 throw new AssertionFailedError( 2650 "Video snapshot timestamp does not match any of capture results!"); 2651 } 2652 2653 /** 2654 * Validate frame jittering from the input simple listener's buffered results 2655 */ 2656 private void validateJittering(SimpleCaptureCallback resultListener) { 2657 double expectedDurationMs = 1000.0 / mVideoFrameRate; 2658 CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 2659 long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP); 2660 while (resultListener.hasMoreResults()) { 2661 CaptureResult currentResult = 2662 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS); 2663 long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP); 2664 double durationMs = (currentTS - prevTS) / 1000000.0; 2665 double durationError = Math.abs(durationMs - expectedDurationMs); 2666 long frameNumber = currentResult.getFrameNumber(); 2667 mCollector.expectTrue( 2668 String.format( 2669 "Resolution %dx%d Frame %d: jittering (%.2fms) exceeds bound [%.2fms,%.2fms]", 2670 mVideoSize.getWidth(), mVideoSize.getHeight(), 2671 frameNumber, durationMs, 2672 expectedDurationMs - FRAME_DURATION_ERROR_TOLERANCE_MS, 2673 expectedDurationMs + FRAME_DURATION_ERROR_TOLERANCE_MS), 2674 durationError <= FRAME_DURATION_ERROR_TOLERANCE_MS); 2675 prevTS = currentTS; 2676 } 2677 } 2678 2679 /** 2680 * Calculate a video bit rate based on the size. The bit rate is scaled 2681 * based on ratio of video size to 1080p size. 2682 */ 2683 private int getVideoBitRate(Size sz) { 2684 int rate = BIT_RATE_1080P; 2685 float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080); 2686 rate = (int)(rate * scaleFactor); 2687 2688 // Clamp to the MIN, MAX range. 2689 return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate)); 2690 } 2691 2692 /** 2693 * Check if the encoder and camera are able to support this size and frame rate. 2694 * Assume the video compression format is AVC. 2695 */ 2696 private boolean isSupported(Size sz, int captureRate, int encodingRate) throws Exception { 2697 // Check camera capability. 2698 if (!isSupportedByCamera(sz, captureRate)) { 2699 return false; 2700 } 2701 2702 // Check encode capability. 2703 if (!isSupportedByAVCEncoder(sz, encodingRate)){ 2704 return false; 2705 } 2706 2707 if(VERBOSE) { 2708 Log.v(TAG, "Both encoder and camera support " + sz.toString() + "@" + encodingRate + "@" 2709 + getVideoBitRate(sz) / 1000 + "Kbps"); 2710 } 2711 2712 return true; 2713 } 2714 2715 private boolean isSupportedByCamera(Size sz, int frameRate) { 2716 // Check if camera can support this sz and frame rate combination. 2717 StreamConfigurationMap config = mStaticInfo. 2718 getValueFromKeyNonNull(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 2719 2720 long minDuration = config.getOutputMinFrameDuration(MediaRecorder.class, sz); 2721 if (minDuration == 0) { 2722 return false; 2723 } 2724 2725 int maxFrameRate = (int) (1e9f / minDuration); 2726 return maxFrameRate >= frameRate; 2727 } 2728 2729 /** 2730 * Check if encoder can support this size and frame rate combination by querying 2731 * MediaCodec capability. Check is based on size and frame rate. Ignore the bit rate 2732 * as the bit rates targeted in this test are well below the bit rate max value specified 2733 * by AVC specification for certain level. 2734 */ 2735 private static boolean isSupportedByAVCEncoder(Size sz, int frameRate) { 2736 MediaFormat format = MediaFormat.createVideoFormat( 2737 MediaFormat.MIMETYPE_VIDEO_AVC, sz.getWidth(), sz.getHeight()); 2738 format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); 2739 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 2740 return mcl.findEncoderForFormat(format) != null; 2741 } 2742 2743 private static class ImageWriterQueuer implements ImageReader.OnImageAvailableListener { 2744 public ImageWriterQueuer(ImageWriter writer) { 2745 mWriter = writer; 2746 } 2747 2748 public void resetInvalidSurfaceFlag() { 2749 synchronized (mLock) { 2750 mExpectInvalidSurface = false; 2751 } 2752 } 2753 2754 // Indicate that the writer surface is about to get released 2755 // and become invalid. 2756 public void expectInvalidSurface() { 2757 // If we sync on 'mLock', we risk a possible deadlock 2758 // during 'mWriter.queueInputImage(image)' which is 2759 // called while the lock is held. 2760 mExpectInvalidSurface = true; 2761 } 2762 2763 @Override 2764 public void onImageAvailable(ImageReader reader) { 2765 Image image = null; 2766 try { 2767 image = reader.acquireNextImage(); 2768 } finally { 2769 synchronized (mLock) { 2770 if (image != null && mWriter != null) { 2771 try { 2772 mWriter.queueInputImage(image); 2773 mQueuedCount++; 2774 } catch (IllegalStateException e) { 2775 // Per API documentation ISE are possible 2776 // in case the writer surface is not valid. 2777 // Re-throw in case we have some other 2778 // unexpected ISE. 2779 if (mExpectInvalidSurface) { 2780 Log.d(TAG, "Invalid writer surface"); 2781 image.close(); 2782 } else { 2783 throw e; 2784 } 2785 } 2786 } else if (image != null) { 2787 image.close(); 2788 } 2789 } 2790 } 2791 } 2792 2793 public int getQueuedCount() { 2794 synchronized (mLock) { 2795 return mQueuedCount; 2796 } 2797 } 2798 2799 public void close() { 2800 synchronized (mLock) { 2801 mWriter = null; 2802 } 2803 } 2804 2805 private Object mLock = new Object(); 2806 private ImageWriter mWriter = null; 2807 private int mQueuedCount = 0; 2808 private boolean mExpectInvalidSurface = false; 2809 } 2810 } 2811