1 /*
2  * Copyright (C) 2012 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.media.cts.R;
20 
21 import android.media.MediaRecorder;
22 import android.media.MediaPlayer;
23 import android.view.SurfaceHolder;
24 import android.test.ActivityInstrumentationTestCase2;
25 import android.os.Environment;
26 import android.content.res.AssetFileDescriptor;
27 import android.content.res.Resources;
28 import android.util.Log;
29 
30 import java.util.Random;
31 
32 /**
33  * Tests for the MediaPlayer.java and MediaRecorder.java APIs
34  *
35  * These testcases make randomized calls to the public APIs available, and
36  * the focus is on whether the randomized calls can lead to crash in
37  * mediaserver process and/or ANRs.
38  *
39  * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
40  * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
41  * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
42  */
43 public class MediaRandomTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
44     private static final String TAG = "MediaRandomTest";
45 
46     private static final String OUTPUT_FILE =
47                 Environment.getExternalStorageDirectory().toString() + "/record.3gp";
48 
49     private static final int NUMBER_OF_RECORDER_RANDOM_ACTIONS = 100000;
50     private static final int NUMBER_OF_PLAYER_RANDOM_ACTIONS   = 100000;
51 
52     private MediaRecorder mRecorder;
53     private MediaPlayer mPlayer;
54     private SurfaceHolder mSurfaceHolder;
55     private Resources mResources;
56 
57     // Modified across multiple threads
58     private volatile boolean mMediaServerDied;
59     private volatile int mAction;
60     private volatile int mParam;
61 
62     @Override
setUp()63     protected void setUp() throws Exception {
64         super.setUp();
65         getInstrumentation().waitForIdleSync();
66         mMediaServerDied = false;
67         mSurfaceHolder = getActivity().getSurfaceHolder();
68         mResources = getInstrumentation().getTargetContext().getResources();
69         try {
70             // Running this on UI thread make sure that
71             // onError callback can be received.
72             runTestOnUiThread(new Runnable() {
73                 public void run() {
74                     mRecorder = new MediaRecorder();
75                     mPlayer = new MediaPlayer();
76                 }
77             });
78         } catch (Throwable e) {
79             e.printStackTrace();
80             fail();
81         }
82     }
83 
84     @Override
tearDown()85     protected void tearDown() throws Exception {
86         if (mRecorder != null) {
87             mRecorder.release();
88             mRecorder = null;
89         }
90         if (mPlayer != null) {
91             mPlayer.release();
92             mPlayer = null;
93         }
94         super.tearDown();
95     }
96 
97     /**
98      * This is a watchdog used to stop the process if it hasn't been pinged
99      * for more than specified milli-seconds. It is used like:
100      *
101      * Watchdog w = new Watchdog(10000);  // 10 seconds.
102      * w.start();       // start the watchdog.
103      * ...
104      * w.ping();
105      * ...
106      * w.ping();
107      * ...
108      * w.end();        // ask the watchdog to stop.
109      * w.join();        // join the thread.
110      */
111     class Watchdog extends Thread {
112         private final long mTimeoutMs;
113         private boolean mWatchdogStop;
114         private boolean mWatchdogPinged;
115 
Watchdog(long timeoutMs)116         public Watchdog(long timeoutMs) {
117             mTimeoutMs = timeoutMs;
118             mWatchdogStop = false;
119             mWatchdogPinged = false;
120         }
121 
run()122         public synchronized void run() {
123             while (true) {
124                 // avoid early termination by "spurious" waitup.
125                 final long startTimeMs = System.currentTimeMillis();
126                 long remainingWaitTimeMs = mTimeoutMs;
127                 do {
128                     try {
129                         wait(remainingWaitTimeMs);
130                     } catch (InterruptedException ex) {
131                         // ignore.
132                     }
133                     remainingWaitTimeMs = mTimeoutMs - (System.currentTimeMillis() - startTimeMs);
134                 } while (remainingWaitTimeMs > 0);
135 
136                 if (mWatchdogStop) {
137                     break;
138                 }
139 
140                 if (!mWatchdogPinged) {
141                     fail("Action " + mAction + " Param " + mParam
142                             + " waited over " + (mTimeoutMs - remainingWaitTimeMs) + " ms");
143                     return;
144                 }
145                 mWatchdogPinged = false;
146             }
147         }
148 
ping()149         public synchronized void ping() {
150             mWatchdogPinged = true;
151             this.notify();
152         }
153 
end()154         public synchronized void end() {
155             mWatchdogStop = true;
156             this.notify();
157         }
158     }
159 
MediaRandomTest()160     public MediaRandomTest() {
161         super("android.media.cts", MediaStubActivity.class);
162     }
163 
loadSource(int resid)164     private void loadSource(int resid) throws Exception {
165         AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
166         try {
167             mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
168                     afd.getLength());
169         } finally {
170             afd.close();
171         }
172     }
testPlayerRandomActionH264()173     public void testPlayerRandomActionH264() throws Exception {
174         testPlayerRandomAction(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz);
175     }
testPlayerRandomActionHEVC()176     public void testPlayerRandomActionHEVC() throws Exception {
177         testPlayerRandomAction(R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz);
178     }
testPlayerRandomAction(int resid)179     private void testPlayerRandomAction(int resid) throws Exception {
180         Watchdog watchdog = new Watchdog(5000);
181         try {
182             mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
183                 @Override
184                 public boolean onError(MediaPlayer mp, int what, int extra) {
185                     if (mPlayer == mp &&
186                         what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
187                         Log.e(TAG, "mediaserver process died");
188                         mMediaServerDied = true;
189                     }
190                     return true;
191                 }
192             });
193             loadSource(resid);
194             mPlayer.setDisplay(mSurfaceHolder);
195             mPlayer.prepare();
196             mPlayer.start();
197 
198             long seed = System.currentTimeMillis();
199             Log.v(TAG, "seed = " + seed);
200             Random r = new Random(seed);
201 
202             watchdog.start();
203             for (int i = 0; i < NUMBER_OF_PLAYER_RANDOM_ACTIONS; i++){
204                 watchdog.ping();
205                 assertTrue(!mMediaServerDied);
206 
207                 mAction = (int)(r.nextInt() % 12);
208                 mParam = (int)(r.nextInt() % 1000000);
209                 try {
210                     switch (mAction) {
211                     case 0:
212                         mPlayer.getCurrentPosition();
213                         break;
214                     case 1:
215                         mPlayer.getDuration();
216                         break;
217                     case 2:
218                         mPlayer.getVideoHeight();
219                         break;
220                     case 3:
221                         mPlayer.getVideoWidth();
222                        break;
223                     case 4:
224                         mPlayer.isPlaying();
225                         break;
226                     case 5:
227                         mPlayer.pause();
228                         break;
229                     case 6:
230                         // Don't add mPlayer.prepare() call here for two reasons:
231                         // 1. calling prepare() is a bad idea since it is a blocking call, and
232                         // 2. when prepare() is in progress, mediaserver died message will not be sent to apps
233                         mPlayer.prepareAsync();
234                         break;
235                     case 7:
236                         mPlayer.seekTo((int)(mParam));
237                         break;
238                     case 8:
239                         mPlayer.setLooping(mParam % 2 == 0);
240                         break;
241                     case 9:
242                         mPlayer.setVolume((mParam % 1000) / 500.0f,
243                                      (mParam / 1000) / 500.0f);
244                         break;
245                     case 10:
246                         mPlayer.start();
247                         break;
248                     case 11:
249                         Thread.sleep(mParam % 20);
250                         break;
251                     }
252                 } catch (Exception e) {
253                 }
254             }
255             mPlayer.stop();
256         } catch (Exception e) {
257             Log.v(TAG, e.toString());
258         } finally {
259             watchdog.end();
260             watchdog.join();
261         }
262     }
263 
testRecorderRandomAction()264     public void testRecorderRandomAction() throws Exception {
265         Watchdog watchdog = new Watchdog(5000);
266         try {
267             long seed = System.currentTimeMillis();
268             Log.v(TAG, "seed = " + seed);
269             Random r = new Random(seed);
270 
271             mMediaServerDied = false;
272             mRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
273                 @Override
274                 public void onError(MediaRecorder recorder, int what, int extra) {
275                     if (mRecorder == recorder &&
276                         what == MediaRecorder.MEDIA_ERROR_SERVER_DIED) {
277                         Log.e(TAG, "mediaserver process died");
278                         mMediaServerDied = true;
279                     }
280                 }
281             });
282 
283             final int[] width  = {176, 352, 320, 640, 1280, 1920};
284             final int[] height = {144, 288, 240, 480,  720, 1080};
285             final int[] audioSource = {
286                     MediaRecorder.AudioSource.DEFAULT,
287                     MediaRecorder.AudioSource.MIC,
288                     MediaRecorder.AudioSource.CAMCORDER,
289             };
290 
291             watchdog.start();
292             for (int i = 0; i < NUMBER_OF_RECORDER_RANDOM_ACTIONS; i++) {
293                 watchdog.ping();
294                 assertTrue(!mMediaServerDied);
295 
296                 mAction = (int)(r.nextInt(14));
297                 mParam = (int)(r.nextInt(1000000));
298                 try {
299                     switch (mAction) {
300                     case 0: {
301                         // We restrict the audio sources because setting some sources
302                         // may cause 2+ second delays because the input device may
303                         // retry - loop (e.g. VOICE_UPLINK for voice call to be initiated).
304                         final int index = mParam % audioSource.length;
305                         mRecorder.setAudioSource(audioSource[index]);
306                         break;
307                     }
308                     case 1:
309                         // XXX:
310                         // Fix gralloc source and change
311                         // mRecorder.setVideoSource(mParam % 3);
312                         mRecorder.setVideoSource(mParam % 2);
313                         break;
314                     case 2:
315                         mRecorder.setOutputFormat(mParam % 5);
316                         break;
317                     case 3:
318                         mRecorder.setAudioEncoder(mParam % 3);
319                         break;
320                     case 4:
321                         mRecorder.setVideoEncoder(mParam % 5);
322                         break;
323                     case 5:
324                         mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
325                         break;
326                     case 6:
327                         int index = mParam % width.length;
328                         mRecorder.setVideoSize(width[index], height[index]);
329                         break;
330                     case 7:
331                         mRecorder.setVideoFrameRate(mParam % 40 - 5);
332                         break;
333                     case 8:
334                         mRecorder.setOutputFile(OUTPUT_FILE);
335                         break;
336                     case 9:
337                         mRecorder.prepare();
338                         break;
339                     case 10:
340                         mRecorder.start();
341                         break;
342                     case 11:
343                         Thread.sleep(mParam % 20);
344                         break;
345                     case 12:
346                         mRecorder.stop();
347                         break;
348                     case 13:
349                         mRecorder.reset();
350                         break;
351                     default:
352                         break;
353                     }
354                 } catch (Exception e) {
355                 }
356             }
357         } catch (Exception e) {
358             Log.v(TAG, e.toString());
359         } finally {
360             watchdog.end();
361             watchdog.join();
362         }
363     }
364 }
365