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