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
17package com.android.mediaframeworktest.stress;
18
19
20import com.android.mediaframeworktest.MediaFrameworkTest;
21
22import java.io.BufferedWriter;
23import java.io.File;
24import java.io.FileWriter;
25import java.io.IOException;
26import java.io.Writer;
27import java.util.concurrent.Semaphore;
28import java.util.concurrent.TimeUnit;
29
30import android.hardware.Camera;
31import android.media.CamcorderProfile;
32import android.media.MediaPlayer;
33import android.media.MediaRecorder;
34import android.os.Environment;
35import android.os.Handler;
36import android.os.Looper;
37import android.test.ActivityInstrumentationTestCase2;
38import android.test.suitebuilder.annotation.LargeTest;
39import android.util.Log;
40import android.view.SurfaceHolder;
41import com.android.mediaframeworktest.MediaRecorderStressTestRunner;
42
43/**
44 * Junit / Instrumentation test case for the media player api
45 */
46public class MediaRecorderStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
47
48    private String TAG = "MediaRecorderStressTest";
49    private MediaRecorder mRecorder;
50    private Camera mCamera;
51
52    private static final int CAMERA_ID = 0;
53    private static final int NUMBER_OF_CAMERA_STRESS_LOOPS = 100;
54    private static final int NUMBER_OF_RECORDER_STRESS_LOOPS = 100;
55    private static final int NUMBER_OF_RECORDERANDPLAY_STRESS_LOOPS = 50;
56    private static final int NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER = 200;
57    private static final int NUMBER_OF_TIME_LAPSE_LOOPS = 25;
58    private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 5* 1000; // 5 seconds
59    private static final int USE_TEST_RUNNER_PROFILE = -1;
60    private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds
61    private static final long WAIT_TIME_CAMERA_TEST = 3 * 1000; // 3 seconds
62    private static final long WAIT_TIME_RECORDER_TEST = 6 * 1000; // 6 seconds
63    private static final String OUTPUT_FILE_EXT = ".3gp";
64    private static final String MEDIA_STRESS_OUTPUT = "mediaStressOutput.txt";
65
66    private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback();
67    private final RecorderErrorCallback mRecorderErrorCallback = new RecorderErrorCallback();
68
69    private Handler mHandler;
70    private Thread mLooperThread;
71    private Writer mOutput;
72
73    public MediaRecorderStressTest() {
74        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
75    }
76
77    protected void setUp() throws Exception {
78        final Semaphore sem = new Semaphore(0);
79        mLooperThread = new Thread() {
80            @Override
81            public void run() {
82                Log.v(TAG, "starting looper");
83                Looper.prepare();
84                mHandler = new Handler();
85                sem.release();
86                Looper.loop();
87                Log.v(TAG, "quit looper");
88            }
89        };
90        mLooperThread.start();
91        if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
92            fail("Failed to start the looper.");
93        }
94        //Insert a 2 second before launching the test activity. This is
95        //the workaround for the race condition of requesting the updated surface.
96        Thread.sleep(2000);
97        getActivity();
98        super.setUp();
99
100        File stressOutFile = new File(String.format("%s/%s",
101                Environment.getExternalStorageDirectory(), MEDIA_STRESS_OUTPUT));
102        mOutput = new BufferedWriter(new FileWriter(stressOutFile, true));
103        mOutput.write(this.getName() + "\n");
104    }
105
106    @Override
107    protected void tearDown() throws Exception {
108        if (mHandler != null) {
109            mHandler.getLooper().quit();
110            mHandler = null;
111        }
112        if (mLooperThread != null) {
113            mLooperThread.join(WAIT_TIMEOUT);
114            if (mLooperThread.isAlive()) {
115                fail("Failed to stop the looper.");
116            }
117            mLooperThread = null;
118        }
119        mOutput.write("\n\n");
120        mOutput.close();
121        super.tearDown();
122    }
123
124    private void runOnLooper(final Runnable command) throws InterruptedException {
125        final Semaphore sem = new Semaphore(0);
126        mHandler.post(new Runnable() {
127            @Override
128            public void run() {
129                try {
130                    command.run();
131                } finally {
132                    sem.release();
133                }
134            }
135        });
136        if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
137            fail("Failed to run the command on the looper.");
138        }
139    }
140
141    private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback {
142        public void onError(int error, android.hardware.Camera camera) {
143            fail(String.format("Camera error, code: %d", error));
144        }
145    }
146
147    private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener {
148        public void onError(MediaRecorder mr, int what, int extra) {
149            fail(String.format("Media recorder error, code: %d\textra: %d", what, extra));
150        }
151    }
152
153    //Test case for stressing the camera preview.
154    @LargeTest
155    public void testStressCamera() throws Exception {
156        SurfaceHolder mSurfaceHolder;
157        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
158        Log.v(TAG, "Camera start preview stress test");
159        mOutput.write("Total number of loops:" + NUMBER_OF_CAMERA_STRESS_LOOPS + "\n");
160        try {
161            Log.v(TAG, "Start preview");
162            mOutput.write("No of loop: ");
163
164            for (int i = 0; i< NUMBER_OF_CAMERA_STRESS_LOOPS; i++) {
165                runOnLooper(new Runnable() {
166                    @Override
167                    public void run() {
168                        mCamera = Camera.open(CAMERA_ID);
169                    }
170                });
171                mCamera.setErrorCallback(mCameraErrorCallback);
172                mCamera.setPreviewDisplay(mSurfaceHolder);
173                mCamera.startPreview();
174                Thread.sleep(WAIT_TIME_CAMERA_TEST);
175                mCamera.stopPreview();
176                mCamera.release();
177                if (i == 0) {
178                    mOutput.write(i + 1);
179                } else {
180                    mOutput.write(String.format(", %d", (i + 1)));
181                }
182            }
183        } catch (Exception e) {
184            Log.e(TAG, e.toString());
185            fail("Camera startup preview stress test");
186        }
187    }
188
189    //Test case for stressing the camera preview.
190    @LargeTest
191    public void testStressRecorder() throws Exception {
192        SurfaceHolder mSurfaceHolder;
193        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
194        Log.v(TAG, "H263 video record: reset after prepare Stress test");
195        mOutput.write("Total number of loops:" + NUMBER_OF_RECORDER_STRESS_LOOPS + "\n");
196        try {
197            mOutput.write("No of loop: ");
198            Log.v(TAG, "Start preview");
199            for (int i = 0; i < NUMBER_OF_RECORDER_STRESS_LOOPS; i++) {
200                runOnLooper(new Runnable() {
201                    @Override
202                    public void run() {
203                        mRecorder = new MediaRecorder();
204                    }
205                });
206                Log.v(TAG, "counter = " + i);
207                String fileName = String.format("%s/temp%d%s",
208                        Environment.getExternalStorageDirectory(),
209                        i, OUTPUT_FILE_EXT);
210
211                Log.v(TAG, fileName);
212                mRecorder.setOnErrorListener(mRecorderErrorCallback);
213                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
214                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
215                mRecorder.setOutputFile(fileName);
216                mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate);
217                mRecorder.setVideoSize(176,144);
218                Log.v(TAG, "setEncoder");
219                mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
220                mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
221                Log.v(TAG, "setPreview");
222                mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
223                Log.v(TAG, "prepare");
224                mRecorder.prepare();
225                Log.v(TAG, "before release");
226                Thread.sleep(WAIT_TIME_RECORDER_TEST);
227                mRecorder.reset();
228                mRecorder.release();
229                if (i == 0) {
230                    mOutput.write(i + 1);
231                } else {
232                    mOutput.write(String.format(", %d", (i + 1)));
233                }
234            }
235        } catch (Exception e) {
236            Log.e(TAG, e.toString());
237            fail("H263 video recording stress test");
238        }
239    }
240
241    //Stress test case for switching camera and video recorder preview.
242    @LargeTest
243    public void testStressCameraSwitchRecorder() throws Exception {
244        SurfaceHolder mSurfaceHolder;
245        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
246        Log.v(TAG, "Camera and video recorder preview switching");
247        mOutput.write("Total number of loops: " +
248                NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER + "\n");
249        try {
250            Log.v(TAG, "Start preview");
251            mOutput.write("No of loop: ");
252            for (int i = 0; i < NUMBER_OF_SWTICHING_LOOPS_BW_CAMERA_AND_RECORDER; i++) {
253                runOnLooper(new Runnable() {
254                    @Override
255                    public void run() {
256                        mCamera = Camera.open(CAMERA_ID);
257                    }
258                });
259                mCamera.setErrorCallback(mCameraErrorCallback);
260                mCamera.setPreviewDisplay(mSurfaceHolder);
261                mCamera.startPreview();
262                Thread.sleep(WAIT_TIME_CAMERA_TEST);
263                mCamera.stopPreview();
264                mCamera.release();
265                mCamera = null;
266                Log.v(TAG, "release camera");
267                String fileName = String.format("%s/temp%d%s",
268                        Environment.getExternalStorageDirectory(),
269                        i, OUTPUT_FILE_EXT);
270                Log.v(TAG, fileName);
271                runOnLooper(new Runnable() {
272                    @Override
273                    public void run() {
274                        mRecorder = new MediaRecorder();
275                    }
276                });
277                mRecorder.setOnErrorListener(mRecorderErrorCallback);
278                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
279                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
280                mRecorder.setOutputFile(fileName);
281                mRecorder.setVideoFrameRate(MediaRecorderStressTestRunner.mFrameRate);
282                mRecorder.setVideoSize(176,144);
283                Log.v(TAG, "Media recorder setEncoder");
284                mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H263);
285                Log.v(TAG, "mediaRecorder setPreview");
286                mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
287                Log.v(TAG, "prepare");
288                mRecorder.prepare();
289                Log.v(TAG, "before release");
290                Thread.sleep(WAIT_TIME_CAMERA_TEST);
291                mRecorder.release();
292                Log.v(TAG, "release video recorder");
293                if (i == 0) {
294                    mOutput.write(i + 1);
295                } else {
296                    mOutput.write(String.format(", %d", (i + 1)));
297                }
298            }
299        } catch (Exception e) {
300            Log.e(TAG, e.toString());
301            fail("Camera and recorder switch mode");
302        }
303    }
304
305    public void validateRecordedVideo(String recordedFile) {
306        try {
307            MediaPlayer mp = new MediaPlayer();
308            mp.setDataSource(recordedFile);
309            mp.prepare();
310            int duration = mp.getDuration();
311            if (duration <= 0){
312                fail("stressRecordAndPlayback");
313            }
314            mp.release();
315        } catch (Exception e) {
316            fail("stressRecordAndPlayback");
317        }
318    }
319
320    public void removeRecordedVideo(String fileName){
321        File video = new File(fileName);
322        Log.v(TAG, "remove recorded video " + fileName);
323        video.delete();
324    }
325
326    // Helper method for record & playback testing with different camcorder profiles
327    private void recordVideoAndPlayback(int profile) throws Exception {
328        int iterations;
329        int recordDuration;
330        boolean removeVideo;
331
332        int videoEncoder;
333        int audioEncoder;
334        int frameRate;
335        int videoWidth;
336        int videoHeight;
337        int bitRate;
338
339        if (profile != USE_TEST_RUNNER_PROFILE) {
340            assertTrue(String.format("Camera doesn't support profile %d", profile),
341                    CamcorderProfile.hasProfile(CAMERA_ID, profile));
342            CamcorderProfile camcorderProfile = CamcorderProfile.get(CAMERA_ID, profile);
343            videoEncoder = camcorderProfile.videoCodec;
344            audioEncoder = camcorderProfile.audioCodec;
345            frameRate = camcorderProfile.videoFrameRate;
346            videoWidth = camcorderProfile.videoFrameWidth;
347            videoHeight = camcorderProfile.videoFrameHeight;
348            bitRate = camcorderProfile.videoBitRate;
349        } else {
350            videoEncoder = MediaRecorderStressTestRunner.mVideoEncoder;
351            audioEncoder = MediaRecorderStressTestRunner.mAudioEncoder;
352            frameRate = MediaRecorderStressTestRunner.mFrameRate;
353            videoWidth = MediaRecorderStressTestRunner.mVideoWidth;
354            videoHeight = MediaRecorderStressTestRunner.mVideoHeight;
355            bitRate = MediaRecorderStressTestRunner.mBitRate;
356        }
357        iterations = MediaRecorderStressTestRunner.mIterations;
358        recordDuration = MediaRecorderStressTestRunner.mDuration;
359        removeVideo = MediaRecorderStressTestRunner.mRemoveVideo;
360
361        SurfaceHolder surfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
362        mOutput.write("Total number of loops: " + iterations + "\n");
363
364        try {
365            mOutput.write("No of loop: ");
366            for (int i = 0; i < iterations; i++) {
367                String fileName = String.format("%s/temp%d%s",
368                        Environment.getExternalStorageDirectory(), i, OUTPUT_FILE_EXT);
369                Log.v(TAG, fileName);
370
371                runOnLooper(new Runnable() {
372                    @Override
373                    public void run() {
374                        mRecorder = new MediaRecorder();
375                    }
376                });
377
378                Log.v(TAG, "iterations : " + iterations);
379                Log.v(TAG, "video encoder : " + videoEncoder);
380                Log.v(TAG, "audio encoder : " + audioEncoder);
381                Log.v(TAG, "frame rate : " + frameRate);
382                Log.v(TAG, "video width : " + videoWidth);
383                Log.v(TAG, "video height : " + videoHeight);
384                Log.v(TAG, "bit rate : " + bitRate);
385                Log.v(TAG, "record duration : " + recordDuration);
386
387                mRecorder.setOnErrorListener(mRecorderErrorCallback);
388                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
389                mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
390                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
391                mRecorder.setOutputFile(fileName);
392                mRecorder.setVideoFrameRate(frameRate);
393                mRecorder.setVideoSize(videoWidth, videoHeight);
394                mRecorder.setVideoEncoder(videoEncoder);
395                mRecorder.setAudioEncoder(audioEncoder);
396                mRecorder.setVideoEncodingBitRate(bitRate);
397
398                Log.v(TAG, "mediaRecorder setPreview");
399                mRecorder.setPreviewDisplay(surfaceHolder.getSurface());
400                mRecorder.prepare();
401                mRecorder.start();
402                Thread.sleep(recordDuration);
403                Log.v(TAG, "Before stop");
404                mRecorder.stop();
405                mRecorder.release();
406
407                //start the playback
408                MediaPlayer mp = new MediaPlayer();
409                mp.setDataSource(fileName);
410                mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
411                mp.prepare();
412                mp.start();
413                Thread.sleep(recordDuration);
414                mp.release();
415                validateRecordedVideo(fileName);
416                if (removeVideo) {
417                    removeRecordedVideo(fileName);
418                }
419                if (i == 0) {
420                    mOutput.write(i + 1);
421                } else {
422                    mOutput.write(String.format(", %d", (i + 1)));
423                }
424            }
425        } catch (Exception e) {
426            Log.e(TAG, e.toString());
427            fail("Record and playback");
428        }
429    }
430
431    // Record and playback stress test @ 1080P quality
432    @LargeTest
433    public void testStressRecordVideoAndPlayback1080P() throws Exception {
434        recordVideoAndPlayback(CamcorderProfile.QUALITY_1080P);
435    }
436
437    // Record and playback stress test @ 720P quality
438    @LargeTest
439    public void testStressRecordVideoAndPlayback720P() throws Exception {
440        recordVideoAndPlayback(CamcorderProfile.QUALITY_720P);
441    }
442
443    // Record and playback stress test @ 480P quality
444    @LargeTest
445    public void testStressRecordVideoAndPlayback480P() throws Exception {
446        recordVideoAndPlayback(CamcorderProfile.QUALITY_480P);
447    }
448
449    // This test method uses the codec info from the test runner. Use this
450    // for more granular control of video encoding.
451    @LargeTest
452    public void defaultStressRecordVideoAndPlayback() throws Exception {
453        recordVideoAndPlayback(USE_TEST_RUNNER_PROFILE);
454    }
455
456    // Test case for stressing time lapse
457    @LargeTest
458    public void testStressTimeLapse() throws Exception {
459        SurfaceHolder mSurfaceHolder;
460        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
461        int recordDuration = MediaRecorderStressTestRunner.mTimeLapseDuration;
462        boolean removeVideo = MediaRecorderStressTestRunner.mRemoveVideo;
463        double captureRate = MediaRecorderStressTestRunner.mCaptureRate;
464        Log.v(TAG, "Start camera time lapse stress:");
465        mOutput.write("Total number of loops: " + NUMBER_OF_TIME_LAPSE_LOOPS + "\n");
466
467        try {
468            for (int i = 0, n = Camera.getNumberOfCameras(); i < n; i++) {
469                mOutput.write("No of loop: camera " + i);
470                for (int j = 0; j < NUMBER_OF_TIME_LAPSE_LOOPS; j++) {
471                    String fileName = String.format("%s/temp%d_%d%s",
472                            Environment.getExternalStorageDirectory(), i, j, OUTPUT_FILE_EXT);
473                    Log.v(TAG, fileName);
474                    runOnLooper(new Runnable() {
475                        @Override
476                        public void run() {
477                            mRecorder = new MediaRecorder();
478                        }
479                    });
480
481                    // Set callback
482                    mRecorder.setOnErrorListener(mRecorderErrorCallback);
483
484                    // Set video source
485                    mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
486
487                    // Set camcorder profile for time lapse
488                    CamcorderProfile profile =
489                        CamcorderProfile.get(j, CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
490                    mRecorder.setProfile(profile);
491
492                    // Set the timelapse setting; 0.1 = 10 sec timelapse, 0.5 = 2 sec timelapse, etc
493                    // http://developer.android.com/guide/topics/media/camera.html#time-lapse-video
494                    mRecorder.setCaptureRate(captureRate);
495
496                    // Set output file
497                    mRecorder.setOutputFile(fileName);
498
499                    // Set the preview display
500                    Log.v(TAG, "mediaRecorder setPreviewDisplay");
501                    mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
502
503                    mRecorder.prepare();
504                    mRecorder.start();
505                    Thread.sleep(recordDuration);
506                    Log.v(TAG, "Before stop");
507                    mRecorder.stop();
508                    mRecorder.release();
509
510                    // Start the playback
511                    MediaPlayer mp = new MediaPlayer();
512                    mp.setDataSource(fileName);
513                    mp.setDisplay(mSurfaceHolder);
514                    mp.prepare();
515                    mp.start();
516                    Thread.sleep(TIME_LAPSE_PLAYBACK_WAIT_TIME);
517                    mp.release();
518                    validateRecordedVideo(fileName);
519                    if (removeVideo) {
520                        removeRecordedVideo(fileName);
521                    }
522
523                    if (j == 0) {
524                        mOutput.write(j + 1);
525                    } else {
526                        mOutput.write(String.format(", %d", (j + 1)));
527                    }
528                }
529            }
530        } catch (IllegalStateException e) {
531            Log.e(TAG, e.toString());
532            fail("Camera time lapse stress test IllegalStateException");
533        } catch (IOException e) {
534            Log.e(TAG, e.toString());
535            fail("Camera time lapse stress test IOException");
536        } catch (Exception e) {
537            Log.e(TAG, e.toString());
538            fail("Camera time lapse stress test Exception");
539        }
540    }
541}
542