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