Home | History | Annotate | Download | only in cts
      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.hardware.Camera;
     21 import android.media.MediaMetadataRetriever;
     22 import android.media.MediaRecorder;
     23 import android.media.MediaRecorder.OnErrorListener;
     24 import android.media.MediaRecorder.OnInfoListener;
     25 import android.media.MediaMetadataRetriever;
     26 import android.os.Environment;
     27 import android.os.ConditionVariable;
     28 import android.test.ActivityInstrumentationTestCase2;
     29 import android.test.UiThreadTest;
     30 import android.view.Surface;
     31 
     32 import android.util.Log;
     33 
     34 import java.io.File;
     35 import java.io.FileDescriptor;
     36 import java.io.FileOutputStream;
     37 import java.lang.InterruptedException;
     38 import java.lang.Runnable;
     39 import java.util.concurrent.CountDownLatch;
     40 import java.util.concurrent.TimeUnit;
     41 
     42 public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
     43     private final String TAG = "MediaRecorderTest";
     44     private final String OUTPUT_PATH;
     45     private final String OUTPUT_PATH2;
     46     private static final float TOLERANCE = 0.0002f;
     47     private static final int RECORD_TIME_MS = 3000;
     48     private static final int RECORD_TIME_LAPSE_MS = 4000;
     49     private static final int RECORD_TIME_LONG_MS = 20000;
     50     private static final int RECORDED_DUR_TOLERANCE_MS = 1000;
     51     private static final int VIDEO_WIDTH = 176;
     52     private static final int VIDEO_HEIGHT = 144;
     53     private static final int VIDEO_BIT_RATE_IN_BPS = 128000;
     54     private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0;
     55     private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
     56     private static final int AUDIO_NUM_CHANNELS = 1;
     57     private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
     58     private static final long MAX_FILE_SIZE = 5000;
     59     private static final int MAX_FILE_SIZE_TIMEOUT_MS = 5 * 60 * 1000;
     60     private static final int MAX_DURATION_MSEC = 2000;
     61     private static final float LATITUDE = 0.0000f;
     62     private static final float LONGITUDE  = -180.0f;
     63     private boolean mOnInfoCalled;
     64     private boolean mOnErrorCalled;
     65     private File mOutFile;
     66     private File mOutFile2;
     67     private Camera mCamera;
     68     private MediaStubActivity mActivity = null;
     69 
     70     private MediaRecorder mMediaRecorder;
     71     private ConditionVariable mMaxDurationCond;
     72     private ConditionVariable mMaxFileSizeCond;
     73 
     74     public MediaRecorderTest() {
     75         super("com.android.cts.media", MediaStubActivity.class);
     76         OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(),
     77                 "record.out").getAbsolutePath();
     78         OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(),
     79                 "record2.out").getAbsolutePath();
     80     }
     81 
     82     private void completeOnUiThread(final Runnable runnable) {
     83         final CountDownLatch latch = new CountDownLatch(1);
     84         getActivity().runOnUiThread(new Runnable() {
     85             @Override
     86             public void run() {
     87                 runnable.run();
     88                 latch.countDown();
     89             }
     90         });
     91         try {
     92             // if UI thread does not run, things will fail anyway
     93             assertTrue(latch.await(10, TimeUnit.SECONDS));
     94         } catch (java.lang.InterruptedException e) {
     95             fail("should not be interrupted");
     96         }
     97     }
     98 
     99     @Override
    100     protected void setUp() throws Exception {
    101         mActivity = getActivity();
    102         completeOnUiThread(new Runnable() {
    103             @Override
    104             public void run() {
    105                 mMediaRecorder = new MediaRecorder();
    106                 mOutFile = new File(OUTPUT_PATH);
    107                 mOutFile2 = new File(OUTPUT_PATH2);
    108 
    109                 mMaxDurationCond = new ConditionVariable();
    110                 mMaxFileSizeCond = new ConditionVariable();
    111 
    112                 mMediaRecorder.setOutputFile(OUTPUT_PATH);
    113                 mMediaRecorder.setOnInfoListener(new OnInfoListener() {
    114                     public void onInfo(MediaRecorder mr, int what, int extra) {
    115                         mOnInfoCalled = true;
    116                         if (what ==
    117                             MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
    118                             Log.v(TAG, "max duration reached");
    119                             mMaxDurationCond.open();
    120                         } else if (what ==
    121                             MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
    122                             Log.v(TAG, "max file size reached");
    123                             mMaxFileSizeCond.open();
    124                         }
    125                     }
    126                 });
    127                 mMediaRecorder.setOnErrorListener(new OnErrorListener() {
    128                     public void onError(MediaRecorder mr, int what, int extra) {
    129                         mOnErrorCalled = true;
    130                     }
    131                 });
    132             }
    133         });
    134         super.setUp();
    135     }
    136 
    137     @Override
    138     protected void tearDown() throws Exception {
    139         mMediaRecorder.release();
    140         mMediaRecorder = null;
    141         if (mOutFile != null && mOutFile.exists()) {
    142             mOutFile.delete();
    143         }
    144         if (mOutFile2 != null && mOutFile2.exists()) {
    145             mOutFile2.delete();
    146         }
    147         if (mCamera != null)  {
    148             mCamera.release();
    149             mCamera = null;
    150         }
    151         mMaxDurationCond.close();
    152         mMaxDurationCond = null;
    153         mMaxFileSizeCond.close();
    154         mMaxFileSizeCond = null;
    155         mActivity = null;
    156         super.tearDown();
    157     }
    158 
    159     public void testRecorderCamera() throws Exception {
    160         if (!hasCamera()) {
    161             return;
    162         }
    163         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    164         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    165         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
    166         mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
    167         mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
    168         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
    169         mMediaRecorder.prepare();
    170         mMediaRecorder.start();
    171         Thread.sleep(RECORD_TIME_MS);
    172         mMediaRecorder.stop();
    173         checkOutputExist();
    174     }
    175 
    176     @UiThreadTest
    177     public void testSetCamera() throws Exception {
    178         recordVideoUsingCamera(false);
    179     }
    180 
    181     public void testRecorderTimelapsedVideo() throws Exception {
    182         recordVideoUsingCamera(true);
    183     }
    184 
    185     private void recordVideoUsingCamera(boolean timelapse) throws Exception {
    186         int nCamera = Camera.getNumberOfCameras();
    187         int durMs = timelapse? RECORD_TIME_LAPSE_MS: RECORD_TIME_MS;
    188         for (int cameraId = 0; cameraId < nCamera; cameraId++) {
    189             mCamera = Camera.open(cameraId);
    190             recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse);
    191             mCamera.release();
    192             mCamera = null;
    193             assertTrue(checkLocationInFile(OUTPUT_PATH));
    194         }
    195     }
    196 
    197     private void recordVideoUsingCamera(
    198             Camera camera, String fileName, int durMs, boolean timelapse) throws Exception {
    199         // FIXME:
    200         // We should add some test case to use Camera.Parameters.getPreviewFpsRange()
    201         // to get the supported video frame rate range.
    202         Camera.Parameters params = camera.getParameters();
    203         int frameRate = params.getPreviewFrameRate();
    204 
    205         camera.unlock();
    206         mMediaRecorder.setCamera(camera);
    207         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    208         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
    209         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    210         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
    211         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    212         mMediaRecorder.setVideoFrameRate(frameRate);
    213         mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
    214         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
    215         mMediaRecorder.setOutputFile(fileName);
    216         mMediaRecorder.setLocation(LATITUDE, LONGITUDE);
    217         final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS;
    218         if (timelapse) {
    219             mMediaRecorder.setCaptureRate(captureRate);
    220         }
    221 
    222         mMediaRecorder.prepare();
    223         mMediaRecorder.start();
    224         Thread.sleep(durMs);
    225         mMediaRecorder.stop();
    226         assertTrue(mOutFile.exists());
    227 
    228         int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs;
    229         boolean hasVideo = true;
    230         boolean hasAudio = timelapse? false: true;
    231         checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName);
    232     }
    233 
    234     private void checkTracksAndDuration(
    235             int targetMs, boolean hasVideo, boolean hasAudio, String fileName) throws Exception {
    236         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
    237         retriever.setDataSource(fileName);
    238         String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
    239         String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO);
    240         assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null);
    241         assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null);
    242         // FIXME:
    243         // If we could use fixed frame rate for video recording, we could also do more accurate
    244         // check on the duration.
    245         String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
    246         assertTrue(durStr != null);
    247         assertTrue(Integer.parseInt(durStr) > 0);
    248         retriever.release();
    249         retriever = null;
    250     }
    251 
    252     private boolean checkLocationInFile(String fileName) {
    253         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
    254         retriever.setDataSource(fileName);
    255         String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
    256         if (location == null) {
    257             retriever.release();
    258             Log.v(TAG, "No location information found in file " + fileName);
    259             return false;
    260         }
    261 
    262         // parsing String location and recover the location inforamtion in floats
    263         // Make sure the tolerance is very small - due to rounding errors?.
    264         Log.v(TAG, "location: " + location);
    265 
    266         // Get the position of the -/+ sign in location String, which indicates
    267         // the beginning of the longtitude.
    268         int index = location.lastIndexOf('-');
    269         if (index == -1) {
    270             index = location.lastIndexOf('+');
    271         }
    272         assertTrue("+ or - is not found", index != -1);
    273         assertTrue("+ or - is only found at the beginning", index != 0);
    274         float latitude = Float.parseFloat(location.substring(0, index - 1));
    275         float longitude = Float.parseFloat(location.substring(index));
    276         assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE);
    277         assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE);
    278         retriever.release();
    279         return true;
    280     }
    281 
    282     private void checkOutputExist() {
    283         assertTrue(mOutFile.exists());
    284         assertTrue(mOutFile.length() > 0);
    285         assertTrue(mOutFile.delete());
    286     }
    287 
    288     public void testRecorderVideo() throws Exception {
    289         if (!hasCamera()) {
    290             return;
    291         }
    292         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    293         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    294         mMediaRecorder.setOutputFile(OUTPUT_PATH2);
    295         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
    296         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
    297         mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
    298 
    299         FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2);
    300         FileDescriptor fd = fos.getFD();
    301         mMediaRecorder.setOutputFile(fd);
    302         long maxFileSize = MAX_FILE_SIZE * 10;
    303         recordMedia(maxFileSize, mOutFile2);
    304         assertFalse(checkLocationInFile(OUTPUT_PATH2));
    305         fos.close();
    306     }
    307 
    308     public void testRecordingAudioInRawFormats() throws Exception {
    309         testRecordAudioInRawFormat(
    310                 MediaRecorder.OutputFormat.AMR_NB,
    311                 MediaRecorder.AudioEncoder.AMR_NB);
    312 
    313         testRecordAudioInRawFormat(
    314                 MediaRecorder.OutputFormat.AMR_WB,
    315                 MediaRecorder.AudioEncoder.AMR_WB);
    316 
    317         testRecordAudioInRawFormat(
    318                 MediaRecorder.OutputFormat.AAC_ADTS,
    319                 MediaRecorder.AudioEncoder.AAC);
    320     }
    321 
    322     private void testRecordAudioInRawFormat(
    323             int fileFormat, int codec) throws Exception {
    324 
    325         if (!hasMicrophone()) {
    326             return;
    327         }
    328         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    329         mMediaRecorder.setOutputFormat(fileFormat);
    330         mMediaRecorder.setOutputFile(OUTPUT_PATH);
    331         mMediaRecorder.setAudioEncoder(codec);
    332         recordMedia(MAX_FILE_SIZE, mOutFile);
    333     }
    334 
    335     public void testGetAudioSourceMax() throws Exception {
    336         final int max = MediaRecorder.getAudioSourceMax();
    337         assertTrue(MediaRecorder.AudioSource.DEFAULT <= max);
    338         assertTrue(MediaRecorder.AudioSource.MIC <= max);
    339         assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max);
    340         assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max);
    341         assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max);
    342         assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max);
    343         assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max);
    344         assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max);
    345     }
    346 
    347     public void testRecorderAudio() throws Exception {
    348         if (!hasMicrophone()) {
    349             return;
    350         }
    351         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    352         assertEquals(0, mMediaRecorder.getMaxAmplitude());
    353         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    354         mMediaRecorder.setOutputFile(OUTPUT_PATH);
    355         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    356         mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS);
    357         mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
    358         mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
    359         recordMedia(MAX_FILE_SIZE, mOutFile);
    360     }
    361 
    362     public void testOnInfoListener() throws Exception {
    363         if (!hasMicrophone()) {
    364             return;
    365         }
    366         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    367         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    368         mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC);
    369         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    370         mMediaRecorder.prepare();
    371         mMediaRecorder.start();
    372         Thread.sleep(RECORD_TIME_MS);
    373         assertTrue(mOnInfoCalled);
    374     }
    375 
    376     public void testSetMaxDuration() throws Exception {
    377         if (!hasMicrophone()) {
    378             return;
    379         }
    380         testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS);
    381     }
    382 
    383     private void testSetMaxDuration(long durationMs, long toleranceMs) throws Exception {
    384         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    385         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    386         mMediaRecorder.setMaxDuration((int)durationMs);
    387         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    388         mMediaRecorder.prepare();
    389         mMediaRecorder.start();
    390         long startTimeMs = System.currentTimeMillis();
    391         if (!mMaxDurationCond.block(durationMs + toleranceMs)) {
    392             fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_DURATION_REACHED");
    393         }
    394         long endTimeMs = System.currentTimeMillis();
    395         long actualDurationMs = endTimeMs - startTimeMs;
    396         mMediaRecorder.stop();
    397         checkRecordedTime(durationMs, actualDurationMs, toleranceMs);
    398     }
    399 
    400     private void checkRecordedTime(long expectedMs, long actualMs, long tolerance) {
    401         assertEquals(expectedMs, actualMs, tolerance);
    402         long actualFileDurationMs = getRecordedFileDurationMs(OUTPUT_PATH);
    403         assertEquals(actualFileDurationMs, actualMs, tolerance);
    404     }
    405 
    406     private int getRecordedFileDurationMs(final String fileName) {
    407         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
    408         retriever.setDataSource(fileName);
    409         String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
    410         assertNotNull(durationStr);
    411         return Integer.parseInt(durationStr);
    412     }
    413 
    414     public void testSetMaxFileSize() throws Exception {
    415         if (!hasMicrophone()) {
    416             return;
    417         }
    418         testSetMaxFileSize(512 * 1024, 50 * 1024);
    419     }
    420 
    421     private void testSetMaxFileSize(
    422             long fileSize, long tolerance) throws Exception {
    423         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    424         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    425         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    426         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    427         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    428         mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
    429         mMediaRecorder.setVideoEncodingBitRate(256000);
    430         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
    431         mMediaRecorder.setMaxFileSize(fileSize);
    432         mMediaRecorder.prepare();
    433         mMediaRecorder.start();
    434 
    435         // Recording a scene with moving objects would greatly help reduce
    436         // the time for waiting.
    437         if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
    438             fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
    439         }
    440         mMediaRecorder.stop();
    441         checkOutputFileSize(OUTPUT_PATH, fileSize, tolerance);
    442     }
    443 
    444     private void checkOutputFileSize(final String fileName, long fileSize, long tolerance) {
    445         assertTrue(mOutFile.exists());
    446         assertEquals(fileSize, mOutFile.length(), tolerance);
    447         assertTrue(mOutFile.delete());
    448     }
    449 
    450     public void testOnErrorListener() throws Exception {
    451         if (!hasMicrophone()) {
    452             return;
    453         }
    454         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
    455         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    456         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    457 
    458         recordMedia(MAX_FILE_SIZE, mOutFile);
    459         // TODO: how can we trigger a recording error?
    460         assertFalse(mOnErrorCalled);
    461     }
    462 
    463     private void recordMedia(long maxFileSize, File outFile) throws Exception {
    464         mMediaRecorder.setMaxFileSize(maxFileSize);
    465         mMediaRecorder.prepare();
    466         mMediaRecorder.start();
    467         Thread.sleep(RECORD_TIME_MS);
    468         mMediaRecorder.stop();
    469 
    470         assertTrue(outFile.exists());
    471 
    472         // The max file size is always guaranteed.
    473         // We just make sure that the margin is not too big
    474         assertTrue(outFile.length() < 1.1 * maxFileSize);
    475         assertTrue(outFile.length() > 0);
    476     }
    477 
    478     private boolean hasCamera() {
    479         return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
    480     }
    481 
    482     private boolean hasMicrophone() {
    483         return mActivity.getPackageManager().hasSystemFeature(
    484                 PackageManager.FEATURE_MICROPHONE);
    485     }
    486 }
    487