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.media.MediaRecorder;
20 import android.media.MediaPlayer;
21 import android.os.Environment;
22 import android.os.ParcelFileDescriptor;
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.io.File;
29 import java.util.Random;
30 
31 /**
32  * Tests for the MediaPlayer.java and MediaRecorder.java APIs
33  *
34  * These testcases make randomized calls to the public APIs available, and
35  * the focus is on whether the randomized calls can lead to crash in
36  * mediaserver process and/or ANRs.
37  *
38  * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
39  * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
40  * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
41  */
42 @NonMediaMainlineTest
43 @MediaHeavyPresubmitTest
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     static final String mInpPrefix = WorkDir.getMediaDirString();
49     private static final String OUTPUT_FILE =
50                 Environment.getExternalStorageDirectory().toString() + "/record.3gp";
51 
52     private static final int NUMBER_OF_RECORDER_RANDOM_ACTIONS = 100000;
53     private static final int NUMBER_OF_PLAYER_RANDOM_ACTIONS   = 100000;
54 
55     private MediaRecorder mRecorder;
56     private MediaPlayer mPlayer;
57     private SurfaceHolder mSurfaceHolder;
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         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(final String res)165     private void loadSource(final String res) throws Exception {
166         Preconditions.assertTestFileExists(mInpPrefix + res);
167         File inpFile = new File(mInpPrefix + res);
168         ParcelFileDescriptor parcelFD =
169                 ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
170         AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
171         try {
172             mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
173                     afd.getLength());
174         } finally {
175             afd.close();
176         }
177     }
testPlayerRandomActionAV1()178     public void testPlayerRandomActionAV1() throws Exception {
179         testPlayerRandomAction(
180                 "video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
181     }
testPlayerRandomActionH264()182     public void testPlayerRandomActionH264() throws Exception {
183         testPlayerRandomAction(
184                 "video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
185     }
testPlayerRandomActionHEVC()186     public void testPlayerRandomActionHEVC() throws Exception {
187         testPlayerRandomAction(
188                 "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
189     }
testPlayerRandomActionMpeg2()190     public void testPlayerRandomActionMpeg2() throws Exception {
191         testPlayerRandomAction(
192                 "video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
193     }
testPlayerRandomAction(final String res)194     private void testPlayerRandomAction(final String res) throws Exception {
195         Watchdog watchdog = new Watchdog(5000);
196         try {
197             mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
198                 @Override
199                 public boolean onError(MediaPlayer mp, int what, int extra) {
200                     if (mPlayer == mp &&
201                         what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
202                         Log.e(TAG, "mediaserver process died");
203                         mMediaServerDied = true;
204                     }
205                     return true;
206                 }
207             });
208             loadSource(res);
209             mPlayer.setDisplay(mSurfaceHolder);
210             mPlayer.prepare();
211             mPlayer.start();
212 
213             long seed = System.currentTimeMillis();
214             Log.v(TAG, "seed = " + seed);
215             Random r = new Random(seed);
216 
217             watchdog.start();
218             for (int i = 0; i < NUMBER_OF_PLAYER_RANDOM_ACTIONS; i++){
219                 watchdog.ping();
220                 assertTrue(!mMediaServerDied);
221 
222                 mAction = (int)(r.nextInt() % 12);
223                 mParam = (int)(r.nextInt() % 1000000);
224                 try {
225                     switch (mAction) {
226                     case 0:
227                         mPlayer.getCurrentPosition();
228                         break;
229                     case 1:
230                         mPlayer.getDuration();
231                         break;
232                     case 2:
233                         mPlayer.getVideoHeight();
234                         break;
235                     case 3:
236                         mPlayer.getVideoWidth();
237                        break;
238                     case 4:
239                         mPlayer.isPlaying();
240                         break;
241                     case 5:
242                         mPlayer.pause();
243                         break;
244                     case 6:
245                         // Don't add mPlayer.prepare() call here for two reasons:
246                         // 1. calling prepare() is a bad idea since it is a blocking call, and
247                         // 2. when prepare() is in progress, mediaserver died message will not be sent to apps
248                         mPlayer.prepareAsync();
249                         break;
250                     case 7:
251                         mPlayer.seekTo((int)(mParam));
252                         break;
253                     case 8:
254                         mPlayer.setLooping(mParam % 2 == 0);
255                         break;
256                     case 9:
257                         mPlayer.setVolume((mParam % 1000) / 500.0f,
258                                      (mParam / 1000) / 500.0f);
259                         break;
260                     case 10:
261                         mPlayer.start();
262                         break;
263                     case 11:
264                         Thread.sleep(mParam % 20);
265                         break;
266                     }
267                 } catch (Exception e) {
268                 }
269             }
270             mPlayer.stop();
271         } catch (Exception e) {
272             Log.v(TAG, e.toString());
273         } finally {
274             watchdog.end();
275             watchdog.join();
276         }
277     }
278 
testRecorderRandomAction()279     public void testRecorderRandomAction() throws Exception {
280         Watchdog watchdog = new Watchdog(5000);
281         try {
282             long seed = System.currentTimeMillis();
283             Log.v(TAG, "seed = " + seed);
284             Random r = new Random(seed);
285 
286             mMediaServerDied = false;
287             mRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
288                 @Override
289                 public void onError(MediaRecorder recorder, int what, int extra) {
290                     if (mRecorder == recorder &&
291                         what == MediaRecorder.MEDIA_ERROR_SERVER_DIED) {
292                         Log.e(TAG, "mediaserver process died");
293                         mMediaServerDied = true;
294                     }
295                 }
296             });
297 
298             final int[] width  = {176, 352, 320, 640, 1280, 1920};
299             final int[] height = {144, 288, 240, 480,  720, 1080};
300             final int[] audioSource = {
301                     MediaRecorder.AudioSource.DEFAULT,
302                     MediaRecorder.AudioSource.MIC,
303                     MediaRecorder.AudioSource.CAMCORDER,
304             };
305 
306             watchdog.start();
307             for (int i = 0; i < NUMBER_OF_RECORDER_RANDOM_ACTIONS; i++) {
308                 watchdog.ping();
309                 assertTrue(!mMediaServerDied);
310 
311                 mAction = (int)(r.nextInt(14));
312                 mParam = (int)(r.nextInt(1000000));
313                 try {
314                     switch (mAction) {
315                     case 0: {
316                         // We restrict the audio sources because setting some sources
317                         // may cause 2+ second delays because the input device may
318                         // retry - loop (e.g. VOICE_UPLINK for voice call to be initiated).
319                         final int index = mParam % audioSource.length;
320                         mRecorder.setAudioSource(audioSource[index]);
321                         break;
322                     }
323                     case 1:
324                         // XXX:
325                         // Fix gralloc source and change
326                         // mRecorder.setVideoSource(mParam % 3);
327                         mRecorder.setVideoSource(mParam % 2);
328                         break;
329                     case 2:
330                         mRecorder.setOutputFormat(mParam % 5);
331                         break;
332                     case 3:
333                         mRecorder.setAudioEncoder(mParam % 3);
334                         break;
335                     case 4:
336                         mRecorder.setVideoEncoder(mParam % 5);
337                         break;
338                     case 5:
339                         mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
340                         break;
341                     case 6:
342                         int index = mParam % width.length;
343                         mRecorder.setVideoSize(width[index], height[index]);
344                         break;
345                     case 7:
346                         mRecorder.setVideoFrameRate(mParam % 40 - 5);
347                         break;
348                     case 8:
349                         mRecorder.setOutputFile(OUTPUT_FILE);
350                         break;
351                     case 9:
352                         mRecorder.prepare();
353                         break;
354                     case 10:
355                         mRecorder.start();
356                         break;
357                     case 11:
358                         Thread.sleep(mParam % 20);
359                         break;
360                     case 12:
361                         mRecorder.stop();
362                         break;
363                     case 13:
364                         mRecorder.reset();
365                         break;
366                     default:
367                         break;
368                     }
369                 } catch (Exception e) {
370                 }
371             }
372         } catch (Exception e) {
373             Log.v(TAG, e.toString());
374         } finally {
375             watchdog.end();
376             watchdog.join();
377         }
378     }
379 }
380