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_TIME_LAPSE_LOOPS = 1;
54    private static final int TIME_LAPSE_PLAYBACK_WAIT_TIME = 30 * 1000; // 30 seconds
55    private static final int USE_TEST_RUNNER_PROFILE = -1;
56    private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds
57    private static final String OUTPUT_FILE_EXT = ".3gp";
58    private static final String MEDIA_STRESS_OUTPUT = "mediaStressOutput.txt";
59
60    private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback();
61    private final RecorderErrorCallback mRecorderErrorCallback = new RecorderErrorCallback();
62
63    private Handler mHandler;
64    private Thread mLooperThread;
65    private Writer mOutput;
66
67    public MediaRecorderStressTest() {
68        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
69    }
70
71    protected void setUp() throws Exception {
72        final Semaphore sem = new Semaphore(0);
73        mLooperThread = new Thread() {
74            @Override
75            public void run() {
76                Log.v(TAG, "starting looper");
77                Looper.prepare();
78                mHandler = new Handler();
79                sem.release();
80                Looper.loop();
81                Log.v(TAG, "quit looper");
82            }
83        };
84        mLooperThread.start();
85        if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
86            fail("Failed to start the looper.");
87        }
88        //Insert a 2 second before launching the test activity. This is
89        //the workaround for the race condition of requesting the updated surface.
90        Thread.sleep(2000);
91        getActivity();
92        super.setUp();
93
94        File stressOutFile = new File(String.format("%s/%s",
95                Environment.getExternalStorageDirectory(), MEDIA_STRESS_OUTPUT));
96        mOutput = new BufferedWriter(new FileWriter(stressOutFile, true));
97        mOutput.write(this.getName() + "\n");
98    }
99
100    @Override
101    protected void tearDown() throws Exception {
102        if (mHandler != null) {
103            mHandler.getLooper().quit();
104            mHandler = null;
105        }
106        if (mLooperThread != null) {
107            mLooperThread.join(WAIT_TIMEOUT);
108            if (mLooperThread.isAlive()) {
109                fail("Failed to stop the looper.");
110            }
111            mLooperThread = null;
112        }
113        mOutput.write("\n\n");
114        mOutput.close();
115        super.tearDown();
116    }
117
118    private void runOnLooper(final Runnable command) throws InterruptedException {
119        final Semaphore sem = new Semaphore(0);
120        mHandler.post(new Runnable() {
121            @Override
122            public void run() {
123                try {
124                    command.run();
125                } finally {
126                    sem.release();
127                }
128            }
129        });
130        if (! sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) {
131            fail("Failed to run the command on the looper.");
132        }
133    }
134
135    private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback {
136        public void onError(int error, android.hardware.Camera camera) {
137            fail(String.format("Camera error, code: %d", error));
138        }
139    }
140
141    private final class RecorderErrorCallback implements MediaRecorder.OnErrorListener {
142        public void onError(MediaRecorder mr, int what, int extra) {
143            fail(String.format("Media recorder error, code: %d\textra: %d", what, extra));
144        }
145    }
146
147    public void validateRecordedVideo(String recordedFile) {
148        try {
149            MediaPlayer mp = new MediaPlayer();
150            mp.setDataSource(recordedFile);
151            mp.prepare();
152            int duration = mp.getDuration();
153            if (duration <= 0){
154                fail("stressRecordAndPlayback");
155            }
156            mp.release();
157        } catch (Exception e) {
158            fail("stressRecordAndPlayback");
159        }
160    }
161
162    public void removeRecordedVideo(String fileName){
163        File video = new File(fileName);
164        Log.v(TAG, "remove recorded video " + fileName);
165        video.delete();
166    }
167
168    // Helper method for record & playback testing with different camcorder profiles
169    private void recordVideoAndPlayback(int profile) throws Exception {
170        int iterations;
171        int recordDuration;
172        boolean removeVideo;
173
174        int videoEncoder;
175        int audioEncoder;
176        int frameRate;
177        int videoWidth;
178        int videoHeight;
179        int bitRate;
180
181        if (profile != USE_TEST_RUNNER_PROFILE) {
182            assertTrue(String.format("Camera doesn't support profile %d", profile),
183                    CamcorderProfile.hasProfile(CAMERA_ID, profile));
184            CamcorderProfile camcorderProfile = CamcorderProfile.get(CAMERA_ID, profile);
185            videoEncoder = camcorderProfile.videoCodec;
186            audioEncoder = camcorderProfile.audioCodec;
187            frameRate = camcorderProfile.videoFrameRate;
188            videoWidth = camcorderProfile.videoFrameWidth;
189            videoHeight = camcorderProfile.videoFrameHeight;
190            bitRate = camcorderProfile.videoBitRate;
191        } else {
192            videoEncoder = MediaRecorderStressTestRunner.mVideoEncoder;
193            audioEncoder = MediaRecorderStressTestRunner.mAudioEncoder;
194            frameRate = MediaRecorderStressTestRunner.mFrameRate;
195            videoWidth = MediaRecorderStressTestRunner.mVideoWidth;
196            videoHeight = MediaRecorderStressTestRunner.mVideoHeight;
197            bitRate = MediaRecorderStressTestRunner.mBitRate;
198        }
199        iterations = MediaRecorderStressTestRunner.mIterations;
200        recordDuration = MediaRecorderStressTestRunner.mDuration;
201        removeVideo = MediaRecorderStressTestRunner.mRemoveVideo;
202
203        SurfaceHolder surfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
204        mOutput.write("Total number of loops: " + iterations + "\n");
205
206        try {
207            mOutput.write("No of loop: ");
208            for (int i = 0; i < iterations; i++) {
209                String fileName = String.format("%s/temp%d%s",
210                        Environment.getExternalStorageDirectory(), i, OUTPUT_FILE_EXT);
211                Log.v(TAG, fileName);
212
213                runOnLooper(new Runnable() {
214                    @Override
215                    public void run() {
216                        mRecorder = new MediaRecorder();
217                    }
218                });
219
220                Log.v(TAG, "iterations : " + iterations);
221                Log.v(TAG, "video encoder : " + videoEncoder);
222                Log.v(TAG, "audio encoder : " + audioEncoder);
223                Log.v(TAG, "frame rate : " + frameRate);
224                Log.v(TAG, "video width : " + videoWidth);
225                Log.v(TAG, "video height : " + videoHeight);
226                Log.v(TAG, "bit rate : " + bitRate);
227                Log.v(TAG, "record duration : " + recordDuration);
228
229                mRecorder.setOnErrorListener(mRecorderErrorCallback);
230                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
231                mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
232                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
233                mRecorder.setOutputFile(fileName);
234                mRecorder.setVideoFrameRate(frameRate);
235                mRecorder.setVideoSize(videoWidth, videoHeight);
236                mRecorder.setVideoEncoder(videoEncoder);
237                mRecorder.setAudioEncoder(audioEncoder);
238                mRecorder.setVideoEncodingBitRate(bitRate);
239
240                Log.v(TAG, "mediaRecorder setPreview");
241                mRecorder.setPreviewDisplay(surfaceHolder.getSurface());
242                mRecorder.prepare();
243                mRecorder.start();
244                Thread.sleep(recordDuration);
245                Log.v(TAG, "Before stop");
246                mRecorder.stop();
247                mRecorder.release();
248
249                //start the playback
250                MediaPlayer mp = new MediaPlayer();
251                mp.setDataSource(fileName);
252                mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
253                mp.prepare();
254                mp.start();
255                Thread.sleep(recordDuration);
256                mp.release();
257                validateRecordedVideo(fileName);
258                if (removeVideo) {
259                    removeRecordedVideo(fileName);
260                }
261                if (i == 0) {
262                    mOutput.write(i + 1);
263                } else {
264                    mOutput.write(String.format(", %d", (i + 1)));
265                }
266            }
267        } catch (Exception e) {
268            Log.e(TAG, e.toString());
269            fail("Record and playback");
270        }
271    }
272
273    // Record and playback stress test @ 1080P quality
274    @LargeTest
275    public void testStressRecordVideoAndPlayback1080P() throws Exception {
276        recordVideoAndPlayback(CamcorderProfile.QUALITY_1080P);
277    }
278
279    // Record and playback stress test @ 720P quality
280    @LargeTest
281    public void testStressRecordVideoAndPlayback720P() throws Exception {
282        recordVideoAndPlayback(CamcorderProfile.QUALITY_720P);
283    }
284
285    // Record and playback stress test @ 480P quality
286    @LargeTest
287    public void testStressRecordVideoAndPlayback480P() throws Exception {
288        recordVideoAndPlayback(CamcorderProfile.QUALITY_480P);
289    }
290
291    // This test method uses the codec info from the test runner. Use this
292    // for more granular control of video encoding.
293    @LargeTest
294    public void defaultStressRecordVideoAndPlayback() throws Exception {
295        recordVideoAndPlayback(USE_TEST_RUNNER_PROFILE);
296    }
297
298    // Test case for stressing time lapse
299    @LargeTest
300    public void testStressTimeLapse() throws Exception {
301        SurfaceHolder mSurfaceHolder;
302        mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
303        int recordDuration = MediaRecorderStressTestRunner.mTimeLapseDuration;
304        boolean removeVideo = MediaRecorderStressTestRunner.mRemoveVideo;
305        double captureRate = MediaRecorderStressTestRunner.mCaptureRate;
306        Log.v(TAG, "Start camera time lapse stress:");
307        mOutput.write("Total number of loops: " + NUMBER_OF_TIME_LAPSE_LOOPS + "\n");
308
309        try {
310            for (int i = 0, n = Camera.getNumberOfCameras(); i < n; i++) {
311                mOutput.write("No of loop: camera " + i);
312                for (int j = 0; j < NUMBER_OF_TIME_LAPSE_LOOPS; j++) {
313                    String fileName = String.format("%s/temp%d_%d%s",
314                            Environment.getExternalStorageDirectory(), i, j, OUTPUT_FILE_EXT);
315                    Log.v(TAG, fileName);
316                    runOnLooper(new Runnable() {
317                        @Override
318                        public void run() {
319                            mRecorder = new MediaRecorder();
320                        }
321                    });
322
323                    // Set callback
324                    mRecorder.setOnErrorListener(mRecorderErrorCallback);
325
326                    // Set video source
327                    mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
328
329                    // Set camcorder profile for time lapse
330                    CamcorderProfile profile =
331                        CamcorderProfile.get(j, CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
332                    mRecorder.setProfile(profile);
333
334                    // Set the timelapse setting; 0.1 = 10 sec timelapse, 0.5 = 2 sec timelapse, etc
335                    // http://developer.android.com/guide/topics/media/camera.html#time-lapse-video
336                    mRecorder.setCaptureRate(captureRate);
337
338                    // Set output file
339                    mRecorder.setOutputFile(fileName);
340
341                    // Set the preview display
342                    Log.v(TAG, "mediaRecorder setPreviewDisplay");
343                    mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
344
345                    mRecorder.prepare();
346                    mRecorder.start();
347                    Thread.sleep(recordDuration);
348                    Log.v(TAG, "Before stop");
349                    mRecorder.stop();
350                    mRecorder.release();
351
352                    // Start the playback
353                    MediaPlayer mp = new MediaPlayer();
354                    mp.setDataSource(fileName);
355                    mp.setDisplay(mSurfaceHolder);
356                    mp.prepare();
357                    mp.start();
358                    Thread.sleep(TIME_LAPSE_PLAYBACK_WAIT_TIME);
359                    mp.release();
360                    validateRecordedVideo(fileName);
361                    if (removeVideo) {
362                        removeRecordedVideo(fileName);
363                    }
364
365                    if (j == 0) {
366                        mOutput.write(j + 1);
367                    } else {
368                        mOutput.write(String.format(", %d", (j + 1)));
369                    }
370                }
371            }
372        } catch (IllegalStateException e) {
373            Log.e(TAG, e.toString());
374            fail("Camera time lapse stress test IllegalStateException");
375        } catch (IOException e) {
376            Log.e(TAG, e.toString());
377            fail("Camera time lapse stress test IOException");
378        } catch (Exception e) {
379            Log.e(TAG, e.toString());
380            fail("Camera time lapse stress test Exception");
381        }
382    }
383}
384