1 /* 2 * Copyright (C) 2011 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.MediaPlayer; 23 import android.net.Uri; 24 import android.os.PersistableBundle; 25 import android.test.ActivityInstrumentationTestCase2; 26 27 import com.android.compatibility.common.util.MediaUtils; 28 29 import java.io.IOException; 30 import java.net.HttpCookie; 31 import java.util.List; 32 import java.util.logging.Logger; 33 import java.util.Map; 34 import java.util.Set; 35 36 /** 37 * Base class for tests which use MediaPlayer to play audio or video. 38 */ 39 public class MediaPlayerTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> { 40 private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName()); 41 42 protected static final int SLEEP_TIME = 1000; 43 protected static final int LONG_SLEEP_TIME = 6000; 44 protected static final int STREAM_RETRIES = 20; 45 protected static boolean sUseScaleToFitMode = false; 46 47 public static class Monitor { 48 private int numSignal; 49 reset()50 public synchronized void reset() { 51 numSignal = 0; 52 } 53 signal()54 public synchronized void signal() { 55 numSignal++; 56 notifyAll(); 57 } 58 waitForSignal()59 public synchronized boolean waitForSignal() throws InterruptedException { 60 return waitForCountedSignals(1) > 0; 61 } 62 waitForCountedSignals(int targetCount)63 public synchronized int waitForCountedSignals(int targetCount) throws InterruptedException { 64 while (numSignal < targetCount) { 65 wait(); 66 } 67 return numSignal; 68 } 69 waitForSignal(long timeoutMs)70 public synchronized boolean waitForSignal(long timeoutMs) throws InterruptedException { 71 return waitForCountedSignals(1, timeoutMs) > 0; 72 } 73 waitForCountedSignals(int targetCount, long timeoutMs)74 public synchronized int waitForCountedSignals(int targetCount, long timeoutMs) 75 throws InterruptedException { 76 if (timeoutMs == 0) { 77 return waitForCountedSignals(targetCount); 78 } 79 long deadline = System.currentTimeMillis() + timeoutMs; 80 while (numSignal < targetCount) { 81 long delay = deadline - System.currentTimeMillis(); 82 if (delay <= 0) { 83 break; 84 } 85 wait(delay); 86 } 87 return numSignal; 88 } 89 isSignalled()90 public synchronized boolean isSignalled() { 91 return numSignal >= 1; 92 } 93 getNumSignal()94 public synchronized int getNumSignal() { 95 return numSignal; 96 } 97 } 98 99 protected Monitor mOnVideoSizeChangedCalled = new Monitor(); 100 protected Monitor mOnVideoRenderingStartCalled = new Monitor(); 101 protected Monitor mOnBufferingUpdateCalled = new Monitor(); 102 protected Monitor mOnPrepareCalled = new Monitor(); 103 protected Monitor mOnSeekCompleteCalled = new Monitor(); 104 protected Monitor mOnCompletionCalled = new Monitor(); 105 protected Monitor mOnInfoCalled = new Monitor(); 106 protected Monitor mOnErrorCalled = new Monitor(); 107 108 protected Context mContext; 109 protected Resources mResources; 110 111 112 protected MediaPlayer mMediaPlayer = null; 113 protected MediaPlayer mMediaPlayer2 = null; 114 protected MediaStubActivity mActivity; 115 MediaPlayerTestBase()116 public MediaPlayerTestBase() { 117 super(MediaStubActivity.class); 118 } 119 120 @Override setUp()121 protected void setUp() throws Exception { 122 super.setUp(); 123 mActivity = getActivity(); 124 getInstrumentation().waitForIdleSync(); 125 try { 126 runTestOnUiThread(new Runnable() { 127 public void run() { 128 mMediaPlayer = new MediaPlayer(); 129 mMediaPlayer2 = new MediaPlayer(); 130 } 131 }); 132 } catch (Throwable e) { 133 e.printStackTrace(); 134 fail(); 135 } 136 mContext = getInstrumentation().getTargetContext(); 137 mResources = mContext.getResources(); 138 } 139 140 @Override tearDown()141 protected void tearDown() throws Exception { 142 if (mMediaPlayer != null) { 143 mMediaPlayer.release(); 144 mMediaPlayer = null; 145 } 146 if (mMediaPlayer2 != null) { 147 mMediaPlayer2.release(); 148 mMediaPlayer2 = null; 149 } 150 mActivity = null; 151 super.tearDown(); 152 } 153 154 // returns true on success loadResource(int resid)155 protected boolean loadResource(int resid) throws Exception { 156 if (!MediaUtils.hasCodecsForResource(mContext, resid)) { 157 return false; 158 } 159 160 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 161 try { 162 mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), 163 afd.getLength()); 164 165 // Although it is only meant for video playback, it should not 166 // cause issues for audio-only playback. 167 int videoScalingMode = sUseScaleToFitMode? 168 MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT 169 : MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING; 170 171 mMediaPlayer.setVideoScalingMode(videoScalingMode); 172 } finally { 173 afd.close(); 174 } 175 sUseScaleToFitMode = !sUseScaleToFitMode; // Alternate the scaling mode 176 return true; 177 } 178 checkLoadResource(int resid)179 protected boolean checkLoadResource(int resid) throws Exception { 180 return MediaUtils.check(loadResource(resid), "no decoder found"); 181 } 182 loadSubtitleSource(int resid)183 protected void loadSubtitleSource(int resid) throws Exception { 184 AssetFileDescriptor afd = mResources.openRawResourceFd(resid); 185 try { 186 mMediaPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(), 187 afd.getLength(), MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP); 188 } finally { 189 afd.close(); 190 } 191 } 192 playLiveVideoTest(String path, int playTime)193 protected void playLiveVideoTest(String path, int playTime) throws Exception { 194 playVideoWithRetries(path, null, null, playTime); 195 } 196 playLiveAudioOnlyTest(String path, int playTime)197 protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception { 198 playVideoWithRetries(path, -1, -1, playTime); 199 } 200 playVideoTest(String path, int width, int height)201 protected void playVideoTest(String path, int width, int height) throws Exception { 202 playVideoWithRetries(path, width, height, 0); 203 } 204 playVideoWithRetries(String path, Integer width, Integer height, int playTime)205 protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime) 206 throws Exception { 207 boolean playedSuccessfully = false; 208 for (int i = 0; i < STREAM_RETRIES; i++) { 209 try { 210 mMediaPlayer.setDataSource(path); 211 playLoadedVideo(width, height, playTime); 212 playedSuccessfully = true; 213 break; 214 } catch (PrepareFailedException e) { 215 // prepare() can fail because of network issues, so try again 216 LOG.warning("prepare() failed on try " + i + ", trying playback again"); 217 } 218 } 219 assertTrue("Stream did not play successfully after all attempts", playedSuccessfully); 220 } 221 playVideoTest(int resid, int width, int height)222 protected void playVideoTest(int resid, int width, int height) throws Exception { 223 if (!checkLoadResource(resid)) { 224 return; // skip 225 } 226 227 playLoadedVideo(width, height, 0); 228 } 229 playLiveVideoTest( Uri uri, Map<String, String> headers, List<HttpCookie> cookies, int playTime)230 protected void playLiveVideoTest( 231 Uri uri, Map<String, String> headers, List<HttpCookie> cookies, 232 int playTime) throws Exception { 233 playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime); 234 } 235 playVideoWithRetries( Uri uri, Map<String, String> headers, List<HttpCookie> cookies, Integer width, Integer height, int playTime)236 protected void playVideoWithRetries( 237 Uri uri, Map<String, String> headers, List<HttpCookie> cookies, 238 Integer width, Integer height, int playTime) throws Exception { 239 boolean playedSuccessfully = false; 240 for (int i = 0; i < STREAM_RETRIES; i++) { 241 try { 242 mMediaPlayer.setDataSource(getInstrumentation().getTargetContext(), 243 uri, headers, cookies); 244 playLoadedVideo(width, height, playTime); 245 playedSuccessfully = true; 246 break; 247 } catch (PrepareFailedException e) { 248 // prepare() can fail because of network issues, so try again 249 // playLoadedVideo already has reset the player so we can try again safely. 250 LOG.warning("prepare() failed on try " + i + ", trying playback again"); 251 } 252 } 253 assertTrue("Stream did not play successfully after all attempts", playedSuccessfully); 254 } 255 256 /** 257 * Play a video which has already been loaded with setDataSource(). 258 * 259 * @param width width of the video to verify, or null to skip verification 260 * @param height height of the video to verify, or null to skip verification 261 * @param playTime length of time to play video, or 0 to play entire video. 262 * with a non-negative value, this method stops the playback after the length of 263 * time or the duration the video is elapsed. With a value of -1, 264 * this method simply starts the video and returns immediately without 265 * stoping the video playback. 266 */ playLoadedVideo(final Integer width, final Integer height, int playTime)267 protected void playLoadedVideo(final Integer width, final Integer height, int playTime) 268 throws Exception { 269 final float leftVolume = 0.5f; 270 final float rightVolume = 0.5f; 271 272 boolean audioOnly = (width != null && width.intValue() == -1) || 273 (height != null && height.intValue() == -1); 274 275 mMediaPlayer.setDisplay(mActivity.getSurfaceHolder()); 276 mMediaPlayer.setScreenOnWhilePlaying(true); 277 mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() { 278 @Override 279 public void onVideoSizeChanged(MediaPlayer mp, int w, int h) { 280 if (w == 0 && h == 0) { 281 // A size of 0x0 can be sent initially one time when using NuPlayer. 282 assertFalse(mOnVideoSizeChangedCalled.isSignalled()); 283 return; 284 } 285 mOnVideoSizeChangedCalled.signal(); 286 if (width != null) { 287 assertEquals(width.intValue(), w); 288 } 289 if (height != null) { 290 assertEquals(height.intValue(), h); 291 } 292 } 293 }); 294 mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 295 @Override 296 public boolean onError(MediaPlayer mp, int what, int extra) { 297 fail("Media player had error " + what + " playing video"); 298 return true; 299 } 300 }); 301 mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() { 302 @Override 303 public boolean onInfo(MediaPlayer mp, int what, int extra) { 304 if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { 305 mOnVideoRenderingStartCalled.signal(); 306 } 307 return true; 308 } 309 }); 310 try { 311 mMediaPlayer.prepare(); 312 } catch (IOException e) { 313 mMediaPlayer.reset(); 314 throw new PrepareFailedException(); 315 } 316 317 mMediaPlayer.start(); 318 if (!audioOnly) { 319 mOnVideoSizeChangedCalled.waitForSignal(); 320 mOnVideoRenderingStartCalled.waitForSignal(); 321 } 322 mMediaPlayer.setVolume(leftVolume, rightVolume); 323 324 // waiting to complete 325 if (playTime == -1) { 326 return; 327 } else if (playTime == 0) { 328 while (mMediaPlayer.isPlaying()) { 329 Thread.sleep(SLEEP_TIME); 330 } 331 } else { 332 Thread.sleep(playTime); 333 } 334 335 // validate a few MediaMetrics. 336 PersistableBundle metrics = mMediaPlayer.getMetrics(); 337 if (metrics == null) { 338 fail("MediaPlayer.getMetrics() returned null metrics"); 339 } else if (metrics.isEmpty()) { 340 fail("MediaPlayer.getMetrics() returned empty metrics"); 341 } else { 342 343 int size = metrics.size(); 344 Set<String> keys = metrics.keySet(); 345 346 if (keys == null) { 347 fail("MediaMetricsSet returned no keys"); 348 } else if (keys.size() != size) { 349 fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()"); 350 } 351 352 // we played something; so one of these should be non-null 353 String vmime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_VIDEO, null); 354 String amime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_AUDIO, null); 355 if (vmime == null && amime == null) { 356 fail("getMetrics() returned neither video nor audio mime value"); 357 } 358 359 long duration = metrics.getLong(MediaPlayer.MetricsConstants.DURATION, -2); 360 if (duration == -2) { 361 fail("getMetrics() didn't return a duration"); 362 } 363 long playing = metrics.getLong(MediaPlayer.MetricsConstants.PLAYING, -2); 364 if (playing == -2) { 365 fail("getMetrics() didn't return a playing time"); 366 } 367 if (!keys.contains(MediaPlayer.MetricsConstants.PLAYING)) { 368 fail("MediaMetricsSet.keys() missing: " + MediaPlayer.MetricsConstants.PLAYING); 369 } 370 } 371 372 mMediaPlayer.stop(); 373 } 374 375 private static class PrepareFailedException extends Exception {} 376 isTv()377 public boolean isTv() { 378 PackageManager pm = getInstrumentation().getTargetContext().getPackageManager(); 379 return pm.hasSystemFeature(pm.FEATURE_TELEVISION) 380 && pm.hasSystemFeature(pm.FEATURE_LEANBACK); 381 } 382 checkTv()383 public boolean checkTv() { 384 return MediaUtils.check(isTv(), "not a TV"); 385 } 386 setOnErrorListener()387 protected void setOnErrorListener() { 388 mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 389 @Override 390 public boolean onError(MediaPlayer mp, int what, int extra) { 391 mOnErrorCalled.signal(); 392 return false; 393 } 394 }); 395 } 396 } 397