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