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 @MediaHeavyPresubmitTest 42 @AppModeFull(reason = "TODO: evaluate and port to instant") 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 } testPlayerRandomActionAV1()173 public void testPlayerRandomActionAV1() throws Exception { 174 testPlayerRandomAction(R.raw.video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz); 175 } testPlayerRandomActionH264()176 public void testPlayerRandomActionH264() throws Exception { 177 testPlayerRandomAction(R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz); 178 } testPlayerRandomActionHEVC()179 public void testPlayerRandomActionHEVC() throws Exception { 180 testPlayerRandomAction(R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz); 181 } testPlayerRandomActionMpeg2()182 public void testPlayerRandomActionMpeg2() throws Exception { 183 testPlayerRandomAction(R.raw.video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz); 184 } testPlayerRandomAction(int resid)185 private void testPlayerRandomAction(int resid) throws Exception { 186 Watchdog watchdog = new Watchdog(5000); 187 try { 188 mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 189 @Override 190 public boolean onError(MediaPlayer mp, int what, int extra) { 191 if (mPlayer == mp && 192 what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) { 193 Log.e(TAG, "mediaserver process died"); 194 mMediaServerDied = true; 195 } 196 return true; 197 } 198 }); 199 loadSource(resid); 200 mPlayer.setDisplay(mSurfaceHolder); 201 mPlayer.prepare(); 202 mPlayer.start(); 203 204 long seed = System.currentTimeMillis(); 205 Log.v(TAG, "seed = " + seed); 206 Random r = new Random(seed); 207 208 watchdog.start(); 209 for (int i = 0; i < NUMBER_OF_PLAYER_RANDOM_ACTIONS; i++){ 210 watchdog.ping(); 211 assertTrue(!mMediaServerDied); 212 213 mAction = (int)(r.nextInt() % 12); 214 mParam = (int)(r.nextInt() % 1000000); 215 try { 216 switch (mAction) { 217 case 0: 218 mPlayer.getCurrentPosition(); 219 break; 220 case 1: 221 mPlayer.getDuration(); 222 break; 223 case 2: 224 mPlayer.getVideoHeight(); 225 break; 226 case 3: 227 mPlayer.getVideoWidth(); 228 break; 229 case 4: 230 mPlayer.isPlaying(); 231 break; 232 case 5: 233 mPlayer.pause(); 234 break; 235 case 6: 236 // Don't add mPlayer.prepare() call here for two reasons: 237 // 1. calling prepare() is a bad idea since it is a blocking call, and 238 // 2. when prepare() is in progress, mediaserver died message will not be sent to apps 239 mPlayer.prepareAsync(); 240 break; 241 case 7: 242 mPlayer.seekTo((int)(mParam)); 243 break; 244 case 8: 245 mPlayer.setLooping(mParam % 2 == 0); 246 break; 247 case 9: 248 mPlayer.setVolume((mParam % 1000) / 500.0f, 249 (mParam / 1000) / 500.0f); 250 break; 251 case 10: 252 mPlayer.start(); 253 break; 254 case 11: 255 Thread.sleep(mParam % 20); 256 break; 257 } 258 } catch (Exception e) { 259 } 260 } 261 mPlayer.stop(); 262 } catch (Exception e) { 263 Log.v(TAG, e.toString()); 264 } finally { 265 watchdog.end(); 266 watchdog.join(); 267 } 268 } 269 testRecorderRandomAction()270 public void testRecorderRandomAction() throws Exception { 271 Watchdog watchdog = new Watchdog(5000); 272 try { 273 long seed = System.currentTimeMillis(); 274 Log.v(TAG, "seed = " + seed); 275 Random r = new Random(seed); 276 277 mMediaServerDied = false; 278 mRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() { 279 @Override 280 public void onError(MediaRecorder recorder, int what, int extra) { 281 if (mRecorder == recorder && 282 what == MediaRecorder.MEDIA_ERROR_SERVER_DIED) { 283 Log.e(TAG, "mediaserver process died"); 284 mMediaServerDied = true; 285 } 286 } 287 }); 288 289 final int[] width = {176, 352, 320, 640, 1280, 1920}; 290 final int[] height = {144, 288, 240, 480, 720, 1080}; 291 final int[] audioSource = { 292 MediaRecorder.AudioSource.DEFAULT, 293 MediaRecorder.AudioSource.MIC, 294 MediaRecorder.AudioSource.CAMCORDER, 295 }; 296 297 watchdog.start(); 298 for (int i = 0; i < NUMBER_OF_RECORDER_RANDOM_ACTIONS; i++) { 299 watchdog.ping(); 300 assertTrue(!mMediaServerDied); 301 302 mAction = (int)(r.nextInt(14)); 303 mParam = (int)(r.nextInt(1000000)); 304 try { 305 switch (mAction) { 306 case 0: { 307 // We restrict the audio sources because setting some sources 308 // may cause 2+ second delays because the input device may 309 // retry - loop (e.g. VOICE_UPLINK for voice call to be initiated). 310 final int index = mParam % audioSource.length; 311 mRecorder.setAudioSource(audioSource[index]); 312 break; 313 } 314 case 1: 315 // XXX: 316 // Fix gralloc source and change 317 // mRecorder.setVideoSource(mParam % 3); 318 mRecorder.setVideoSource(mParam % 2); 319 break; 320 case 2: 321 mRecorder.setOutputFormat(mParam % 5); 322 break; 323 case 3: 324 mRecorder.setAudioEncoder(mParam % 3); 325 break; 326 case 4: 327 mRecorder.setVideoEncoder(mParam % 5); 328 break; 329 case 5: 330 mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface()); 331 break; 332 case 6: 333 int index = mParam % width.length; 334 mRecorder.setVideoSize(width[index], height[index]); 335 break; 336 case 7: 337 mRecorder.setVideoFrameRate(mParam % 40 - 5); 338 break; 339 case 8: 340 mRecorder.setOutputFile(OUTPUT_FILE); 341 break; 342 case 9: 343 mRecorder.prepare(); 344 break; 345 case 10: 346 mRecorder.start(); 347 break; 348 case 11: 349 Thread.sleep(mParam % 20); 350 break; 351 case 12: 352 mRecorder.stop(); 353 break; 354 case 13: 355 mRecorder.reset(); 356 break; 357 default: 358 break; 359 } 360 } catch (Exception e) { 361 } 362 } 363 } catch (Exception e) { 364 Log.v(TAG, e.toString()); 365 } finally { 366 watchdog.end(); 367 watchdog.join(); 368 } 369 } 370 } 371