1 /* 2 * Copyright 2018 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.Context; 19 import android.content.pm.PackageManager; 20 import android.content.res.AssetFileDescriptor; 21 import android.content.res.Resources; 22 import android.media.AudioAttributes; 23 import android.media.AudioManager; 24 import android.media.DataSourceDesc; 25 import android.media.MediaPlayer2; 26 import android.media.MediaTimestamp; 27 import android.media.TimedMetaData; 28 import android.media.TimedText; 29 import android.media.cts.TestUtils.Monitor; 30 import android.net.Uri; 31 import android.os.PersistableBundle; 32 import android.test.ActivityInstrumentationTestCase2; 33 import android.view.SurfaceHolder; 34 35 import com.android.compatibility.common.util.MediaUtils; 36 37 import java.io.IOException; 38 import java.net.HttpCookie; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Set; 43 import java.util.concurrent.ExecutorService; 44 import java.util.concurrent.Executors; 45 import java.util.logging.Logger; 46 47 /** 48 * Base class for tests which use MediaPlayer2 to play audio or video. 49 */ 50 public class MediaPlayer2TestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> { 51 private static final Logger LOG = Logger.getLogger(MediaPlayer2TestBase.class.getName()); 52 53 protected static final int SLEEP_TIME = 1000; 54 protected static final int LONG_SLEEP_TIME = 6000; 55 protected static final int STREAM_RETRIES = 20; 56 protected static boolean sUseScaleToFitMode = false; 57 58 protected Monitor mOnVideoSizeChangedCalled = new Monitor(); 59 protected Monitor mOnVideoRenderingStartCalled = new Monitor(); 60 protected Monitor mOnBufferingUpdateCalled = new Monitor(); 61 protected Monitor mOnPrepareCalled = new Monitor(); 62 protected Monitor mOnPlayCalled = new Monitor(); 63 protected Monitor mOnDeselectTrackCalled = new Monitor(); 64 protected Monitor mOnSeekCompleteCalled = new Monitor(); 65 protected Monitor mOnCompletionCalled = new Monitor(); 66 protected Monitor mOnInfoCalled = new Monitor(); 67 protected Monitor mOnErrorCalled = new Monitor(); 68 protected int mCallStatus; 69 70 protected Context mContext; 71 protected Resources mResources; 72 73 protected ExecutorService mExecutor; 74 75 protected MediaPlayer2 mPlayer = null; 76 protected MediaPlayer2 mPlayer2 = null; 77 protected MediaStubActivity mActivity; 78 79 protected final Object mEventCbLock = new Object(); 80 protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks = 81 new ArrayList<MediaPlayer2.MediaPlayer2EventCallback>(); 82 protected final Object mEventCbLock2 = new Object(); 83 protected List<MediaPlayer2.MediaPlayer2EventCallback> mEventCallbacks2 = 84 new ArrayList<MediaPlayer2.MediaPlayer2EventCallback>(); 85 86 // convenience functions to create MediaPlayer2 87 protected static MediaPlayer2 createMediaPlayer2(Context context, Uri uri) { 88 return createMediaPlayer2(context, uri, null); 89 } 90 91 protected static MediaPlayer2 createMediaPlayer2(Context context, Uri uri, 92 SurfaceHolder holder) { 93 AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 94 int s = am.generateAudioSessionId(); 95 return createMediaPlayer2(context, uri, holder, null, s > 0 ? s : 0); 96 } 97 98 protected static MediaPlayer2 createMediaPlayer2(Context context, Uri uri, SurfaceHolder holder, 99 AudioAttributes audioAttributes, int audioSessionId) { 100 try { 101 MediaPlayer2 mp = MediaPlayer2.create(); 102 final AudioAttributes aa = audioAttributes != null ? audioAttributes : 103 new AudioAttributes.Builder().build(); 104 mp.setAudioAttributes(aa); 105 mp.setAudioSessionId(audioSessionId); 106 mp.setDataSource(new DataSourceDesc.Builder() 107 .setDataSource(context, uri) 108 .build()); 109 if (holder != null) { 110 mp.setDisplay(holder); 111 } 112 Monitor onPrepareCalled = new Monitor(); 113 ExecutorService executor = Executors.newFixedThreadPool(1); 114 MediaPlayer2.MediaPlayer2EventCallback ecb = 115 new MediaPlayer2.MediaPlayer2EventCallback() { 116 @Override 117 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 118 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) { 119 onPrepareCalled.signal(); 120 } 121 } 122 }; 123 mp.setMediaPlayer2EventCallback(executor, ecb); 124 mp.prepare(); 125 onPrepareCalled.waitForSignal(); 126 mp.clearMediaPlayer2EventCallback(); 127 executor.shutdown(); 128 return mp; 129 } catch (IllegalArgumentException ex) { 130 LOG.warning("create failed:" + ex); 131 // fall through 132 } catch (SecurityException ex) { 133 LOG.warning("create failed:" + ex); 134 // fall through 135 } catch (InterruptedException ex) { 136 LOG.warning("create failed:" + ex); 137 // fall through 138 } 139 return null; 140 } 141 142 protected static MediaPlayer2 createMediaPlayer2(Context context, int resid) { 143 AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); 144 int s = am.generateAudioSessionId(); 145 return createMediaPlayer2(context, resid, null, s > 0 ? s : 0); 146 } 147 148 protected static MediaPlayer2 createMediaPlayer2(Context context, int resid, 149 AudioAttributes audioAttributes, int audioSessionId) { 150 try { 151 AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid); 152 if (afd == null) { 153 return null; 154 } 155 156 MediaPlayer2 mp = MediaPlayer2.create(); 157 158 final AudioAttributes aa = audioAttributes != null ? audioAttributes : 159 new AudioAttributes.Builder().build(); 160 mp.setAudioAttributes(aa); 161 mp.setAudioSessionId(audioSessionId); 162 163 mp.setDataSource(new DataSourceDesc.Builder() 164 .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()) 165 .build()); 166 167 Monitor onPrepareCalled = new Monitor(); 168 ExecutorService executor = Executors.newFixedThreadPool(1); 169 MediaPlayer2.MediaPlayer2EventCallback ecb = 170 new MediaPlayer2.MediaPlayer2EventCallback() { 171 @Override 172 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 173 if (what == MediaPlayer2.MEDIA_INFO_PREPARED) { 174 onPrepareCalled.signal(); 175 } 176 } 177 }; 178 mp.setMediaPlayer2EventCallback(executor, ecb); 179 mp.prepare(); 180 onPrepareCalled.waitForSignal(); 181 mp.clearMediaPlayer2EventCallback(); 182 afd.close(); 183 executor.shutdown(); 184 return mp; 185 } catch (IOException ex) { 186 LOG.warning("create failed:" + ex); 187 // fall through 188 } catch (IllegalArgumentException ex) { 189 LOG.warning("create failed:" + ex); 190 // fall through 191 } catch (SecurityException ex) { 192 LOG.warning("create failed:" + ex); 193 // fall through 194 } catch (InterruptedException ex) { 195 LOG.warning("create failed:" + ex); 196 // fall through 197 } 198 return null; 199 } 200 201 public MediaPlayer2TestBase() { 202 super(MediaStubActivity.class); 203 } 204 205 @Override 206 protected void setUp() throws Exception { 207 super.setUp(); 208 mActivity = getActivity(); 209 getInstrumentation().waitForIdleSync(); 210 try { 211 runTestOnUiThread(new Runnable() { 212 public void run() { 213 mPlayer = MediaPlayer2.create(); 214 mPlayer2 = MediaPlayer2.create(); 215 } 216 }); 217 } catch (Throwable e) { 218 e.printStackTrace(); 219 fail(); 220 } 221 mContext = getInstrumentation().getTargetContext(); 222 mResources = mContext.getResources(); 223 mExecutor = Executors.newFixedThreadPool(1); 224 225 setUpMP2ECb(mPlayer, mEventCbLock, mEventCallbacks); 226 setUpMP2ECb(mPlayer2, mEventCbLock2, mEventCallbacks2); 227 } 228 229 @Override 230 protected void tearDown() throws Exception { 231 if (mPlayer != null) { 232 mPlayer.close(); 233 mPlayer = null; 234 } 235 if (mPlayer2 != null) { 236 mPlayer2.close(); 237 mPlayer2 = null; 238 } 239 mExecutor.shutdown(); 240 mActivity = null; 241 super.tearDown(); 242 } 243 244 protected void setUpMP2ECb(MediaPlayer2 mp, Object cbLock, 245 List<MediaPlayer2.MediaPlayer2EventCallback> ecbs) { 246 mp.setMediaPlayer2EventCallback(mExecutor, new MediaPlayer2.MediaPlayer2EventCallback() { 247 @Override 248 public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) { 249 synchronized (cbLock) { 250 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 251 ecb.onVideoSizeChanged(mp, dsd, w, h); 252 } 253 } 254 } 255 256 @Override 257 public void onTimedText(MediaPlayer2 mp, DataSourceDesc dsd, TimedText text) { 258 synchronized (cbLock) { 259 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 260 ecb.onTimedText(mp, dsd, text); 261 } 262 } 263 } 264 265 @Override 266 public void onTimedMetaDataAvailable(MediaPlayer2 mp, DataSourceDesc dsd, 267 TimedMetaData data) { 268 synchronized (cbLock) { 269 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 270 ecb.onTimedMetaDataAvailable(mp, dsd, data); 271 } 272 } 273 } 274 275 @Override 276 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 277 synchronized (cbLock) { 278 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 279 ecb.onError(mp, dsd, what, extra); 280 } 281 } 282 } 283 284 @Override 285 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 286 synchronized (cbLock) { 287 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 288 ecb.onInfo(mp, dsd, what, extra); 289 } 290 } 291 } 292 293 @Override 294 public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, int what, int status) { 295 synchronized (cbLock) { 296 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 297 ecb.onCallCompleted(mp, dsd, what, status); 298 } 299 } 300 } 301 302 @Override 303 public void onMediaTimeChanged(MediaPlayer2 mp, DataSourceDesc dsd, 304 MediaTimestamp timestamp) { 305 synchronized (cbLock) { 306 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 307 ecb.onMediaTimeChanged(mp, dsd, timestamp); 308 } 309 } 310 } 311 312 @Override 313 public void onCommandLabelReached(MediaPlayer2 mp, Object label) { 314 synchronized (cbLock) { 315 for (MediaPlayer2.MediaPlayer2EventCallback ecb : ecbs) { 316 ecb.onCommandLabelReached(mp, label); 317 } 318 } 319 } 320 }); 321 } 322 323 // returns true on success 324 protected boolean loadResource(int resid) throws Exception { 325 if (!MediaUtils.hasCodecsForResource(mContext, resid)) { 326 return false; 327 } 328 329 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 330 try { 331 mPlayer.setDataSource(new DataSourceDesc.Builder() 332 .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()) 333 .build()); 334 335 // Although it is only meant for video playback, it should not 336 // cause issues for audio-only playback. 337 int videoScalingMode = sUseScaleToFitMode? 338 MediaPlayer2.VIDEO_SCALING_MODE_SCALE_TO_FIT 339 : MediaPlayer2.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING; 340 341 mPlayer.setVideoScalingMode(videoScalingMode); 342 } finally { 343 // TODO: close afd only after setDataSource is confirmed. 344 // afd.close(); 345 } 346 sUseScaleToFitMode = !sUseScaleToFitMode; // Alternate the scaling mode 347 return true; 348 } 349 350 protected DataSourceDesc createDataSourceDesc(int resid) throws Exception { 351 if (!MediaUtils.hasCodecsForResource(mContext, resid)) { 352 return null; 353 } 354 355 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 356 return new DataSourceDesc.Builder() 357 .setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()) 358 .build(); 359 } 360 361 protected boolean checkLoadResource(int resid) throws Exception { 362 return MediaUtils.check(loadResource(resid), "no decoder found"); 363 } 364 365 protected void loadSubtitleSource(int resid) throws Exception { 366 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 367 try { 368 mPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(), 369 afd.getLength(), MediaPlayer2.MEDIA_MIMETYPE_TEXT_SUBRIP); 370 } finally { 371 afd.close(); 372 } 373 } 374 375 protected void playLiveVideoTest(String path, int playTime) throws Exception { 376 playVideoWithRetries(path, null, null, playTime); 377 } 378 379 protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception { 380 playVideoWithRetries(path, -1, -1, playTime); 381 } 382 383 protected void playVideoTest(String path, int width, int height) throws Exception { 384 playVideoWithRetries(path, width, height, 0); 385 } 386 387 protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime) 388 throws Exception { 389 boolean playedSuccessfully = false; 390 final Uri uri = Uri.parse(path); 391 for (int i = 0; i < STREAM_RETRIES; i++) { 392 try { 393 mPlayer.setDataSource(new DataSourceDesc.Builder() 394 .setDataSource(mContext, uri) 395 .build()); 396 playLoadedVideo(width, height, playTime); 397 playedSuccessfully = true; 398 break; 399 } catch (PrepareFailedException e) { 400 // prepare() can fail because of network issues, so try again 401 LOG.warning("prepare() failed on try " + i + ", trying playback again"); 402 } 403 } 404 assertTrue("Stream did not play successfully after all attempts", playedSuccessfully); 405 } 406 407 protected void playVideoTest(int resid, int width, int height) throws Exception { 408 if (!checkLoadResource(resid)) { 409 return; // skip 410 } 411 412 playLoadedVideo(width, height, 0); 413 } 414 415 protected void playLiveVideoTest( 416 Uri uri, Map<String, String> headers, List<HttpCookie> cookies, 417 int playTime) throws Exception { 418 playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime); 419 } 420 421 protected void playVideoWithRetries( 422 Uri uri, Map<String, String> headers, List<HttpCookie> cookies, 423 Integer width, Integer height, int playTime) throws Exception { 424 boolean playedSuccessfully = false; 425 for (int i = 0; i < STREAM_RETRIES; i++) { 426 try { 427 mPlayer.setDataSource(new DataSourceDesc.Builder() 428 .setDataSource(getInstrumentation().getTargetContext(), 429 uri, headers, cookies) 430 .build()); 431 playLoadedVideo(width, height, playTime); 432 playedSuccessfully = true; 433 break; 434 } catch (PrepareFailedException e) { 435 // prepare() can fail because of network issues, so try again 436 // playLoadedVideo already has reset the player so we can try again safely. 437 LOG.warning("prepare() failed on try " + i + ", trying playback again"); 438 } 439 } 440 assertTrue("Stream did not play successfully after all attempts", playedSuccessfully); 441 } 442 443 /** 444 * Play a video which has already been loaded with setDataSource(). 445 * 446 * @param width width of the video to verify, or null to skip verification 447 * @param height height of the video to verify, or null to skip verification 448 * @param playTime length of time to play video, or 0 to play entire video. 449 * with a non-negative value, this method stops the playback after the length of 450 * time or the duration the video is elapsed. With a value of -1, 451 * this method simply starts the video and returns immediately without 452 * stoping the video playback. 453 */ 454 protected void playLoadedVideo(final Integer width, final Integer height, int playTime) 455 throws Exception { 456 final float volume = 0.5f; 457 458 boolean audioOnly = (width != null && width.intValue() == -1) || 459 (height != null && height.intValue() == -1); 460 461 mPlayer.setDisplay(mActivity.getSurfaceHolder()); 462 mPlayer.setScreenOnWhilePlaying(true); 463 464 synchronized (mEventCbLock) { 465 mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() { 466 @Override 467 public void onVideoSizeChanged(MediaPlayer2 mp, DataSourceDesc dsd, int w, int h) { 468 if (w == 0 && h == 0) { 469 // A size of 0x0 can be sent initially one time when using NuPlayer. 470 assertFalse(mOnVideoSizeChangedCalled.isSignalled()); 471 return; 472 } 473 mOnVideoSizeChangedCalled.signal(); 474 if (width != null) { 475 assertEquals(width.intValue(), w); 476 } 477 if (height != null) { 478 assertEquals(height.intValue(), h); 479 } 480 } 481 482 @Override 483 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 484 fail("Media player had error " + what + " playing video"); 485 } 486 487 @Override 488 public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 489 if (what == MediaPlayer2.MEDIA_INFO_VIDEO_RENDERING_START) { 490 mOnVideoRenderingStartCalled.signal(); 491 } else if (what == MediaPlayer2.MEDIA_INFO_PREPARED) { 492 mOnPrepareCalled.signal(); 493 } 494 } 495 496 @Override 497 public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd, 498 int what, int status) { 499 if (what == MediaPlayer2.CALL_COMPLETED_PLAY) { 500 mOnPlayCalled.signal(); 501 } 502 } 503 }); 504 } 505 try { 506 mOnPrepareCalled.reset(); 507 mPlayer.prepare(); 508 mOnPrepareCalled.waitForSignal(); 509 } catch (Exception e) { 510 mPlayer.reset(); 511 throw new PrepareFailedException(); 512 } 513 514 mOnPlayCalled.reset(); 515 mPlayer.play(); 516 mOnPlayCalled.waitForSignal(); 517 if (!audioOnly) { 518 mOnVideoSizeChangedCalled.waitForSignal(); 519 mOnVideoRenderingStartCalled.waitForSignal(); 520 } 521 mPlayer.setPlayerVolume(volume); 522 523 // waiting to complete 524 if (playTime == -1) { 525 return; 526 } else if (playTime == 0) { 527 while (mPlayer.isPlaying()) { 528 Thread.sleep(SLEEP_TIME); 529 } 530 } else { 531 Thread.sleep(playTime); 532 } 533 534 // validate a few MediaMetrics. 535 PersistableBundle metrics = mPlayer.getMetrics(); 536 if (metrics == null) { 537 fail("MediaPlayer2.getMetrics() returned null metrics"); 538 } else if (metrics.isEmpty()) { 539 fail("MediaPlayer2.getMetrics() returned empty metrics"); 540 } else { 541 542 int size = metrics.size(); 543 Set<String> keys = metrics.keySet(); 544 545 if (keys == null) { 546 fail("MediaMetricsSet returned no keys"); 547 } else if (keys.size() != size) { 548 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()"); 549 } 550 551 // we played something; so one of these should be non-null 552 String vmime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_VIDEO, null); 553 String amime = metrics.getString(MediaPlayer2.MetricsConstants.MIME_TYPE_AUDIO, null); 554 if (vmime == null && amime == null) { 555 fail("getMetrics() returned neither video nor audio mime value"); 556 } 557 558 long duration = metrics.getLong(MediaPlayer2.MetricsConstants.DURATION, -2); 559 if (duration == -2) { 560 fail("getMetrics() didn't return a duration"); 561 } 562 long playing = metrics.getLong(MediaPlayer2.MetricsConstants.PLAYING, -2); 563 if (playing == -2) { 564 fail("getMetrics() didn't return a playing time"); 565 } 566 if (!keys.contains(MediaPlayer2.MetricsConstants.PLAYING)) { 567 fail("MediaMetricsSet.keys() missing: " + MediaPlayer2.MetricsConstants.PLAYING); 568 } 569 } 570 571 mPlayer.stop(); 572 } 573 574 private static class PrepareFailedException extends Exception {} 575 576 public boolean isTv() { 577 PackageManager pm = getInstrumentation().getTargetContext().getPackageManager(); 578 return pm.hasSystemFeature(pm.FEATURE_TELEVISION) 579 && pm.hasSystemFeature(pm.FEATURE_LEANBACK); 580 } 581 582 public boolean checkTv() { 583 return MediaUtils.check(isTv(), "not a TV"); 584 } 585 586 protected void setOnErrorListener() { 587 synchronized (mEventCbLock) { 588 mEventCallbacks.add(new MediaPlayer2.MediaPlayer2EventCallback() { 589 @Override 590 public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) { 591 mOnErrorCalled.signal(); 592 } 593 }); 594 } 595 } 596 } 597