blob: 52fd69f6f353e9d09bd9921fa01a2e41269539db [file] [log] [blame]
Zhijun Hea52636b2014-02-25 11:10:11 -08001/*
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
12package android.hardware.camera2.cts;
13
14import static android.hardware.camera2.cts.CameraTestUtils.*;
Eino-Ville Talvalac0dd0222014-09-04 15:07:58 -070015import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
Zhijun Hea52636b2014-02-25 11:10:11 -080016
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -070017import android.graphics.ImageFormat;
Ruben Brunk77034932014-07-01 16:56:52 -070018import android.hardware.camera2.CameraCharacteristics;
Eino-Ville Talvalab80c6612014-07-06 17:58:46 -070019import android.hardware.camera2.CameraCaptureSession;
Eino-Ville Talvalae7faaf72015-06-30 13:31:20 -070020import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
Zhijun Hea52636b2014-02-25 11:10:11 -080021import android.hardware.camera2.CameraDevice;
22import android.hardware.camera2.CaptureRequest;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -070023import android.hardware.camera2.CaptureResult;
Ruben Brunk77034932014-07-01 16:56:52 -070024import android.hardware.camera2.params.StreamConfigurationMap;
Igor Murashkind5a33df2014-05-14 17:01:09 -070025import android.util.Size;
Zhijun Hea52636b2014-02-25 11:10:11 -080026import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
27import android.media.CamcorderProfile;
Chong Zhang484e2dc2015-05-19 17:28:41 -070028import android.media.MediaCodec;
Zhijun Hea52636b2014-02-25 11:10:11 -080029import android.media.MediaCodecInfo;
30import android.media.MediaCodecInfo.CodecCapabilities;
31import android.media.MediaCodecInfo.CodecProfileLevel;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -070032import android.media.Image;
33import android.media.ImageReader;
Zhijun Hea52636b2014-02-25 11:10:11 -080034import android.media.MediaCodecList;
Yin-Chia Yeh601e6332014-11-19 13:47:56 -080035import android.media.MediaExtractor;
Lajos Molnar85bbb7c2014-11-04 15:47:16 -080036import android.media.MediaFormat;
Zhijun Hea52636b2014-02-25 11:10:11 -080037import android.media.MediaRecorder;
38import android.os.Environment;
Zhijun Hea52636b2014-02-25 11:10:11 -080039import android.os.SystemClock;
40import android.test.suitebuilder.annotation.LargeTest;
41import android.util.Log;
Igor Murashkina6e6d532014-05-27 18:31:34 -070042import android.util.Range;
Zhijun Hea52636b2014-02-25 11:10:11 -080043import android.view.Surface;
44
Eino-Ville Talvalac0dd0222014-09-04 15:07:58 -070045import com.android.ex.camera2.blocking.BlockingSessionCallback;
Eino-Ville Talvalab80c6612014-07-06 17:58:46 -070046
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -070047import junit.framework.AssertionFailedError;
Zhijun Hea52636b2014-02-25 11:10:11 -080048
49import java.io.File;
50import java.util.ArrayList;
Yin-Chia Yeha8bcc722015-05-21 14:29:55 -070051import java.util.Arrays;
Zhijun Hea52636b2014-02-25 11:10:11 -080052import java.util.List;
Yin-Chia Yeh94db6eb2015-08-13 13:20:56 -070053import java.util.HashMap;
Zhijun Hea52636b2014-02-25 11:10:11 -080054
55/**
56 * CameraDevice video recording use case tests by using MediaRecorder and
57 * MediaCodec.
58 */
59@LargeTest
60public class RecordingTest extends Camera2SurfaceViewTestCase {
61 private static final String TAG = "RecordingTest";
62 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
Zhijun He67c2cc72014-09-02 08:48:53 -070063 private static final boolean DEBUG_DUMP = Log.isLoggable(TAG, Log.DEBUG);
Zhijun Hed5f04112014-09-03 17:12:03 -070064 private static final int RECORDING_DURATION_MS = 3000;
Zhijun Hebb69dad2015-07-01 11:39:25 -070065 private static final float DURATION_MARGIN = 0.2f;
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -070066 private static final double FRAME_DURATION_ERROR_TOLERANCE_MS = 3.0;
Zhijun Hea52636b2014-02-25 11:10:11 -080067 private static final int BIT_RATE_1080P = 16000000;
68 private static final int BIT_RATE_MIN = 64000;
69 private static final int BIT_RATE_MAX = 40000000;
70 private static final int VIDEO_FRAME_RATE = 30;
71 private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
72 private static final int[] mCamcorderProfileList = {
Yin-Chia Yeh601e6332014-11-19 13:47:56 -080073 CamcorderProfile.QUALITY_HIGH,
Zhijun Hef507c882014-06-10 15:01:47 -070074 CamcorderProfile.QUALITY_2160P,
Zhijun Hea52636b2014-02-25 11:10:11 -080075 CamcorderProfile.QUALITY_1080P,
Zhijun Hea52636b2014-02-25 11:10:11 -080076 CamcorderProfile.QUALITY_720P,
Yin-Chia Yeh601e6332014-11-19 13:47:56 -080077 CamcorderProfile.QUALITY_480P,
Zhijun Hea52636b2014-02-25 11:10:11 -080078 CamcorderProfile.QUALITY_CIF,
Zhijun Hea52636b2014-02-25 11:10:11 -080079 CamcorderProfile.QUALITY_QCIF,
80 CamcorderProfile.QUALITY_QVGA,
Yin-Chia Yeh601e6332014-11-19 13:47:56 -080081 CamcorderProfile.QUALITY_LOW,
Zhijun Hea52636b2014-02-25 11:10:11 -080082 };
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -070083 private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5;
Yin-Chia Yeh313007f2014-06-03 15:23:51 -070084 private static final int BURST_VIDEO_SNAPSHOT_NUM = 3;
Zhijun He4a3a58e2014-07-10 19:24:31 -070085 private static final int SLOWMO_SLOW_FACTOR = 4;
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -070086 private static final int MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED = 4;
Zhijun Hea52636b2014-02-25 11:10:11 -080087 private List<Size> mSupportedVideoSizes;
88 private Surface mRecordingSurface;
Chong Zhang484e2dc2015-05-19 17:28:41 -070089 private Surface mPersistentSurface;
Zhijun Hea52636b2014-02-25 11:10:11 -080090 private MediaRecorder mMediaRecorder;
Zhijun Hea52636b2014-02-25 11:10:11 -080091 private String mOutMediaFileName;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -070092 private int mVideoFrameRate;
93 private Size mVideoSize;
Yin-Chia Yeh203b9802014-12-18 11:31:09 -080094 private long mRecordingStartTime;
Zhijun Hea52636b2014-02-25 11:10:11 -080095
96 @Override
97 protected void setUp() throws Exception {
98 super.setUp();
99 }
100
101 @Override
102 protected void tearDown() throws Exception {
103 super.tearDown();
104 }
105
Chong Zhang484e2dc2015-05-19 17:28:41 -0700106 private void doBasicRecording() throws Exception {
Zhijun Hea52636b2014-02-25 11:10:11 -0800107 for (int i = 0; i < mCameraIds.length; i++) {
108 try {
109 Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
110 // Re-use the MediaRecorder object for the same camera device.
111 mMediaRecorder = new MediaRecorder();
112 openDevice(mCameraIds[i]);
Eino-Ville Talvalaa0d88252015-07-07 18:03:49 -0700113 if (!mStaticInfo.isColorOutputSupported()) {
114 Log.i(TAG, "Camera " + mCameraIds[i] +
115 " does not support color outputs, skipping");
116 continue;
117 }
Zhijun He15fb6ed2014-08-14 10:30:59 -0700118 initSupportedVideoSize(mCameraIds[i]);
Zhijun Hea52636b2014-02-25 11:10:11 -0800119
Yin-Chia Yeh601e6332014-11-19 13:47:56 -0800120 basicRecordingTestByCamera(mCamcorderProfileList);
Zhijun Hea52636b2014-02-25 11:10:11 -0800121 } finally {
122 closeDevice();
Ruben Brunk77034932014-07-01 16:56:52 -0700123 releaseRecorder();
Zhijun Hea52636b2014-02-25 11:10:11 -0800124 }
125 }
126 }
127
128 /**
129 * <p>
Chong Zhang484e2dc2015-05-19 17:28:41 -0700130 * Test basic camera recording.
131 * </p>
132 * <p>
133 * This test covers the typical basic use case of camera recording.
134 * MediaRecorder is used to record the audio and video, CamcorderProfile is
135 * used to configure the MediaRecorder. It goes through the pre-defined
136 * CamcorderProfile list, test each profile configuration and validate the
137 * recorded video. Preview is set to the video size.
138 * </p>
139 */
140 public void testBasicRecording() throws Exception {
141 doBasicRecording();
142 }
143
144 /**
145 * <p>
146 * Test basic camera recording from a persistent input surface.
147 * </p>
148 * <p>
149 * This test is similar to testBasicRecording except that MediaRecorder records
150 * from a persistent input surface that's used across multiple recording sessions.
151 * </p>
152 */
153 public void testRecordingFromPersistentSurface() throws Exception {
154 mPersistentSurface = MediaCodec.createPersistentInputSurface();
155 assertNotNull("Failed to create persistent input surface!", mPersistentSurface);
156
157 try {
158 doBasicRecording();
159 } finally {
160 mPersistentSurface.release();
161 mPersistentSurface = null;
162 }
163 }
164
165 /**
166 * <p>
Zhijun Hea52636b2014-02-25 11:10:11 -0800167 * Test camera recording for all supported sizes by using MediaRecorder.
168 * </p>
169 * <p>
170 * This test covers camera recording for all supported sizes by camera. MediaRecorder
171 * is used to encode the video. Preview is set to the video size. Recorded videos are
172 * validated according to the recording configuration.
173 * </p>
174 */
175 public void testSupportedVideoSizes() throws Exception {
176 for (int i = 0; i < mCameraIds.length; i++) {
177 try {
178 Log.i(TAG, "Testing supported video size recording for camera " + mCameraIds[i]);
179 // Re-use the MediaRecorder object for the same camera device.
180 mMediaRecorder = new MediaRecorder();
181 openDevice(mCameraIds[i]);
Eino-Ville Talvalaa0d88252015-07-07 18:03:49 -0700182 if (!mStaticInfo.isColorOutputSupported()) {
183 Log.i(TAG, "Camera " + mCameraIds[i] +
184 " does not support color outputs, skipping");
185 continue;
186 }
Zhijun He15fb6ed2014-08-14 10:30:59 -0700187 initSupportedVideoSize(mCameraIds[i]);
Zhijun Hea52636b2014-02-25 11:10:11 -0800188
189 recordingSizeTestByCamera();
190 } finally {
191 closeDevice();
Ruben Brunk77034932014-07-01 16:56:52 -0700192 releaseRecorder();
Zhijun Hea52636b2014-02-25 11:10:11 -0800193 }
194 }
195 }
196
197 /**
198 * Test different start/stop orders of Camera and Recorder.
199 *
200 * <p>The recording should be working fine for any kind of start/stop orders.</p>
201 */
202 public void testCameraRecorderOrdering() {
203 // TODO: need implement
204 }
205
206 /**
207 * <p>
208 * Test camera recording for all supported sizes by using MediaCodec.
209 * </p>
210 * <p>
211 * This test covers video only recording for all supported sizes (camera and
212 * encoder). MediaCodec is used to encode the video. The recorded videos are
213 * validated according to the recording configuration.
214 * </p>
215 */
216 public void testMediaCodecRecording() throws Exception {
217 // TODO. Need implement.
218 }
219
220 /**
221 * <p>
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700222 * Test video snapshot for each camera.
Zhijun Hea52636b2014-02-25 11:10:11 -0800223 * </p>
224 * <p>
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700225 * This test covers video snapshot typical use case. The MediaRecorder is used to record the
226 * video for each available video size. The largest still capture size is selected to
227 * capture the JPEG image. The still capture images are validated according to the capture
228 * configuration. The timestamp of capture result before and after video snapshot is also
229 * checked to make sure no frame drop caused by video snapshot.
Zhijun Hea52636b2014-02-25 11:10:11 -0800230 * </p>
231 */
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700232 public void testVideoSnapshot() throws Exception {
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700233 videoSnapshotHelper(/*burstTest*/false);
234 }
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700235
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700236 /**
237 * <p>
238 * Test burst video snapshot for each camera.
239 * </p>
240 * <p>
241 * This test covers burst video snapshot capture. The MediaRecorder is used to record the
242 * video for each available video size. The largest still capture size is selected to
243 * capture the JPEG image. {@value #BURST_VIDEO_SNAPSHOT_NUM} video snapshot requests will be
244 * sent during the test. The still capture images are validated according to the capture
245 * configuration.
246 * </p>
247 */
248 public void testBurstVideoSnapshot() throws Exception {
249 videoSnapshotHelper(/*burstTest*/true);
Zhijun Hea52636b2014-02-25 11:10:11 -0800250 }
251
Zhijun He4a3a58e2014-07-10 19:24:31 -0700252 /**
253 * Test timelapse recording, where capture rate is slower than video (playback) frame rate.
254 */
255 public void testTimelapseRecording() throws Exception {
Zhijun Hea52636b2014-02-25 11:10:11 -0800256 // TODO. Need implement.
257 }
258
Zhijun He4a3a58e2014-07-10 19:24:31 -0700259 public void testSlowMotionRecording() throws Exception {
260 slowMotionRecording();
261 }
262
Zhijun He96697e02015-06-03 15:28:16 -0700263 public void testConstrainedHighSpeedRecording() throws Exception {
264 constrainedHighSpeedRecording();
265 }
266
Zhijun He4a3a58e2014-07-10 19:24:31 -0700267 /**
Yin-Chia Yeh601e6332014-11-19 13:47:56 -0800268 * <p>
269 * Test recording framerate accuracy when switching from low FPS to high FPS.
270 * </p>
271 * <p>
272 * This test first record a video with profile of lowest framerate then record a video with
273 * profile of highest framerate. Make sure that the video framerate are still accurate.
274 * </p>
275 */
276 public void testRecordingFramerateLowToHigh() throws Exception {
277 for (int i = 0; i < mCameraIds.length; i++) {
278 try {
279 Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
280 // Re-use the MediaRecorder object for the same camera device.
281 mMediaRecorder = new MediaRecorder();
282 openDevice(mCameraIds[i]);
Eino-Ville Talvalaa0d88252015-07-07 18:03:49 -0700283 if (!mStaticInfo.isColorOutputSupported()) {
284 Log.i(TAG, "Camera " + mCameraIds[i] +
285 " does not support color outputs, skipping");
286 continue;
287 }
Yin-Chia Yeh601e6332014-11-19 13:47:56 -0800288 initSupportedVideoSize(mCameraIds[i]);
289
290 int minFpsProfileId = -1, minFps = 1000;
291 int maxFpsProfileId = -1, maxFps = 0;
292 int cameraId = Integer.valueOf(mCamera.getId());
293
294 for (int profileId : mCamcorderProfileList) {
295 if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
296 continue;
297 }
298 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
299 if (profile.videoFrameRate < minFps) {
300 minFpsProfileId = profileId;
301 minFps = profile.videoFrameRate;
302 }
303 if (profile.videoFrameRate > maxFps) {
304 maxFpsProfileId = profileId;
305 maxFps = profile.videoFrameRate;
306 }
307 }
308
309 int camcorderProfileList[] = new int[] {minFpsProfileId, maxFpsProfileId};
310 basicRecordingTestByCamera(camcorderProfileList);
311 } finally {
312 closeDevice();
313 releaseRecorder();
314 }
315 }
316 }
317
318 /**
Zhijun He4a3a58e2014-07-10 19:24:31 -0700319 * Test slow motion recording where capture rate (camera output) is different with
320 * video (playback) frame rate for each camera if high speed recording is supported
321 * by both camera and encoder.
322 *
323 * <p>
324 * Normal recording use cases make the capture rate (camera output frame
325 * rate) the same as the video (playback) frame rate. This guarantees that
326 * the motions in the scene play at the normal speed. If the capture rate is
327 * faster than video frame rate, for a given time duration, more number of
328 * frames are captured than it can be played in the same time duration. This
329 * generates "slow motion" effect during playback.
330 * </p>
331 */
332 private void slowMotionRecording() throws Exception {
333 for (String id : mCameraIds) {
334 try {
335 Log.i(TAG, "Testing slow motion recording for camera " + id);
336 // Re-use the MediaRecorder object for the same camera device.
337 mMediaRecorder = new MediaRecorder();
338 openDevice(id);
Eino-Ville Talvalaa0d88252015-07-07 18:03:49 -0700339 if (!mStaticInfo.isColorOutputSupported()) {
340 Log.i(TAG, "Camera " + id +
341 " does not support color outputs, skipping");
342 continue;
343 }
Zhijun He4a3a58e2014-07-10 19:24:31 -0700344 if (!mStaticInfo.isHighSpeedVideoSupported()) {
345 continue;
346 }
347
348 StreamConfigurationMap config =
349 mStaticInfo.getValueFromKeyNonNull(
350 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
351 Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes();
352 for (Size size : highSpeedVideoSizes) {
353 Range<Integer> fpsRange = getHighestHighSpeedFixedFpsRangeForSize(config, size);
354 mCollector.expectNotNull("Unable to find the fixed frame rate fps range for " +
355 "size " + size, fpsRange);
356 if (fpsRange == null) {
357 continue;
358 }
359
360 int captureRate = fpsRange.getLower();
361 int videoFramerate = captureRate / SLOWMO_SLOW_FACTOR;
Zhijun He96697e02015-06-03 15:28:16 -0700362 // Skip the test if the highest recording FPS supported by CamcorderProfile
363 if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) {
364 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps"
365 + " is not supported by CamcorderProfile");
Zhijun He4a3a58e2014-07-10 19:24:31 -0700366 continue;
367 }
368
369 mOutMediaFileName = VIDEO_FILE_PATH + "/test_slowMo_video.mp4";
370 if (DEBUG_DUMP) {
371 mOutMediaFileName = VIDEO_FILE_PATH + "/test_slowMo_video_" + id + "_"
372 + size.toString() + ".mp4";
373 }
374
375 prepareRecording(size, videoFramerate, captureRate);
376
Zhijun He7936cef2014-08-28 20:19:33 -0700377 // prepare preview surface by using video size.
Yin-Chia Yehabee4d52015-09-01 17:17:00 -0700378 updatePreviewSurfaceWithVideo(size, captureRate);
Zhijun He4a3a58e2014-07-10 19:24:31 -0700379
380 // Start recording
Zhijun He9e8a60b2014-11-03 11:03:22 -0800381 SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
Zhijun He4a3a58e2014-07-10 19:24:31 -0700382 startSlowMotionRecording(/*useMediaRecorder*/true, videoFramerate, captureRate,
Zhijun He96697e02015-06-03 15:28:16 -0700383 fpsRange, resultListener, /*useHighSpeedSession*/false);
Zhijun He4a3a58e2014-07-10 19:24:31 -0700384
385 // Record certain duration.
386 SystemClock.sleep(RECORDING_DURATION_MS);
387
388 // Stop recording and preview
389 stopRecording(/*useMediaRecorder*/true);
Zhijun He9e8a60b2014-11-03 11:03:22 -0800390 // Convert number of frames camera produced into the duration in unit of ms.
391 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
392 videoFramerate);
Zhijun He4a3a58e2014-07-10 19:24:31 -0700393
394 // Validation.
Zhijun He9e8a60b2014-11-03 11:03:22 -0800395 validateRecording(size, durationMs);
Zhijun He4a3a58e2014-07-10 19:24:31 -0700396 }
397
398 } finally {
399 closeDevice();
400 releaseRecorder();
401 }
402 }
403 }
404
Zhijun He96697e02015-06-03 15:28:16 -0700405 private void constrainedHighSpeedRecording() throws Exception {
406 for (String id : mCameraIds) {
407 try {
408 Log.i(TAG, "Testing constrained high speed recording for camera " + id);
409 // Re-use the MediaRecorder object for the same camera device.
410 mMediaRecorder = new MediaRecorder();
411 openDevice(id);
412
413 if (!mStaticInfo.isConstrainedHighSpeedVideoSupported()) {
414 continue;
415 }
416
417 StreamConfigurationMap config =
418 mStaticInfo.getValueFromKeyNonNull(
419 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
420 Size[] highSpeedVideoSizes = config.getHighSpeedVideoSizes();
421 for (Size size : highSpeedVideoSizes) {
422 List<Range<Integer>> fixedFpsRanges =
423 getHighSpeedFixedFpsRangeForSize(config, size);
424 mCollector.expectTrue("Unable to find the fixed frame rate fps range for " +
425 "size " + size, fixedFpsRanges.size() > 0);
426 // Test recording for each FPS range
427 for (Range<Integer> fpsRange : fixedFpsRanges) {
428 int captureRate = fpsRange.getLower();
429 final int VIDEO_FRAME_RATE = 30;
430 // Skip the test if the highest recording FPS supported by CamcorderProfile
431 if (fpsRange.getUpper() > getFpsFromHighSpeedProfileForSize(size)) {
432 Log.w(TAG, "high speed recording " + size + "@" + captureRate + "fps"
433 + " is not supported by CamcorderProfile");
434 continue;
435 }
436
437 mOutMediaFileName = VIDEO_FILE_PATH + "/test_cslowMo_video_" + captureRate +
438 "fps_" + id + "_" + size.toString() + ".mp4";
439
440 prepareRecording(size, VIDEO_FRAME_RATE, captureRate);
441
442 // prepare preview surface by using video size.
Yin-Chia Yehabee4d52015-09-01 17:17:00 -0700443 updatePreviewSurfaceWithVideo(size, captureRate);
Zhijun He96697e02015-06-03 15:28:16 -0700444
445 // Start recording
446 SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
447 startSlowMotionRecording(/*useMediaRecorder*/true, VIDEO_FRAME_RATE,
448 captureRate, fpsRange, resultListener,
449 /*useHighSpeedSession*/true);
450
451 // Record certain duration.
452 SystemClock.sleep(RECORDING_DURATION_MS);
453
454 // Stop recording and preview
455 stopRecording(/*useMediaRecorder*/true);
456 // Convert number of frames camera produced into the duration in unit of ms.
457 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
458 VIDEO_FRAME_RATE);
459
460 // Validation.
461 validateRecording(size, durationMs);
462 }
463 }
464
465 } finally {
466 closeDevice();
467 releaseRecorder();
468 }
469 }
470 }
471
472 /**
473 * Get high speed FPS from CamcorderProfiles for a given size.
474 *
475 * @param size The size used to search the CamcorderProfiles for the FPS.
476 * @return high speed video FPS, 0 if the given size is not supported by the CamcorderProfiles.
477 */
478 private int getFpsFromHighSpeedProfileForSize(Size size) {
479 for (int quality = CamcorderProfile.QUALITY_HIGH_SPEED_480P;
480 quality <= CamcorderProfile.QUALITY_HIGH_SPEED_2160P; quality++) {
481 if (CamcorderProfile.hasProfile(quality)) {
482 CamcorderProfile profile = CamcorderProfile.get(quality);
483 if (size.equals(new Size(profile.videoFrameWidth, profile.videoFrameHeight))){
484 return profile.videoFrameRate;
485 }
486 }
487 }
488
489 return 0;
490 }
491
Zhijun He4a3a58e2014-07-10 19:24:31 -0700492 private Range<Integer> getHighestHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config,
493 Size size) {
494 Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size);
495 Range<Integer> maxRange = availableFpsRanges[0];
496 boolean foundRange = false;
497 for (Range<Integer> range : availableFpsRanges) {
Yin-Chia Yeh28dba4e2015-05-18 11:53:14 -0700498 if (range.getLower().equals(range.getUpper()) && range.getLower() >= maxRange.getLower()) {
Zhijun He4a3a58e2014-07-10 19:24:31 -0700499 foundRange = true;
500 maxRange = range;
501 }
502 }
503
504 if (!foundRange) {
505 return null;
506 }
507 return maxRange;
508 }
509
Zhijun He96697e02015-06-03 15:28:16 -0700510 private List<Range<Integer>> getHighSpeedFixedFpsRangeForSize(StreamConfigurationMap config,
511 Size size) {
512 Range<Integer>[] availableFpsRanges = config.getHighSpeedVideoFpsRangesFor(size);
513 List<Range<Integer>> fixedRanges = new ArrayList<Range<Integer>>();
514 for (Range<Integer> range : availableFpsRanges) {
515 if (range.getLower().equals(range.getUpper())) {
516 fixedRanges.add(range);
517 }
518 }
519 return fixedRanges;
520 }
521
Zhijun He4a3a58e2014-07-10 19:24:31 -0700522 private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate,
Zhijun He9e8a60b2014-11-03 11:03:22 -0800523 int captureRate, Range<Integer> fpsRange,
Zhijun He96697e02015-06-03 15:28:16 -0700524 CameraCaptureSession.CaptureCallback listener, boolean useHighSpeedSession) throws Exception {
Zhijun He4a3a58e2014-07-10 19:24:31 -0700525 List<Surface> outputSurfaces = new ArrayList<Surface>(2);
526 assertTrue("Both preview and recording surfaces should be valid",
527 mPreviewSurface.isValid() && mRecordingSurface.isValid());
528 outputSurfaces.add(mPreviewSurface);
529 outputSurfaces.add(mRecordingSurface);
530 // Video snapshot surface
531 if (mReaderSurface != null) {
532 outputSurfaces.add(mReaderSurface);
533 }
Eino-Ville Talvalac0dd0222014-09-04 15:07:58 -0700534 mSessionListener = new BlockingSessionCallback();
Zhijun He96697e02015-06-03 15:28:16 -0700535 mSession = configureCameraSession(mCamera, outputSurfaces, useHighSpeedSession,
536 mSessionListener, mHandler);
Zhijun He4a3a58e2014-07-10 19:24:31 -0700537
Zhijun He96697e02015-06-03 15:28:16 -0700538 // Create slow motion request list
539 List<CaptureRequest> slowMoRequests = null;
540 if (useHighSpeedSession) {
541 CaptureRequest.Builder requestBuilder =
542 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
Zhijun Hede1bd022015-06-15 09:32:16 -0700543 requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
Zhijun He96697e02015-06-03 15:28:16 -0700544 requestBuilder.addTarget(mPreviewSurface);
545 requestBuilder.addTarget(mRecordingSurface);
Eino-Ville Talvalae7faaf72015-06-30 13:31:20 -0700546 slowMoRequests = ((CameraConstrainedHighSpeedCaptureSession) mSession).
547 createHighSpeedRequestList(requestBuilder.build());
Zhijun He96697e02015-06-03 15:28:16 -0700548 } else {
549 CaptureRequest.Builder recordingRequestBuilder =
550 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
551 recordingRequestBuilder.set(CaptureRequest.CONTROL_MODE,
552 CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
553 recordingRequestBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,
554 CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO);
Zhijun Hea891dcf2014-07-31 12:49:21 -0700555
Zhijun He96697e02015-06-03 15:28:16 -0700556 CaptureRequest.Builder recordingOnlyBuilder =
557 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
558 recordingOnlyBuilder.set(CaptureRequest.CONTROL_MODE,
559 CaptureRequest.CONTROL_MODE_USE_SCENE_MODE);
560 recordingOnlyBuilder.set(CaptureRequest.CONTROL_SCENE_MODE,
561 CaptureRequest.CONTROL_SCENE_MODE_HIGH_SPEED_VIDEO);
562 int slowMotionFactor = captureRate / videoFrameRate;
Zhijun He4a3a58e2014-07-10 19:24:31 -0700563
Zhijun He96697e02015-06-03 15:28:16 -0700564 // Make sure camera output frame rate is set to correct value.
565 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
566 recordingRequestBuilder.addTarget(mRecordingSurface);
567 recordingRequestBuilder.addTarget(mPreviewSurface);
568 recordingOnlyBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
569 recordingOnlyBuilder.addTarget(mRecordingSurface);
Zhijun He4a3a58e2014-07-10 19:24:31 -0700570
Zhijun He96697e02015-06-03 15:28:16 -0700571 slowMoRequests = new ArrayList<CaptureRequest>();
572 slowMoRequests.add(recordingRequestBuilder.build());// Preview + recording.
Zhijun He4a3a58e2014-07-10 19:24:31 -0700573
Zhijun He96697e02015-06-03 15:28:16 -0700574 for (int i = 0; i < slowMotionFactor - 1; i++) {
575 slowMoRequests.add(recordingOnlyBuilder.build()); // Recording only.
576 }
Zhijun He4a3a58e2014-07-10 19:24:31 -0700577 }
Zhijun He96697e02015-06-03 15:28:16 -0700578
Zhijun He9e8a60b2014-11-03 11:03:22 -0800579 mSession.setRepeatingBurst(slowMoRequests, listener, mHandler);
Zhijun He4a3a58e2014-07-10 19:24:31 -0700580
581 if (useMediaRecorder) {
582 mMediaRecorder.start();
583 } else {
584 // TODO: need implement MediaCodec path.
585 }
586
587 }
588
Zhijun Hea52636b2014-02-25 11:10:11 -0800589 /**
590 * Test camera recording by using each available CamcorderProfile for a
591 * given camera. preview size is set to the video size.
592 */
Yin-Chia Yeh601e6332014-11-19 13:47:56 -0800593 private void basicRecordingTestByCamera(int[] camcorderProfileList) throws Exception {
Yin-Chia Yehcf9f0342015-03-26 11:47:30 -0700594 Size maxPreviewSize = mOrderedPreviewSizes.get(0);
Yin-Chia Yeha8bcc722015-05-21 14:29:55 -0700595 List<Range<Integer> > fpsRanges = Arrays.asList(
596 mStaticInfo.getAeAvailableTargetFpsRangesChecked());
597 int cameraId = Integer.valueOf(mCamera.getId());
Yin-Chia Yeh601e6332014-11-19 13:47:56 -0800598 for (int profileId : camcorderProfileList) {
Ruben Brunk77034932014-07-01 16:56:52 -0700599 if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
600 allowedUnsupported(cameraId, profileId)) {
Zhijun Hea52636b2014-02-25 11:10:11 -0800601 continue;
602 }
603
604 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
605 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
Yin-Chia Yeha8bcc722015-05-21 14:29:55 -0700606 Range<Integer> fpsRange = new Range(profile.videoFrameRate, profile.videoFrameRate);
Yin-Chia Yehcf9f0342015-03-26 11:47:30 -0700607 if (mStaticInfo.isHardwareLevelLegacy() &&
608 (videoSz.getWidth() > maxPreviewSize.getWidth() ||
609 videoSz.getHeight() > maxPreviewSize.getHeight())) {
610 // Skip. Legacy mode can only do recording up to max preview size
611 continue;
612 }
Ruben Brunk77034932014-07-01 16:56:52 -0700613 assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId +
614 " must be one of the camera device supported video size!",
615 mSupportedVideoSizes.contains(videoSz));
Yin-Chia Yeha8bcc722015-05-21 14:29:55 -0700616 assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId +
617 ") must be one of the camera device available FPS range!",
618 fpsRanges.contains(fpsRange));
Zhijun Hea52636b2014-02-25 11:10:11 -0800619
620 if (VERBOSE) {
621 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
622 }
623
624 // Configure preview and recording surfaces.
625 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
626 if (DEBUG_DUMP) {
627 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
628 + videoSz.toString() + ".mp4";
629 }
630
631 prepareRecordingWithProfile(profile);
632
Zhijun He7936cef2014-08-28 20:19:33 -0700633 // prepare preview surface by using video size.
Yin-Chia Yehabee4d52015-09-01 17:17:00 -0700634 updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
Zhijun Hea52636b2014-02-25 11:10:11 -0800635
636 // Start recording
Zhijun He9e8a60b2014-11-03 11:03:22 -0800637 SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
638 startRecording(/* useMediaRecorder */true, resultListener);
Zhijun Hea52636b2014-02-25 11:10:11 -0800639
640 // Record certain duration.
641 SystemClock.sleep(RECORDING_DURATION_MS);
642
643 // Stop recording and preview
644 stopRecording(/* useMediaRecorder */true);
Zhijun He9e8a60b2014-11-03 11:03:22 -0800645 // Convert number of frames camera produced into the duration in unit of ms.
646 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
647 profile.videoFrameRate);
648
649 if (VERBOSE) {
650 Log.v(TAG, "video frame rate: " + profile.videoFrameRate +
651 ", num of frames produced: " + resultListener.getTotalNumFrames());
652 }
Zhijun Hea52636b2014-02-25 11:10:11 -0800653
654 // Validation.
Zhijun He9e8a60b2014-11-03 11:03:22 -0800655 validateRecording(videoSz, durationMs);
Zhijun Hea52636b2014-02-25 11:10:11 -0800656 }
657 }
658
659 /**
660 * Test camera recording for each supported video size by camera, preview
661 * size is set to the video size.
662 */
663 private void recordingSizeTestByCamera() throws Exception {
664 for (Size sz : mSupportedVideoSizes) {
665 if (!isSupported(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE)) {
666 continue;
667 }
668
669 if (VERBOSE) {
670 Log.v(TAG, "Testing camera recording with video size " + sz.toString());
671 }
672
673 // Configure preview and recording surfaces.
674 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
675 if (DEBUG_DUMP) {
676 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + mCamera.getId() + "_"
677 + sz.toString() + ".mp4";
678 }
679
680 // Use AVC and AAC a/v compression format.
Zhijun He4a3a58e2014-07-10 19:24:31 -0700681 prepareRecording(sz, VIDEO_FRAME_RATE, VIDEO_FRAME_RATE);
Zhijun Hea52636b2014-02-25 11:10:11 -0800682
Zhijun He7936cef2014-08-28 20:19:33 -0700683 // prepare preview surface by using video size.
Yin-Chia Yehabee4d52015-09-01 17:17:00 -0700684 updatePreviewSurfaceWithVideo(sz, VIDEO_FRAME_RATE);
Zhijun Hea52636b2014-02-25 11:10:11 -0800685
686 // Start recording
Zhijun He9e8a60b2014-11-03 11:03:22 -0800687 SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
688 startRecording(/* useMediaRecorder */true, resultListener);
Zhijun Hea52636b2014-02-25 11:10:11 -0800689
690 // Record certain duration.
691 SystemClock.sleep(RECORDING_DURATION_MS);
692
693 // Stop recording and preview
694 stopRecording(/* useMediaRecorder */true);
Zhijun He9e8a60b2014-11-03 11:03:22 -0800695 // Convert number of frames camera produced into the duration in unit of ms.
696 int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
697 VIDEO_FRAME_RATE);
Zhijun Hea52636b2014-02-25 11:10:11 -0800698
699 // Validation.
Zhijun He9e8a60b2014-11-03 11:03:22 -0800700 validateRecording(sz, durationMs);
Zhijun Hea52636b2014-02-25 11:10:11 -0800701 }
702 }
703
704 /**
Zhijun He15fb6ed2014-08-14 10:30:59 -0700705 * Initialize the supported video sizes.
706 */
707 private void initSupportedVideoSize(String cameraId) throws Exception {
708 Size maxVideoSize = SIZE_BOUND_1080P;
709 if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_2160P)) {
710 maxVideoSize = SIZE_BOUND_2160P;
711 }
712 mSupportedVideoSizes =
713 getSupportedVideoSizes(cameraId, mCameraManager, maxVideoSize);
714 }
715
716 /**
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700717 * Simple wrapper to wrap normal/burst video snapshot tests
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700718 */
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700719 private void videoSnapshotHelper(boolean burstTest) throws Exception {
720 for (String id : mCameraIds) {
721 try {
722 Log.i(TAG, "Testing video snapshot for camera " + id);
723 // Re-use the MediaRecorder object for the same camera device.
724 mMediaRecorder = new MediaRecorder();
Zhijun He15fb6ed2014-08-14 10:30:59 -0700725
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700726 openDevice(id);
Zhijun He15fb6ed2014-08-14 10:30:59 -0700727
Eino-Ville Talvalaa0d88252015-07-07 18:03:49 -0700728 if (!mStaticInfo.isColorOutputSupported()) {
729 Log.i(TAG, "Camera " + id +
730 " does not support color outputs, skipping");
731 continue;
732 }
733
Zhijun He15fb6ed2014-08-14 10:30:59 -0700734 initSupportedVideoSize(id);
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700735
Yin-Chia Yehe9163232014-08-11 13:05:26 -0700736 videoSnapshotTestByCamera(burstTest);
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700737 } finally {
738 closeDevice();
Ruben Brunk77034932014-07-01 16:56:52 -0700739 releaseRecorder();
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700740 }
741 }
742 }
743
744 /**
Ruben Brunk77034932014-07-01 16:56:52 -0700745 * Returns {@code true} if the {@link CamcorderProfile} ID is allowed to be unsupported.
746 *
747 * <p>This only allows unsupported profiles when using the LEGACY mode of the Camera API.</p>
748 *
749 * @param profileId a {@link CamcorderProfile} ID to check.
750 * @return {@code true} if supported.
751 */
752 private boolean allowedUnsupported(int cameraId, int profileId) {
753 if (!mStaticInfo.isHardwareLevelLegacy()) {
754 return false;
755 }
756
757 switch(profileId) {
758 case CamcorderProfile.QUALITY_2160P:
759 case CamcorderProfile.QUALITY_1080P:
760 case CamcorderProfile.QUALITY_HIGH:
761 return !CamcorderProfile.hasProfile(cameraId, profileId) ||
762 CamcorderProfile.get(cameraId, profileId).videoFrameWidth >= 1080;
763 }
764 return false;
765 }
766
767 /**
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700768 * Test video snapshot for each available CamcorderProfile for a given camera.
769 *
770 * <p>
771 * Preview size is set to the video size. For the burst test, frame drop and jittering
772 * is not checked.
773 * </p>
774 *
Ruben Brunk77034932014-07-01 16:56:52 -0700775 * @param burstTest Perform burst capture or single capture. For burst capture
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700776 * {@value #BURST_VIDEO_SNAPSHOT_NUM} capture requests will be sent.
777 */
Yin-Chia Yehe9163232014-08-11 13:05:26 -0700778 private void videoSnapshotTestByCamera(boolean burstTest)
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700779 throws Exception {
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700780 final int NUM_SINGLE_SHOT_TEST = 5;
781 final int FRAMEDROP_TOLERANCE = 8;
hyeonsoo.jeon87d7f9d2015-04-12 16:32:31 +0900782 final int FRAME_SIZE_15M = 15000000;
783 final float FRAME_DROP_TOLERENCE_FACTOR = 1.5f;
784 int kFrameDrop_Tolerence = FRAMEDROP_TOLERANCE;
785
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700786 for (int profileId : mCamcorderProfileList) {
787 int cameraId = Integer.valueOf(mCamera.getId());
Ruben Brunk77034932014-07-01 16:56:52 -0700788 if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
789 allowedUnsupported(cameraId, profileId)) {
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700790 continue;
791 }
792
793 CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
794 Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
Zhijun He7936cef2014-08-28 20:19:33 -0700795 Size maxPreviewSize = mOrderedPreviewSizes.get(0);
Eino-Ville Talvala92ff17d2014-09-23 15:26:39 -0700796
Yin-Chia Yeh5671fc02015-05-07 11:27:55 -0700797 if (mStaticInfo.isHardwareLevelLegacy() &&
798 (videoSz.getWidth() > maxPreviewSize.getWidth() ||
799 videoSz.getHeight() > maxPreviewSize.getHeight())) {
800 // Skip. Legacy mode can only do recording up to max preview size
801 continue;
802 }
803
Yin-Chia Yeh92b8d6e2015-06-10 13:23:14 -0700804 if (!mSupportedVideoSizes.contains(videoSz)) {
805 mCollector.addMessage("Video size " + videoSz.toString() + " for profile ID " +
806 profileId + " must be one of the camera device supported video size!");
807 continue;
808 }
809
Eino-Ville Talvala92ff17d2014-09-23 15:26:39 -0700810 // For LEGACY, find closest supported smaller or equal JPEG size to the current video
811 // size; if no size is smaller than the video, pick the smallest JPEG size. The assert
812 // for video size above guarantees that for LIMITED or FULL, we select videoSz here.
Yin-Chia Yeh94db6eb2015-08-13 13:20:56 -0700813 // Also check for minFrameDuration here to make sure jpeg stream won't slow down
814 // video capture
Eino-Ville Talvala92ff17d2014-09-23 15:26:39 -0700815 Size videoSnapshotSz = mOrderedStillSizes.get(mOrderedStillSizes.size() - 1);
Yin-Chia Yeh94db6eb2015-08-13 13:20:56 -0700816 // Allow a bit tolerance so we don't fail for a few nano seconds of difference
817 final float FRAME_DURATION_TOLERANCE = 0.01f;
818 long videoFrameDuration = (long) (1e9 / profile.videoFrameRate *
819 (1.0 + FRAME_DURATION_TOLERANCE));
820 HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
821 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG);
Eino-Ville Talvala92ff17d2014-09-23 15:26:39 -0700822 for (int i = mOrderedStillSizes.size() - 2; i >= 0; i--) {
823 Size candidateSize = mOrderedStillSizes.get(i);
Yin-Chia Yeh75c49112015-08-24 15:51:24 -0700824 if (mStaticInfo.isHardwareLevelLegacy()) {
825 // Legacy level doesn't report min frame duration
826 if (candidateSize.getWidth() <= videoSz.getWidth() &&
827 candidateSize.getHeight() <= videoSz.getHeight()) {
828 videoSnapshotSz = candidateSize;
829 }
830 } else {
831 Long jpegFrameDuration = minFrameDurationMap.get(candidateSize);
832 assertTrue("Cannot find minimum frame duration for jpeg size " + candidateSize,
833 jpegFrameDuration != null);
834 if (candidateSize.getWidth() <= videoSz.getWidth() &&
835 candidateSize.getHeight() <= videoSz.getHeight() &&
836 jpegFrameDuration <= videoFrameDuration) {
837 videoSnapshotSz = candidateSize;
838 }
Eino-Ville Talvala92ff17d2014-09-23 15:26:39 -0700839 }
Eino-Ville Talvala92ff17d2014-09-23 15:26:39 -0700840 }
841
Zhijun He7936cef2014-08-28 20:19:33 -0700842 /**
843 * Only test full res snapshot when below conditions are all true.
844 * 1. Camera is a FULL device
845 * 2. video size is up to max preview size, which will be bounded by 1080p.
Yin-Chia Yeh94db6eb2015-08-13 13:20:56 -0700846 * 3. Full resolution jpeg stream can keep up to video stream speed.
847 * When full res jpeg stream cannot keep up to video stream speed, search
848 * the largest jpeg size that can susptain video speed instead.
Zhijun He7936cef2014-08-28 20:19:33 -0700849 */
850 if (mStaticInfo.isHardwareLevelFull() &&
851 videoSz.getWidth() <= maxPreviewSize.getWidth() &&
852 videoSz.getHeight() <= maxPreviewSize.getHeight()) {
Yin-Chia Yeh94db6eb2015-08-13 13:20:56 -0700853 for (Size jpegSize : mOrderedStillSizes) {
854 Long jpegFrameDuration = minFrameDurationMap.get(jpegSize);
855 assertTrue("Cannot find minimum frame duration for jpeg size " + jpegSize,
856 jpegFrameDuration != null);
857 if (jpegFrameDuration <= videoFrameDuration) {
858 videoSnapshotSz = jpegSize;
859 break;
860 }
861 if (jpegSize.equals(videoSz)) {
862 throw new AssertionFailedError(
863 "Cannot find adequate video snapshot size for video size" +
864 videoSz);
865 }
866 }
Zhijun He7936cef2014-08-28 20:19:33 -0700867 }
868
Yin-Chia Yeh94db6eb2015-08-13 13:20:56 -0700869 Log.i(TAG, "Testing video snapshot size " + videoSnapshotSz +
870 " for video size " + videoSz);
871 if (videoSnapshotSz.getWidth() * videoSnapshotSz.getHeight() > FRAME_SIZE_15M)
872 kFrameDrop_Tolerence = (int)(FRAMEDROP_TOLERANCE * FRAME_DROP_TOLERENCE_FACTOR);
873
Yin-Chia Yehe9163232014-08-11 13:05:26 -0700874 createImageReader(
875 videoSnapshotSz, ImageFormat.JPEG,
876 MAX_VIDEO_SNAPSHOT_IMAGES, /*listener*/null);
877
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700878 if (VERBOSE) {
879 Log.v(TAG, "Testing camera recording with video size " + videoSz.toString());
880 }
881
882 // Configure preview and recording surfaces.
883 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video.mp4";
884 if (DEBUG_DUMP) {
885 mOutMediaFileName = VIDEO_FILE_PATH + "/test_video_" + cameraId + "_"
886 + videoSz.toString() + ".mp4";
887 }
888
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700889 int numTestIterations = burstTest ? 1 : NUM_SINGLE_SHOT_TEST;
890 int totalDroppedFrames = 0;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700891
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700892 for (int numTested = 0; numTested < numTestIterations; numTested++) {
893 prepareRecordingWithProfile(profile);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700894
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700895 // prepare video snapshot
896 SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
897 SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
898 CaptureRequest.Builder videoSnapshotRequestBuilder =
899 mCamera.createCaptureRequest((mStaticInfo.isHardwareLevelLegacy()) ?
900 CameraDevice.TEMPLATE_RECORD :
901 CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700902
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700903 // prepare preview surface by using video size.
Yin-Chia Yehabee4d52015-09-01 17:17:00 -0700904 updatePreviewSurfaceWithVideo(videoSz, profile.videoFrameRate);
Ruben Brunk77034932014-07-01 16:56:52 -0700905
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700906 prepareVideoSnapshot(videoSnapshotRequestBuilder, imageListener);
907 CaptureRequest request = videoSnapshotRequestBuilder.build();
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700908
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700909 // Start recording
910 startRecording(/* useMediaRecorder */true, resultListener);
911 long startTime = SystemClock.elapsedRealtime();
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700912
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700913 // Record certain duration.
914 SystemClock.sleep(RECORDING_DURATION_MS / 2);
915
916 // take video snapshot
917 if (burstTest) {
918 List<CaptureRequest> requests =
919 new ArrayList<CaptureRequest>(BURST_VIDEO_SNAPSHOT_NUM);
920 for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
921 requests.add(request);
922 }
923 mSession.captureBurst(requests, resultListener, mHandler);
924 } else {
925 mSession.capture(request, resultListener, mHandler);
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700926 }
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700927
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700928 // make sure recording is still going after video snapshot
929 SystemClock.sleep(RECORDING_DURATION_MS / 2);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700930
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700931 // Stop recording and preview
Yin-Chia Yeh203b9802014-12-18 11:31:09 -0800932 int durationMs = stopRecording(/* useMediaRecorder */true);
933 // For non-burst test, use number of frames to also double check video frame rate.
934 // Burst video snapshot is allowed to cause frame rate drop, so do not use number
935 // of frames to estimate duration
936 if (!burstTest) {
937 durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
Yin-Chia Yeh601e6332014-11-19 13:47:56 -0800938 profile.videoFrameRate);
Yin-Chia Yeh203b9802014-12-18 11:31:09 -0800939 }
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700940
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700941 // Validation recorded video
Yin-Chia Yeh601e6332014-11-19 13:47:56 -0800942 validateRecording(videoSz, durationMs);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700943
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700944 if (burstTest) {
945 for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
946 Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
947 validateVideoSnapshotCapture(image, videoSnapshotSz);
948 image.close();
949 }
950 } else {
951 // validate video snapshot image
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700952 Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
953 validateVideoSnapshotCapture(image, videoSnapshotSz);
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700954
955 // validate if there is framedrop around video snapshot
956 totalDroppedFrames += validateFrameDropAroundVideoSnapshot(
957 resultListener, image.getTimestamp());
958
959 //TODO: validate jittering. Should move to PTS
960 //validateJittering(resultListener);
961
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700962 image.close();
963 }
Yin-Chia Yeh313007f2014-06-03 15:23:51 -0700964 }
Yin-Chia Yehe9163232014-08-11 13:05:26 -0700965
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700966 if (!burstTest) {
967 Log.w(TAG, String.format("Camera %d Video size %s: Number of dropped frames " +
968 "detected in %d trials is %d frames.", cameraId, videoSz.toString(),
969 numTestIterations, totalDroppedFrames));
970 mCollector.expectLessOrEqual(
971 String.format(
972 "Camera %d Video size %s: Number of dropped frames %d must not"
973 + " be larger than %d",
974 cameraId, videoSz.toString(), totalDroppedFrames,
hyeonsoo.jeon87d7f9d2015-04-12 16:32:31 +0900975 kFrameDrop_Tolerence),
976 kFrameDrop_Tolerence, totalDroppedFrames);
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -0700977 }
Yin-Chia Yehe9163232014-08-11 13:05:26 -0700978 closeImageReader();
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700979 }
980 }
981
982 /**
983 * Configure video snapshot request according to the still capture size
984 */
985 private void prepareVideoSnapshot(
986 CaptureRequest.Builder requestBuilder,
987 ImageReader.OnImageAvailableListener imageListener)
988 throws Exception {
989 mReader.setOnImageAvailableListener(imageListener, mHandler);
Ruben Brunk77034932014-07-01 16:56:52 -0700990 assertNotNull("Recording surface must be non-null!", mRecordingSurface);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700991 requestBuilder.addTarget(mRecordingSurface);
Ruben Brunk77034932014-07-01 16:56:52 -0700992 assertNotNull("Preview surface must be non-null!", mPreviewSurface);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700993 requestBuilder.addTarget(mPreviewSurface);
Ruben Brunk77034932014-07-01 16:56:52 -0700994 assertNotNull("Reader surface must be non-null!", mReaderSurface);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -0700995 requestBuilder.addTarget(mReaderSurface);
996 }
997
998 /**
Zhijun He7936cef2014-08-28 20:19:33 -0700999 * Update preview size with video size.
1000 *
1001 * <p>Preview size will be capped with max preview size.</p>
1002 *
1003 * @param videoSize The video size used for preview.
Yin-Chia Yehabee4d52015-09-01 17:17:00 -07001004 * @param videoFrameRate The video frame rate
1005 *
Zhijun He7936cef2014-08-28 20:19:33 -07001006 */
Yin-Chia Yehabee4d52015-09-01 17:17:00 -07001007 private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
Zhijun He7936cef2014-08-28 20:19:33 -07001008 if (mOrderedPreviewSizes == null) {
1009 throw new IllegalStateException("supported preview size list is not initialized yet");
1010 }
Yin-Chia Yehabee4d52015-09-01 17:17:00 -07001011 final float FRAME_DURATION_TOLERANCE = 0.01f;
1012 long videoFrameDuration = (long) (1e9 / videoFrameRate *
1013 (1.0 + FRAME_DURATION_TOLERANCE));
1014 HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
1015 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE);
Zhijun He7936cef2014-08-28 20:19:33 -07001016 Size maxPreviewSize = mOrderedPreviewSizes.get(0);
Yin-Chia Yehabee4d52015-09-01 17:17:00 -07001017 Size previewSize = null;
Zhijun He7936cef2014-08-28 20:19:33 -07001018 if (videoSize.getWidth() > maxPreviewSize.getWidth() ||
1019 videoSize.getHeight() > maxPreviewSize.getHeight()) {
Yin-Chia Yehabee4d52015-09-01 17:17:00 -07001020 for (Size s : mOrderedPreviewSizes) {
1021 Long frameDuration = minFrameDurationMap.get(s);
1022 if (mStaticInfo.isHardwareLevelLegacy()) {
1023 // Legacy doesn't report min frame duration
1024 frameDuration = new Long(0);
1025 }
1026 assertTrue("Cannot find minimum frame duration for private size" + s,
1027 frameDuration != null);
1028 if (frameDuration <= videoFrameDuration &&
1029 s.getWidth() <= videoSize.getWidth() &&
1030 s.getHeight() <= videoSize.getHeight()) {
1031 Log.w(TAG, "Overwrite preview size from " + videoSize.toString() +
1032 " to " + s.toString());
1033 previewSize = s;
1034 break;
1035 // If all preview size doesn't work then we fallback to video size
1036 }
1037 }
Zhijun He7936cef2014-08-28 20:19:33 -07001038 }
Yin-Chia Yehabee4d52015-09-01 17:17:00 -07001039 if (previewSize == null) {
1040 previewSize = videoSize;
1041 }
Zhijun He7936cef2014-08-28 20:19:33 -07001042 updatePreviewSurface(previewSize);
1043 }
1044
1045 /**
Zhijun Hea52636b2014-02-25 11:10:11 -08001046 * Configure MediaRecorder recording session with CamcorderProfile, prepare
1047 * the recording surface.
1048 */
1049 private void prepareRecordingWithProfile(CamcorderProfile profile)
1050 throws Exception {
1051 // Prepare MediaRecorder.
1052 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1053 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
1054 mMediaRecorder.setProfile(profile);
1055 mMediaRecorder.setOutputFile(mOutMediaFileName);
Chong Zhang484e2dc2015-05-19 17:28:41 -07001056 if (mPersistentSurface != null) {
1057 mMediaRecorder.setInputSurface(mPersistentSurface);
1058 mRecordingSurface = mPersistentSurface;
1059 }
Zhijun Hea52636b2014-02-25 11:10:11 -08001060 mMediaRecorder.prepare();
Chong Zhang484e2dc2015-05-19 17:28:41 -07001061 if (mPersistentSurface == null) {
1062 mRecordingSurface = mMediaRecorder.getSurface();
1063 }
Zhijun Hea52636b2014-02-25 11:10:11 -08001064 assertNotNull("Recording surface must be non-null!", mRecordingSurface);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001065 mVideoFrameRate = profile.videoFrameRate;
1066 mVideoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
Zhijun Hea52636b2014-02-25 11:10:11 -08001067 }
1068
1069 /**
1070 * Configure MediaRecorder recording session with CamcorderProfile, prepare
1071 * the recording surface. Use AVC for video compression, AAC for audio compression.
1072 * Both are required for android devices by android CDD.
1073 */
Zhijun He4a3a58e2014-07-10 19:24:31 -07001074 private void prepareRecording(Size sz, int videoFrameRate, int captureRate)
1075 throws Exception {
Zhijun Hea52636b2014-02-25 11:10:11 -08001076 // Prepare MediaRecorder.
1077 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
1078 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
1079 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
1080 mMediaRecorder.setOutputFile(mOutMediaFileName);
1081 mMediaRecorder.setVideoEncodingBitRate(getVideoBitRate(sz));
Zhijun He4a3a58e2014-07-10 19:24:31 -07001082 mMediaRecorder.setVideoFrameRate(videoFrameRate);
1083 mMediaRecorder.setCaptureRate(captureRate);
Zhijun Hea52636b2014-02-25 11:10:11 -08001084 mMediaRecorder.setVideoSize(sz.getWidth(), sz.getHeight());
1085 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
1086 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
Chong Zhang484e2dc2015-05-19 17:28:41 -07001087 if (mPersistentSurface != null) {
1088 mMediaRecorder.setInputSurface(mPersistentSurface);
1089 mRecordingSurface = mPersistentSurface;
1090 }
Zhijun Hea52636b2014-02-25 11:10:11 -08001091 mMediaRecorder.prepare();
Chong Zhang484e2dc2015-05-19 17:28:41 -07001092 if (mPersistentSurface == null) {
1093 mRecordingSurface = mMediaRecorder.getSurface();
1094 }
Zhijun Hea52636b2014-02-25 11:10:11 -08001095 assertNotNull("Recording surface must be non-null!", mRecordingSurface);
Zhijun He4a3a58e2014-07-10 19:24:31 -07001096 mVideoFrameRate = videoFrameRate;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001097 mVideoSize = sz;
Zhijun Hea52636b2014-02-25 11:10:11 -08001098 }
1099
Eino-Ville Talvalab80c6612014-07-06 17:58:46 -07001100 private void startRecording(boolean useMediaRecorder,
Eino-Ville Talvalac0dd0222014-09-04 15:07:58 -07001101 CameraCaptureSession.CaptureCallback listener) throws Exception {
Zhijun Hea52636b2014-02-25 11:10:11 -08001102 List<Surface> outputSurfaces = new ArrayList<Surface>(2);
1103 assertTrue("Both preview and recording surfaces should be valid",
1104 mPreviewSurface.isValid() && mRecordingSurface.isValid());
1105 outputSurfaces.add(mPreviewSurface);
1106 outputSurfaces.add(mRecordingSurface);
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001107 // Video snapshot surface
1108 if (mReaderSurface != null) {
1109 outputSurfaces.add(mReaderSurface);
1110 }
Eino-Ville Talvalac0dd0222014-09-04 15:07:58 -07001111 mSessionListener = new BlockingSessionCallback();
Eino-Ville Talvalab80c6612014-07-06 17:58:46 -07001112 mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
Zhijun Hea52636b2014-02-25 11:10:11 -08001113
1114 CaptureRequest.Builder recordingRequestBuilder =
1115 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
1116 // Make sure camera output frame rate is set to correct value.
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001117 Range<Integer> fpsRange = Range.create(mVideoFrameRate, mVideoFrameRate);
Zhijun Hea52636b2014-02-25 11:10:11 -08001118 recordingRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, fpsRange);
1119 recordingRequestBuilder.addTarget(mRecordingSurface);
1120 recordingRequestBuilder.addTarget(mPreviewSurface);
Eino-Ville Talvalab80c6612014-07-06 17:58:46 -07001121 mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler);
Zhijun Hea52636b2014-02-25 11:10:11 -08001122
1123 if (useMediaRecorder) {
1124 mMediaRecorder.start();
1125 } else {
1126 // TODO: need implement MediaCodec path.
1127 }
Yin-Chia Yeh203b9802014-12-18 11:31:09 -08001128 mRecordingStartTime = SystemClock.elapsedRealtime();
Zhijun Hea52636b2014-02-25 11:10:11 -08001129 }
1130
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001131 private void startRecording(boolean useMediaRecorder) throws Exception {
1132 startRecording(useMediaRecorder, null);
Zhijun Hea52636b2014-02-25 11:10:11 -08001133 }
1134
1135 private void stopCameraStreaming() throws Exception {
1136 if (VERBOSE) {
1137 Log.v(TAG, "Stopping camera streaming and waiting for idle");
1138 }
1139 // Stop repeating, wait for captures to complete, and disconnect from
1140 // surfaces
Eino-Ville Talvalab80c6612014-07-06 17:58:46 -07001141 mSession.close();
1142 mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
Zhijun Hea52636b2014-02-25 11:10:11 -08001143 }
1144
Yin-Chia Yeh203b9802014-12-18 11:31:09 -08001145 // Stop recording and return the estimated video duration in milliseconds.
1146 private int stopRecording(boolean useMediaRecorder) throws Exception {
1147 long stopRecordingTime = SystemClock.elapsedRealtime();
Zhijun Hea52636b2014-02-25 11:10:11 -08001148 if (useMediaRecorder) {
1149 stopCameraStreaming();
1150
1151 mMediaRecorder.stop();
1152 // Can reuse the MediaRecorder object after reset.
1153 mMediaRecorder.reset();
1154 } else {
1155 // TODO: need implement MediaCodec path.
1156 }
Chong Zhang484e2dc2015-05-19 17:28:41 -07001157 if (mPersistentSurface == null && mRecordingSurface != null) {
Zhijun Hea52636b2014-02-25 11:10:11 -08001158 mRecordingSurface.release();
1159 mRecordingSurface = null;
1160 }
Yin-Chia Yeh203b9802014-12-18 11:31:09 -08001161 return (int) (stopRecordingTime - mRecordingStartTime);
Zhijun Hea52636b2014-02-25 11:10:11 -08001162 }
1163
Ruben Brunk77034932014-07-01 16:56:52 -07001164 private void releaseRecorder() {
Zhijun Hea52636b2014-02-25 11:10:11 -08001165 if (mMediaRecorder != null) {
1166 mMediaRecorder.release();
1167 mMediaRecorder = null;
1168 }
1169 }
1170
Zhijun Hebb69dad2015-07-01 11:39:25 -07001171 private void validateRecording(Size sz, int expectedDurationMs) throws Exception {
Zhijun Hea52636b2014-02-25 11:10:11 -08001172 File outFile = new File(mOutMediaFileName);
1173 assertTrue("No video is recorded", outFile.exists());
1174
Yin-Chia Yeh601e6332014-11-19 13:47:56 -08001175 MediaExtractor extractor = new MediaExtractor();
Zhijun Hea52636b2014-02-25 11:10:11 -08001176 try {
Yin-Chia Yeh601e6332014-11-19 13:47:56 -08001177 extractor.setDataSource(mOutMediaFileName);
1178 long durationUs = 0;
1179 int width = -1, height = -1;
1180 int numTracks = extractor.getTrackCount();
1181 final String VIDEO_MIME_TYPE = "video";
1182 for (int i = 0; i < numTracks; i++) {
1183 MediaFormat format = extractor.getTrackFormat(i);
1184 String mime = format.getString(MediaFormat.KEY_MIME);
1185 if (mime.contains(VIDEO_MIME_TYPE)) {
1186 Log.i(TAG, "video format is: " + format.toString());
1187 durationUs = format.getLong(MediaFormat.KEY_DURATION);
1188 width = format.getInteger(MediaFormat.KEY_WIDTH);
1189 height = format.getInteger(MediaFormat.KEY_HEIGHT);
1190 break;
1191 }
1192 }
1193 Size videoSz = new Size(width, height);
Eino-Ville Talvalab80c6612014-07-06 17:58:46 -07001194 assertTrue("Video size doesn't match, expected " + sz.toString() +
1195 " got " + videoSz.toString(), videoSz.equals(sz));
Yin-Chia Yeh601e6332014-11-19 13:47:56 -08001196 int duration = (int) (durationUs / 1000);
Zhijun Hed5f04112014-09-03 17:12:03 -07001197 if (VERBOSE) {
1198 Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms",
Zhijun Hebb69dad2015-07-01 11:39:25 -07001199 duration, expectedDurationMs));
Zhijun Hed5f04112014-09-03 17:12:03 -07001200 }
Ruben Brunk77034932014-07-01 16:56:52 -07001201
1202 // TODO: Don't skip this for video snapshot
1203 if (!mStaticInfo.isHardwareLevelLegacy()) {
1204 assertTrue(String.format(
Yin-Chia Yeh601e6332014-11-19 13:47:56 -08001205 "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.",
Zhijun Hebb69dad2015-07-01 11:39:25 -07001206 mCamera.getId(), duration, expectedDurationMs),
1207 Math.abs(duration - expectedDurationMs) <
1208 DURATION_MARGIN * expectedDurationMs);
Ruben Brunk77034932014-07-01 16:56:52 -07001209 }
Zhijun Hea52636b2014-02-25 11:10:11 -08001210 } finally {
Yin-Chia Yeh601e6332014-11-19 13:47:56 -08001211 extractor.release();
Zhijun Hea52636b2014-02-25 11:10:11 -08001212 if (!DEBUG_DUMP) {
1213 outFile.delete();
1214 }
1215 }
1216 }
1217
1218 /**
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001219 * Validate video snapshot capture image object sanity and test.
1220 *
1221 * <p> Check for size, format and jpeg decoding</p>
1222 *
1223 * @param image The JPEG image to be verified.
1224 * @param size The JPEG capture size to be verified against.
1225 */
1226 private void validateVideoSnapshotCapture(Image image, Size size) {
1227 CameraTestUtils.validateImage(image, size.getWidth(), size.getHeight(),
1228 ImageFormat.JPEG, /*filePath*/null);
1229 }
1230
1231 /**
1232 * Validate if video snapshot causes frame drop.
1233 * Here frame drop is defined as frame duration >= 2 * expected frame duration.
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -07001234 * Return the estimated number of frames dropped during video snapshot
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001235 */
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -07001236 private int validateFrameDropAroundVideoSnapshot(
Eino-Ville Talvalac0dd0222014-09-04 15:07:58 -07001237 SimpleCaptureCallback resultListener, long imageTimeStamp) {
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001238 double expectedDurationMs = 1000.0 / mVideoFrameRate;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001239 CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1240 long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP);
1241 while (!resultListener.hasMoreResults()) {
1242 CaptureResult currentResult =
1243 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1244 long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP);
1245 if (currentTS == imageTimeStamp) {
1246 // validate the timestamp before and after, then return
1247 CaptureResult nextResult =
1248 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1249 long nextTS = getValueNotNull(nextResult, CaptureResult.SENSOR_TIMESTAMP);
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001250 double durationMs = (currentTS - prevTS) / 1000000.0;
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -07001251 int totalFramesDropped = 0;
Ruben Brunk77034932014-07-01 16:56:52 -07001252
1253 // Snapshots in legacy mode pause the preview briefly. Skip the duration
1254 // requirements for legacy mode unless this is fixed.
1255 if (!mStaticInfo.isHardwareLevelLegacy()) {
1256 mCollector.expectTrue(
1257 String.format(
1258 "Video %dx%d Frame drop detected before video snapshot: " +
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001259 "duration %.2fms (expected %.2fms)",
Ruben Brunk77034932014-07-01 16:56:52 -07001260 mVideoSize.getWidth(), mVideoSize.getHeight(),
1261 durationMs, expectedDurationMs
1262 ),
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001263 durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
Ruben Brunk77034932014-07-01 16:56:52 -07001264 );
Zhijun Heccd62d02014-09-23 11:21:23 -07001265 // Log a warning is there is any frame drop detected.
1266 if (durationMs >= expectedDurationMs * 2) {
1267 Log.w(TAG, String.format(
1268 "Video %dx%d Frame drop detected before video snapshot: " +
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001269 "duration %.2fms (expected %.2fms)",
Zhijun Heccd62d02014-09-23 11:21:23 -07001270 mVideoSize.getWidth(), mVideoSize.getHeight(),
1271 durationMs, expectedDurationMs
1272 ));
1273 }
1274
Yin-Chia Yehba8c3fa2015-05-14 16:02:35 -07001275 durationMs = (nextTS - currentTS) / 1000000.0;
Ruben Brunk77034932014-07-01 16:56:52 -07001276 mCollector.expectTrue(
1277 String.format(
1278 "Video %dx%d Frame drop detected after video snapshot: " +
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001279 "duration %.2fms (expected %.2fms)",
Ruben Brunk77034932014-07-01 16:56:52 -07001280 mVideoSize.getWidth(), mVideoSize.getHeight(),
1281 durationMs, expectedDurationMs
1282 ),
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001283 durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_INTERVAL_ALLOWED)
Ruben Brunk77034932014-07-01 16:56:52 -07001284 );
Zhijun Heccd62d02014-09-23 11:21:23 -07001285 // Log a warning is there is any frame drop detected.
1286 if (durationMs >= expectedDurationMs * 2) {
1287 Log.w(TAG, String.format(
1288 "Video %dx%d Frame drop detected after video snapshot: " +
Yin-Chia Yehba8c3fa2015-05-14 16:02:35 -07001289 "duration %fms (expected %fms)",
Zhijun Heccd62d02014-09-23 11:21:23 -07001290 mVideoSize.getWidth(), mVideoSize.getHeight(),
1291 durationMs, expectedDurationMs
1292 ));
1293 }
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -07001294
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001295 double totalDurationMs = (nextTS - prevTS) / 1000000.0;
1296 // Minus 2 for the expected 2 frames interval
1297 totalFramesDropped = (int) (totalDurationMs / expectedDurationMs) - 2;
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -07001298 if (totalFramesDropped < 0) {
1299 Log.w(TAG, "totalFrameDropped is " + totalFramesDropped +
1300 ". Video frame rate might be too fast.");
1301 }
1302 totalFramesDropped = Math.max(0, totalFramesDropped);
Ruben Brunk77034932014-07-01 16:56:52 -07001303 }
Yin-Chia Yeh44ad2ab2014-09-29 10:17:24 -07001304 return totalFramesDropped;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001305 }
1306 prevTS = currentTS;
1307 }
1308 throw new AssertionFailedError(
1309 "Video snapshot timestamp does not match any of capture results!");
1310 }
1311
1312 /**
1313 * Validate frame jittering from the input simple listener's buffered results
1314 */
Eino-Ville Talvalac0dd0222014-09-04 15:07:58 -07001315 private void validateJittering(SimpleCaptureCallback resultListener) {
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001316 double expectedDurationMs = 1000.0 / mVideoFrameRate;
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001317 CaptureResult prevResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1318 long prevTS = getValueNotNull(prevResult, CaptureResult.SENSOR_TIMESTAMP);
1319 while (!resultListener.hasMoreResults()) {
1320 CaptureResult currentResult =
1321 resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
1322 long currentTS = getValueNotNull(currentResult, CaptureResult.SENSOR_TIMESTAMP);
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001323 double durationMs = (currentTS - prevTS) / 1000000.0;
1324 double durationError = Math.abs(durationMs - expectedDurationMs);
Igor Murashkincf4b8602014-07-25 16:56:53 -07001325 long frameNumber = currentResult.getFrameNumber();
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001326 mCollector.expectTrue(
1327 String.format(
Yin-Chia Yeh480e7a52015-04-17 16:00:17 -07001328 "Resolution %dx%d Frame %d: jittering (%.2fms) exceeds bound [%.2fms,%.2fms]",
Yin-Chia Yeh6e3dd282014-06-02 11:45:46 -07001329 mVideoSize.getWidth(), mVideoSize.getHeight(),
1330 frameNumber, durationMs,
1331 expectedDurationMs - FRAME_DURATION_ERROR_TOLERANCE_MS,
1332 expectedDurationMs + FRAME_DURATION_ERROR_TOLERANCE_MS),
1333 durationError <= FRAME_DURATION_ERROR_TOLERANCE_MS);
1334 prevTS = currentTS;
1335 }
1336 }
1337
1338 /**
Zhijun Hea52636b2014-02-25 11:10:11 -08001339 * Calculate a video bit rate based on the size. The bit rate is scaled
1340 * based on ratio of video size to 1080p size.
1341 */
1342 private int getVideoBitRate(Size sz) {
1343 int rate = BIT_RATE_1080P;
1344 float scaleFactor = sz.getHeight() * sz.getWidth() / (float)(1920 * 1080);
1345 rate = (int)(rate * scaleFactor);
1346
1347 // Clamp to the MIN, MAX range.
1348 return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
1349 }
1350
1351 /**
1352 * Check if the encoder and camera are able to support this size and frame rate.
1353 * Assume the video compression format is AVC.
1354 */
1355 private boolean isSupported(Size sz, int captureRate, int encodingRate) throws Exception {
1356 // Check camera capability.
1357 if (!isSupportedByCamera(sz, captureRate)) {
1358 return false;
1359 }
1360
1361 // Check encode capability.
1362 if (!isSupportedByAVCEncoder(sz, encodingRate)){
1363 return false;
1364 }
1365
1366 if(VERBOSE) {
1367 Log.v(TAG, "Both encoder and camera support " + sz.toString() + "@" + encodingRate + "@"
1368 + getVideoBitRate(sz) / 1000 + "Kbps");
1369 }
1370
1371 return true;
1372 }
1373
1374 private boolean isSupportedByCamera(Size sz, int frameRate) {
1375 // Check if camera can support this sz and frame rate combination.
Ruben Brunk77034932014-07-01 16:56:52 -07001376 StreamConfigurationMap config = mStaticInfo.
1377 getValueFromKeyNonNull(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
1378
1379 long minDuration = config.getOutputMinFrameDuration(MediaRecorder.class, sz);
Eino-Ville Talvalac8fe8ce2014-07-28 12:55:23 -07001380 if (minDuration == 0) {
Zhijun Hea52636b2014-02-25 11:10:11 -08001381 return false;
1382 }
Zhijun Hea52636b2014-02-25 11:10:11 -08001383
Ruben Brunk77034932014-07-01 16:56:52 -07001384 int maxFrameRate = (int) (1e9f / minDuration);
1385 return maxFrameRate >= frameRate;
Zhijun Hea52636b2014-02-25 11:10:11 -08001386 }
1387
1388 /**
1389 * Check if encoder can support this size and frame rate combination by querying
1390 * MediaCodec capability. Check is based on size and frame rate. Ignore the bit rate
1391 * as the bit rates targeted in this test are well below the bit rate max value specified
1392 * by AVC specification for certain level.
1393 */
1394 private static boolean isSupportedByAVCEncoder(Size sz, int frameRate) {
Lajos Molnar85bbb7c2014-11-04 15:47:16 -08001395 MediaFormat format = MediaFormat.createVideoFormat(
1396 MediaFormat.MIMETYPE_VIDEO_AVC, sz.getWidth(), sz.getHeight());
Lajos Molnar56ff8e32014-12-11 10:24:41 -08001397 format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
Lajos Molnar85bbb7c2014-11-04 15:47:16 -08001398 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
Lajos Molnar56ff8e32014-12-11 10:24:41 -08001399 return mcl.findEncoderForFormat(format) != null;
Zhijun Hea52636b2014-02-25 11:10:11 -08001400 }
1401}