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.graphics.Canvas; 20 import android.graphics.Color; 21 import android.graphics.Paint; 22 import android.hardware.Camera; 23 import android.media.CamcorderProfile; 24 import android.media.EncoderCapabilities; 25 import android.media.MediaCodec; 26 import android.media.MediaCodecInfo; 27 import android.media.MediaCodecList; 28 import android.media.MediaExtractor; 29 import android.media.MediaFormat; 30 import android.media.MediaMetadataRetriever; 31 import android.media.MediaRecorder; 32 import android.media.EncoderCapabilities.VideoEncoderCap; 33 import android.media.MediaCodecInfo.CodecCapabilities; 34 import android.media.MediaCodecInfo.CodecProfileLevel; 35 import android.media.MediaRecorder.OnErrorListener; 36 import android.media.MediaRecorder.OnInfoListener; 37 import android.media.MediaMetadataRetriever; 38 import android.opengl.GLES20; 39 import android.os.ConditionVariable; 40 import android.os.Environment; 41 import android.os.ParcelFileDescriptor; 42 import android.os.PersistableBundle; 43 import android.support.test.filters.SmallTest; 44 import android.platform.test.annotations.RequiresDevice; 45 import android.test.ActivityInstrumentationTestCase2; 46 import android.test.UiThreadTest; 47 import android.view.Surface; 48 49 import android.util.Log; 50 51 import com.android.compatibility.common.util.MediaUtils; 52 53 import java.io.File; 54 import java.io.FileDescriptor; 55 import java.io.FileOutputStream; 56 import java.io.IOException; 57 import java.io.RandomAccessFile; 58 import java.lang.InterruptedException; 59 import java.lang.Runnable; 60 import java.util.ArrayList; 61 import java.util.List; 62 import java.util.Set; 63 import java.util.concurrent.CountDownLatch; 64 import java.util.concurrent.TimeUnit; 65 66 import static android.media.MediaCodecInfo.CodecProfileLevel.*; 67 68 @SmallTest 69 @RequiresDevice 70 public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaStubActivity> { 71 private final String TAG = "MediaRecorderTest"; 72 private final String OUTPUT_PATH; 73 private final String OUTPUT_PATH2; 74 private static final float TOLERANCE = 0.0002f; 75 private static final int RECORD_TIME_MS = 3000; 76 private static final int RECORD_TIME_LAPSE_MS = 6000; 77 private static final int RECORD_TIME_LONG_MS = 20000; 78 private static final int RECORDED_DUR_TOLERANCE_MS = 1000; 79 // Tolerate 4 frames off at maximum 80 private static final float RECORDED_DUR_TOLERANCE_FRAMES = 4f; 81 private static final int VIDEO_WIDTH = 176; 82 private static final int VIDEO_HEIGHT = 144; 83 private static int mVideoWidth = VIDEO_WIDTH; 84 private static int mVideoHeight = VIDEO_HEIGHT; 85 private static final int VIDEO_BIT_RATE_IN_BPS = 128000; 86 private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0; 87 private static final int AUDIO_BIT_RATE_IN_BPS = 12200; 88 private static final int AUDIO_NUM_CHANNELS = 1; 89 private static final int AUDIO_SAMPLE_RATE_HZ = 8000; 90 private static final long MAX_FILE_SIZE = 5000; 91 private static final int MAX_FILE_SIZE_TIMEOUT_MS = 5 * 60 * 1000; 92 private static final int MAX_DURATION_MSEC = 2000; 93 private static final float LATITUDE = 0.0000f; 94 private static final float LONGITUDE = -180.0f; 95 private static final int NORMAL_FPS = 30; 96 private static final int TIME_LAPSE_FPS = 5; 97 private static final int SLOW_MOTION_FPS = 120; 98 private static final List<VideoEncoderCap> mVideoEncoders = 99 EncoderCapabilities.getVideoEncoders(); 100 101 private boolean mOnInfoCalled; 102 private boolean mOnErrorCalled; 103 private File mOutFile; 104 private File mOutFile2; 105 private Camera mCamera; 106 private MediaStubActivity mActivity = null; 107 private int mFileIndex; 108 109 private MediaRecorder mMediaRecorder; 110 private ConditionVariable mMaxDurationCond; 111 private ConditionVariable mMaxFileSizeCond; 112 private ConditionVariable mMaxFileSizeApproachingCond; 113 private ConditionVariable mNextOutputFileStartedCond; 114 private boolean mExpectMaxFileCond; 115 116 // movie length, in frames 117 private static final int NUM_FRAMES = 120; 118 119 private static final int TEST_R0 = 0; // RGB equivalent of {0,0,0} (BT.601) 120 private static final int TEST_G0 = 136; 121 private static final int TEST_B0 = 0; 122 private static final int TEST_R1 = 236; // RGB equivalent of {120,160,200} (BT.601) 123 private static final int TEST_G1 = 50; 124 private static final int TEST_B1 = 186; 125 126 private final static String AVC = MediaFormat.MIMETYPE_VIDEO_AVC; 127 128 public MediaRecorderTest() { 129 super("android.media.cts", MediaStubActivity.class); 130 OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(), 131 "record.out").getAbsolutePath(); 132 OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(), 133 "record2.out").getAbsolutePath(); 134 } 135 136 private void completeOnUiThread(final Runnable runnable) { 137 final CountDownLatch latch = new CountDownLatch(1); 138 getActivity().runOnUiThread(new Runnable() { 139 @Override 140 public void run() { 141 runnable.run(); 142 latch.countDown(); 143 } 144 }); 145 try { 146 // if UI thread does not run, things will fail anyway 147 assertTrue(latch.await(10, TimeUnit.SECONDS)); 148 } catch (java.lang.InterruptedException e) { 149 fail("should not be interrupted"); 150 } 151 } 152 153 @Override 154 protected void setUp() throws Exception { 155 mActivity = getActivity(); 156 completeOnUiThread(new Runnable() { 157 @Override 158 public void run() { 159 mMediaRecorder = new MediaRecorder(); 160 mOutFile = new File(OUTPUT_PATH); 161 mOutFile2 = new File(OUTPUT_PATH2); 162 mFileIndex = 0; 163 164 mMaxDurationCond = new ConditionVariable(); 165 mMaxFileSizeCond = new ConditionVariable(); 166 mMaxFileSizeApproachingCond = new ConditionVariable(); 167 mNextOutputFileStartedCond = new ConditionVariable(); 168 mExpectMaxFileCond = true; 169 170 mMediaRecorder.setOutputFile(OUTPUT_PATH); 171 mMediaRecorder.setOnInfoListener(new OnInfoListener() { 172 public void onInfo(MediaRecorder mr, int what, int extra) { 173 mOnInfoCalled = true; 174 if (what == 175 MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 176 Log.v(TAG, "max duration reached"); 177 mMaxDurationCond.open(); 178 } else if (what == 179 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 180 Log.v(TAG, "max file size reached"); 181 mMaxFileSizeCond.open(); 182 } 183 } 184 }); 185 mMediaRecorder.setOnErrorListener(new OnErrorListener() { 186 public void onError(MediaRecorder mr, int what, int extra) { 187 mOnErrorCalled = true; 188 } 189 }); 190 } 191 }); 192 super.setUp(); 193 } 194 195 @Override 196 protected void tearDown() throws Exception { 197 if (mMediaRecorder != null) { 198 mMediaRecorder.release(); 199 mMediaRecorder = null; 200 } 201 if (mOutFile != null && mOutFile.exists()) { 202 mOutFile.delete(); 203 } 204 if (mOutFile2 != null && mOutFile2.exists()) { 205 mOutFile2.delete(); 206 } 207 if (mCamera != null) { 208 mCamera.release(); 209 mCamera = null; 210 } 211 mMaxDurationCond.close(); 212 mMaxDurationCond = null; 213 mMaxFileSizeCond.close(); 214 mMaxFileSizeCond = null; 215 mMaxFileSizeApproachingCond.close(); 216 mMaxFileSizeApproachingCond = null; 217 mNextOutputFileStartedCond.close(); 218 mNextOutputFileStartedCond = null; 219 mActivity = null; 220 super.tearDown(); 221 } 222 223 public void testRecorderCamera() throws Exception { 224 int width; 225 int height; 226 Camera camera = null; 227 if (!hasCamera()) { 228 return; 229 } 230 // Try to get camera profile for QUALITY_LOW; if unavailable, 231 // set the video size to default value. 232 CamcorderProfile profile = CamcorderProfile.get( 233 0 /* cameraId */, CamcorderProfile.QUALITY_LOW); 234 if (profile != null) { 235 width = profile.videoFrameWidth; 236 height = profile.videoFrameHeight; 237 } else { 238 width = VIDEO_WIDTH; 239 height = VIDEO_HEIGHT; 240 } 241 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 242 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 243 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 244 mMediaRecorder.setVideoSize(width, height); 245 mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS); 246 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 247 mMediaRecorder.prepare(); 248 mMediaRecorder.start(); 249 Thread.sleep(RECORD_TIME_MS); 250 251 252 // verify some getMetrics() behaviors while we're here. 253 PersistableBundle metrics = mMediaRecorder.getMetrics(); 254 if (metrics == null) { 255 fail("MediaRecorder.getMetrics() returned null metrics"); 256 } else if (metrics.isEmpty()) { 257 fail("MediaRecorder.getMetrics() returned empty metrics"); 258 } else { 259 int size = metrics.size(); 260 Set<String> keys = metrics.keySet(); 261 262 if (size == 0) { 263 fail("MediaRecorder.getMetrics().size() reports empty record"); 264 } 265 266 if (keys == null) { 267 fail("MediaMetricsSet returned no keys"); 268 } else if (keys.size() != size) { 269 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()"); 270 } 271 272 // ensure existence of some known fields 273 int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1); 274 if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) { 275 fail("getMetrics() videoEncodeBitrate set " + 276 VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate); 277 } 278 279 // valid values are -1.0 and >= 0; 280 // tolerate some floating point rounding variability 281 double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2); 282 if (captureFrameRate < 0.) { 283 assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001); 284 } 285 } 286 287 288 mMediaRecorder.stop(); 289 checkOutputExist(); 290 } 291 292 public void testRecorderMPEG2TS() throws Exception { 293 int width; 294 int height; 295 Camera camera = null; 296 if (!hasCamera()) { 297 MediaUtils.skipTest("no camera"); 298 return; 299 } 300 if (!hasMicrophone() || !hasAac()) { 301 MediaUtils.skipTest("no audio codecs or microphone"); 302 return; 303 } 304 // Try to get camera profile for QUALITY_LOW; if unavailable, 305 // set the video size to default value. 306 CamcorderProfile profile = CamcorderProfile.get( 307 0 /* cameraId */, CamcorderProfile.QUALITY_LOW); 308 if (profile != null) { 309 width = profile.videoFrameWidth; 310 height = profile.videoFrameHeight; 311 } else { 312 width = VIDEO_WIDTH; 313 height = VIDEO_HEIGHT; 314 } 315 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 316 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 317 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS); 318 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 319 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 320 mMediaRecorder.setVideoSize(width, height); 321 mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS); 322 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 323 mMediaRecorder.prepare(); 324 mMediaRecorder.start(); 325 Thread.sleep(RECORD_TIME_MS); 326 327 // verify some getMetrics() behaviors while we're here. 328 PersistableBundle metrics = mMediaRecorder.getMetrics(); 329 if (metrics == null) { 330 fail("MediaRecorder.getMetrics() returned null metrics"); 331 } else if (metrics.isEmpty()) { 332 fail("MediaRecorder.getMetrics() returned empty metrics"); 333 } else { 334 int size = metrics.size(); 335 Set<String> keys = metrics.keySet(); 336 337 if (size == 0) { 338 fail("MediaRecorder.getMetrics().size() reports empty record"); 339 } 340 341 if (keys == null) { 342 fail("MediaMetricsSet returned no keys"); 343 } else if (keys.size() != size) { 344 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()"); 345 } 346 347 // ensure existence of some known fields 348 int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1); 349 if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) { 350 fail("getMetrics() videoEncodeBitrate set " + 351 VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate); 352 } 353 354 // valid values are -1.0 and >= 0; 355 // tolerate some floating point rounding variability 356 double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2); 357 if (captureFrameRate < 0.) { 358 assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001); 359 } 360 } 361 362 mMediaRecorder.stop(); 363 checkOutputExist(); 364 } 365 366 @UiThreadTest 367 public void testSetCamera() throws Exception { 368 recordVideoUsingCamera(false, false); 369 } 370 371 public void testRecorderTimelapsedVideo() throws Exception { 372 recordVideoUsingCamera(true, false); 373 } 374 375 public void testRecorderPauseResume() throws Exception { 376 recordVideoUsingCamera(false, true); 377 } 378 379 public void testRecorderPauseResumeOnTimeLapse() throws Exception { 380 recordVideoUsingCamera(true, true); 381 } 382 383 private void recordVideoUsingCamera(boolean timelapse, boolean pause) throws Exception { 384 int nCamera = Camera.getNumberOfCameras(); 385 int durMs = timelapse? RECORD_TIME_LAPSE_MS: RECORD_TIME_MS; 386 for (int cameraId = 0; cameraId < nCamera; cameraId++) { 387 mCamera = Camera.open(cameraId); 388 setSupportedResolution(mCamera); 389 recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse, pause); 390 mCamera.release(); 391 mCamera = null; 392 assertTrue(checkLocationInFile(OUTPUT_PATH)); 393 } 394 } 395 396 private void setSupportedResolution(Camera camera) { 397 Camera.Parameters parameters = camera.getParameters(); 398 List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes(); 399 // getSupportedVideoSizes returns null when separate video/preview size 400 // is not supported. 401 if (videoSizes == null) { 402 videoSizes = parameters.getSupportedPreviewSizes(); 403 } 404 int minVideoWidth = Integer.MAX_VALUE; 405 int minVideoHeight = Integer.MAX_VALUE; 406 for (Camera.Size size : videoSizes) 407 { 408 if (size.width == VIDEO_WIDTH && size.height == VIDEO_HEIGHT) { 409 mVideoWidth = VIDEO_WIDTH; 410 mVideoHeight = VIDEO_HEIGHT; 411 return; 412 } 413 if (size.width < minVideoWidth || size.height < minVideoHeight) { 414 minVideoWidth = size.width; 415 minVideoHeight = size.height; 416 } 417 } 418 // Use minimum resolution to avoid that one frame size exceeds file size limit. 419 mVideoWidth = minVideoWidth; 420 mVideoHeight = minVideoHeight; 421 } 422 423 private void recordVideoUsingCamera( 424 Camera camera, String fileName, int durMs, boolean timelapse, boolean pause) 425 throws Exception { 426 // FIXME: 427 // We should add some test case to use Camera.Parameters.getPreviewFpsRange() 428 // to get the supported video frame rate range. 429 Camera.Parameters params = camera.getParameters(); 430 int frameRate = params.getPreviewFrameRate(); 431 432 camera.unlock(); 433 mMediaRecorder.setCamera(camera); 434 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 435 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 436 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 437 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 438 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 439 mMediaRecorder.setVideoFrameRate(frameRate); 440 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 441 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 442 mMediaRecorder.setOutputFile(fileName); 443 mMediaRecorder.setLocation(LATITUDE, LONGITUDE); 444 final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS; 445 if (timelapse) { 446 mMediaRecorder.setCaptureRate(captureRate); 447 } 448 449 mMediaRecorder.prepare(); 450 mMediaRecorder.start(); 451 if (pause) { 452 Thread.sleep(durMs / 2); 453 mMediaRecorder.pause(); 454 Thread.sleep(durMs / 2); 455 mMediaRecorder.resume(); 456 Thread.sleep(durMs / 2); 457 } else { 458 Thread.sleep(durMs); 459 } 460 mMediaRecorder.stop(); 461 assertTrue(mOutFile.exists()); 462 463 int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs; 464 boolean hasVideo = true; 465 boolean hasAudio = timelapse? false: true; 466 checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName, frameRate); 467 } 468 469 private void checkTracksAndDuration( 470 int targetMs, boolean hasVideo, boolean hasAudio, String fileName, 471 float frameRate) throws Exception { 472 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 473 retriever.setDataSource(fileName); 474 String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); 475 String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO); 476 assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null); 477 assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null); 478 // FIXME: 479 // If we could use fixed frame rate for video recording, we could also do more accurate 480 // check on the duration. 481 String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 482 assertTrue(durStr != null); 483 int duration = Integer.parseInt(durStr); 484 assertTrue("duration is non-positive: dur = " + duration, duration > 0); 485 if (targetMs != 0) { 486 float toleranceMs = RECORDED_DUR_TOLERANCE_FRAMES * (1000f / frameRate); 487 assertTrue(String.format("duration is too large: dur = %d, target = %d, tolerance = %f", 488 duration, targetMs, toleranceMs), 489 duration <= targetMs + toleranceMs); 490 } 491 492 retriever.release(); 493 retriever = null; 494 } 495 496 private boolean checkLocationInFile(String fileName) { 497 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 498 retriever.setDataSource(fileName); 499 String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); 500 if (location == null) { 501 retriever.release(); 502 Log.v(TAG, "No location information found in file " + fileName); 503 return false; 504 } 505 506 // parsing String location and recover the location inforamtion in floats 507 // Make sure the tolerance is very small - due to rounding errors?. 508 Log.v(TAG, "location: " + location); 509 510 // Get the position of the -/+ sign in location String, which indicates 511 // the beginning of the longtitude. 512 int index = location.lastIndexOf('-'); 513 if (index == -1) { 514 index = location.lastIndexOf('+'); 515 } 516 assertTrue("+ or - is not found", index != -1); 517 assertTrue("+ or - is only found at the beginning", index != 0); 518 float latitude = Float.parseFloat(location.substring(0, index - 1)); 519 int lastIndex = location.lastIndexOf('/', index); 520 if (lastIndex == -1) { 521 lastIndex = location.length(); 522 } 523 float longitude = Float.parseFloat(location.substring(index, lastIndex - 1)); 524 assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE); 525 assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE); 526 retriever.release(); 527 return true; 528 } 529 530 private void checkOutputExist() { 531 assertTrue(mOutFile.exists()); 532 assertTrue(mOutFile.length() > 0); 533 assertTrue(mOutFile.delete()); 534 } 535 536 public void testRecorderVideo() throws Exception { 537 if (!hasCamera()) { 538 return; 539 } 540 mCamera = Camera.open(0); 541 setSupportedResolution(mCamera); 542 mCamera.unlock(); 543 544 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 545 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 546 mMediaRecorder.setOutputFile(OUTPUT_PATH2); 547 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 548 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 549 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 550 551 FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2); 552 FileDescriptor fd = fos.getFD(); 553 mMediaRecorder.setOutputFile(fd); 554 long maxFileSize = MAX_FILE_SIZE * 10; 555 recordMedia(maxFileSize, mOutFile2); 556 assertFalse(checkLocationInFile(OUTPUT_PATH2)); 557 fos.close(); 558 } 559 560 public void testSetOutputFile() throws Exception { 561 if (!hasCamera()) { 562 return; 563 } 564 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 565 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 566 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 567 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 568 mMediaRecorder.setOutputFile(mOutFile); 569 long maxFileSize = MAX_FILE_SIZE * 10; 570 recordMedia(maxFileSize, mOutFile); 571 } 572 573 public void testRecordingAudioInRawFormats() throws Exception { 574 int testsRun = 0; 575 if (hasAmrNb()) { 576 testsRun += testRecordAudioInRawFormat( 577 MediaRecorder.OutputFormat.AMR_NB, 578 MediaRecorder.AudioEncoder.AMR_NB); 579 } 580 581 if (hasAmrWb()) { 582 testsRun += testRecordAudioInRawFormat( 583 MediaRecorder.OutputFormat.AMR_WB, 584 MediaRecorder.AudioEncoder.AMR_WB); 585 } 586 587 if (hasAac()) { 588 testsRun += testRecordAudioInRawFormat( 589 MediaRecorder.OutputFormat.AAC_ADTS, 590 MediaRecorder.AudioEncoder.AAC); 591 } 592 if (testsRun == 0) { 593 MediaUtils.skipTest("no audio codecs or microphone"); 594 } 595 } 596 597 private int testRecordAudioInRawFormat( 598 int fileFormat, int codec) throws Exception { 599 if (!hasMicrophone()) { 600 return 0; // skip 601 } 602 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 603 mMediaRecorder.setOutputFormat(fileFormat); 604 mMediaRecorder.setOutputFile(OUTPUT_PATH); 605 mMediaRecorder.setAudioEncoder(codec); 606 recordMedia(MAX_FILE_SIZE, mOutFile); 607 return 1; 608 } 609 610 public void testRecordAudioFromAudioSourceUnprocessed() throws Exception { 611 if (!hasMicrophone() || !hasAmrNb()) { 612 MediaUtils.skipTest("no audio codecs or microphone"); 613 return; 614 } 615 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.UNPROCESSED); 616 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 617 mMediaRecorder.setOutputFile(OUTPUT_PATH); 618 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 619 recordMedia(MAX_FILE_SIZE, mOutFile); 620 } 621 622 public void testGetAudioSourceMax() throws Exception { 623 final int max = MediaRecorder.getAudioSourceMax(); 624 assertTrue(MediaRecorder.AudioSource.DEFAULT <= max); 625 assertTrue(MediaRecorder.AudioSource.MIC <= max); 626 assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max); 627 assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max); 628 assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max); 629 assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max); 630 assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max); 631 assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max); 632 assertTrue(MediaRecorder.AudioSource.UNPROCESSED <= max); 633 } 634 635 public void testRecorderAudio() throws Exception { 636 if (!hasMicrophone() || !hasAac()) { 637 MediaUtils.skipTest("no audio codecs or microphone"); 638 return; 639 } 640 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 641 assertEquals(0, mMediaRecorder.getMaxAmplitude()); 642 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 643 mMediaRecorder.setOutputFile(OUTPUT_PATH); 644 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 645 mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS); 646 mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ); 647 mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS); 648 recordMedia(MAX_FILE_SIZE, mOutFile); 649 } 650 651 public void testOnInfoListener() throws Exception { 652 if (!hasMicrophone() || !hasAac()) { 653 MediaUtils.skipTest("no audio codecs or microphone"); 654 return; 655 } 656 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 657 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 658 mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC); 659 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 660 mMediaRecorder.prepare(); 661 mMediaRecorder.start(); 662 Thread.sleep(RECORD_TIME_MS); 663 assertTrue(mOnInfoCalled); 664 } 665 666 public void testSetMaxDuration() throws Exception { 667 if (!hasMicrophone() || !hasAac()) { 668 MediaUtils.skipTest("no audio codecs or microphone"); 669 return; 670 } 671 testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS); 672 } 673 674 private void testSetMaxDuration(long durationMs, long toleranceMs) throws Exception { 675 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 676 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 677 mMediaRecorder.setMaxDuration((int)durationMs); 678 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 679 mMediaRecorder.prepare(); 680 mMediaRecorder.start(); 681 long startTimeMs = System.currentTimeMillis(); 682 if (!mMaxDurationCond.block(durationMs + toleranceMs)) { 683 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_DURATION_REACHED"); 684 } 685 long endTimeMs = System.currentTimeMillis(); 686 long actualDurationMs = endTimeMs - startTimeMs; 687 mMediaRecorder.stop(); 688 checkRecordedTime(durationMs, actualDurationMs, toleranceMs); 689 } 690 691 private void checkRecordedTime(long expectedMs, long actualMs, long tolerance) { 692 assertEquals(expectedMs, actualMs, tolerance); 693 long actualFileDurationMs = getRecordedFileDurationMs(OUTPUT_PATH); 694 assertEquals(actualFileDurationMs, actualMs, tolerance); 695 } 696 697 private int getRecordedFileDurationMs(final String fileName) { 698 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 699 retriever.setDataSource(fileName); 700 String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 701 assertNotNull(durationStr); 702 return Integer.parseInt(durationStr); 703 } 704 705 public void testSetMaxFileSize() throws Exception { 706 testSetMaxFileSize(512 * 1024, 50 * 1024); 707 } 708 709 private void testSetMaxFileSize( 710 long fileSize, long tolerance) throws Exception { 711 if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) { 712 MediaUtils.skipTest("no microphone, camera, or codecs"); 713 return; 714 } 715 mCamera = Camera.open(0); 716 setSupportedResolution(mCamera); 717 mCamera.unlock(); 718 719 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 720 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 721 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 722 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 723 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 724 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 725 mMediaRecorder.setVideoEncodingBitRate(256000); 726 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 727 mMediaRecorder.setMaxFileSize(fileSize); 728 mMediaRecorder.prepare(); 729 mMediaRecorder.start(); 730 731 // Recording a scene with moving objects would greatly help reduce 732 // the time for waiting. 733 if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 734 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 735 } 736 mMediaRecorder.stop(); 737 checkOutputFileSize(OUTPUT_PATH, fileSize, tolerance); 738 } 739 740 /** 741 * Returns the first codec capable of encoding the specified MIME type, or null if no 742 * match was found. 743 */ 744 private static CodecCapabilities getCapsForPreferredCodecForMediaType(String mimeType) { 745 // FIXME: select codecs based on the complete use-case, not just the mime 746 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 747 for (MediaCodecInfo info : mcl.getCodecInfos()) { 748 if (!info.isEncoder()) { 749 continue; 750 } 751 752 String[] types = info.getSupportedTypes(); 753 for (int j = 0; j < types.length; j++) { 754 if (types[j].equalsIgnoreCase(mimeType)) { 755 return info.getCapabilitiesForType(mimeType); 756 } 757 } 758 } 759 return null; 760 } 761 762 /** 763 * Generates a frame of data using GL commands. 764 */ 765 private void generateSurfaceFrame(int frameIndex, int width, int height) { 766 frameIndex %= 8; 767 768 int startX, startY; 769 if (frameIndex < 4) { 770 // (0,0) is bottom-left in GL 771 startX = frameIndex * (width / 4); 772 startY = height / 2; 773 } else { 774 startX = (7 - frameIndex) * (width / 4); 775 startY = 0; 776 } 777 778 GLES20.glDisable(GLES20.GL_SCISSOR_TEST); 779 GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f); 780 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 781 GLES20.glEnable(GLES20.GL_SCISSOR_TEST); 782 GLES20.glScissor(startX, startY, width / 4, height / 2); 783 GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f); 784 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 785 } 786 787 /** 788 * Generates the presentation time for frame N, in microseconds. 789 */ 790 private static long computePresentationTime( 791 long startTimeOffset, int frameIndex, int frameRate) { 792 return startTimeOffset + frameIndex * 1000000 / frameRate; 793 } 794 795 private void testLevel(String mediaType, int width, int height, int framerate, 796 int bitrate, int profile, int requestedLevel, int... expectedLevels) throws Exception { 797 CodecCapabilities cap = getCapsForPreferredCodecForMediaType(mediaType); 798 if (cap == null) { // not supported 799 return; 800 } 801 MediaCodecInfo.VideoCapabilities vCap = cap.getVideoCapabilities(); 802 if (!vCap.areSizeAndRateSupported(width, height, framerate) 803 || !vCap.getBitrateRange().contains(bitrate * 1000)) { 804 Log.i(TAG, "Skip the test"); 805 return; 806 } 807 808 Surface surface = MediaCodec.createPersistentInputSurface(); 809 if (surface == null) { 810 return; 811 } 812 InputSurface encSurface = new InputSurface(surface); 813 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 814 mMediaRecorder.setInputSurface(encSurface.getSurface()); 815 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 816 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 817 mMediaRecorder.setOutputFile(mOutFile); 818 819 try { 820 mMediaRecorder.setVideoEncodingProfileLevel(-1, requestedLevel); 821 fail("Expect IllegalArgumentException."); 822 } catch (IllegalArgumentException e) { 823 // Expect exception. 824 } 825 try { 826 mMediaRecorder.setVideoEncodingProfileLevel(profile, -1); 827 fail("Expect IllegalArgumentException."); 828 } catch (IllegalArgumentException e) { 829 // Expect exception. 830 } 831 832 mMediaRecorder.setVideoEncodingProfileLevel(profile, requestedLevel); 833 mMediaRecorder.setVideoSize(width, height); 834 mMediaRecorder.setVideoEncodingBitRate(bitrate * 1000); 835 mMediaRecorder.setVideoFrameRate(framerate); 836 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 837 mMediaRecorder.prepare(); 838 encSurface.updateSize(width, height); 839 mMediaRecorder.start(); 840 841 842 long startNsec = System.nanoTime(); 843 long startTimeOffset = 3000 / framerate; 844 for (int i = 0; i < NUM_FRAMES; i++) { 845 encSurface.makeCurrent(); 846 generateSurfaceFrame(i, width, height); 847 long time = startNsec + 848 computePresentationTime(startTimeOffset, i, framerate) * 1000; 849 encSurface.setPresentationTime(time); 850 encSurface.swapBuffers(); 851 } 852 853 mMediaRecorder.stop(); 854 855 assertTrue(mOutFile.exists()); 856 assertTrue(mOutFile.length() > 0); 857 858 // Verify the recorded file profile/level, 859 MediaExtractor ex = new MediaExtractor(); 860 ex.setDataSource(OUTPUT_PATH); 861 for (int i = 0; i < ex.getTrackCount(); i++) { 862 MediaFormat format = ex.getTrackFormat(i); 863 String mime = format.getString(MediaFormat.KEY_MIME); 864 if (mime.startsWith("video/")) { 865 int finalProfile = format.getInteger(MediaFormat.KEY_PROFILE); 866 if (!(finalProfile == profile || 867 (mediaType.equals(AVC) 868 && profile == AVCProfileBaseline 869 && finalProfile == AVCProfileConstrainedBaseline) || 870 (mediaType.equals(AVC) 871 && profile == AVCProfileHigh 872 && finalProfile == AVCProfileConstrainedHigh))) { 873 fail("Incorrect profile: " + finalProfile + " Expected: " + profile); 874 } 875 int finalLevel = format.getInteger(MediaFormat.KEY_LEVEL); 876 boolean match = false; 877 String expectLvls = new String(); 878 for (int level : expectedLevels) { 879 expectLvls += level; 880 if (finalLevel == level) { 881 match = true; 882 break; 883 } 884 } 885 if (!match) { 886 fail("Incorrect Level: " + finalLevel + " Expected: " + expectLvls); 887 } 888 } 889 } 890 mOutFile.delete(); 891 if (encSurface != null) { 892 encSurface.release(); 893 encSurface = null; 894 } 895 } 896 897 public void testProfileAvcBaselineLevel1() throws Exception { 898 int profile = AVCProfileBaseline; 899 900 if (!hasH264()) { 901 MediaUtils.skipTest("no Avc codecs"); 902 return; 903 } 904 905 /* W H fps kbps profile request level expected levels */ 906 testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1, AVCLevel1); 907 // Enable them when vendor fixes the failure 908 //testLevel(AVC, 178, 144, 15, 64, profile, AVCLevel1, AVCLevel11); 909 //testLevel(AVC, 178, 146, 15, 64, profile, AVCLevel1, AVCLevel11); 910 //testLevel(AVC, 176, 144, 16, 64, profile, AVCLevel1, AVCLevel11); 911 //testLevel(AVC, 176, 144, 15, 65, profile, AVCLevel1, AVCLevel1b); 912 testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1b, AVCLevel1, 913 AVCLevel1b); 914 // testLevel(AVC, 176, 144, 15, 65, profile, AVCLevel2, AVCLevel1b, 915 // AVCLevel11, AVCLevel12, AVCLevel13, AVCLevel2); 916 } 917 918 919 public void testRecordExceedFileSizeLimit() throws Exception { 920 if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) { 921 MediaUtils.skipTest("no microphone, camera, or codecs"); 922 return; 923 } 924 long fileSize = 128 * 1024; 925 long tolerance = 50 * 1024; 926 List<String> recordFileList = new ArrayList<String>(); 927 mFileIndex = 0; 928 929 // Override the handler in setup for test. 930 mMediaRecorder.setOnInfoListener(new OnInfoListener() { 931 public void onInfo(MediaRecorder mr, int what, int extra) { 932 mOnInfoCalled = true; 933 if (what == 934 MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 935 Log.v(TAG, "max duration reached"); 936 mMaxDurationCond.open(); 937 } else if (what == 938 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 939 if (!mExpectMaxFileCond) { 940 fail("Do not expect MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 941 } else { 942 Log.v(TAG, "max file size reached"); 943 mMaxFileSizeCond.open(); 944 } 945 } else if (what == 946 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING) { 947 Log.v(TAG, "max file size is approaching"); 948 mMaxFileSizeApproachingCond.open(); 949 950 // Test passing a read-only FileDescriptor and expect IOException. 951 if (mFileIndex == 1) { 952 RandomAccessFile file = null; 953 try { 954 String path = OUTPUT_PATH + '0'; 955 file = new RandomAccessFile(path, "r"); 956 mMediaRecorder.setNextOutputFile(file.getFD()); 957 fail("Expect IOException."); 958 } catch (IOException e) { 959 // Expected. 960 } finally { 961 try { 962 file.close(); 963 } catch (IOException e) { 964 fail("Fail to close file"); 965 } 966 } 967 } 968 969 // Test passing a read-only FileDescriptor and expect IOException. 970 if (mFileIndex == 2) { 971 ParcelFileDescriptor out = null; 972 String path = null; 973 try { 974 path = OUTPUT_PATH + '0'; 975 out = ParcelFileDescriptor.open(new File(path), 976 ParcelFileDescriptor.MODE_READ_ONLY | ParcelFileDescriptor.MODE_CREATE); 977 mMediaRecorder.setNextOutputFile(out.getFileDescriptor()); 978 fail("Expect IOException."); 979 } catch (IOException e) { 980 // Expected. 981 } finally { 982 try { 983 out.close(); 984 } catch (IOException e) { 985 fail("Fail to close file"); 986 } 987 } 988 } 989 990 // Test passing a write-only FileDescriptor and expect NO IOException. 991 if (mFileIndex == 3) { 992 try { 993 ParcelFileDescriptor out = null; 994 String path = OUTPUT_PATH + mFileIndex; 995 out = ParcelFileDescriptor.open(new File(path), 996 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE); 997 mMediaRecorder.setNextOutputFile(out.getFileDescriptor()); 998 out.close(); 999 recordFileList.add(path); 1000 mFileIndex++; 1001 } catch (IOException e) { 1002 fail("Fail to set next output file error: " + e); 1003 } 1004 } else if (mFileIndex < 6) { 1005 try { 1006 String path = OUTPUT_PATH + mFileIndex; 1007 File nextFile = new File(path); 1008 mMediaRecorder.setNextOutputFile(nextFile); 1009 recordFileList.add(path); 1010 mFileIndex++; 1011 } catch (IOException e) { 1012 fail("Fail to set next output file error: " + e); 1013 } 1014 } 1015 } else if (what == 1016 MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED) { 1017 Log.v(TAG, "Next output file started"); 1018 mNextOutputFileStartedCond.open(); 1019 } 1020 } 1021 }); 1022 mExpectMaxFileCond = false; 1023 mMediaRecorder.setOutputFile(OUTPUT_PATH + mFileIndex); 1024 recordFileList.add(OUTPUT_PATH + mFileIndex); 1025 mFileIndex++; 1026 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 1027 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 1028 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 1029 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 1030 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 1031 mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT); 1032 mMediaRecorder.setVideoEncodingBitRate(256000); 1033 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 1034 mMediaRecorder.setMaxFileSize(fileSize); 1035 mMediaRecorder.prepare(); 1036 mMediaRecorder.start(); 1037 1038 // Record total 5 files including previous one. 1039 int fileCount = 0; 1040 while (fileCount < 5) { 1041 if (!mMaxFileSizeApproachingCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 1042 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING"); 1043 } 1044 if (!mNextOutputFileStartedCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 1045 fail("timed out waiting for MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED"); 1046 } 1047 fileCount++; 1048 mMaxFileSizeApproachingCond.close(); 1049 mNextOutputFileStartedCond.close(); 1050 } 1051 1052 mExpectMaxFileCond = true; 1053 if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 1054 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 1055 } 1056 mMediaRecorder.stop(); 1057 1058 for (String filePath : recordFileList) { 1059 checkOutputFileSize(filePath, fileSize, tolerance); 1060 } 1061 } 1062 1063 private void checkOutputFileSize(final String fileName, long fileSize, long tolerance) { 1064 File file = new File(fileName); 1065 assertTrue(file.exists()); 1066 assertEquals(fileSize, file.length(), tolerance); 1067 assertTrue(file.delete()); 1068 } 1069 1070 public void testOnErrorListener() throws Exception { 1071 if (!hasMicrophone() || !hasAac()) { 1072 MediaUtils.skipTest("no audio codecs or microphone"); 1073 return; 1074 } 1075 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 1076 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 1077 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 1078 1079 recordMedia(MAX_FILE_SIZE, mOutFile); 1080 // TODO: how can we trigger a recording error? 1081 assertFalse(mOnErrorCalled); 1082 } 1083 1084 private void setupRecorder(String filename, boolean useSurface, boolean hasAudio) 1085 throws Exception { 1086 int codec = MediaRecorder.VideoEncoder.H264; 1087 int frameRate = getMaxFrameRateForCodec(codec); 1088 if (mMediaRecorder == null) { 1089 mMediaRecorder = new MediaRecorder(); 1090 } 1091 1092 if (!useSurface) { 1093 mCamera = Camera.open(0); 1094 Camera.Parameters params = mCamera.getParameters(); 1095 frameRate = params.getPreviewFrameRate(); 1096 setSupportedResolution(mCamera); 1097 mCamera.unlock(); 1098 mMediaRecorder.setCamera(mCamera); 1099 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 1100 } 1101 1102 mMediaRecorder.setVideoSource(useSurface ? 1103 MediaRecorder.VideoSource.SURFACE : MediaRecorder.VideoSource.CAMERA); 1104 1105 if (hasAudio) { 1106 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 1107 } 1108 1109 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 1110 mMediaRecorder.setOutputFile(filename); 1111 1112 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 1113 mMediaRecorder.setVideoFrameRate(frameRate); 1114 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 1115 1116 if (hasAudio) { 1117 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 1118 } 1119 } 1120 1121 private Surface tryGetSurface(boolean shouldThrow) throws Exception { 1122 Surface surface = null; 1123 try { 1124 surface = mMediaRecorder.getSurface(); 1125 assertFalse("failed to throw IllegalStateException", shouldThrow); 1126 } catch (IllegalStateException e) { 1127 assertTrue("threw unexpected exception: " + e, shouldThrow); 1128 } 1129 return surface; 1130 } 1131 1132 private boolean validateGetSurface(boolean useSurface) { 1133 Log.v(TAG,"validateGetSurface, useSurface=" + useSurface); 1134 if (!useSurface && !hasCamera()) { 1135 // pass if testing camera source but no hardware 1136 return true; 1137 } 1138 Surface surface = null; 1139 boolean success = true; 1140 try { 1141 setupRecorder(OUTPUT_PATH, useSurface, false /* hasAudio */); 1142 1143 /* Test: getSurface() before prepare() 1144 * should throw IllegalStateException 1145 */ 1146 surface = tryGetSurface(true /* shouldThow */); 1147 1148 mMediaRecorder.prepare(); 1149 1150 /* Test: getSurface() after prepare() 1151 * should succeed for surface source 1152 * should fail for camera source 1153 */ 1154 surface = tryGetSurface(!useSurface); 1155 1156 mMediaRecorder.start(); 1157 1158 /* Test: getSurface() after start() 1159 * should succeed for surface source 1160 * should fail for camera source 1161 */ 1162 surface = tryGetSurface(!useSurface); 1163 1164 try { 1165 mMediaRecorder.stop(); 1166 } catch (Exception e) { 1167 // stop() could fail if the recording is empty, as we didn't render anything. 1168 // ignore any failure in stop, we just want it stopped. 1169 } 1170 1171 /* Test: getSurface() after stop() 1172 * should throw IllegalStateException 1173 */ 1174 surface = tryGetSurface(true /* shouldThow */); 1175 } catch (Exception e) { 1176 Log.d(TAG, e.toString()); 1177 success = false; 1178 } finally { 1179 // reset to clear states, as stop() might have failed 1180 mMediaRecorder.reset(); 1181 1182 if (mCamera != null) { 1183 mCamera.release(); 1184 mCamera = null; 1185 } 1186 if (surface != null) { 1187 surface.release(); 1188 surface = null; 1189 } 1190 } 1191 1192 return success; 1193 } 1194 1195 private void trySetInputSurface(Surface surface) throws Exception { 1196 boolean testBadArgument = (surface == null); 1197 try { 1198 mMediaRecorder.setInputSurface(testBadArgument ? new Surface() : surface); 1199 fail("failed to throw exception"); 1200 } catch (IllegalArgumentException e) { 1201 // OK only if testing bad arg 1202 assertTrue("threw unexpected exception: " + e, testBadArgument); 1203 } catch (IllegalStateException e) { 1204 // OK only if testing error case other than bad arg 1205 assertFalse("threw unexpected exception: " + e, testBadArgument); 1206 } 1207 } 1208 1209 private boolean validatePersistentSurface(boolean errorCase) { 1210 Log.v(TAG, "validatePersistentSurface, errorCase=" + errorCase); 1211 1212 Surface surface = MediaCodec.createPersistentInputSurface(); 1213 if (surface == null) { 1214 return false; 1215 } 1216 Surface dummy = null; 1217 1218 boolean success = true; 1219 try { 1220 setupRecorder(OUTPUT_PATH, true /* useSurface */, false /* hasAudio */); 1221 1222 if (errorCase) { 1223 /* 1224 * Test: should throw if called with non-persistent surface 1225 */ 1226 trySetInputSurface(null); 1227 } else { 1228 /* 1229 * Test: should succeed if called with a persistent surface before prepare() 1230 */ 1231 mMediaRecorder.setInputSurface(surface); 1232 } 1233 1234 /* 1235 * Test: getSurface() should fail before prepare 1236 */ 1237 dummy = tryGetSurface(true /* shouldThow */); 1238 1239 mMediaRecorder.prepare(); 1240 1241 /* 1242 * Test: setInputSurface() should fail after prepare 1243 */ 1244 trySetInputSurface(surface); 1245 1246 /* 1247 * Test: getSurface() should fail if setInputSurface() succeeded 1248 */ 1249 dummy = tryGetSurface(!errorCase /* shouldThow */); 1250 1251 mMediaRecorder.start(); 1252 1253 /* 1254 * Test: setInputSurface() should fail after start 1255 */ 1256 trySetInputSurface(surface); 1257 1258 /* 1259 * Test: getSurface() should fail if setInputSurface() succeeded 1260 */ 1261 dummy = tryGetSurface(!errorCase /* shouldThow */); 1262 1263 try { 1264 mMediaRecorder.stop(); 1265 } catch (Exception e) { 1266 // stop() could fail if the recording is empty, as we didn't render anything. 1267 // ignore any failure in stop, we just want it stopped. 1268 } 1269 1270 /* 1271 * Test: getSurface() should fail after stop 1272 */ 1273 dummy = tryGetSurface(true /* shouldThow */); 1274 } catch (Exception e) { 1275 Log.d(TAG, e.toString()); 1276 success = false; 1277 } finally { 1278 // reset to clear states, as stop() might have failed 1279 mMediaRecorder.reset(); 1280 1281 if (mCamera != null) { 1282 mCamera.release(); 1283 mCamera = null; 1284 } 1285 if (surface != null) { 1286 surface.release(); 1287 surface = null; 1288 } 1289 if (dummy != null) { 1290 dummy.release(); 1291 dummy = null; 1292 } 1293 } 1294 1295 return success; 1296 } 1297 1298 public void testGetSurfaceApi() { 1299 if (!hasH264()) { 1300 MediaUtils.skipTest("no codecs"); 1301 return; 1302 } 1303 1304 if (hasCamera()) { 1305 // validate getSurface() with CAMERA source 1306 assertTrue(validateGetSurface(false /* useSurface */)); 1307 } 1308 1309 // validate getSurface() with SURFACE source 1310 assertTrue(validateGetSurface(true /* useSurface */)); 1311 } 1312 1313 public void testPersistentSurfaceApi() { 1314 if (!hasH264()) { 1315 MediaUtils.skipTest("no codecs"); 1316 return; 1317 } 1318 1319 // test valid use case 1320 assertTrue(validatePersistentSurface(false /* errorCase */)); 1321 1322 // test invalid use case 1323 assertTrue(validatePersistentSurface(true /* errorCase */)); 1324 } 1325 1326 private static int getMaxFrameRateForCodec(int codec) { 1327 for (VideoEncoderCap cap : mVideoEncoders) { 1328 if (cap.mCodec == codec) { 1329 return cap.mMaxFrameRate < NORMAL_FPS ? cap.mMaxFrameRate : NORMAL_FPS; 1330 } 1331 } 1332 fail("didn't find max FPS for codec"); 1333 return -1; 1334 } 1335 1336 private boolean recordFromSurface( 1337 String filename, 1338 int captureRate, 1339 boolean hasAudio, 1340 Surface persistentSurface) { 1341 Log.v(TAG, "recordFromSurface"); 1342 Surface surface = null; 1343 try { 1344 setupRecorder(filename, true /* useSurface */, hasAudio); 1345 1346 int sleepTimeMs; 1347 if (captureRate > 0) { 1348 mMediaRecorder.setCaptureRate(captureRate); 1349 sleepTimeMs = 1000 / captureRate; 1350 } else { 1351 sleepTimeMs = 1000 / getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264); 1352 } 1353 1354 if (persistentSurface != null) { 1355 Log.v(TAG, "using persistent surface"); 1356 surface = persistentSurface; 1357 mMediaRecorder.setInputSurface(surface); 1358 } 1359 1360 mMediaRecorder.prepare(); 1361 1362 if (persistentSurface == null) { 1363 surface = mMediaRecorder.getSurface(); 1364 } 1365 1366 Paint paint = new Paint(); 1367 paint.setTextSize(16); 1368 paint.setColor(Color.RED); 1369 int i; 1370 1371 /* Test: draw 10 frames at 30fps before start 1372 * these should be dropped and not causing malformed stream. 1373 */ 1374 for(i = 0; i < 10; i++) { 1375 Canvas canvas = surface.lockCanvas(null); 1376 int background = (i * 255 / 99); 1377 canvas.drawARGB(255, background, background, background); 1378 String text = "Frame #" + i; 1379 canvas.drawText(text, 50, 50, paint); 1380 surface.unlockCanvasAndPost(canvas); 1381 Thread.sleep(sleepTimeMs); 1382 } 1383 1384 Log.v(TAG, "start"); 1385 mMediaRecorder.start(); 1386 1387 /* Test: draw another 90 frames at 30fps after start */ 1388 for(i = 10; i < 100; i++) { 1389 Canvas canvas = surface.lockCanvas(null); 1390 int background = (i * 255 / 99); 1391 canvas.drawARGB(255, background, background, background); 1392 String text = "Frame #" + i; 1393 canvas.drawText(text, 50, 50, paint); 1394 surface.unlockCanvasAndPost(canvas); 1395 Thread.sleep(sleepTimeMs); 1396 } 1397 1398 Log.v(TAG, "stop"); 1399 mMediaRecorder.stop(); 1400 } catch (Exception e) { 1401 Log.v(TAG, "record video failed: " + e.toString()); 1402 return false; 1403 } finally { 1404 // We need to test persistent surface across multiple MediaRecorder 1405 // instances, so must destroy mMediaRecorder here. 1406 if (mMediaRecorder != null) { 1407 mMediaRecorder.release(); 1408 mMediaRecorder = null; 1409 } 1410 1411 // release surface if not using persistent surface 1412 if (persistentSurface == null && surface != null) { 1413 surface.release(); 1414 surface = null; 1415 } 1416 } 1417 return true; 1418 } 1419 1420 private boolean checkCaptureFps(String filename, int captureRate) { 1421 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 1422 1423 retriever.setDataSource(filename); 1424 1425 // verify capture rate meta key is present and correct 1426 String captureFps = retriever.extractMetadata( 1427 MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE); 1428 1429 if (captureFps == null) { 1430 Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is missing"); 1431 return false; 1432 } 1433 1434 if (Math.abs(Float.parseFloat(captureFps) - captureRate) > 0.001) { 1435 Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is incorrect: " 1436 + captureFps + "vs. " + captureRate); 1437 return false; 1438 } 1439 1440 // verify other meta keys here if necessary 1441 return true; 1442 } 1443 1444 private boolean testRecordFromSurface(boolean persistent, boolean timelapse) { 1445 Log.v(TAG, "testRecordFromSurface: " + 1446 "persistent=" + persistent + ", timelapse=" + timelapse); 1447 boolean success = false; 1448 Surface surface = null; 1449 int noOfFailure = 0; 1450 1451 if (!hasH264()) { 1452 MediaUtils.skipTest("no codecs"); 1453 return true; 1454 } 1455 1456 final float frameRate = getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264); 1457 1458 try { 1459 if (persistent) { 1460 surface = MediaCodec.createPersistentInputSurface(); 1461 } 1462 1463 for (int k = 0; k < 2; k++) { 1464 String filename = (k == 0) ? OUTPUT_PATH : OUTPUT_PATH2; 1465 boolean hasAudio = false; 1466 int captureRate = 0; 1467 1468 if (timelapse) { 1469 // if timelapse/slow-mo, k chooses between low/high capture fps 1470 captureRate = (k == 0) ? TIME_LAPSE_FPS : SLOW_MOTION_FPS; 1471 } else { 1472 // otherwise k chooses between no-audio and audio 1473 hasAudio = (k == 0) ? false : true; 1474 } 1475 1476 if (hasAudio && (!hasMicrophone() || !hasAmrNb())) { 1477 // audio test waived if no audio support 1478 continue; 1479 } 1480 1481 Log.v(TAG, "testRecordFromSurface - round " + k); 1482 success = recordFromSurface(filename, captureRate, hasAudio, surface); 1483 if (success) { 1484 checkTracksAndDuration(0, true /* hasVideo */, hasAudio, filename, frameRate); 1485 1486 // verify capture fps meta key 1487 if (timelapse && !checkCaptureFps(filename, captureRate)) { 1488 noOfFailure++; 1489 } 1490 } 1491 if (!success) { 1492 noOfFailure++; 1493 } 1494 } 1495 } catch (Exception e) { 1496 Log.v(TAG, e.toString()); 1497 noOfFailure++; 1498 } finally { 1499 if (surface != null) { 1500 Log.v(TAG, "releasing persistent surface"); 1501 surface.release(); 1502 surface = null; 1503 } 1504 } 1505 return (noOfFailure == 0); 1506 } 1507 1508 // Test recording from surface source with/without audio) 1509 public void testSurfaceRecording() { 1510 assertTrue(testRecordFromSurface(false /* persistent */, false /* timelapse */)); 1511 } 1512 1513 // Test recording from persistent surface source with/without audio 1514 public void testPersistentSurfaceRecording() { 1515 assertTrue(testRecordFromSurface(true /* persistent */, false /* timelapse */)); 1516 } 1517 1518 // Test timelapse recording from surface without audio 1519 public void testSurfaceRecordingTimeLapse() { 1520 assertTrue(testRecordFromSurface(false /* persistent */, true /* timelapse */)); 1521 } 1522 1523 // Test timelapse recording from persisent surface without audio 1524 public void testPersistentSurfaceRecordingTimeLapse() { 1525 assertTrue(testRecordFromSurface(true /* persistent */, true /* timelapse */)); 1526 } 1527 1528 private void recordMedia(long maxFileSize, File outFile) throws Exception { 1529 mMediaRecorder.setMaxFileSize(maxFileSize); 1530 mMediaRecorder.prepare(); 1531 mMediaRecorder.start(); 1532 Thread.sleep(RECORD_TIME_MS); 1533 mMediaRecorder.stop(); 1534 1535 assertTrue(outFile.exists()); 1536 1537 // The max file size is always guaranteed. 1538 // We just make sure that the margin is not too big 1539 assertTrue(outFile.length() < 1.1 * maxFileSize); 1540 assertTrue(outFile.length() > 0); 1541 } 1542 1543 private boolean hasCamera() { 1544 return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); 1545 } 1546 1547 private boolean hasMicrophone() { 1548 return mActivity.getPackageManager().hasSystemFeature( 1549 PackageManager.FEATURE_MICROPHONE); 1550 } 1551 1552 private static boolean hasAmrNb() { 1553 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB); 1554 } 1555 1556 private static boolean hasAmrWb() { 1557 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_WB); 1558 } 1559 1560 private static boolean hasAac() { 1561 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC); 1562 } 1563 1564 private static boolean hasH264() { 1565 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC); 1566 } 1567 1568 public void testSetCaptureRate() throws Exception { 1569 // No exception expected for 30fps 1570 mMediaRecorder.setCaptureRate(30.0); 1571 try { 1572 mMediaRecorder.setCaptureRate(-1.0); 1573 fail("Should fail setting negative fps"); 1574 } catch (Exception ex) { 1575 // expected 1576 } 1577 // No exception expected for 1/24hr 1578 mMediaRecorder.setCaptureRate(1.0 / 86400.0); 1579 try { 1580 mMediaRecorder.setCaptureRate(1.0 / 90000.0); 1581 fail("Should fail setting smaller fps than one frame per day"); 1582 } catch (Exception ex) { 1583 // expected 1584 } 1585 try { 1586 mMediaRecorder.setCaptureRate(0); 1587 fail("Should fail setting zero fps"); 1588 } catch (Exception ex) { 1589 // expected 1590 } 1591 } 1592 } 1593