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