| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package android.media.cts; |
| |
| import android.content.Context; |
| |
| import android.content.pm.PackageManager; |
| import android.content.res.AssetFileDescriptor; |
| import android.content.res.Resources; |
| import android.media.MediaCodec; |
| import android.media.MediaCodecInfo; |
| import android.media.MediaCodecList; |
| import android.media.MediaExtractor; |
| import android.media.MediaFormat; |
| import android.media.MediaPlayer; |
| import android.test.ActivityInstrumentationTestCase2; |
| |
| import java.io.IOException; |
| import java.util.logging.Logger; |
| |
| /** |
| * Base class for tests which use MediaPlayer to play audio or video. |
| */ |
| public class MediaPlayerTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> { |
| private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName()); |
| |
| protected static final int SLEEP_TIME = 1000; |
| protected static final int LONG_SLEEP_TIME = 6000; |
| protected static final int STREAM_RETRIES = 20; |
| protected static boolean sUseScaleToFitMode = false; |
| |
| public static class Monitor { |
| private int numSignal; |
| |
| public synchronized void reset() { |
| numSignal = 0; |
| } |
| |
| public synchronized void signal() { |
| numSignal++; |
| notifyAll(); |
| } |
| |
| public synchronized boolean waitForSignal() throws InterruptedException { |
| return waitForCountedSignals(1) > 0; |
| } |
| |
| public synchronized int waitForCountedSignals(int targetCount) throws InterruptedException { |
| while (numSignal < targetCount) { |
| wait(); |
| } |
| return numSignal; |
| } |
| |
| public synchronized boolean waitForSignal(long timeoutMs) throws InterruptedException { |
| return waitForCountedSignals(1, timeoutMs) > 0; |
| } |
| |
| public synchronized int waitForCountedSignals(int targetCount, long timeoutMs) |
| throws InterruptedException { |
| if (timeoutMs == 0) { |
| return waitForCountedSignals(targetCount); |
| } |
| long deadline = System.currentTimeMillis() + timeoutMs; |
| while (numSignal < targetCount) { |
| long delay = deadline - System.currentTimeMillis(); |
| if (delay <= 0) { |
| break; |
| } |
| wait(delay); |
| } |
| return numSignal; |
| } |
| |
| public synchronized boolean isSignalled() { |
| return numSignal >= 1; |
| } |
| |
| public synchronized int getNumSignal() { |
| return numSignal; |
| } |
| } |
| |
| protected Monitor mOnVideoSizeChangedCalled = new Monitor(); |
| protected Monitor mOnVideoRenderingStartCalled = new Monitor(); |
| protected Monitor mOnBufferingUpdateCalled = new Monitor(); |
| protected Monitor mOnPrepareCalled = new Monitor(); |
| protected Monitor mOnSeekCompleteCalled = new Monitor(); |
| protected Monitor mOnCompletionCalled = new Monitor(); |
| protected Monitor mOnInfoCalled = new Monitor(); |
| protected Monitor mOnErrorCalled = new Monitor(); |
| |
| protected Context mContext; |
| protected Resources mResources; |
| |
| |
| protected MediaPlayer mMediaPlayer = null; |
| protected MediaPlayer mMediaPlayer2 = null; |
| protected MediaStubActivity mActivity; |
| |
| public MediaPlayerTestBase() { |
| super(MediaStubActivity.class); |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mActivity = getActivity(); |
| getInstrumentation().waitForIdleSync(); |
| try { |
| runTestOnUiThread(new Runnable() { |
| public void run() { |
| mMediaPlayer = new MediaPlayer(); |
| mMediaPlayer2 = new MediaPlayer(); |
| } |
| }); |
| } catch (Throwable e) { |
| e.printStackTrace(); |
| fail(); |
| } |
| mContext = getInstrumentation().getTargetContext(); |
| mResources = mContext.getResources(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| if (mMediaPlayer != null) { |
| mMediaPlayer.release(); |
| mMediaPlayer = null; |
| } |
| if (mMediaPlayer2 != null) { |
| mMediaPlayer2.release(); |
| mMediaPlayer2 = null; |
| } |
| mActivity = null; |
| super.tearDown(); |
| } |
| |
| protected void loadResource(int resid) throws Exception { |
| if (!supportsPlayback(resid)) { |
| throw new UnsupportedCodecException(); |
| } |
| |
| AssetFileDescriptor afd = mResources.openRawResourceFd(resid); |
| try { |
| mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), |
| afd.getLength()); |
| |
| // Although it is only meant for video playback, it should not |
| // cause issues for audio-only playback. |
| int videoScalingMode = sUseScaleToFitMode? |
| MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT |
| : MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING; |
| |
| mMediaPlayer.setVideoScalingMode(videoScalingMode); |
| } finally { |
| afd.close(); |
| } |
| sUseScaleToFitMode = !sUseScaleToFitMode; // Alternate the scaling mode |
| } |
| |
| protected void loadSubtitleSource(int resid) throws Exception { |
| AssetFileDescriptor afd = mResources.openRawResourceFd(resid); |
| try { |
| mMediaPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(), |
| afd.getLength(), MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP); |
| } finally { |
| afd.close(); |
| } |
| } |
| |
| protected void playLiveVideoTest(String path, int playTime) throws Exception { |
| playVideoWithRetries(path, null, null, playTime); |
| } |
| |
| protected void playVideoTest(String path, int width, int height) throws Exception { |
| playVideoWithRetries(path, width, height, 0); |
| } |
| |
| protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime) |
| throws Exception { |
| boolean playedSuccessfully = false; |
| for (int i = 0; i < STREAM_RETRIES; i++) { |
| try { |
| mMediaPlayer.setDataSource(path); |
| playLoadedVideo(width, height, playTime); |
| playedSuccessfully = true; |
| break; |
| } catch (PrepareFailedException e) { |
| // prepare() can fail because of network issues, so try again |
| LOG.warning("prepare() failed on try " + i + ", trying playback again"); |
| } |
| } |
| assertTrue("Stream did not play successfully after all attempts", playedSuccessfully); |
| } |
| |
| protected void playVideoTest(int resid, int width, int height) throws Exception { |
| if (!supportsPlayback(resid)) { |
| LOG.info("SKIPPING playVideoTest() for resid=" + resid |
| + " Could not find a codec for playback."); |
| return; |
| } |
| |
| loadResource(resid); |
| playLoadedVideo(width, height, 0); |
| } |
| |
| /** |
| * Play a video which has already been loaded with setDataSource(). |
| * |
| * @param width width of the video to verify, or null to skip verification |
| * @param height height of the video to verify, or null to skip verification |
| * @param playTime length of time to play video, or 0 to play entire video. |
| * with a non-negative value, this method stops the playback after the length of |
| * time or the duration the video is elapsed. With a value of -1, |
| * this method simply starts the video and returns immediately without |
| * stoping the video playback. |
| */ |
| protected void playLoadedVideo(final Integer width, final Integer height, int playTime) |
| throws Exception { |
| final float leftVolume = 0.5f; |
| final float rightVolume = 0.5f; |
| |
| mMediaPlayer.setDisplay(mActivity.getSurfaceHolder()); |
| mMediaPlayer.setScreenOnWhilePlaying(true); |
| mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() { |
| @Override |
| public void onVideoSizeChanged(MediaPlayer mp, int w, int h) { |
| if (w == 0 && h == 0) { |
| // A size of 0x0 can be sent initially one time when using NuPlayer. |
| assertFalse(mOnVideoSizeChangedCalled.isSignalled()); |
| return; |
| } |
| mOnVideoSizeChangedCalled.signal(); |
| if (width != null) { |
| assertEquals(width.intValue(), w); |
| } |
| if (height != null) { |
| assertEquals(height.intValue(), h); |
| } |
| } |
| }); |
| mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { |
| @Override |
| public boolean onError(MediaPlayer mp, int what, int extra) { |
| fail("Media player had error " + what + " playing video"); |
| return true; |
| } |
| }); |
| mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() { |
| @Override |
| public boolean onInfo(MediaPlayer mp, int what, int extra) { |
| if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { |
| mOnVideoRenderingStartCalled.signal(); |
| } |
| return true; |
| } |
| }); |
| try { |
| mMediaPlayer.prepare(); |
| } catch (IOException e) { |
| mMediaPlayer.reset(); |
| throw new PrepareFailedException(); |
| } |
| |
| mMediaPlayer.start(); |
| mOnVideoSizeChangedCalled.waitForSignal(); |
| mOnVideoRenderingStartCalled.waitForSignal(); |
| mMediaPlayer.setVolume(leftVolume, rightVolume); |
| |
| // waiting to complete |
| if (playTime == -1) { |
| return; |
| } else if (playTime == 0) { |
| while (mMediaPlayer.isPlaying()) { |
| Thread.sleep(SLEEP_TIME); |
| } |
| } else { |
| Thread.sleep(playTime); |
| } |
| mMediaPlayer.stop(); |
| } |
| |
| private static class PrepareFailedException extends Exception {} |
| public static class UnsupportedCodecException extends Exception {} |
| |
| public boolean supportsPlayback(int resid) throws IOException { |
| // First determine if the device supports playback of the requested resource. |
| AssetFileDescriptor fd = mResources.openRawResourceFd(resid); |
| MediaExtractor ex = new MediaExtractor(); |
| ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); |
| MediaFormat format = ex.getTrackFormat(0); |
| String mimeType = format.getString(MediaFormat.KEY_MIME); |
| return hasCodecForMimeType(mimeType, false); |
| } |
| |
| public boolean supportsPlayback(String path) throws IOException { |
| MediaExtractor ex = new MediaExtractor(); |
| ex.setDataSource(path); |
| MediaFormat format = ex.getTrackFormat(0); |
| String mimeType = format.getString(MediaFormat.KEY_MIME); |
| return hasCodecForMimeType(mimeType, false); |
| } |
| |
| public static boolean hasCodecForMimeType(String mimeType, boolean encoder) { |
| MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS); |
| for (MediaCodecInfo info : list.getCodecInfos()) { |
| if (encoder != info.isEncoder()) { |
| continue; |
| } |
| |
| for (String type : info.getSupportedTypes()) { |
| if (type.equalsIgnoreCase(mimeType)) { |
| LOG.info("Found codec for mimeType=" + mimeType + " codec=" + info.getName()); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static MediaCodecInfo getMediaCodecInfo(String mimeType, boolean isEncoder) { |
| MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS); |
| for (MediaCodecInfo info : list.getCodecInfos()) { |
| if (isEncoder != info.isEncoder()) { |
| continue; |
| } |
| for (String type : info.getSupportedTypes()) { |
| if (type.equalsIgnoreCase(mimeType)) { |
| return info; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static boolean hasH264(boolean encoder) { |
| return hasCodecForMimeType("video/avc", encoder); |
| } |
| |
| public static boolean hasHEVC(boolean encoder) { |
| return hasCodecForMimeType("video/hevc", encoder); |
| } |
| |
| public static boolean hasH263(boolean encoder) { |
| return hasCodecForMimeType("video/3gpp", encoder); |
| } |
| |
| public static boolean hasMpeg4(boolean encoder) { |
| return hasCodecForMimeType("video/mp4v-es", encoder); |
| } |
| |
| public static boolean hasVP8(boolean encoder) { |
| return hasCodecForMimeType("video/x-vnd.on2.vp8", encoder); |
| } |
| |
| public static boolean hasVP9(boolean encoder) { |
| return hasCodecForMimeType("video/x-vnd.on2.vp9", encoder); |
| } |
| |
| public boolean hasAudioOutput() { |
| return getInstrumentation().getTargetContext().getPackageManager() |
| .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT); |
| } |
| |
| public boolean isTv() { |
| PackageManager packageManager = getInstrumentation().getTargetContext().getPackageManager(); |
| return packageManager.hasSystemFeature("android.hardware.type.television") || |
| packageManager.hasSystemFeature("android.software.leanback"); |
| } |
| |
| private static boolean isFormatSupported( |
| String mimeType, int w, int h, int frameRate, boolean isEncoder) { |
| MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS); |
| MediaFormat format = MediaFormat.createVideoFormat(mimeType, w, h); |
| format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); |
| String codec = isEncoder |
| ? mcl.findEncoderForFormat(format) |
| : mcl.findDecoderForFormat(format); |
| return (codec != null); |
| } |
| |
| public static boolean isDecodeFormatSupported(String mimeType, int w, int h, int frameRate) { |
| return isFormatSupported(mimeType, w, h, frameRate, false /* isEncoder */); |
| } |
| } |