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