1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.media.cts; 17 18 19 import android.content.pm.PackageManager; 20 import android.hardware.Camera; 21 import android.media.MediaMetadataRetriever; 22 import android.media.MediaRecorder; 23 import android.media.MediaRecorder.OnErrorListener; 24 import android.media.MediaRecorder.OnInfoListener; 25 import android.media.MediaMetadataRetriever; 26 import android.os.Environment; 27 import android.os.ConditionVariable; 28 import android.test.ActivityInstrumentationTestCase2; 29 import android.test.UiThreadTest; 30 import android.view.Surface; 31 32 import android.util.Log; 33 34 import java.io.File; 35 import java.io.FileDescriptor; 36 import java.io.FileOutputStream; 37 import java.lang.InterruptedException; 38 import java.lang.Runnable; 39 import java.util.concurrent.CountDownLatch; 40 import java.util.concurrent.TimeUnit; 41 42 public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaStubActivity> { 43 private final String TAG = "MediaRecorderTest"; 44 private final String OUTPUT_PATH; 45 private final String OUTPUT_PATH2; 46 private static final float TOLERANCE = 0.0002f; 47 private static final int RECORD_TIME_MS = 3000; 48 private static final int RECORD_TIME_LAPSE_MS = 4000; 49 private static final int RECORD_TIME_LONG_MS = 20000; 50 private static final int RECORDED_DUR_TOLERANCE_MS = 1000; 51 private static final int VIDEO_WIDTH = 176; 52 private static final int VIDEO_HEIGHT = 144; 53 private static final int VIDEO_BIT_RATE_IN_BPS = 128000; 54 private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0; 55 private static final int AUDIO_BIT_RATE_IN_BPS = 12200; 56 private static final int AUDIO_NUM_CHANNELS = 1; 57 private static final int AUDIO_SAMPLE_RATE_HZ = 8000; 58 private static final long MAX_FILE_SIZE = 5000; 59 private static final int MAX_FILE_SIZE_TIMEOUT_MS = 5 * 60 * 1000; 60 private static final int MAX_DURATION_MSEC = 2000; 61 private static final float LATITUDE = 0.0000f; 62 private static final float LONGITUDE = -180.0f; 63 private boolean mOnInfoCalled; 64 private boolean mOnErrorCalled; 65 private File mOutFile; 66 private File mOutFile2; 67 private Camera mCamera; 68 private MediaStubActivity mActivity = null; 69 70 private MediaRecorder mMediaRecorder; 71 private ConditionVariable mMaxDurationCond; 72 private ConditionVariable mMaxFileSizeCond; 73 74 public MediaRecorderTest() { 75 super("com.android.cts.media", MediaStubActivity.class); 76 OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(), 77 "record.out").getAbsolutePath(); 78 OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(), 79 "record2.out").getAbsolutePath(); 80 } 81 82 private void completeOnUiThread(final Runnable runnable) { 83 final CountDownLatch latch = new CountDownLatch(1); 84 getActivity().runOnUiThread(new Runnable() { 85 @Override 86 public void run() { 87 runnable.run(); 88 latch.countDown(); 89 } 90 }); 91 try { 92 // if UI thread does not run, things will fail anyway 93 assertTrue(latch.await(10, TimeUnit.SECONDS)); 94 } catch (java.lang.InterruptedException e) { 95 fail("should not be interrupted"); 96 } 97 } 98 99 @Override 100 protected void setUp() throws Exception { 101 mActivity = getActivity(); 102 completeOnUiThread(new Runnable() { 103 @Override 104 public void run() { 105 mMediaRecorder = new MediaRecorder(); 106 mOutFile = new File(OUTPUT_PATH); 107 mOutFile2 = new File(OUTPUT_PATH2); 108 109 mMaxDurationCond = new ConditionVariable(); 110 mMaxFileSizeCond = new ConditionVariable(); 111 112 mMediaRecorder.setOutputFile(OUTPUT_PATH); 113 mMediaRecorder.setOnInfoListener(new OnInfoListener() { 114 public void onInfo(MediaRecorder mr, int what, int extra) { 115 mOnInfoCalled = true; 116 if (what == 117 MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 118 Log.v(TAG, "max duration reached"); 119 mMaxDurationCond.open(); 120 } else if (what == 121 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 122 Log.v(TAG, "max file size reached"); 123 mMaxFileSizeCond.open(); 124 } 125 } 126 }); 127 mMediaRecorder.setOnErrorListener(new OnErrorListener() { 128 public void onError(MediaRecorder mr, int what, int extra) { 129 mOnErrorCalled = true; 130 } 131 }); 132 } 133 }); 134 super.setUp(); 135 } 136 137 @Override 138 protected void tearDown() throws Exception { 139 mMediaRecorder.release(); 140 mMediaRecorder = null; 141 if (mOutFile != null && mOutFile.exists()) { 142 mOutFile.delete(); 143 } 144 if (mOutFile2 != null && mOutFile2.exists()) { 145 mOutFile2.delete(); 146 } 147 if (mCamera != null) { 148 mCamera.release(); 149 mCamera = null; 150 } 151 mMaxDurationCond.close(); 152 mMaxDurationCond = null; 153 mMaxFileSizeCond.close(); 154 mMaxFileSizeCond = null; 155 mActivity = null; 156 super.tearDown(); 157 } 158 159 public void testRecorderCamera() throws Exception { 160 if (!hasCamera()) { 161 return; 162 } 163 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 164 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 165 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 166 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 167 mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS); 168 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 169 mMediaRecorder.prepare(); 170 mMediaRecorder.start(); 171 Thread.sleep(RECORD_TIME_MS); 172 mMediaRecorder.stop(); 173 checkOutputExist(); 174 } 175 176 @UiThreadTest 177 public void testSetCamera() throws Exception { 178 recordVideoUsingCamera(false); 179 } 180 181 public void testRecorderTimelapsedVideo() throws Exception { 182 recordVideoUsingCamera(true); 183 } 184 185 private void recordVideoUsingCamera(boolean timelapse) throws Exception { 186 int nCamera = Camera.getNumberOfCameras(); 187 int durMs = timelapse? RECORD_TIME_LAPSE_MS: RECORD_TIME_MS; 188 for (int cameraId = 0; cameraId < nCamera; cameraId++) { 189 mCamera = Camera.open(cameraId); 190 recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse); 191 mCamera.release(); 192 mCamera = null; 193 assertTrue(checkLocationInFile(OUTPUT_PATH)); 194 } 195 } 196 197 private void recordVideoUsingCamera( 198 Camera camera, String fileName, int durMs, boolean timelapse) throws Exception { 199 // FIXME: 200 // We should add some test case to use Camera.Parameters.getPreviewFpsRange() 201 // to get the supported video frame rate range. 202 Camera.Parameters params = camera.getParameters(); 203 int frameRate = params.getPreviewFrameRate(); 204 205 camera.unlock(); 206 mMediaRecorder.setCamera(camera); 207 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 208 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 209 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 210 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 211 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 212 mMediaRecorder.setVideoFrameRate(frameRate); 213 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 214 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 215 mMediaRecorder.setOutputFile(fileName); 216 mMediaRecorder.setLocation(LATITUDE, LONGITUDE); 217 final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS; 218 if (timelapse) { 219 mMediaRecorder.setCaptureRate(captureRate); 220 } 221 222 mMediaRecorder.prepare(); 223 mMediaRecorder.start(); 224 Thread.sleep(durMs); 225 mMediaRecorder.stop(); 226 assertTrue(mOutFile.exists()); 227 228 int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs; 229 boolean hasVideo = true; 230 boolean hasAudio = timelapse? false: true; 231 checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName); 232 } 233 234 private void checkTracksAndDuration( 235 int targetMs, boolean hasVideo, boolean hasAudio, String fileName) throws Exception { 236 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 237 retriever.setDataSource(fileName); 238 String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); 239 String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO); 240 assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null); 241 assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null); 242 // FIXME: 243 // If we could use fixed frame rate for video recording, we could also do more accurate 244 // check on the duration. 245 String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 246 assertTrue(durStr != null); 247 assertTrue(Integer.parseInt(durStr) > 0); 248 retriever.release(); 249 retriever = null; 250 } 251 252 private boolean checkLocationInFile(String fileName) { 253 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 254 retriever.setDataSource(fileName); 255 String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); 256 if (location == null) { 257 retriever.release(); 258 Log.v(TAG, "No location information found in file " + fileName); 259 return false; 260 } 261 262 // parsing String location and recover the location inforamtion in floats 263 // Make sure the tolerance is very small - due to rounding errors?. 264 Log.v(TAG, "location: " + location); 265 266 // Get the position of the -/+ sign in location String, which indicates 267 // the beginning of the longtitude. 268 int index = location.lastIndexOf('-'); 269 if (index == -1) { 270 index = location.lastIndexOf('+'); 271 } 272 assertTrue("+ or - is not found", index != -1); 273 assertTrue("+ or - is only found at the beginning", index != 0); 274 float latitude = Float.parseFloat(location.substring(0, index - 1)); 275 float longitude = Float.parseFloat(location.substring(index)); 276 assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE); 277 assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE); 278 retriever.release(); 279 return true; 280 } 281 282 private void checkOutputExist() { 283 assertTrue(mOutFile.exists()); 284 assertTrue(mOutFile.length() > 0); 285 assertTrue(mOutFile.delete()); 286 } 287 288 public void testRecorderVideo() throws Exception { 289 if (!hasCamera()) { 290 return; 291 } 292 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 293 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 294 mMediaRecorder.setOutputFile(OUTPUT_PATH2); 295 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 296 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 297 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 298 299 FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2); 300 FileDescriptor fd = fos.getFD(); 301 mMediaRecorder.setOutputFile(fd); 302 long maxFileSize = MAX_FILE_SIZE * 10; 303 recordMedia(maxFileSize, mOutFile2); 304 assertFalse(checkLocationInFile(OUTPUT_PATH2)); 305 fos.close(); 306 } 307 308 public void testRecordingAudioInRawFormats() throws Exception { 309 testRecordAudioInRawFormat( 310 MediaRecorder.OutputFormat.AMR_NB, 311 MediaRecorder.AudioEncoder.AMR_NB); 312 313 testRecordAudioInRawFormat( 314 MediaRecorder.OutputFormat.AMR_WB, 315 MediaRecorder.AudioEncoder.AMR_WB); 316 317 testRecordAudioInRawFormat( 318 MediaRecorder.OutputFormat.AAC_ADTS, 319 MediaRecorder.AudioEncoder.AAC); 320 } 321 322 private void testRecordAudioInRawFormat( 323 int fileFormat, int codec) throws Exception { 324 325 if (!hasMicrophone()) { 326 return; 327 } 328 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 329 mMediaRecorder.setOutputFormat(fileFormat); 330 mMediaRecorder.setOutputFile(OUTPUT_PATH); 331 mMediaRecorder.setAudioEncoder(codec); 332 recordMedia(MAX_FILE_SIZE, mOutFile); 333 } 334 335 public void testGetAudioSourceMax() throws Exception { 336 final int max = MediaRecorder.getAudioSourceMax(); 337 assertTrue(MediaRecorder.AudioSource.DEFAULT <= max); 338 assertTrue(MediaRecorder.AudioSource.MIC <= max); 339 assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max); 340 assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max); 341 assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max); 342 assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max); 343 assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max); 344 assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max); 345 } 346 347 public void testRecorderAudio() throws Exception { 348 if (!hasMicrophone()) { 349 return; 350 } 351 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 352 assertEquals(0, mMediaRecorder.getMaxAmplitude()); 353 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 354 mMediaRecorder.setOutputFile(OUTPUT_PATH); 355 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 356 mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS); 357 mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ); 358 mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS); 359 recordMedia(MAX_FILE_SIZE, mOutFile); 360 } 361 362 public void testOnInfoListener() throws Exception { 363 if (!hasMicrophone()) { 364 return; 365 } 366 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 367 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 368 mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC); 369 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 370 mMediaRecorder.prepare(); 371 mMediaRecorder.start(); 372 Thread.sleep(RECORD_TIME_MS); 373 assertTrue(mOnInfoCalled); 374 } 375 376 public void testSetMaxDuration() throws Exception { 377 if (!hasMicrophone()) { 378 return; 379 } 380 testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS); 381 } 382 383 private void testSetMaxDuration(long durationMs, long toleranceMs) throws Exception { 384 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 385 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 386 mMediaRecorder.setMaxDuration((int)durationMs); 387 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 388 mMediaRecorder.prepare(); 389 mMediaRecorder.start(); 390 long startTimeMs = System.currentTimeMillis(); 391 if (!mMaxDurationCond.block(durationMs + toleranceMs)) { 392 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_DURATION_REACHED"); 393 } 394 long endTimeMs = System.currentTimeMillis(); 395 long actualDurationMs = endTimeMs - startTimeMs; 396 mMediaRecorder.stop(); 397 checkRecordedTime(durationMs, actualDurationMs, toleranceMs); 398 } 399 400 private void checkRecordedTime(long expectedMs, long actualMs, long tolerance) { 401 assertEquals(expectedMs, actualMs, tolerance); 402 long actualFileDurationMs = getRecordedFileDurationMs(OUTPUT_PATH); 403 assertEquals(actualFileDurationMs, actualMs, tolerance); 404 } 405 406 private int getRecordedFileDurationMs(final String fileName) { 407 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 408 retriever.setDataSource(fileName); 409 String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 410 assertNotNull(durationStr); 411 return Integer.parseInt(durationStr); 412 } 413 414 public void testSetMaxFileSize() throws Exception { 415 if (!hasMicrophone()) { 416 return; 417 } 418 testSetMaxFileSize(512 * 1024, 50 * 1024); 419 } 420 421 private void testSetMaxFileSize( 422 long fileSize, long tolerance) throws Exception { 423 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 424 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 425 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 426 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 427 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 428 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 429 mMediaRecorder.setVideoEncodingBitRate(256000); 430 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 431 mMediaRecorder.setMaxFileSize(fileSize); 432 mMediaRecorder.prepare(); 433 mMediaRecorder.start(); 434 435 // Recording a scene with moving objects would greatly help reduce 436 // the time for waiting. 437 if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 438 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 439 } 440 mMediaRecorder.stop(); 441 checkOutputFileSize(OUTPUT_PATH, fileSize, tolerance); 442 } 443 444 private void checkOutputFileSize(final String fileName, long fileSize, long tolerance) { 445 assertTrue(mOutFile.exists()); 446 assertEquals(fileSize, mOutFile.length(), tolerance); 447 assertTrue(mOutFile.delete()); 448 } 449 450 public void testOnErrorListener() throws Exception { 451 if (!hasMicrophone()) { 452 return; 453 } 454 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 455 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 456 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 457 458 recordMedia(MAX_FILE_SIZE, mOutFile); 459 // TODO: how can we trigger a recording error? 460 assertFalse(mOnErrorCalled); 461 } 462 463 private void recordMedia(long maxFileSize, File outFile) throws Exception { 464 mMediaRecorder.setMaxFileSize(maxFileSize); 465 mMediaRecorder.prepare(); 466 mMediaRecorder.start(); 467 Thread.sleep(RECORD_TIME_MS); 468 mMediaRecorder.stop(); 469 470 assertTrue(outFile.exists()); 471 472 // The max file size is always guaranteed. 473 // We just make sure that the margin is not too big 474 assertTrue(outFile.length() < 1.1 * maxFileSize); 475 assertTrue(outFile.length() > 0); 476 } 477 478 private boolean hasCamera() { 479 return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); 480 } 481 482 private boolean hasMicrophone() { 483 return mActivity.getPackageManager().hasSystemFeature( 484 PackageManager.FEATURE_MICROPHONE); 485 } 486 } 487