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