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.recorder.cts; 17 18 import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel1; 19 import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel1b; 20 import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline; 21 import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileConstrainedBaseline; 22 import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileConstrainedHigh; 23 import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.assertTrue; 29 import static org.junit.Assert.fail; 30 31 import android.content.pm.PackageManager; 32 import android.graphics.Canvas; 33 import android.graphics.Color; 34 import android.graphics.Paint; 35 import android.hardware.Camera; 36 import android.media.AudioFormat; 37 import android.media.AudioManager; 38 import android.media.AudioRecordingConfiguration; 39 import android.media.CamcorderProfile; 40 import android.media.EncoderCapabilities; 41 import android.media.EncoderCapabilities.VideoEncoderCap; 42 import android.media.MediaCodec; 43 import android.media.MediaCodecInfo; 44 import android.media.MediaCodecInfo.CodecCapabilities; 45 import android.media.MediaCodecList; 46 import android.media.MediaExtractor; 47 import android.media.MediaFormat; 48 import android.media.MediaMetadataRetriever; 49 import android.media.MediaRecorder; 50 import android.media.MediaRecorder.OnErrorListener; 51 import android.media.MediaRecorder.OnInfoListener; 52 import android.media.MicrophoneDirection; 53 import android.media.MicrophoneInfo; 54 import android.media.cts.InputSurface; 55 import android.media.cts.MediaTestBase; 56 import android.media.metrics.LogSessionId; 57 import android.media.metrics.MediaMetricsManager; 58 import android.media.metrics.RecordingSession; 59 import android.opengl.GLES20; 60 import android.os.Build; 61 import android.os.ConditionVariable; 62 import android.os.Environment; 63 import android.os.ParcelFileDescriptor; 64 import android.os.PersistableBundle; 65 import android.os.SystemProperties; 66 import android.platform.test.annotations.AppModeFull; 67 import android.platform.test.annotations.RequiresDevice; 68 import android.util.Log; 69 import android.view.Surface; 70 71 import androidx.test.InstrumentationRegistry; 72 import androidx.test.annotation.UiThreadTest; 73 import androidx.test.ext.junit.runners.AndroidJUnit4; 74 import androidx.test.filters.SmallTest; 75 76 import com.android.compatibility.common.util.ApiLevelUtil; 77 import com.android.compatibility.common.util.CddTest; 78 import com.android.compatibility.common.util.MediaUtils; 79 80 import org.junit.After; 81 import org.junit.Before; 82 import org.junit.Test; 83 import org.junit.runner.RunWith; 84 85 import java.io.File; 86 import java.io.FileDescriptor; 87 import java.io.FileOutputStream; 88 import java.io.IOException; 89 import java.io.RandomAccessFile; 90 import java.util.ArrayList; 91 import java.util.List; 92 import java.util.Set; 93 import java.util.concurrent.CountDownLatch; 94 import java.util.concurrent.Executor; 95 import java.util.concurrent.TimeUnit; 96 97 @SmallTest 98 @RequiresDevice 99 @AppModeFull(reason = "TODO: evaluate and port to instant") 100 @RunWith(AndroidJUnit4.class) 101 public class MediaRecorderTest extends MediaTestBase { 102 private final String TAG = "MediaRecorderTest"; 103 private final String OUTPUT_PATH; 104 private final String OUTPUT_PATH2; 105 private static final float TOLERANCE = 0.0002f; 106 private static final int RECORD_TIME_MS = 3000; 107 private static final int RECORD_TIME_LAPSE_MS = 6000; 108 private static final int RECORD_TIME_LONG_MS = 20000; 109 private static final int RECORDED_DUR_TOLERANCE_MS = 1000; 110 private static final int TEST_TIMING_TOLERANCE_MS = 70; 111 // Tolerate 4 frames off at maximum 112 private static final float RECORDED_DUR_TOLERANCE_FRAMES = 4f; 113 private static final int VIDEO_WIDTH = 176; 114 private static final int VIDEO_HEIGHT = 144; 115 private static int mVideoWidth = VIDEO_WIDTH; 116 private static int mVideoHeight = VIDEO_HEIGHT; 117 private static final int VIDEO_BIT_RATE_IN_BPS = 128000; 118 private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0; 119 private static final int AUDIO_BIT_RATE_IN_BPS = 12200; 120 private static final int AUDIO_NUM_CHANNELS = 1; 121 private static final int AUDIO_SAMPLE_RATE_HZ = 8000; 122 private static final long MAX_FILE_SIZE = 5000; 123 private static final int MAX_FILE_SIZE_TIMEOUT_MS = 5 * 60 * 1000; 124 private static final int MAX_DURATION_MSEC = 2000; 125 private static final float LATITUDE = 0.0000f; 126 private static final float LONGITUDE = -180.0f; 127 private static final int NORMAL_FPS = 30; 128 private static final int TIME_LAPSE_FPS = 5; 129 private static final int SLOW_MOTION_FPS = 120; 130 // limiting the ro.hw_timeout_multiplier to 6 to accommodate slower devices 131 private static final int HW_TIMEOUT_MULTIPLIER = Math.min(6, 132 SystemProperties.getInt("ro.hw_timeout_multiplier", 1)); 133 private static final List<VideoEncoderCap> mVideoEncoders = 134 EncoderCapabilities.getVideoEncoders(); 135 136 private boolean mOnInfoCalled; 137 private boolean mOnErrorCalled; 138 private File mOutFile; 139 private File mOutFile2; 140 private Camera mCamera; 141 private int mFileIndex; 142 143 private MediaRecorder mMediaRecorder; 144 private ConditionVariable mMaxDurationCond; 145 private ConditionVariable mMaxFileSizeCond; 146 private ConditionVariable mMaxFileSizeApproachingCond; 147 private ConditionVariable mNextOutputFileStartedCond; 148 private boolean mExpectMaxFileCond; 149 150 // movie length, in frames 151 private static final int NUM_FRAMES = 120; 152 153 private static final int TEST_R0 = 0; // RGB equivalent of {0,0,0} (BT.601) 154 private static final int TEST_G0 = 136; 155 private static final int TEST_B0 = 0; 156 private static final int TEST_R1 = 236; // RGB equivalent of {120,160,200} (BT.601) 157 private static final int TEST_G1 = 50; 158 private static final int TEST_B1 = 186; 159 160 private final static String AVC = MediaFormat.MIMETYPE_VIDEO_AVC; 161 162 private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R); 163 private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S); 164 MediaRecorderTest()165 public MediaRecorderTest() { 166 OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(), 167 "record.out").getAbsolutePath(); 168 OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(), 169 "record2.out").getAbsolutePath(); 170 } 171 completeOnUiThread(final Runnable runnable)172 private void completeOnUiThread(final Runnable runnable) { 173 final CountDownLatch latch = new CountDownLatch(1); 174 getActivity().runOnUiThread(new Runnable() { 175 @Override 176 public void run() { 177 runnable.run(); 178 latch.countDown(); 179 } 180 }); 181 try { 182 // if UI thread does not run, things will fail anyway 183 assertTrue(latch.await(10, TimeUnit.SECONDS)); 184 } catch (java.lang.InterruptedException e) { 185 fail("should not be interrupted"); 186 } 187 } 188 189 @Before 190 @Override setUp()191 public void setUp() throws Throwable { 192 super.setUp(); 193 completeOnUiThread(new Runnable() { 194 @Override 195 public void run() { 196 mMediaRecorder = new MediaRecorder(); 197 mOutFile = new File(OUTPUT_PATH); 198 mOutFile2 = new File(OUTPUT_PATH2); 199 mFileIndex = 0; 200 201 mMaxDurationCond = new ConditionVariable(); 202 mMaxFileSizeCond = new ConditionVariable(); 203 mMaxFileSizeApproachingCond = new ConditionVariable(); 204 mNextOutputFileStartedCond = new ConditionVariable(); 205 mExpectMaxFileCond = true; 206 207 mMediaRecorder.setOutputFile(OUTPUT_PATH); 208 mMediaRecorder.setOnInfoListener(new OnInfoListener() { 209 public void onInfo(MediaRecorder mr, int what, int extra) { 210 mOnInfoCalled = true; 211 if (what == 212 MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 213 Log.v(TAG, "max duration reached"); 214 mMaxDurationCond.open(); 215 } else if (what == 216 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 217 Log.v(TAG, "max file size reached"); 218 mMaxFileSizeCond.open(); 219 } 220 } 221 }); 222 mMediaRecorder.setOnErrorListener(new OnErrorListener() { 223 public void onError(MediaRecorder mr, int what, int extra) { 224 mOnErrorCalled = true; 225 } 226 }); 227 } 228 }); 229 } 230 231 @After 232 @Override tearDown()233 public void tearDown() { 234 if (mMediaRecorder != null) { 235 mMediaRecorder.release(); 236 mMediaRecorder = null; 237 } 238 if (mOutFile != null && mOutFile.exists()) { 239 mOutFile.delete(); 240 } 241 if (mOutFile2 != null && mOutFile2.exists()) { 242 mOutFile2.delete(); 243 } 244 if (mCamera != null) { 245 mCamera.release(); 246 mCamera = null; 247 } 248 mMaxDurationCond.close(); 249 mMaxDurationCond = null; 250 mMaxFileSizeCond.close(); 251 mMaxFileSizeCond = null; 252 mMaxFileSizeApproachingCond.close(); 253 mMaxFileSizeApproachingCond = null; 254 mNextOutputFileStartedCond.close(); 255 mNextOutputFileStartedCond = null; 256 mActivity = null; 257 super.tearDown(); 258 } 259 260 @Test testRecorderCamera()261 public void testRecorderCamera() throws Exception { 262 int width; 263 int height; 264 Camera camera = null; 265 if (!hasCamera()) { 266 MediaUtils.skipTest("no camera"); 267 return; 268 } 269 // Try to get camera profile for QUALITY_LOW; if unavailable, 270 // set the video size to default value. 271 CamcorderProfile profile = CamcorderProfile.get( 272 0 /* cameraId */, CamcorderProfile.QUALITY_LOW); 273 if (profile != null) { 274 width = profile.videoFrameWidth; 275 height = profile.videoFrameHeight; 276 } else { 277 width = VIDEO_WIDTH; 278 height = VIDEO_HEIGHT; 279 } 280 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 281 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 282 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 283 mMediaRecorder.setVideoSize(width, height); 284 mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS); 285 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 286 mMediaRecorder.prepare(); 287 mMediaRecorder.start(); 288 Thread.sleep(RECORD_TIME_MS); 289 290 291 // verify some getMetrics() behaviors while we're here. 292 PersistableBundle metrics = mMediaRecorder.getMetrics(); 293 if (metrics == null) { 294 fail("MediaRecorder.getMetrics() returned null metrics"); 295 } else if (metrics.isEmpty()) { 296 fail("MediaRecorder.getMetrics() returned empty metrics"); 297 } else { 298 int size = metrics.size(); 299 Set<String> keys = metrics.keySet(); 300 301 if (size == 0) { 302 fail("MediaRecorder.getMetrics().size() reports empty record"); 303 } 304 305 if (keys == null) { 306 fail("MediaMetricsSet returned no keys"); 307 } else if (keys.size() != size) { 308 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()"); 309 } 310 311 // ensure existence of some known fields 312 int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1); 313 if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) { 314 fail("getMetrics() videoEncodeBitrate set " + 315 VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate); 316 } 317 318 // valid values are -1.0 and >= 0; 319 // tolerate some floating point rounding variability 320 double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2); 321 if (captureFrameRate < 0.) { 322 assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001); 323 } 324 } 325 326 327 mMediaRecorder.stop(); 328 checkOutputExist(); 329 } 330 331 @Test testRecorderMPEG2TS()332 public void testRecorderMPEG2TS() throws Exception { 333 int width; 334 int height; 335 Camera camera = null; 336 if (!hasCamera()) { 337 MediaUtils.skipTest("no camera"); 338 return; 339 } 340 if (!hasMicrophone() || !hasAac()) { 341 MediaUtils.skipTest("no audio codecs or microphone"); 342 return; 343 } 344 // Try to get camera profile for QUALITY_LOW; if unavailable, 345 // set the video size to default value. 346 CamcorderProfile profile = CamcorderProfile.get( 347 0 /* cameraId */, CamcorderProfile.QUALITY_LOW); 348 if (profile != null) { 349 width = profile.videoFrameWidth; 350 height = profile.videoFrameHeight; 351 } else { 352 width = VIDEO_WIDTH; 353 height = VIDEO_HEIGHT; 354 } 355 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 356 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 357 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS); 358 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 359 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 360 mMediaRecorder.setVideoSize(width, height); 361 mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS); 362 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 363 mMediaRecorder.prepare(); 364 mMediaRecorder.start(); 365 Thread.sleep(RECORD_TIME_MS); 366 367 // verify some getMetrics() behaviors while we're here. 368 PersistableBundle metrics = mMediaRecorder.getMetrics(); 369 if (metrics == null) { 370 fail("MediaRecorder.getMetrics() returned null metrics"); 371 } else if (metrics.isEmpty()) { 372 fail("MediaRecorder.getMetrics() returned empty metrics"); 373 } else { 374 int size = metrics.size(); 375 Set<String> keys = metrics.keySet(); 376 377 if (size == 0) { 378 fail("MediaRecorder.getMetrics().size() reports empty record"); 379 } 380 381 if (keys == null) { 382 fail("MediaMetricsSet returned no keys"); 383 } else if (keys.size() != size) { 384 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()"); 385 } 386 387 // ensure existence of some known fields 388 int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1); 389 if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) { 390 fail("getMetrics() videoEncodeBitrate set " + 391 VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate); 392 } 393 394 // valid values are -1.0 and >= 0; 395 // tolerate some floating point rounding variability 396 double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2); 397 if (captureFrameRate < 0.) { 398 assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001); 399 } 400 } 401 402 mMediaRecorder.stop(); 403 checkOutputExist(); 404 } 405 406 @Test 407 @UiThreadTest testSetCamera()408 public void testSetCamera() throws Exception { 409 recordVideoUsingCamera(false, false); 410 } 411 412 @Test testRecorderTimelapsedVideo()413 public void testRecorderTimelapsedVideo() throws Exception { 414 recordVideoUsingCamera(true, false); 415 } 416 417 @Test testRecorderPauseResume()418 public void testRecorderPauseResume() throws Exception { 419 recordVideoUsingCamera(false, true); 420 } 421 422 @Test testRecorderPauseResumeOnTimeLapse()423 public void testRecorderPauseResumeOnTimeLapse() throws Exception { 424 recordVideoUsingCamera(true, true); 425 } 426 recordVideoUsingCamera(boolean timelapse, boolean pause)427 private void recordVideoUsingCamera(boolean timelapse, boolean pause) throws Exception { 428 int nCamera = Camera.getNumberOfCameras(); 429 int durMs = timelapse? RECORD_TIME_LAPSE_MS: RECORD_TIME_MS; 430 for (int cameraId = 0; cameraId < nCamera; cameraId++) { 431 mCamera = Camera.open(cameraId); 432 setSupportedResolution(mCamera); 433 recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse, pause); 434 mCamera.release(); 435 mCamera = null; 436 assertTrue(checkLocationInFile(OUTPUT_PATH)); 437 } 438 } 439 setSupportedResolution(Camera camera)440 private void setSupportedResolution(Camera camera) { 441 Camera.Parameters parameters = camera.getParameters(); 442 List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes(); 443 // getSupportedVideoSizes returns null when separate video/preview size 444 // is not supported. 445 if (videoSizes == null) { 446 videoSizes = parameters.getSupportedPreviewSizes(); 447 } 448 int minVideoWidth = Integer.MAX_VALUE; 449 int minVideoHeight = Integer.MAX_VALUE; 450 for (Camera.Size size : videoSizes) 451 { 452 if (size.width == VIDEO_WIDTH && size.height == VIDEO_HEIGHT) { 453 mVideoWidth = VIDEO_WIDTH; 454 mVideoHeight = VIDEO_HEIGHT; 455 return; 456 } 457 if (size.width < minVideoWidth || size.height < minVideoHeight) { 458 minVideoWidth = size.width; 459 minVideoHeight = size.height; 460 } 461 } 462 // Use minimum resolution to avoid that one frame size exceeds file size limit. 463 mVideoWidth = minVideoWidth; 464 mVideoHeight = minVideoHeight; 465 } 466 recordVideoUsingCamera( Camera camera, String fileName, int durMs, boolean timelapse, boolean pause)467 private void recordVideoUsingCamera( 468 Camera camera, String fileName, int durMs, boolean timelapse, boolean pause) 469 throws Exception { 470 // FIXME: 471 // We should add some test case to use Camera.Parameters.getPreviewFpsRange() 472 // to get the supported video frame rate range. 473 Camera.Parameters params = camera.getParameters(); 474 int frameRate = params.getPreviewFrameRate(); 475 476 camera.unlock(); 477 mMediaRecorder.setCamera(camera); 478 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 479 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 480 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 481 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 482 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 483 mMediaRecorder.setVideoFrameRate(frameRate); 484 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 485 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 486 mMediaRecorder.setOutputFile(fileName); 487 mMediaRecorder.setLocation(LATITUDE, LONGITUDE); 488 final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS; 489 if (timelapse) { 490 mMediaRecorder.setCaptureRate(captureRate); 491 } 492 493 mMediaRecorder.prepare(); 494 mMediaRecorder.start(); 495 if (pause) { 496 Thread.sleep(durMs / 2); 497 mMediaRecorder.pause(); 498 Thread.sleep(durMs / 2); 499 mMediaRecorder.resume(); 500 Thread.sleep(durMs / 2); 501 } else { 502 Thread.sleep(durMs); 503 } 504 mMediaRecorder.stop(); 505 assertTrue(mOutFile.exists()); 506 507 int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs; 508 boolean hasVideo = true; 509 boolean hasAudio = timelapse? false: true; 510 checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName, frameRate); 511 } 512 checkTracksAndDuration( int targetMs, boolean hasVideo, boolean hasAudio, String fileName, float frameRate)513 private void checkTracksAndDuration( 514 int targetMs, boolean hasVideo, boolean hasAudio, String fileName, 515 float frameRate) throws Exception { 516 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 517 retriever.setDataSource(fileName); 518 String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO); 519 String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO); 520 assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null); 521 assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null); 522 // FIXME: 523 // If we could use fixed frame rate for video recording, we could also do more accurate 524 // check on the duration. 525 String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 526 assertTrue(durStr != null); 527 int duration = Integer.parseInt(durStr); 528 assertTrue("duration is non-positive: dur = " + duration, duration > 0); 529 if (targetMs != 0) { 530 float toleranceMs = RECORDED_DUR_TOLERANCE_FRAMES * (1000f / frameRate); 531 assertTrue(String.format("duration is too large: dur = %d, target = %d, tolerance = %f", 532 duration, targetMs, toleranceMs), 533 duration <= targetMs + toleranceMs); 534 } 535 536 retriever.release(); 537 retriever = null; 538 } 539 checkLocationInFile(String fileName)540 private boolean checkLocationInFile(String fileName) throws IOException { 541 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 542 retriever.setDataSource(fileName); 543 String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); 544 if (location == null) { 545 retriever.release(); 546 Log.v(TAG, "No location information found in file " + fileName); 547 return false; 548 } 549 550 // parsing String location and recover the location inforamtion in floats 551 // Make sure the tolerance is very small - due to rounding errors?. 552 Log.v(TAG, "location: " + location); 553 554 // Trim the trailing slash, if any. 555 int lastIndex = location.lastIndexOf('/'); 556 if (lastIndex != -1) { 557 location = location.substring(0, lastIndex); 558 } 559 560 // Get the position of the -/+ sign in location String, which indicates 561 // the beginning of the longtitude. 562 int index = location.lastIndexOf('-'); 563 if (index == -1) { 564 index = location.lastIndexOf('+'); 565 } 566 assertTrue("+ or - is not found", index != -1); 567 assertTrue("+ or - is only found at the beginning", index != 0); 568 float latitude = Float.parseFloat(location.substring(0, index)); 569 float longitude = Float.parseFloat(location.substring(index)); 570 assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE); 571 assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE); 572 retriever.release(); 573 return true; 574 } 575 checkOutputExist()576 private void checkOutputExist() { 577 assertTrue(mOutFile.exists()); 578 assertTrue(mOutFile.length() > 0); 579 assertTrue(mOutFile.delete()); 580 } 581 582 @Test testRecorderVideo()583 public void testRecorderVideo() throws Exception { 584 if (!hasCamera()) { 585 MediaUtils.skipTest("no camera"); 586 return; 587 } 588 mCamera = Camera.open(0); 589 setSupportedResolution(mCamera); 590 mCamera.unlock(); 591 592 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 593 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 594 mMediaRecorder.setOutputFile(OUTPUT_PATH2); 595 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 596 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 597 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 598 599 FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2); 600 FileDescriptor fd = fos.getFD(); 601 mMediaRecorder.setOutputFile(fd); 602 long maxFileSize = mVideoHeight >= 720 ? MAX_FILE_SIZE * 50 : MAX_FILE_SIZE * 10; 603 recordMedia(maxFileSize, mOutFile2); 604 assertFalse(checkLocationInFile(OUTPUT_PATH2)); 605 fos.close(); 606 } 607 608 @Test testSetOutputFile()609 public void testSetOutputFile() throws Exception { 610 if (!hasCamera()) { 611 MediaUtils.skipTest("no camera"); 612 return; 613 } 614 615 int width; 616 int height; 617 618 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 619 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 620 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 621 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 622 // Try to get camera profile for QUALITY_LOW; if unavailable, 623 // set the video size to default value. 624 CamcorderProfile profile = CamcorderProfile.get( 625 0 /* cameraId */, CamcorderProfile.QUALITY_LOW); 626 if (profile != null) { 627 width = profile.videoFrameWidth; 628 height = profile.videoFrameHeight; 629 } else { 630 width = VIDEO_WIDTH; 631 height = VIDEO_HEIGHT; 632 } 633 mMediaRecorder.setVideoSize(width, height); 634 mMediaRecorder.setOutputFile(mOutFile); 635 636 // For some devices, the QUALITY_LOW profile can be as high as 720p. In that case use 637 // the bigger max file size to make sure the file will be able to contain at least single 638 // valid frame. 639 long maxFileSize = height >= 720 ? MAX_FILE_SIZE * 50 : MAX_FILE_SIZE * 10; 640 recordMedia(maxFileSize, mOutFile); 641 } 642 643 @Test testRecordingAudioInRawFormats()644 public void testRecordingAudioInRawFormats() throws Exception { 645 int testsRun = 0; 646 if (hasAmrNb()) { 647 testsRun += testRecordAudioInRawFormat( 648 MediaRecorder.OutputFormat.AMR_NB, 649 MediaRecorder.AudioEncoder.AMR_NB); 650 } 651 652 if (hasAmrWb()) { 653 testsRun += testRecordAudioInRawFormat( 654 MediaRecorder.OutputFormat.AMR_WB, 655 MediaRecorder.AudioEncoder.AMR_WB); 656 } 657 658 if (hasAac()) { 659 testsRun += testRecordAudioInRawFormat( 660 MediaRecorder.OutputFormat.AAC_ADTS, 661 MediaRecorder.AudioEncoder.AAC); 662 } 663 if (testsRun == 0) { 664 MediaUtils.skipTest("no audio codecs or microphone"); 665 } 666 } 667 testRecordAudioInRawFormat( int fileFormat, int codec)668 private int testRecordAudioInRawFormat( 669 int fileFormat, int codec) throws Exception { 670 if (!hasMicrophone()) { 671 return 0; // skip 672 } 673 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 674 mMediaRecorder.setOutputFormat(fileFormat); 675 mMediaRecorder.setOutputFile(OUTPUT_PATH); 676 mMediaRecorder.setAudioEncoder(codec); 677 recordMedia(MAX_FILE_SIZE, mOutFile); 678 return 1; 679 } 680 configureDefaultMediaRecorder()681 private void configureDefaultMediaRecorder() { 682 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 683 mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS); 684 mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ); 685 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 686 mMediaRecorder.setOutputFile(OUTPUT_PATH); 687 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 688 mMediaRecorder.setMaxFileSize(MAX_FILE_SIZE * 10); 689 } 690 691 @Test 692 @CddTest(requirement="5.4.1/C-1-4") testGetActiveMicrophones()693 public void testGetActiveMicrophones() throws Exception { 694 if (!hasMicrophone() || !hasAac()) { 695 MediaUtils.skipTest("no audio codecs or microphone"); 696 return; 697 } 698 configureDefaultMediaRecorder(); 699 mMediaRecorder.prepare(); 700 mMediaRecorder.start(); 701 Thread.sleep(1000); 702 List<MicrophoneInfo> activeMicrophones = mMediaRecorder.getActiveMicrophones(); 703 assertTrue(activeMicrophones.size() > 0); 704 for (MicrophoneInfo activeMicrophone : activeMicrophones) { 705 printMicrophoneInfo(activeMicrophone); 706 } 707 mMediaRecorder.stop(); 708 } 709 printMicrophoneInfo(MicrophoneInfo microphone)710 private void printMicrophoneInfo(MicrophoneInfo microphone) { 711 Log.i(TAG, "deviceId:" + microphone.getDescription()); 712 Log.i(TAG, "portId:" + microphone.getId()); 713 Log.i(TAG, "type:" + microphone.getType()); 714 Log.i(TAG, "address:" + microphone.getAddress()); 715 Log.i(TAG, "deviceLocation:" + microphone.getLocation()); 716 Log.i(TAG, "deviceGroup:" + microphone.getGroup() 717 + " index:" + microphone.getIndexInTheGroup()); 718 MicrophoneInfo.Coordinate3F position = microphone.getPosition(); 719 Log.i(TAG, "position:" + position.x + "," + position.y + "," + position.z); 720 MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation(); 721 Log.i(TAG, "orientation:" + orientation.x + "," + orientation.y + "," + orientation.z); 722 Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse()); 723 Log.i(TAG, "channelMapping:" + microphone.getChannelMapping()); 724 Log.i(TAG, "sensitivity:" + microphone.getSensitivity()); 725 Log.i(TAG, "max spl:" + microphone.getMaxSpl()); 726 Log.i(TAG, "min spl:" + microphone.getMinSpl()); 727 Log.i(TAG, "directionality:" + microphone.getDirectionality()); 728 Log.i(TAG, "******"); 729 } 730 731 @Test testRecordAudioFromAudioSourceUnprocessed()732 public void testRecordAudioFromAudioSourceUnprocessed() throws Exception { 733 if (!hasMicrophone() || !hasAmrNb()) { 734 MediaUtils.skipTest("no audio codecs or microphone"); 735 return; 736 } 737 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.UNPROCESSED); 738 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); 739 mMediaRecorder.setOutputFile(OUTPUT_PATH); 740 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); 741 recordMedia(MAX_FILE_SIZE, mOutFile); 742 } 743 744 @Test testGetAudioSourceMax()745 public void testGetAudioSourceMax() throws Exception { 746 final int max = MediaRecorder.getAudioSourceMax(); 747 assertTrue(MediaRecorder.AudioSource.DEFAULT <= max); 748 assertTrue(MediaRecorder.AudioSource.MIC <= max); 749 assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max); 750 assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max); 751 assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max); 752 assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max); 753 assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max); 754 assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max); 755 assertTrue(MediaRecorder.AudioSource.UNPROCESSED <= max); 756 assertTrue(MediaRecorder.AudioSource.VOICE_PERFORMANCE <= max); 757 } 758 759 @Test testRecorderAudio()760 public void testRecorderAudio() throws Exception { 761 if (!hasMicrophone() || !hasAac()) { 762 MediaUtils.skipTest("no audio codecs or microphone"); 763 return; 764 } 765 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 766 assertEquals(0, mMediaRecorder.getMaxAmplitude()); 767 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 768 mMediaRecorder.setOutputFile(OUTPUT_PATH); 769 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 770 mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS); 771 mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ); 772 mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS); 773 recordMedia(MAX_FILE_SIZE, mOutFile); 774 } 775 776 @Test testOnInfoListener()777 public void testOnInfoListener() throws Exception { 778 if (!hasMicrophone() || !hasAac()) { 779 MediaUtils.skipTest("no audio codecs or microphone"); 780 return; 781 } 782 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 783 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 784 mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC); 785 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 786 mMediaRecorder.prepare(); 787 mMediaRecorder.start(); 788 Thread.sleep(RECORD_TIME_MS); 789 assertTrue(mOnInfoCalled); 790 } 791 792 @Test testSetMaxDuration()793 public void testSetMaxDuration() throws Exception { 794 if (!hasMicrophone() || !hasAac()) { 795 MediaUtils.skipTest("no audio codecs or microphone"); 796 return; 797 } 798 testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS * HW_TIMEOUT_MULTIPLIER); 799 } 800 testSetMaxDuration(long durationMs, long toleranceMs)801 private void testSetMaxDuration(long durationMs, long toleranceMs) throws Exception { 802 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 803 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 804 mMediaRecorder.setMaxDuration((int)durationMs); 805 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 806 mMediaRecorder.prepare(); 807 mMediaRecorder.start(); 808 long startTimeMs = System.currentTimeMillis(); 809 if (!mMaxDurationCond.block(durationMs + toleranceMs)) { 810 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_DURATION_REACHED"); 811 } 812 long endTimeMs = System.currentTimeMillis(); 813 long actualDurationMs = endTimeMs - startTimeMs; 814 mMediaRecorder.stop(); 815 checkRecordedTime(durationMs, actualDurationMs, toleranceMs); 816 } 817 checkRecordedTime(long expectedMs, long actualMs, long tolerance)818 private void checkRecordedTime(long expectedMs, long actualMs, long tolerance) { 819 assertEquals(expectedMs, actualMs, tolerance); 820 long actualFileDurationMs = getRecordedFileDurationMs(OUTPUT_PATH); 821 assertEquals(actualFileDurationMs, actualMs, tolerance); 822 } 823 getRecordedFileDurationMs(final String fileName)824 private int getRecordedFileDurationMs(final String fileName) { 825 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 826 retriever.setDataSource(fileName); 827 String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); 828 assertNotNull(durationStr); 829 return Integer.parseInt(durationStr); 830 } 831 832 @Test testSetMaxFileSize()833 public void testSetMaxFileSize() throws Exception { 834 testSetMaxFileSize(512 * 1024, 50 * 1024); 835 } 836 testSetMaxFileSize( long fileSize, long tolerance)837 private void testSetMaxFileSize( 838 long fileSize, long tolerance) throws Exception { 839 if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) { 840 MediaUtils.skipTest("no microphone, camera, or codecs"); 841 return; 842 } 843 mCamera = Camera.open(0); 844 setSupportedResolution(mCamera); 845 mCamera.unlock(); 846 847 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 848 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 849 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 850 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 851 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 852 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 853 mMediaRecorder.setVideoEncodingBitRate(256000); 854 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 855 mMediaRecorder.setMaxFileSize(fileSize); 856 mMediaRecorder.prepare(); 857 mMediaRecorder.start(); 858 859 // Recording a scene with moving objects would greatly help reduce 860 // the time for waiting. 861 if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 862 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 863 } 864 mMediaRecorder.stop(); 865 checkOutputFileSize(OUTPUT_PATH, fileSize, tolerance); 866 } 867 868 /** 869 * Returns the first codec capable of encoding the specified MIME type, or null if no 870 * match was found. 871 */ getCapsForPreferredCodecForMediaType(String mimeType)872 private static CodecCapabilities getCapsForPreferredCodecForMediaType(String mimeType) { 873 // FIXME: select codecs based on the complete use-case, not just the mime 874 MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); 875 for (MediaCodecInfo info : mcl.getCodecInfos()) { 876 if (!info.isEncoder()) { 877 continue; 878 } 879 880 String[] types = info.getSupportedTypes(); 881 for (int j = 0; j < types.length; j++) { 882 if (types[j].equalsIgnoreCase(mimeType)) { 883 return info.getCapabilitiesForType(mimeType); 884 } 885 } 886 } 887 return null; 888 } 889 890 /** 891 * Generates a frame of data using GL commands. 892 */ generateSurfaceFrame(int frameIndex, int width, int height)893 private void generateSurfaceFrame(int frameIndex, int width, int height) { 894 frameIndex %= 8; 895 896 int startX, startY; 897 if (frameIndex < 4) { 898 // (0,0) is bottom-left in GL 899 startX = frameIndex * (width / 4); 900 startY = height / 2; 901 } else { 902 startX = (7 - frameIndex) * (width / 4); 903 startY = 0; 904 } 905 906 GLES20.glDisable(GLES20.GL_SCISSOR_TEST); 907 GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f); 908 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 909 GLES20.glEnable(GLES20.GL_SCISSOR_TEST); 910 GLES20.glScissor(startX, startY, width / 4, height / 2); 911 GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f); 912 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 913 } 914 915 /** 916 * Generates the presentation time for frame N, in microseconds. 917 */ computePresentationTime( long startTimeOffset, int frameIndex, int frameRate)918 private static long computePresentationTime( 919 long startTimeOffset, int frameIndex, int frameRate) { 920 return startTimeOffset + frameIndex * 1000000 / frameRate; 921 } 922 testLevel(String mediaType, int width, int height, int framerate, int bitrate, int profile, int requestedLevel, int... expectedLevels)923 private int testLevel(String mediaType, int width, int height, int framerate, int bitrate, 924 int profile, int requestedLevel, int... expectedLevels) throws Exception { 925 CodecCapabilities cap = getCapsForPreferredCodecForMediaType(mediaType); 926 if (cap == null) { // not supported 927 return 0; 928 } 929 MediaCodecInfo.VideoCapabilities vCap = cap.getVideoCapabilities(); 930 if (!vCap.areSizeAndRateSupported(width, height, framerate) 931 || !vCap.getBitrateRange().contains(bitrate * 1000)) { 932 Log.i(TAG, "Skip the test"); 933 return 0; 934 } 935 936 Surface surface = MediaCodec.createPersistentInputSurface(); 937 if (surface == null) { 938 return 0; 939 } 940 InputSurface encSurface = new InputSurface(surface); 941 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 942 mMediaRecorder.setInputSurface(encSurface.getSurface()); 943 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 944 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 945 mMediaRecorder.setOutputFile(mOutFile); 946 947 try { 948 mMediaRecorder.setVideoEncodingProfileLevel(-1, requestedLevel); 949 fail("Expect IllegalArgumentException."); 950 } catch (IllegalArgumentException e) { 951 // Expect exception. 952 } 953 try { 954 mMediaRecorder.setVideoEncodingProfileLevel(profile, -1); 955 fail("Expect IllegalArgumentException."); 956 } catch (IllegalArgumentException e) { 957 // Expect exception. 958 } 959 960 mMediaRecorder.setVideoEncodingProfileLevel(profile, requestedLevel); 961 mMediaRecorder.setVideoSize(width, height); 962 mMediaRecorder.setVideoEncodingBitRate(bitrate * 1000); 963 mMediaRecorder.setVideoFrameRate(framerate); 964 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 965 mMediaRecorder.prepare(); 966 encSurface.updateSize(width, height); 967 mMediaRecorder.start(); 968 969 970 long startNsec = System.nanoTime(); 971 long startTimeOffset = 3000 / framerate; 972 for (int i = 0; i < NUM_FRAMES; i++) { 973 encSurface.makeCurrent(); 974 generateSurfaceFrame(i, width, height); 975 long time = startNsec + 976 computePresentationTime(startTimeOffset, i, framerate) * 1000; 977 encSurface.setPresentationTime(time); 978 encSurface.swapBuffers(); 979 } 980 981 mMediaRecorder.stop(); 982 983 assertTrue(mOutFile.exists()); 984 assertTrue(mOutFile.length() > 0); 985 986 // Verify the recorded file profile/level, 987 MediaExtractor ex = new MediaExtractor(); 988 ex.setDataSource(OUTPUT_PATH); 989 for (int i = 0; i < ex.getTrackCount(); i++) { 990 MediaFormat format = ex.getTrackFormat(i); 991 String mime = format.getString(MediaFormat.KEY_MIME); 992 if (mime.startsWith("video/")) { 993 int finalProfile = format.getInteger(MediaFormat.KEY_PROFILE); 994 if (!(finalProfile == profile || 995 (mediaType.equals(AVC) 996 && profile == AVCProfileBaseline 997 && finalProfile == AVCProfileConstrainedBaseline) || 998 (mediaType.equals(AVC) 999 && profile == AVCProfileHigh 1000 && finalProfile == AVCProfileConstrainedHigh))) { 1001 fail("Incorrect profile: " + finalProfile + " Expected: " + profile); 1002 } 1003 int finalLevel = format.getInteger(MediaFormat.KEY_LEVEL); 1004 boolean match = false; 1005 String expectLvls = new String(); 1006 for (int level : expectedLevels) { 1007 expectLvls += level; 1008 if (finalLevel == level) { 1009 match = true; 1010 break; 1011 } 1012 } 1013 if (!match) { 1014 fail("Incorrect Level: " + finalLevel + " Expected: " + expectLvls); 1015 } 1016 } 1017 } 1018 mOutFile.delete(); 1019 if (encSurface != null) { 1020 encSurface.release(); 1021 encSurface = null; 1022 } 1023 return 1; 1024 } 1025 1026 @Test testProfileAvcBaselineLevel1()1027 public void testProfileAvcBaselineLevel1() throws Exception { 1028 int testsRun = 0; 1029 int profile = AVCProfileBaseline; 1030 1031 if (!hasH264()) { 1032 MediaUtils.skipTest("no Avc codecs"); 1033 return; 1034 } 1035 1036 /* W H fps kbps profile request level expected levels */ 1037 testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1, AVCLevel1); 1038 // Enable them when vendor fixes the failure 1039 //testLevel(AVC, 178, 144, 15, 64, profile, AVCLevel1, AVCLevel11); 1040 //testLevel(AVC, 178, 146, 15, 64, profile, AVCLevel1, AVCLevel11); 1041 //testLevel(AVC, 176, 144, 16, 64, profile, AVCLevel1, AVCLevel11); 1042 //testLevel(AVC, 176, 144, 15, 65, profile, AVCLevel1, AVCLevel1b); 1043 testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1b, AVCLevel1, AVCLevel1b); 1044 // testLevel(AVC, 176, 144, 15, 65, profile, AVCLevel2, AVCLevel1b, 1045 // AVCLevel11, AVCLevel12, AVCLevel13, AVCLevel2); 1046 if (testsRun == 0) { 1047 MediaUtils.skipTest("VideoCapabilities or surface not found"); 1048 } 1049 } 1050 getVideoEncoderFromMimeType(String mimeType)1051 private int getVideoEncoderFromMimeType(String mimeType) { 1052 if (mimeType.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) { 1053 return MediaRecorder.VideoEncoder.H264; 1054 } else if (mimeType.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) { 1055 return MediaRecorder.VideoEncoder.HEVC; 1056 } else if (mimeType.equals(MediaFormat.MIMETYPE_VIDEO_AV1)) { 1057 return MediaRecorder.VideoEncoder.AV1; 1058 } else { 1059 Log.e(TAG, "The codec test for " + mimeType + " is not yet supported"); 1060 return -1; 1061 } 1062 } 1063 testCodec(String mediaType, int width, int height, int framerate, int bitrate)1064 private int testCodec(String mediaType, int width, int height, int framerate, int bitrate) 1065 throws Exception { 1066 int videoEncoder = getVideoEncoderFromMimeType(mediaType); 1067 if (videoEncoder == -1) { 1068 Log.i(TAG, "Skip the test"); 1069 return 0; 1070 } 1071 CodecCapabilities cap = getCapsForPreferredCodecForMediaType(mediaType); 1072 if (cap == null) { // not supported 1073 return 0; 1074 } 1075 MediaCodecInfo.VideoCapabilities vCap = cap.getVideoCapabilities(); 1076 if (!vCap.areSizeAndRateSupported(width, height, framerate) 1077 || !vCap.getBitrateRange().contains(bitrate * 1000)) { 1078 Log.i(TAG, "Skip the test"); 1079 return 0; 1080 } 1081 1082 Surface surface = MediaCodec.createPersistentInputSurface(); 1083 if (surface == null) { 1084 return 0; 1085 } 1086 InputSurface encSurface = new InputSurface(surface); 1087 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 1088 mMediaRecorder.setInputSurface(encSurface.getSurface()); 1089 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 1090 mMediaRecorder.setVideoEncoder(videoEncoder); 1091 mMediaRecorder.setOutputFile(mOutFile); 1092 1093 mMediaRecorder.setVideoSize(width, height); 1094 mMediaRecorder.setVideoEncodingBitRate(bitrate * 1000); 1095 mMediaRecorder.setVideoFrameRate(framerate); 1096 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 1097 mMediaRecorder.prepare(); 1098 encSurface.updateSize(width, height); 1099 mMediaRecorder.start(); 1100 1101 1102 long startNsec = System.nanoTime(); 1103 long startTimeOffset = 3000 / framerate; 1104 for (int i = 0; i < NUM_FRAMES; i++) { 1105 encSurface.makeCurrent(); 1106 generateSurfaceFrame(i, width, height); 1107 long time = startNsec 1108 + computePresentationTime(startTimeOffset, i, framerate) * 1000; 1109 encSurface.setPresentationTime(time); 1110 encSurface.swapBuffers(); 1111 } 1112 1113 mMediaRecorder.stop(); 1114 1115 assertTrue(mOutFile.exists()); 1116 assertTrue(mOutFile.length() > 0); 1117 1118 mOutFile.delete(); 1119 if (encSurface != null) { 1120 encSurface.release(); 1121 encSurface = null; 1122 } 1123 return 1; 1124 } 1125 1126 @Test testAV1SDRecording()1127 public void testAV1SDRecording() throws Exception { 1128 int testsRun = 0; 1129 1130 if (!hasAV1()) { 1131 MediaUtils.skipTest("no AV1 codecs"); 1132 return; 1133 } 1134 try { 1135 testsRun += testCodec(MediaFormat.MIMETYPE_VIDEO_AV1, 640, 480, 15, 200); 1136 } catch (Exception e) { 1137 fail("Fail to record video: " + e.toString()); 1138 } 1139 1140 if (testsRun == 0) { 1141 MediaUtils.skipTest("VideoCapabilities or surface not found"); 1142 } 1143 } 1144 1145 @Test testRecordExceedFileSizeLimit()1146 public void testRecordExceedFileSizeLimit() throws Exception { 1147 if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) { 1148 MediaUtils.skipTest("no microphone, camera, or codecs"); 1149 return; 1150 } 1151 long fileSize = 1028 * 1024; 1152 long tolerance = 400 * 1024; 1153 int width; 1154 int height; 1155 List<String> recordFileList = new ArrayList<String>(); 1156 mFileIndex = 0; 1157 1158 // Override the handler in setup for test. 1159 mMediaRecorder.setOnInfoListener(new OnInfoListener() { 1160 public void onInfo(MediaRecorder mr, int what, int extra) { 1161 mOnInfoCalled = true; 1162 if (what == 1163 MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { 1164 Log.v(TAG, "max duration reached"); 1165 mMaxDurationCond.open(); 1166 } else if (what == 1167 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 1168 if (!mExpectMaxFileCond) { 1169 fail("Do not expect MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 1170 } else { 1171 Log.v(TAG, "max file size reached"); 1172 mMaxFileSizeCond.open(); 1173 } 1174 } else if (what == 1175 MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING) { 1176 Log.v(TAG, "max file size is approaching"); 1177 mMaxFileSizeApproachingCond.open(); 1178 1179 // Test passing a read-only FileDescriptor and expect IOException. 1180 if (mFileIndex == 1) { 1181 RandomAccessFile file = null; 1182 try { 1183 String path = OUTPUT_PATH + '0'; 1184 file = new RandomAccessFile(path, "r"); 1185 mMediaRecorder.setNextOutputFile(file.getFD()); 1186 fail("Expect IOException."); 1187 } catch (IOException e) { 1188 // Expected. 1189 } finally { 1190 try { 1191 file.close(); 1192 } catch (IOException e) { 1193 fail("Fail to close file"); 1194 } 1195 } 1196 } 1197 1198 // Test passing a read-only FileDescriptor and expect IOException. 1199 if (mFileIndex == 2) { 1200 ParcelFileDescriptor out = null; 1201 String path = null; 1202 try { 1203 path = OUTPUT_PATH + '0'; 1204 out = ParcelFileDescriptor.open(new File(path), 1205 ParcelFileDescriptor.MODE_READ_ONLY | ParcelFileDescriptor.MODE_CREATE); 1206 mMediaRecorder.setNextOutputFile(out.getFileDescriptor()); 1207 fail("Expect IOException."); 1208 } catch (IOException e) { 1209 // Expected. 1210 } finally { 1211 try { 1212 out.close(); 1213 } catch (IOException e) { 1214 fail("Fail to close file"); 1215 } 1216 } 1217 } 1218 1219 // Test passing a write-only FileDescriptor and expect NO IOException. 1220 if (mFileIndex == 3) { 1221 try { 1222 ParcelFileDescriptor out = null; 1223 String path = OUTPUT_PATH + mFileIndex; 1224 out = ParcelFileDescriptor.open(new File(path), 1225 ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE); 1226 mMediaRecorder.setNextOutputFile(out.getFileDescriptor()); 1227 out.close(); 1228 recordFileList.add(path); 1229 mFileIndex++; 1230 } catch (IOException e) { 1231 fail("Fail to set next output file error: " + e); 1232 } 1233 } else if (mFileIndex < 6) { 1234 try { 1235 String path = OUTPUT_PATH + mFileIndex; 1236 File nextFile = new File(path); 1237 mMediaRecorder.setNextOutputFile(nextFile); 1238 recordFileList.add(path); 1239 mFileIndex++; 1240 } catch (IOException e) { 1241 fail("Fail to set next output file error: " + e); 1242 } 1243 } 1244 } else if (what == 1245 MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED) { 1246 Log.v(TAG, "Next output file started"); 1247 mNextOutputFileStartedCond.open(); 1248 } 1249 } 1250 }); 1251 mExpectMaxFileCond = false; 1252 mMediaRecorder.setOutputFile(OUTPUT_PATH + mFileIndex); 1253 recordFileList.add(OUTPUT_PATH + mFileIndex); 1254 mFileIndex++; 1255 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 1256 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 1257 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 1258 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 1259 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 1260 // Try to get camera profile for QUALITY_HIGH; if unavailable, 1261 // set the video size to default value. 1262 CamcorderProfile profile = CamcorderProfile.get( 1263 0 /* cameraId */, CamcorderProfile.QUALITY_HIGH); 1264 if (profile != null) { 1265 width = profile.videoFrameWidth; 1266 height = profile.videoFrameHeight; 1267 } else { 1268 width = VIDEO_WIDTH; 1269 height = VIDEO_HEIGHT; 1270 } 1271 mMediaRecorder.setVideoSize(width, height); 1272 mMediaRecorder.setVideoEncodingBitRate(256000); 1273 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 1274 mMediaRecorder.setMaxFileSize(fileSize); 1275 mMediaRecorder.prepare(); 1276 mMediaRecorder.start(); 1277 1278 // Record total 5 files including previous one. 1279 int fileCount = 0; 1280 while (fileCount < 5) { 1281 if (!mMaxFileSizeApproachingCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 1282 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING"); 1283 } 1284 if (!mNextOutputFileStartedCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 1285 fail("timed out waiting for MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED"); 1286 } 1287 fileCount++; 1288 mMaxFileSizeApproachingCond.close(); 1289 mNextOutputFileStartedCond.close(); 1290 } 1291 1292 mExpectMaxFileCond = true; 1293 if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) { 1294 fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED"); 1295 } 1296 mMediaRecorder.stop(); 1297 1298 for (String filePath : recordFileList) { 1299 checkOutputFileSize(filePath, fileSize, tolerance); 1300 } 1301 } 1302 checkOutputFileSize(final String fileName, long fileSize, long tolerance)1303 private void checkOutputFileSize(final String fileName, long fileSize, long tolerance) { 1304 File file = new File(fileName); 1305 assertTrue(file.exists()); 1306 assertEquals(fileSize, file.length(), tolerance); 1307 assertTrue(file.delete()); 1308 } 1309 1310 @Test testOnErrorListener()1311 public void testOnErrorListener() throws Exception { 1312 if (!hasMicrophone() || !hasAac()) { 1313 MediaUtils.skipTest("no audio codecs or microphone"); 1314 return; 1315 } 1316 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); 1317 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 1318 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 1319 1320 recordMedia(MAX_FILE_SIZE, mOutFile); 1321 // TODO: how can we trigger a recording error? 1322 assertFalse(mOnErrorCalled); 1323 } 1324 setupRecorder(String filename, boolean useSurface, boolean hasAudio)1325 private void setupRecorder(String filename, boolean useSurface, boolean hasAudio) 1326 throws Exception { 1327 int codec = MediaRecorder.VideoEncoder.H264; 1328 int frameRate = getMaxFrameRateForCodec(codec); 1329 if (mMediaRecorder == null) { 1330 mMediaRecorder = new MediaRecorder(); 1331 } 1332 1333 if (!useSurface) { 1334 mCamera = Camera.open(0); 1335 Camera.Parameters params = mCamera.getParameters(); 1336 frameRate = params.getPreviewFrameRate(); 1337 setSupportedResolution(mCamera); 1338 mCamera.unlock(); 1339 mMediaRecorder.setCamera(mCamera); 1340 mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface()); 1341 } 1342 1343 mMediaRecorder.setVideoSource(useSurface ? 1344 MediaRecorder.VideoSource.SURFACE : MediaRecorder.VideoSource.CAMERA); 1345 1346 if (hasAudio) { 1347 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 1348 } 1349 1350 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); 1351 mMediaRecorder.setOutputFile(filename); 1352 1353 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 1354 mMediaRecorder.setVideoFrameRate(frameRate); 1355 mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight); 1356 1357 if (hasAudio) { 1358 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 1359 } 1360 } 1361 tryGetSurface(boolean shouldThrow)1362 private Surface tryGetSurface(boolean shouldThrow) throws Exception { 1363 Surface surface = null; 1364 try { 1365 surface = mMediaRecorder.getSurface(); 1366 assertFalse("failed to throw IllegalStateException", shouldThrow); 1367 } catch (IllegalStateException e) { 1368 assertTrue("threw unexpected exception: " + e, shouldThrow); 1369 } 1370 return surface; 1371 } 1372 validateGetSurface(boolean useSurface)1373 private boolean validateGetSurface(boolean useSurface) { 1374 Log.v(TAG,"validateGetSurface, useSurface=" + useSurface); 1375 if (!useSurface && !hasCamera()) { 1376 // pass if testing camera source but no hardware 1377 return true; 1378 } 1379 Surface surface = null; 1380 boolean success = true; 1381 try { 1382 setupRecorder(OUTPUT_PATH, useSurface, false /* hasAudio */); 1383 1384 /* Test: getSurface() before prepare() 1385 * should throw IllegalStateException 1386 */ 1387 surface = tryGetSurface(true /* shouldThow */); 1388 1389 mMediaRecorder.prepare(); 1390 1391 /* Test: getSurface() after prepare() 1392 * should succeed for surface source 1393 * should fail for camera source 1394 */ 1395 surface = tryGetSurface(!useSurface); 1396 1397 mMediaRecorder.start(); 1398 1399 /* Test: getSurface() after start() 1400 * should succeed for surface source 1401 * should fail for camera source 1402 */ 1403 surface = tryGetSurface(!useSurface); 1404 1405 try { 1406 mMediaRecorder.stop(); 1407 } catch (Exception e) { 1408 // stop() could fail if the recording is empty, as we didn't render anything. 1409 // ignore any failure in stop, we just want it stopped. 1410 } 1411 1412 /* Test: getSurface() after stop() 1413 * should throw IllegalStateException 1414 */ 1415 surface = tryGetSurface(true /* shouldThow */); 1416 } catch (Exception e) { 1417 Log.d(TAG, e.toString()); 1418 success = false; 1419 } finally { 1420 // reset to clear states, as stop() might have failed 1421 mMediaRecorder.reset(); 1422 1423 if (mCamera != null) { 1424 mCamera.release(); 1425 mCamera = null; 1426 } 1427 if (surface != null) { 1428 surface.release(); 1429 surface = null; 1430 } 1431 } 1432 1433 return success; 1434 } 1435 trySetInputSurface(Surface surface)1436 private void trySetInputSurface(Surface surface) throws Exception { 1437 boolean testBadArgument = (surface == null); 1438 try { 1439 mMediaRecorder.setInputSurface(testBadArgument ? new Surface() : surface); 1440 fail("failed to throw exception"); 1441 } catch (IllegalArgumentException e) { 1442 // OK only if testing bad arg 1443 assertTrue("threw unexpected exception: " + e, testBadArgument); 1444 } catch (IllegalStateException e) { 1445 // OK only if testing error case other than bad arg 1446 assertFalse("threw unexpected exception: " + e, testBadArgument); 1447 } 1448 } 1449 validatePersistentSurface(boolean errorCase)1450 private boolean validatePersistentSurface(boolean errorCase) { 1451 Log.v(TAG, "validatePersistentSurface, errorCase=" + errorCase); 1452 1453 Surface surface = MediaCodec.createPersistentInputSurface(); 1454 if (surface == null) { 1455 return false; 1456 } 1457 Surface placeholder = null; 1458 1459 boolean success = true; 1460 try { 1461 setupRecorder(OUTPUT_PATH, true /* useSurface */, false /* hasAudio */); 1462 1463 if (errorCase) { 1464 /* 1465 * Test: should throw if called with non-persistent surface 1466 */ 1467 trySetInputSurface(null); 1468 } else { 1469 /* 1470 * Test: should succeed if called with a persistent surface before prepare() 1471 */ 1472 mMediaRecorder.setInputSurface(surface); 1473 } 1474 1475 /* 1476 * Test: getSurface() should fail before prepare 1477 */ 1478 placeholder = tryGetSurface(true /* shouldThow */); 1479 1480 mMediaRecorder.prepare(); 1481 1482 /* 1483 * Test: setInputSurface() should fail after prepare 1484 */ 1485 trySetInputSurface(surface); 1486 1487 /* 1488 * Test: getSurface() should fail if setInputSurface() succeeded 1489 */ 1490 placeholder = tryGetSurface(!errorCase /* shouldThow */); 1491 1492 mMediaRecorder.start(); 1493 1494 /* 1495 * Test: setInputSurface() should fail after start 1496 */ 1497 trySetInputSurface(surface); 1498 1499 /* 1500 * Test: getSurface() should fail if setInputSurface() succeeded 1501 */ 1502 placeholder = tryGetSurface(!errorCase /* shouldThow */); 1503 1504 try { 1505 mMediaRecorder.stop(); 1506 } catch (Exception e) { 1507 // stop() could fail if the recording is empty, as we didn't render anything. 1508 // ignore any failure in stop, we just want it stopped. 1509 } 1510 1511 /* 1512 * Test: getSurface() should fail after stop 1513 */ 1514 placeholder = tryGetSurface(true /* shouldThow */); 1515 } catch (Exception e) { 1516 Log.d(TAG, e.toString()); 1517 success = false; 1518 } finally { 1519 // reset to clear states, as stop() might have failed 1520 mMediaRecorder.reset(); 1521 1522 if (mCamera != null) { 1523 mCamera.release(); 1524 mCamera = null; 1525 } 1526 if (surface != null) { 1527 surface.release(); 1528 surface = null; 1529 } 1530 if (placeholder != null) { 1531 placeholder.release(); 1532 placeholder = null; 1533 } 1534 } 1535 1536 return success; 1537 } 1538 1539 @Test testGetSurfaceApi()1540 public void testGetSurfaceApi() { 1541 if (!hasH264()) { 1542 MediaUtils.skipTest("no codecs"); 1543 return; 1544 } 1545 1546 if (hasCamera()) { 1547 // validate getSurface() with CAMERA source 1548 assertTrue(validateGetSurface(false /* useSurface */)); 1549 } 1550 1551 // validate getSurface() with SURFACE source 1552 assertTrue(validateGetSurface(true /* useSurface */)); 1553 } 1554 1555 @Test testPersistentSurfaceApi()1556 public void testPersistentSurfaceApi() { 1557 if (!hasH264()) { 1558 MediaUtils.skipTest("no codecs"); 1559 return; 1560 } 1561 1562 // test valid use case 1563 assertTrue(validatePersistentSurface(false /* errorCase */)); 1564 1565 // test invalid use case 1566 assertTrue(validatePersistentSurface(true /* errorCase */)); 1567 } 1568 getMaxFrameRateForCodec(int codec)1569 private static int getMaxFrameRateForCodec(int codec) { 1570 for (VideoEncoderCap cap : mVideoEncoders) { 1571 if (cap.mCodec == codec) { 1572 return cap.mMaxFrameRate < NORMAL_FPS ? cap.mMaxFrameRate : NORMAL_FPS; 1573 } 1574 } 1575 fail("didn't find max FPS for codec"); 1576 return -1; 1577 } 1578 recordFromSurface( String filename, int captureRate, boolean hasAudio, Surface persistentSurface)1579 private boolean recordFromSurface( 1580 String filename, 1581 int captureRate, 1582 boolean hasAudio, 1583 Surface persistentSurface) { 1584 Log.v(TAG, "recordFromSurface"); 1585 Surface surface = null; 1586 try { 1587 setupRecorder(filename, true /* useSurface */, hasAudio); 1588 1589 int sleepTimeMs; 1590 if (captureRate > 0) { 1591 mMediaRecorder.setCaptureRate(captureRate); 1592 sleepTimeMs = 1000 / captureRate; 1593 } else { 1594 sleepTimeMs = 1000 / getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264); 1595 } 1596 1597 if (persistentSurface != null) { 1598 Log.v(TAG, "using persistent surface"); 1599 surface = persistentSurface; 1600 mMediaRecorder.setInputSurface(surface); 1601 } 1602 1603 mMediaRecorder.prepare(); 1604 1605 if (persistentSurface == null) { 1606 surface = mMediaRecorder.getSurface(); 1607 } 1608 1609 Paint paint = new Paint(); 1610 paint.setTextSize(16); 1611 paint.setColor(Color.RED); 1612 int i; 1613 1614 /* Test: draw 10 frames at 30fps before start 1615 * these should be dropped and not causing malformed stream. 1616 */ 1617 for(i = 0; i < 10; i++) { 1618 Canvas canvas = surface.lockCanvas(null); 1619 int background = (i * 255 / 99); 1620 canvas.drawARGB(255, background, background, background); 1621 String text = "Frame #" + i; 1622 canvas.drawText(text, 50, 50, paint); 1623 surface.unlockCanvasAndPost(canvas); 1624 Thread.sleep(sleepTimeMs); 1625 } 1626 1627 Log.v(TAG, "start"); 1628 mMediaRecorder.start(); 1629 1630 /* Test: draw another 90 frames at 30fps after start */ 1631 for(i = 10; i < 100; i++) { 1632 Canvas canvas = surface.lockCanvas(null); 1633 int background = (i * 255 / 99); 1634 canvas.drawARGB(255, background, background, background); 1635 String text = "Frame #" + i; 1636 canvas.drawText(text, 50, 50, paint); 1637 surface.unlockCanvasAndPost(canvas); 1638 Thread.sleep(sleepTimeMs); 1639 } 1640 1641 Log.v(TAG, "stop"); 1642 mMediaRecorder.stop(); 1643 } catch (Exception e) { 1644 Log.v(TAG, "record video failed: " + e.toString()); 1645 return false; 1646 } finally { 1647 // We need to test persistent surface across multiple MediaRecorder 1648 // instances, so must destroy mMediaRecorder here. 1649 if (mMediaRecorder != null) { 1650 mMediaRecorder.release(); 1651 mMediaRecorder = null; 1652 } 1653 1654 // release surface if not using persistent surface 1655 if (persistentSurface == null && surface != null) { 1656 surface.release(); 1657 surface = null; 1658 } 1659 } 1660 return true; 1661 } 1662 checkCaptureFps(String filename, int captureRate)1663 private boolean checkCaptureFps(String filename, int captureRate) { 1664 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); 1665 1666 retriever.setDataSource(filename); 1667 1668 // verify capture rate meta key is present and correct 1669 String captureFps = retriever.extractMetadata( 1670 MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE); 1671 1672 if (captureFps == null) { 1673 Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is missing"); 1674 return false; 1675 } 1676 1677 if (Math.abs(Float.parseFloat(captureFps) - captureRate) > 0.001) { 1678 Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is incorrect: " 1679 + captureFps + "vs. " + captureRate); 1680 return false; 1681 } 1682 1683 // verify other meta keys here if necessary 1684 return true; 1685 } 1686 testRecordFromSurface(boolean persistent, boolean timelapse)1687 private boolean testRecordFromSurface(boolean persistent, boolean timelapse) { 1688 Log.v(TAG, "testRecordFromSurface: " + 1689 "persistent=" + persistent + ", timelapse=" + timelapse); 1690 boolean success = false; 1691 Surface surface = null; 1692 int noOfFailure = 0; 1693 1694 if (!hasH264()) { 1695 MediaUtils.skipTest("no codecs"); 1696 return true; 1697 } 1698 1699 final float frameRate = getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264); 1700 1701 try { 1702 if (persistent) { 1703 surface = MediaCodec.createPersistentInputSurface(); 1704 } 1705 1706 for (int k = 0; k < 2; k++) { 1707 String filename = (k == 0) ? OUTPUT_PATH : OUTPUT_PATH2; 1708 boolean hasAudio = false; 1709 int captureRate = 0; 1710 1711 if (timelapse) { 1712 // if timelapse/slow-mo, k chooses between low/high capture fps 1713 captureRate = (k == 0) ? TIME_LAPSE_FPS : SLOW_MOTION_FPS; 1714 } else { 1715 // otherwise k chooses between no-audio and audio 1716 hasAudio = (k == 0) ? false : true; 1717 } 1718 1719 if (hasAudio && (!hasMicrophone() || !hasAmrNb())) { 1720 // audio test waived if no audio support 1721 continue; 1722 } 1723 1724 Log.v(TAG, "testRecordFromSurface - round " + k); 1725 success = recordFromSurface(filename, captureRate, hasAudio, surface); 1726 if (success) { 1727 checkTracksAndDuration(0, true /* hasVideo */, hasAudio, filename, frameRate); 1728 1729 // verify capture fps meta key 1730 if (timelapse && !checkCaptureFps(filename, captureRate)) { 1731 noOfFailure++; 1732 } 1733 } 1734 if (!success) { 1735 noOfFailure++; 1736 } 1737 } 1738 } catch (Exception e) { 1739 Log.v(TAG, e.toString()); 1740 noOfFailure++; 1741 } finally { 1742 if (surface != null) { 1743 Log.v(TAG, "releasing persistent surface"); 1744 surface.release(); 1745 surface = null; 1746 } 1747 } 1748 return (noOfFailure == 0); 1749 } 1750 1751 // Test recording from surface source with/without audio) 1752 @Test testSurfaceRecording()1753 public void testSurfaceRecording() { 1754 assertTrue(testRecordFromSurface(false /* persistent */, false /* timelapse */)); 1755 } 1756 1757 // Test recording from persistent surface source with/without audio 1758 @Test testPersistentSurfaceRecording()1759 public void testPersistentSurfaceRecording() { 1760 assertTrue(testRecordFromSurface(true /* persistent */, false /* timelapse */)); 1761 } 1762 1763 // Test timelapse recording from surface without audio 1764 @Test testSurfaceRecordingTimeLapse()1765 public void testSurfaceRecordingTimeLapse() { 1766 assertTrue(testRecordFromSurface(false /* persistent */, true /* timelapse */)); 1767 } 1768 1769 // Test timelapse recording from persisent surface without audio 1770 @Test testPersistentSurfaceRecordingTimeLapse()1771 public void testPersistentSurfaceRecordingTimeLapse() { 1772 assertTrue(testRecordFromSurface(true /* persistent */, true /* timelapse */)); 1773 } 1774 recordMedia(long maxFileSize, File outFile)1775 private void recordMedia(long maxFileSize, File outFile) throws Exception { 1776 mMediaRecorder.setMaxFileSize(maxFileSize); 1777 mMediaRecorder.prepare(); 1778 mMediaRecorder.start(); 1779 Thread.sleep(RECORD_TIME_MS); 1780 mMediaRecorder.stop(); 1781 1782 assertTrue(outFile.exists()); 1783 1784 // The max file size is always guaranteed. 1785 // We just make sure that the margin is not too big 1786 assertTrue(outFile.length() < 1.1 * maxFileSize); 1787 assertTrue(outFile.length() > 0); 1788 } 1789 hasCamera()1790 private boolean hasCamera() { 1791 return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); 1792 } 1793 hasMicrophone()1794 private boolean hasMicrophone() { 1795 return mActivity.getPackageManager().hasSystemFeature( 1796 PackageManager.FEATURE_MICROPHONE); 1797 } 1798 hasAmrNb()1799 private static boolean hasAmrNb() { 1800 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB); 1801 } 1802 hasAmrWb()1803 private static boolean hasAmrWb() { 1804 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_WB); 1805 } 1806 hasAac()1807 private static boolean hasAac() { 1808 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC); 1809 } 1810 hasH264()1811 private static boolean hasH264() { 1812 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC); 1813 } 1814 hasAV1()1815 private static boolean hasAV1() { 1816 return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AV1); 1817 } 1818 1819 @Test testSetCaptureRate()1820 public void testSetCaptureRate() throws Exception { 1821 // No exception expected for 30fps 1822 mMediaRecorder.setCaptureRate(30.0); 1823 try { 1824 mMediaRecorder.setCaptureRate(-1.0); 1825 fail("Should fail setting negative fps"); 1826 } catch (Exception ex) { 1827 // expected 1828 } 1829 // No exception expected for 1/24hr 1830 mMediaRecorder.setCaptureRate(1.0 / 86400.0); 1831 try { 1832 mMediaRecorder.setCaptureRate(1.0 / 90000.0); 1833 fail("Should fail setting smaller fps than one frame per day"); 1834 } catch (Exception ex) { 1835 // expected 1836 } 1837 try { 1838 mMediaRecorder.setCaptureRate(0); 1839 fail("Should fail setting zero fps"); 1840 } catch (Exception ex) { 1841 // expected 1842 } 1843 } 1844 1845 @Test testAudioRecordInfoCallback()1846 public void testAudioRecordInfoCallback() throws Exception { 1847 if (!hasMicrophone() || !hasAac()) { 1848 MediaUtils.skipTest("no audio codecs or microphone"); 1849 return; 1850 } 1851 MyAudioRecordingCallback callback = new MyAudioRecordingCallback( 1852 0 /*unused*/, MediaRecorder.AudioSource.DEFAULT); 1853 mMediaRecorder.registerAudioRecordingCallback(mExec, callback); 1854 configureDefaultMediaRecorder(); 1855 mMediaRecorder.prepare(); 1856 mMediaRecorder.start(); 1857 callback.await(TEST_TIMING_TOLERANCE_MS); 1858 assertTrue(callback.mCalled); 1859 assertTrue(callback.mConfigs.size() <= 1); 1860 if (callback.mConfigs.size() == 1) { 1861 checkRecordingConfig(callback.mConfigs.get(0)); 1862 } 1863 Thread.sleep(RECORD_TIME_MS); 1864 mMediaRecorder.stop(); 1865 mMediaRecorder.unregisterAudioRecordingCallback(callback); 1866 } 1867 1868 @Test testGetActiveRecordingConfiguration()1869 public void testGetActiveRecordingConfiguration() throws Exception { 1870 if (!hasMicrophone() || !hasAac()) { 1871 MediaUtils.skipTest("no audio codecs or microphone"); 1872 return; 1873 } 1874 configureDefaultMediaRecorder(); 1875 mMediaRecorder.prepare(); 1876 mMediaRecorder.start(); 1877 Thread.sleep(1000); 1878 AudioRecordingConfiguration config = mMediaRecorder.getActiveRecordingConfiguration(); 1879 checkRecordingConfig(config); 1880 mMediaRecorder.stop(); 1881 } 1882 1883 private Executor mExec = new Executor() { 1884 @Override 1885 public void execute(Runnable command) { 1886 command.run(); 1887 } 1888 }; 1889 checkRecordingConfig(AudioRecordingConfiguration config)1890 private static void checkRecordingConfig(AudioRecordingConfiguration config) { 1891 assertNotNull(config); 1892 AudioFormat format = config.getClientFormat(); 1893 assertEquals(AUDIO_NUM_CHANNELS, format.getChannelCount()); 1894 assertEquals(AUDIO_SAMPLE_RATE_HZ, format.getSampleRate()); 1895 assertEquals(MediaRecorder.AudioSource.MIC, config.getAudioSource()); 1896 assertNotNull(config.getAudioDevice()); 1897 assertNotNull(config.getClientEffects()); 1898 assertNotNull(config.getEffects()); 1899 // no requirement here, just testing the API 1900 config.isClientSilenced(); 1901 } 1902 1903 /* 1904 * Microphone Direction API tests 1905 */ 1906 @Test testSetPreferredMicrophoneDirection()1907 public void testSetPreferredMicrophoneDirection() { 1908 if (!hasMicrophone()) { 1909 return; 1910 } 1911 1912 try { 1913 boolean succecss = 1914 mMediaRecorder.setPreferredMicrophoneDirection( 1915 MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER); 1916 1917 // Can't actually test this as HAL may not have implemented it 1918 // Just verify that it doesn't crash or throw an exception 1919 // assertTrue(succecss); 1920 } catch (Exception ex) { 1921 Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex); 1922 assertTrue(false); 1923 } 1924 return; 1925 } 1926 1927 @Test testSetPreferredMicrophoneFieldDimension()1928 public void testSetPreferredMicrophoneFieldDimension() { 1929 if (!hasMicrophone()) { 1930 return; 1931 } 1932 1933 try { 1934 boolean succecss = mMediaRecorder.setPreferredMicrophoneFieldDimension(1.0f); 1935 1936 // Can't actually test this as HAL may not have implemented it 1937 // Just verify that it doesn't crash or throw an exception 1938 // assertTrue(succecss); 1939 } catch (Exception ex) { 1940 Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex); 1941 assertTrue(false); 1942 } 1943 return; 1944 } 1945 1946 @Test testPrivacySensitive()1947 public void testPrivacySensitive() throws Exception { 1948 if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return; 1949 if (!hasMicrophone() || !hasAac()) { 1950 MediaUtils.skipTest("no audio codecs or microphone"); 1951 return; 1952 } 1953 for (final boolean privacyOn : new boolean[] { false, true} ) { 1954 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 1955 mMediaRecorder.setPrivacySensitive(privacyOn); 1956 assertEquals(privacyOn, mMediaRecorder.isPrivacySensitive()); 1957 mMediaRecorder.reset(); 1958 } 1959 } 1960 1961 @Test testPrivacySensitiveDefaults()1962 public void testPrivacySensitiveDefaults() throws Exception { 1963 if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return; 1964 if (!hasMicrophone() || !hasAac()) { 1965 MediaUtils.skipTest("no audio codecs or microphone"); 1966 return; 1967 } 1968 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 1969 assertFalse(mMediaRecorder.isPrivacySensitive()); 1970 mMediaRecorder.reset(); 1971 1972 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION); 1973 assertTrue(mMediaRecorder.isPrivacySensitive()); 1974 } 1975 1976 @Test testSetGetLogSessionId()1977 public void testSetGetLogSessionId() { 1978 if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return; 1979 MediaRecorder recorder = new MediaRecorder(); 1980 assertEquals(recorder.getLogSessionId(), LogSessionId.LOG_SESSION_ID_NONE); 1981 1982 final MediaMetricsManager mediaMetricsManager = 1983 InstrumentationRegistry.getTargetContext() 1984 .getSystemService(MediaMetricsManager.class); 1985 final RecordingSession recordingSession = mediaMetricsManager.createRecordingSession(); 1986 recorder.setLogSessionId(recordingSession.getSessionId()); 1987 assertEquals(recordingSession.getSessionId(), recorder.getLogSessionId()); 1988 1989 recorder.release(); 1990 } 1991 1992 private static class MyAudioRecordingCallback extends AudioManager.AudioRecordingCallback { 1993 public boolean mCalled; 1994 public List<AudioRecordingConfiguration> mConfigs; 1995 private final int mTestSource; 1996 private final int mTestSession; 1997 private CountDownLatch mCountDownLatch; 1998 reset()1999 void reset() { 2000 mCountDownLatch = new CountDownLatch(1); 2001 mCalled = false; 2002 mConfigs = new ArrayList<AudioRecordingConfiguration>(); 2003 } 2004 MyAudioRecordingCallback(int session, int source)2005 MyAudioRecordingCallback(int session, int source) { 2006 mTestSource = source; 2007 mTestSession = session; 2008 reset(); 2009 } 2010 2011 @Override onRecordingConfigChanged(List<AudioRecordingConfiguration> configs)2012 public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) { 2013 mCalled = true; 2014 mConfigs = configs; 2015 mCountDownLatch.countDown(); 2016 } 2017 await(long timeoutMs)2018 void await(long timeoutMs) { 2019 try { 2020 mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS); 2021 } catch (InterruptedException e) { 2022 } 2023 } 2024 } 2025 2026 } 2027