• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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