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