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