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 import android.content.pm.PackageManager; 19 import android.cts.util.MediaUtils; 20 import android.graphics.Canvas; 21 import android.graphics.Color; 22 import android.graphics.Paint; 23 import android.hardware.Camera; 24 import android.media.CamcorderProfile; 25 import android.media.EncoderCapabilities; 26 import android.media.MediaCodec; 27 import android.media.MediaFormat; 28 import android.media.MediaMetadataRetriever; 29 import android.media.MediaRecorder; 30 import android.media.EncoderCapabilities.VideoEncoderCap; 31 import android.media.MediaRecorder.OnErrorListener; 32 import android.media.MediaRecorder.OnInfoListener; 33 import android.media.MediaMetadataRetriever; 34 import android.os.Environment; 35 import android.os.ConditionVariable; 36 import android.test.ActivityInstrumentationTestCase2; 37 import android.test.UiThreadTest; 38 import android.view.Surface; 39 40 import android.util.Log; 41 42 import java.io.File; 43 import java.io.FileDescriptor; 44 import java.io.FileOutputStream; 45 import java.lang.InterruptedException; 46 import java.lang.Runnable; 47 import java.util.List; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.TimeUnit; 50 51 public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaStubActivity> { 52 private final String TAG = "MediaRecorderTest"; 53 private final String OUTPUT_PATH; 54 private final String OUTPUT_PATH2; 55 private static final float TOLERANCE = 0.0002f; 56 private static final int RECORD_TIME_MS = 3000; 57 private static final int RECORD_TIME_LAPSE_MS = 6000; 58 private static final int RECORD_TIME_LONG_MS = 20000; 59 private static final int RECORDED_DUR_TOLERANCE_MS = 1000; 60 // Tolerate 4 frames off at maximum 61 private static final float RECORDED_DUR_TOLERANCE_FRAMES = 4f; 62 private static final int VIDEO_WIDTH = 176; 63 private static final int VIDEO_HEIGHT = 144; 64 private static int mVideoWidth = VIDEO_WIDTH; 65 private static int mVideoHeight = VIDEO_HEIGHT; 66 private static final int VIDEO_BIT_RATE_IN_BPS = 128000; 67 private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0; 68 private static final int AUDIO_BIT_RATE_IN_BPS = 12200; 69 private static final int AUDIO_NUM_CHANNELS = 1; 70 private static final int AUDIO_SAMPLE_RATE_HZ = 8000; 71 private static final long MAX_FILE_SIZE = 5000; 72 private static final int MAX_FILE_SIZE_TIMEOUT_MS = 5 * 60 * 1000; 73 private static final int MAX_DURATION_MSEC = 2000; 74 private static final float LATITUDE = 0.0000f; 75 private static final float LONGITUDE = -180.0f; 76 private static final int NORMAL_FPS = 30; 77 private static final int TIME_LAPSE_FPS = 5; 78 private static final int SLOW_MOTION_FPS = 120; 79 private static final List<VideoEncoderCap> mVideoEncoders = 80 EncoderCapabilities.getVideoEncoders(); 81 82 private boolean mOnInfoCalled; 83 private boolean mOnErrorCalled; 84 private File mOutFile; 85 private File mOutFile2; 86 private Camera mCamera; 87 private MediaStubActivity mActivity = null; 88 89 private MediaRecorder mMediaRecorder; 90 private ConditionVariable mMaxDurationCond; 91 private ConditionVariable mMaxFileSizeCond; 92 MediaRecorderTest()93 public MediaRecorderTest() { 94 super("android.media.cts", MediaStubActivity.class); 95 OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(), 96 "record.out").getAbsolutePath(); 97 OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(), 98 "record2.out").getAbsolutePath(); 99 } 100 completeOnUiThread(final Runnable runnable)101 private void completeOnUiThread(final Runnable runnable) { 102 final CountDownLatch latch = new CountDownLatch(1); 103 getActivity().runOnUiThread(new Runnable() { 104 @Override 105 public void run() { 106 runnable.run(); 107 latch.countDown(); 108 } 109 }); 110 try { 111 // if UI thread does not run, things will fail anyway 112 assertTrue(latch.await(10, TimeUnit.SECONDS)); 113 } catch (java.lang.InterruptedException e) { 114 fail("should not be interrupted"); 115 } 116 } 117 118 @Override setUp()119 protected void setUp() throws Exception { 120 mActivity = getActivity(); 121 completeOnUiThread(new Runnable() { 122 @Override 123 public void run() { 124 mMediaRecorder = new MediaRecorder(); 125 mOutFile = new File(OUTPUT_PATH); 126 mOutFile2 = new File(OUTPUT_PATH2); 127 128 mMaxDurationCond = new ConditionVariable(); 129 mMaxFileSizeCond = new ConditionVariable(); 130 131 mMediaRecorder.setOutputFile(OUTPUT_PATH); 132 mMediaRecorder.setOnInfoListener(new OnInfoListener() { 133 public void onInfo(MediaRecorder mr, int what, int extra) { 134 mOnInfoCalled = true; 135 if (what == 136 MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 137 Log.v(TAG, "max duration reached"); 138 mMaxDurationCond.open(); 139 } else if (what == 140 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 141 Log.v(TAG, "max file size reached"); 142 mMaxFileSizeCond.open(); 143 } 144 } 145 }); 146 mMediaRecorder.setOnErrorListener(new OnErrorListener() { 147 public void onError(MediaRecorder mr, int what, int extra) { 148 mOnErrorCalled = true; 149 } 150 }); 151 } 152 }); 153 super.setUp(); 154 } 155 156 @Override tearDown()157 protected void tearDown() throws Exception { 158 if (mMediaRecorder != null) { 159 mMediaRecorder.release(); 160 mMediaRecorder = null; 161 } 162 if (mOutFile != null && mOutFile.exists()) { 163 mOutFile.delete(); 164 } 165 if (mOutFile2 != null && mOutFile2.exists()) { 166 mOutFile2.delete(); 167 } 168 if (mCamera != null) { 169 mCamera.release(); 170 mCamera = null; 171 } 172 mMaxDurationCond.close(); 173 mMaxDurationCond = null; 174 mMaxFileSizeCond.close(); 175 mMaxFileSizeCond = null; 176 mActivity = null; 177 super.tearDown(); 178 } 179 testRecorderCamera()180 public void testRecorderCamera() throws Exception { 181 int width; 182 int height; 183 Camera camera = null; 184 if (!hasCamera()) { 185 return; 186 } 187 // Try to get camera profile for QUALITY_LOW; if unavailable, 188 // set the video size to default value. 189 CamcorderProfile profile = CamcorderProfile.get( 190 0 /* cameraId */, CamcorderProfile.QUALITY_LOW); 191 if (profile != null) { 192 width = profile.videoFrameWidth; 193 height = profile.videoFrameHeight; 194 } else { 195 width = VIDEO_WIDTH; 196 height = VIDEO_HEIGHT; 197 } 198 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 199 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 200 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 201 mMediaRecorder.setVideoSize(width, height); 202 mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS); 203 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 204 mMediaRecorder.prepare(); 205 mMediaRecorder.start(); 206 Thread.sleep(RECORD_TIME_MS); 207 mMediaRecorder.stop(); 208 checkOutputExist(); 209 } 210 211 @UiThreadTest testSetCamera()212 public void testSetCamera() throws Exception { 213 recordVideoUsingCamera(false, false); 214 } 215 testRecorderTimelapsedVideo()216 public void testRecorderTimelapsedVideo() throws Exception { 217 recordVideoUsingCamera(true, false); 218 } 219 testRecorderPauseResume()220 public void testRecorderPauseResume() throws Exception { 221 recordVideoUsingCamera(false, true); 222 } 223 testRecorderPauseResumeOnTimeLapse()224 public void testRecorderPauseResumeOnTimeLapse() throws Exception { 225 recordVideoUsingCamera(true, true); 226 } 227 recordVideoUsingCamera(boolean timelapse, boolean pause)228 private void recordVideoUsingCamera(boolean timelapse, boolean pause) throws Exception { 229 int nCamera = Camera.getNumberOfCameras(); 230 int durMs = timelapse? RECORD_TIME_LAPSE_MS: RECORD_TIME_MS; 231 for (int cameraId = 0; cameraId < nCamera; cameraId++) { 232 mCamera = Camera.open(cameraId); 233 setSupportedResolution(mCamera); 234 recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse, pause); 235 mCamera.release(); 236 mCamera = null; 237 assertTrue(checkLocationInFile(OUTPUT_PATH)); 238 } 239 } 240 setSupportedResolution(Camera camera)241 private void setSupportedResolution(Camera camera) { 242 Camera.Parameters parameters = camera.getParameters(); 243 List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes(); 244 // getSupportedVideoSizes returns null when separate video/preview size 245 // is not supported. 246 if (videoSizes == null) { 247 videoSizes = parameters.getSupportedPreviewSizes(); 248 } 249 for (Camera.Size size : videoSizes) 250 { 251 if (size.width == VIDEO_WIDTH && size.height == VIDEO_HEIGHT) { 252 mVideoWidth = VIDEO_WIDTH; 253 mVideoHeight = VIDEO_HEIGHT; 254 return; 255 } 256 } 257 mVideoWidth = videoSizes.get(0).width; 258 mVideoHeight = videoSizes.get(0).height; 259 } 260 recordVideoUsingCamera( Camera camera, String fileName, int durMs, boolean timelapse, boolean pause)261 private void recordVideoUsingCamera( 262 Camera camera, String fileName, int durMs, boolean timelapse, boolean pause) 263 throws Exception { 264 // FIXME: 265 // We should add some test case to use Camera.Parameters.getPreviewFpsRange() 266 // to get the supported video frame rate range. 267 Camera.Parameters params = camera.getParameters(); 268 int frameRate = params.getPreviewFrameRate(); 269 270 camera.unlock(); 271 mMediaRecorder.setCamera(camera); 272 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 273 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 274 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 275 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 276 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 277 mMediaRecorder.setVideoFrameRate(frameRate); 278 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 279 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 280 mMediaRecorder.setOutputFile(fileName); 281 mMediaRecorder.setLocation(LATITUDE, LONGITUDE); 282 final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS; 283 if (timelapse) { 284 mMediaRecorder.setCaptureRate(captureRate); 285 } 286 287 mMediaRecorder.prepare(); 288 mMediaRecorder.start(); 289 if (pause) { 290 Thread.sleep(durMs / 2); 291 mMediaRecorder.pause(); 292 Thread.sleep(durMs / 2); 293 mMediaRecorder.resume(); 294 Thread.sleep(durMs / 2); 295 } else { 296 Thread.sleep(durMs); 297 } 298 mMediaRecorder.stop(); 299 assertTrue(mOutFile.exists()); 300 301 int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs; 302 boolean hasVideo = true; 303 boolean hasAudio = timelapse? false: true; 304 checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName, frameRate); 305 } 306 checkTracksAndDuration( int targetMs, boolean hasVideo, boolean hasAudio, String fileName, float frameRate)307 private void checkTracksAndDuration( 308 int targetMs, boolean hasVideo, boolean hasAudio, String fileName, 309 float frameRate) throws Exception { 310 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 311 retriever.setDataSource(fileName); 312 String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); 313 String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO); 314 assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null); 315 assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null); 316 // FIXME: 317 // If we could use fixed frame rate for video recording, we could also do more accurate 318 // check on the duration. 319 String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 320 assertTrue(durStr != null); 321 int duration = Integer.parseInt(durStr); 322 assertTrue("duration is non-positive: dur = " + duration, duration > 0); 323 if (targetMs != 0) { 324 float toleranceMs = RECORDED_DUR_TOLERANCE_FRAMES * (1000f / frameRate); 325 assertTrue(String.format("duration is too large: dur = %d, target = %d, tolerance = %f", 326 duration, targetMs, toleranceMs), 327 duration <= targetMs + toleranceMs); 328 } 329 330 retriever.release(); 331 retriever = null; 332 } 333 checkLocationInFile(String fileName)334 private boolean checkLocationInFile(String fileName) { 335 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 336 retriever.setDataSource(fileName); 337 String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); 338 if (location == null) { 339 retriever.release(); 340 Log.v(TAG, "No location information found in file " + fileName); 341 return false; 342 } 343 344 // parsing String location and recover the location inforamtion in floats 345 // Make sure the tolerance is very small - due to rounding errors?. 346 Log.v(TAG, "location: " + location); 347 348 // Get the position of the -/+ sign in location String, which indicates 349 // the beginning of the longtitude. 350 int index = location.lastIndexOf('-'); 351 if (index == -1) { 352 index = location.lastIndexOf('+'); 353 } 354 assertTrue("+ or - is not found", index != -1); 355 assertTrue("+ or - is only found at the beginning", index != 0); 356 float latitude = Float.parseFloat(location.substring(0, index - 1)); 357 float longitude = Float.parseFloat(location.substring(index)); 358 assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE); 359 assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE); 360 retriever.release(); 361 return true; 362 } 363 checkOutputExist()364 private void checkOutputExist() { 365 assertTrue(mOutFile.exists()); 366 assertTrue(mOutFile.length() > 0); 367 assertTrue(mOutFile.delete()); 368 } 369 testRecorderVideo()370 public void testRecorderVideo() throws Exception { 371 if (!hasCamera()) { 372 return; 373 } 374 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 375 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 376 mMediaRecorder.setOutputFile(OUTPUT_PATH2); 377 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 378 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 379 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 380 381 FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2); 382 FileDescriptor fd = fos.getFD(); 383 mMediaRecorder.setOutputFile(fd); 384 long maxFileSize = MAX_FILE_SIZE * 10; 385 recordMedia(maxFileSize, mOutFile2); 386 assertFalse(checkLocationInFile(OUTPUT_PATH2)); 387 fos.close(); 388 } 389 testRecordingAudioInRawFormats()390 public void testRecordingAudioInRawFormats() throws Exception { 391 int testsRun = 0; 392 if (hasAmrNb()) { 393 testsRun += testRecordAudioInRawFormat( 394 MediaRecorder.OutputFormat.AMR_NB, 395 MediaRecorder.AudioEncoder.AMR_NB); 396 } 397 398 if (hasAmrWb()) { 399 testsRun += testRecordAudioInRawFormat( 400 MediaRecorder.OutputFormat.AMR_WB, 401 MediaRecorder.AudioEncoder.AMR_WB); 402 } 403 404 if (hasAac()) { 405 testsRun += testRecordAudioInRawFormat( 406 MediaRecorder.OutputFormat.AAC_ADTS, 407 MediaRecorder.AudioEncoder.AAC); 408 } 409 if (testsRun == 0) { 410 MediaUtils.skipTest("no audio codecs or microphone"); 411 } 412 } 413 testRecordAudioInRawFormat( int fileFormat, int codec)414 private int testRecordAudioInRawFormat( 415 int fileFormat, int codec) throws Exception { 416 if (!hasMicrophone()) { 417 return 0; // skip 418 } 419 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 420 mMediaRecorder.setOutputFormat(fileFormat); 421 mMediaRecorder.setOutputFile(OUTPUT_PATH); 422 mMediaRecorder.setAudioEncoder(codec); 423 recordMedia(MAX_FILE_SIZE, mOutFile); 424 return 1; 425 } 426 testRecordAudioFromAudioSourceUnprocessed()427 public void testRecordAudioFromAudioSourceUnprocessed() throws Exception { 428 if (!hasMicrophone()) { 429 return; // skip 430 } 431 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.UNPROCESSED); 432 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 433 mMediaRecorder.setOutputFile(OUTPUT_PATH); 434 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 435 recordMedia(MAX_FILE_SIZE, mOutFile); 436 } 437 testGetAudioSourceMax()438 public void testGetAudioSourceMax() throws Exception { 439 final int max = MediaRecorder.getAudioSourceMax(); 440 assertTrue(MediaRecorder.AudioSource.DEFAULT <= max); 441 assertTrue(MediaRecorder.AudioSource.MIC <= max); 442 assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max); 443 assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max); 444 assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max); 445 assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max); 446 assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max); 447 assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max); 448 assertTrue(MediaRecorder.AudioSource.UNPROCESSED <= max); 449 } 450 testRecorderAudio()451 public void testRecorderAudio() throws Exception { 452 if (!hasMicrophone() || !hasAmrNb()) { 453 MediaUtils.skipTest("no audio codecs or microphone"); 454 return; 455 } 456 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 457 assertEquals(0, mMediaRecorder.getMaxAmplitude()); 458 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 459 mMediaRecorder.setOutputFile(OUTPUT_PATH); 460 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 461 mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS); 462 mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ); 463 mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS); 464 recordMedia(MAX_FILE_SIZE, mOutFile); 465 } 466 testOnInfoListener()467 public void testOnInfoListener() throws Exception { 468 if (!hasMicrophone() || !hasAmrNb()) { 469 MediaUtils.skipTest("no audio codecs or microphone"); 470 return; 471 } 472 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 473 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 474 mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC); 475 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 476 mMediaRecorder.prepare(); 477 mMediaRecorder.start(); 478 Thread.sleep(RECORD_TIME_MS); 479 assertTrue(mOnInfoCalled); 480 } 481 testSetMaxDuration()482 public void testSetMaxDuration() throws Exception { 483 if (!hasMicrophone() || !hasAmrNb()) { 484 MediaUtils.skipTest("no audio codecs or microphone"); 485 return; 486 } 487 testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS); 488 } 489 testSetMaxDuration(long durationMs, long toleranceMs)490 private void testSetMaxDuration(long durationMs, long toleranceMs) throws Exception { 491 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 492 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 493 mMediaRecorder.setMaxDuration((int)durationMs); 494 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 495 mMediaRecorder.prepare(); 496 mMediaRecorder.start(); 497 long startTimeMs = System.currentTimeMillis(); 498 if (!mMaxDurationCond.block(durationMs + toleranceMs)) { 499 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_DURATION_REACHED"); 500 } 501 long endTimeMs = System.currentTimeMillis(); 502 long actualDurationMs = endTimeMs - startTimeMs; 503 mMediaRecorder.stop(); 504 checkRecordedTime(durationMs, actualDurationMs, toleranceMs); 505 } 506 checkRecordedTime(long expectedMs, long actualMs, long tolerance)507 private void checkRecordedTime(long expectedMs, long actualMs, long tolerance) { 508 assertEquals(expectedMs, actualMs, tolerance); 509 long actualFileDurationMs = getRecordedFileDurationMs(OUTPUT_PATH); 510 assertEquals(actualFileDurationMs, actualMs, tolerance); 511 } 512 getRecordedFileDurationMs(final String fileName)513 private int getRecordedFileDurationMs(final String fileName) { 514 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 515 retriever.setDataSource(fileName); 516 String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 517 assertNotNull(durationStr); 518 return Integer.parseInt(durationStr); 519 } 520 testSetMaxFileSize()521 public void testSetMaxFileSize() throws Exception { 522 testSetMaxFileSize(512 * 1024, 50 * 1024); 523 } 524 testSetMaxFileSize( long fileSize, long tolerance)525 private void testSetMaxFileSize( 526 long fileSize, long tolerance) throws Exception { 527 if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) { 528 MediaUtils.skipTest("no microphone, camera, or codecs"); 529 return; 530 } 531 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 532 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 533 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 534 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 535 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 536 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 537 mMediaRecorder.setVideoEncodingBitRate(256000); 538 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 539 mMediaRecorder.setMaxFileSize(fileSize); 540 mMediaRecorder.prepare(); 541 mMediaRecorder.start(); 542 543 // Recording a scene with moving objects would greatly help reduce 544 // the time for waiting. 545 if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 546 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 547 } 548 mMediaRecorder.stop(); 549 checkOutputFileSize(OUTPUT_PATH, fileSize, tolerance); 550 } 551 checkOutputFileSize(final String fileName, long fileSize, long tolerance)552 private void checkOutputFileSize(final String fileName, long fileSize, long tolerance) { 553 assertTrue(mOutFile.exists()); 554 assertEquals(fileSize, mOutFile.length(), tolerance); 555 assertTrue(mOutFile.delete()); 556 } 557 testOnErrorListener()558 public void testOnErrorListener() throws Exception { 559 if (!hasMicrophone() || !hasAmrNb()) { 560 MediaUtils.skipTest("no audio codecs or microphone"); 561 return; 562 } 563 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 564 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 565 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 566 567 recordMedia(MAX_FILE_SIZE, mOutFile); 568 // TODO: how can we trigger a recording error? 569 assertFalse(mOnErrorCalled); 570 } 571 setupRecorder(String filename, boolean useSurface, boolean hasAudio)572 private void setupRecorder(String filename, boolean useSurface, boolean hasAudio) 573 throws Exception { 574 int codec = MediaRecorder.VideoEncoder.H264; 575 int frameRate = getMaxFrameRateForCodec(codec); 576 if (mMediaRecorder == null) { 577 mMediaRecorder = new MediaRecorder(); 578 } 579 580 if (!useSurface) { 581 mCamera = Camera.open(0); 582 Camera.Parameters params = mCamera.getParameters(); 583 frameRate = params.getPreviewFrameRate(); 584 mCamera.unlock(); 585 mMediaRecorder.setCamera(mCamera); 586 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 587 } 588 589 mMediaRecorder.setVideoSource(useSurface ? 590 MediaRecorder.VideoSource.SURFACE : MediaRecorder.VideoSource.CAMERA); 591 592 if (hasAudio) { 593 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 594 } 595 596 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 597 mMediaRecorder.setOutputFile(filename); 598 599 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 600 mMediaRecorder.setVideoFrameRate(frameRate); 601 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 602 603 if (hasAudio) { 604 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 605 } 606 } 607 tryGetSurface(boolean shouldThrow)608 private Surface tryGetSurface(boolean shouldThrow) throws Exception { 609 Surface surface = null; 610 try { 611 surface = mMediaRecorder.getSurface(); 612 assertFalse("failed to throw IllegalStateException", shouldThrow); 613 } catch (IllegalStateException e) { 614 assertTrue("threw unexpected exception: " + e, shouldThrow); 615 } 616 return surface; 617 } 618 validateGetSurface(boolean useSurface)619 private boolean validateGetSurface(boolean useSurface) { 620 Log.v(TAG,"validateGetSurface, useSurface=" + useSurface); 621 if (!useSurface && !hasCamera()) { 622 // pass if testing camera source but no hardware 623 return true; 624 } 625 Surface surface = null; 626 boolean success = true; 627 try { 628 setupRecorder(OUTPUT_PATH, useSurface, false /* hasAudio */); 629 630 /* Test: getSurface() before prepare() 631 * should throw IllegalStateException 632 */ 633 surface = tryGetSurface(true /* shouldThow */); 634 635 mMediaRecorder.prepare(); 636 637 /* Test: getSurface() after prepare() 638 * should succeed for surface source 639 * should fail for camera source 640 */ 641 surface = tryGetSurface(!useSurface); 642 643 mMediaRecorder.start(); 644 645 /* Test: getSurface() after start() 646 * should succeed for surface source 647 * should fail for camera source 648 */ 649 surface = tryGetSurface(!useSurface); 650 651 try { 652 mMediaRecorder.stop(); 653 } catch (Exception e) { 654 // stop() could fail if the recording is empty, as we didn't render anything. 655 // ignore any failure in stop, we just want it stopped. 656 } 657 658 /* Test: getSurface() after stop() 659 * should throw IllegalStateException 660 */ 661 surface = tryGetSurface(true /* shouldThow */); 662 } catch (Exception e) { 663 Log.d(TAG, e.toString()); 664 success = false; 665 } finally { 666 // reset to clear states, as stop() might have failed 667 mMediaRecorder.reset(); 668 669 if (mCamera != null) { 670 mCamera.release(); 671 mCamera = null; 672 } 673 if (surface != null) { 674 surface.release(); 675 surface = null; 676 } 677 } 678 679 return success; 680 } 681 trySetInputSurface(Surface surface)682 private void trySetInputSurface(Surface surface) throws Exception { 683 boolean testBadArgument = (surface == null); 684 try { 685 mMediaRecorder.setInputSurface(testBadArgument ? new Surface() : surface); 686 fail("failed to throw exception"); 687 } catch (IllegalArgumentException e) { 688 // OK only if testing bad arg 689 assertTrue("threw unexpected exception: " + e, testBadArgument); 690 } catch (IllegalStateException e) { 691 // OK only if testing error case other than bad arg 692 assertFalse("threw unexpected exception: " + e, testBadArgument); 693 } 694 } 695 validatePersistentSurface(boolean errorCase)696 private boolean validatePersistentSurface(boolean errorCase) { 697 Log.v(TAG, "validatePersistentSurface, errorCase=" + errorCase); 698 699 Surface surface = MediaCodec.createPersistentInputSurface(); 700 if (surface == null) { 701 return false; 702 } 703 Surface dummy = null; 704 705 boolean success = true; 706 try { 707 setupRecorder(OUTPUT_PATH, true /* useSurface */, false /* hasAudio */); 708 709 if (errorCase) { 710 /* 711 * Test: should throw if called with non-persistent surface 712 */ 713 trySetInputSurface(null); 714 } else { 715 /* 716 * Test: should succeed if called with a persistent surface before prepare() 717 */ 718 mMediaRecorder.setInputSurface(surface); 719 } 720 721 /* 722 * Test: getSurface() should fail before prepare 723 */ 724 dummy = tryGetSurface(true /* shouldThow */); 725 726 mMediaRecorder.prepare(); 727 728 /* 729 * Test: setInputSurface() should fail after prepare 730 */ 731 trySetInputSurface(surface); 732 733 /* 734 * Test: getSurface() should fail if setInputSurface() succeeded 735 */ 736 dummy = tryGetSurface(!errorCase /* shouldThow */); 737 738 mMediaRecorder.start(); 739 740 /* 741 * Test: setInputSurface() should fail after start 742 */ 743 trySetInputSurface(surface); 744 745 /* 746 * Test: getSurface() should fail if setInputSurface() succeeded 747 */ 748 dummy = tryGetSurface(!errorCase /* shouldThow */); 749 750 try { 751 mMediaRecorder.stop(); 752 } catch (Exception e) { 753 // stop() could fail if the recording is empty, as we didn't render anything. 754 // ignore any failure in stop, we just want it stopped. 755 } 756 757 /* 758 * Test: getSurface() should fail after stop 759 */ 760 dummy = tryGetSurface(true /* shouldThow */); 761 } catch (Exception e) { 762 Log.d(TAG, e.toString()); 763 success = false; 764 } finally { 765 // reset to clear states, as stop() might have failed 766 mMediaRecorder.reset(); 767 768 if (mCamera != null) { 769 mCamera.release(); 770 mCamera = null; 771 } 772 if (surface != null) { 773 surface.release(); 774 surface = null; 775 } 776 if (dummy != null) { 777 dummy.release(); 778 dummy = null; 779 } 780 } 781 782 return success; 783 } 784 testGetSurfaceApi()785 public void testGetSurfaceApi() { 786 if (!hasH264()) { 787 MediaUtils.skipTest("no codecs"); 788 return; 789 } 790 791 if (hasCamera()) { 792 // validate getSurface() with CAMERA source 793 assertTrue(validateGetSurface(false /* useSurface */)); 794 } 795 796 // validate getSurface() with SURFACE source 797 assertTrue(validateGetSurface(true /* useSurface */)); 798 } 799 testPersistentSurfaceApi()800 public void testPersistentSurfaceApi() { 801 if (!hasH264()) { 802 MediaUtils.skipTest("no codecs"); 803 return; 804 } 805 806 // test valid use case 807 assertTrue(validatePersistentSurface(false /* errorCase */)); 808 809 // test invalid use case 810 assertTrue(validatePersistentSurface(true /* errorCase */)); 811 } 812 getMaxFrameRateForCodec(int codec)813 private static int getMaxFrameRateForCodec(int codec) { 814 for (VideoEncoderCap cap : mVideoEncoders) { 815 if (cap.mCodec == codec) { 816 return cap.mMaxFrameRate < NORMAL_FPS ? cap.mMaxFrameRate : NORMAL_FPS; 817 } 818 } 819 fail("didn't find max FPS for codec"); 820 return -1; 821 } 822 recordFromSurface( String filename, int captureRate, boolean hasAudio, Surface persistentSurface)823 private boolean recordFromSurface( 824 String filename, 825 int captureRate, 826 boolean hasAudio, 827 Surface persistentSurface) { 828 Log.v(TAG, "recordFromSurface"); 829 Surface surface = null; 830 try { 831 setupRecorder(filename, true /* useSurface */, hasAudio); 832 833 int sleepTimeMs; 834 if (captureRate > 0) { 835 mMediaRecorder.setCaptureRate(captureRate); 836 sleepTimeMs = 1000 / captureRate; 837 } else { 838 sleepTimeMs = 1000 / getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264); 839 } 840 841 if (persistentSurface != null) { 842 Log.v(TAG, "using persistent surface"); 843 surface = persistentSurface; 844 mMediaRecorder.setInputSurface(surface); 845 } 846 847 mMediaRecorder.prepare(); 848 849 if (persistentSurface == null) { 850 surface = mMediaRecorder.getSurface(); 851 } 852 853 Paint paint = new Paint(); 854 paint.setTextSize(16); 855 paint.setColor(Color.RED); 856 int i; 857 858 /* Test: draw 10 frames at 30fps before start 859 * these should be dropped and not causing malformed stream. 860 */ 861 for(i = 0; i < 10; i++) { 862 Canvas canvas = surface.lockCanvas(null); 863 int background = (i * 255 / 99); 864 canvas.drawARGB(255, background, background, background); 865 String text = "Frame #" + i; 866 canvas.drawText(text, 50, 50, paint); 867 surface.unlockCanvasAndPost(canvas); 868 Thread.sleep(sleepTimeMs); 869 } 870 871 Log.v(TAG, "start"); 872 mMediaRecorder.start(); 873 874 /* Test: draw another 90 frames at 30fps after start */ 875 for(i = 10; i < 100; i++) { 876 Canvas canvas = surface.lockCanvas(null); 877 int background = (i * 255 / 99); 878 canvas.drawARGB(255, background, background, background); 879 String text = "Frame #" + i; 880 canvas.drawText(text, 50, 50, paint); 881 surface.unlockCanvasAndPost(canvas); 882 Thread.sleep(sleepTimeMs); 883 } 884 885 Log.v(TAG, "stop"); 886 mMediaRecorder.stop(); 887 } catch (Exception e) { 888 Log.v(TAG, "record video failed: " + e.toString()); 889 return false; 890 } finally { 891 // We need to test persistent surface across multiple MediaRecorder 892 // instances, so must destroy mMediaRecorder here. 893 if (mMediaRecorder != null) { 894 mMediaRecorder.release(); 895 mMediaRecorder = null; 896 } 897 898 // release surface if not using persistent surface 899 if (persistentSurface == null && surface != null) { 900 surface.release(); 901 surface = null; 902 } 903 } 904 return true; 905 } 906 checkCaptureFps(String filename, int captureRate)907 private boolean checkCaptureFps(String filename, int captureRate) { 908 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 909 910 retriever.setDataSource(filename); 911 912 // verify capture rate meta key is present and correct 913 String captureFps = retriever.extractMetadata( 914 MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE); 915 916 if (captureFps == null) { 917 Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is missing"); 918 return false; 919 } 920 921 if (Math.abs(Float.parseFloat(captureFps) - captureRate) > 0.001) { 922 Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is incorrect: " 923 + captureFps + "vs. " + captureRate); 924 return false; 925 } 926 927 // verify other meta keys here if necessary 928 return true; 929 } 930 testRecordFromSurface(boolean persistent, boolean timelapse)931 private boolean testRecordFromSurface(boolean persistent, boolean timelapse) { 932 Log.v(TAG, "testRecordFromSurface: " + 933 "persistent=" + persistent + ", timelapse=" + timelapse); 934 boolean success = false; 935 Surface surface = null; 936 int noOfFailure = 0; 937 final float frameRate = getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264); 938 939 if (!hasH264()) { 940 MediaUtils.skipTest("no codecs"); 941 return true; 942 } 943 944 try { 945 if (persistent) { 946 surface = MediaCodec.createPersistentInputSurface(); 947 } 948 949 for (int k = 0; k < 2; k++) { 950 String filename = (k == 0) ? OUTPUT_PATH : OUTPUT_PATH2; 951 boolean hasAudio = false; 952 int captureRate = 0; 953 954 if (timelapse) { 955 // if timelapse/slow-mo, k chooses between low/high capture fps 956 captureRate = (k == 0) ? TIME_LAPSE_FPS : SLOW_MOTION_FPS; 957 } else { 958 // otherwise k chooses between no-audio and audio 959 hasAudio = (k == 0) ? false : true; 960 } 961 962 if (hasAudio && (!hasMicrophone() || !hasAmrNb())) { 963 // audio test waived if no audio support 964 continue; 965 } 966 967 Log.v(TAG, "testRecordFromSurface - round " + k); 968 success = recordFromSurface(filename, captureRate, hasAudio, surface); 969 if (success) { 970 checkTracksAndDuration(0, true /* hasVideo */, hasAudio, filename, frameRate); 971 972 // verify capture fps meta key 973 if (timelapse && !checkCaptureFps(filename, captureRate)) { 974 noOfFailure++; 975 } 976 } 977 if (!success) { 978 noOfFailure++; 979 } 980 } 981 } catch (Exception e) { 982 Log.v(TAG, e.toString()); 983 noOfFailure++; 984 } finally { 985 if (surface != null) { 986 Log.v(TAG, "releasing persistent surface"); 987 surface.release(); 988 surface = null; 989 } 990 } 991 return (noOfFailure == 0); 992 } 993 994 // Test recording from surface source with/without audio) testSurfaceRecording()995 public void testSurfaceRecording() { 996 assertTrue(testRecordFromSurface(false /* persistent */, false /* timelapse */)); 997 } 998 999 // Test recording from persistent surface source with/without audio testPersistentSurfaceRecording()1000 public void testPersistentSurfaceRecording() { 1001 assertTrue(testRecordFromSurface(true /* persistent */, false /* timelapse */)); 1002 } 1003 1004 // Test timelapse recording from surface without audio testSurfaceRecordingTimeLapse()1005 public void testSurfaceRecordingTimeLapse() { 1006 assertTrue(testRecordFromSurface(false /* persistent */, true /* timelapse */)); 1007 } 1008 1009 // Test timelapse recording from persisent surface without audio testPersistentSurfaceRecordingTimeLapse()1010 public void testPersistentSurfaceRecordingTimeLapse() { 1011 assertTrue(testRecordFromSurface(true /* persistent */, true /* timelapse */)); 1012 } 1013 recordMedia(long maxFileSize, File outFile)1014 private void recordMedia(long maxFileSize, File outFile) throws Exception { 1015 mMediaRecorder.setMaxFileSize(maxFileSize); 1016 mMediaRecorder.prepare(); 1017 mMediaRecorder.start(); 1018 Thread.sleep(RECORD_TIME_MS); 1019 mMediaRecorder.stop(); 1020 1021 assertTrue(outFile.exists()); 1022 1023 // The max file size is always guaranteed. 1024 // We just make sure that the margin is not too big 1025 assertTrue(outFile.length() < 1.1 * maxFileSize); 1026 assertTrue(outFile.length() > 0); 1027 } 1028 hasCamera()1029 private boolean hasCamera() { 1030 return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); 1031 } 1032 hasMicrophone()1033 private boolean hasMicrophone() { 1034 return mActivity.getPackageManager().hasSystemFeature( 1035 PackageManager.FEATURE_MICROPHONE); 1036 } 1037 hasAmrNb()1038 private static boolean hasAmrNb() { 1039 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB); 1040 } 1041 hasAmrWb()1042 private static boolean hasAmrWb() { 1043 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_WB); 1044 } 1045 hasAac()1046 private static boolean hasAac() { 1047 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC); 1048 } 1049 hasH264()1050 private static boolean hasH264() { 1051 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC); 1052 } 1053 } 1054