1/*
2 * Copyright (C) 2009 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 */
16package android.media.cts;
17
18import com.android.cts.media.R;
19
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.content.res.AssetFileDescriptor;
23import android.media.AudioManager;
24import android.media.MediaPlayer;
25import android.media.MediaRecorder;
26import android.media.MediaMetadataRetriever;
27import android.media.TimedText;
28import android.media.audiofx.AudioEffect;
29import android.media.audiofx.Visualizer;
30import android.media.cts.MediaPlayerTestBase.Monitor;
31import android.net.Uri;
32import android.os.Environment;
33import android.os.PowerManager;
34import android.os.SystemClock;
35import android.util.Log;
36
37import java.io.File;
38import java.util.StringTokenizer;
39import java.util.UUID;
40import java.util.Vector;
41import java.util.concurrent.CountDownLatch;
42
43/**
44 * Tests for the MediaPlayer API and local video/audio playback.
45 *
46 * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
47 * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
48 * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
49 */
50public class MediaPlayerTest extends MediaPlayerTestBase {
51
52    private String RECORDED_FILE;
53    private static final String LOG_TAG = "MediaPlayerTest";
54
55    private static final int  RECORDED_VIDEO_WIDTH  = 176;
56    private static final int  RECORDED_VIDEO_HEIGHT = 144;
57    private static final long RECORDED_DURATION_MS  = 3000;
58    private Vector<Integer> mTimedTextTrackIndex = new Vector<Integer>();
59    private int mSelectedTimedTextIndex;
60    private Monitor mOnTimedTextCalled = new Monitor();
61
62    private File mOutFile;
63
64    @Override
65    protected void setUp() throws Exception {
66        super.setUp();
67        RECORDED_FILE = new File(Environment.getExternalStorageDirectory(),
68                "mediaplayer_record.out").getAbsolutePath();
69        mOutFile = new File(RECORDED_FILE);
70    }
71
72    @Override
73    protected void tearDown() throws Exception {
74        super.tearDown();
75        if (mOutFile != null && mOutFile.exists()) {
76            mOutFile.delete();
77        }
78    }
79    public void testPlayNullSource() throws Exception {
80        try {
81            mMediaPlayer.setDataSource((String) null);
82            fail("Null URI was accepted");
83        } catch (RuntimeException e) {
84            // expected
85        }
86    }
87
88    public void testPlayAudio() throws Exception {
89        final int mp3Duration = 34909;
90        final int tolerance = 70;
91        final int seekDuration = 100;
92        final int resid = R.raw.testmp3_2;
93
94        MediaPlayer mp = MediaPlayer.create(mContext, resid);
95        try {
96            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
97            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
98
99            assertFalse(mp.isPlaying());
100            mp.start();
101            assertTrue(mp.isPlaying());
102
103            assertFalse(mp.isLooping());
104            mp.setLooping(true);
105            assertTrue(mp.isLooping());
106
107            assertEquals(mp3Duration, mp.getDuration(), tolerance);
108            int pos = mp.getCurrentPosition();
109            assertTrue(pos >= 0);
110            assertTrue(pos < mp3Duration - seekDuration);
111
112            mp.seekTo(pos + seekDuration);
113            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
114
115            // test pause and restart
116            mp.pause();
117            Thread.sleep(SLEEP_TIME);
118            assertFalse(mp.isPlaying());
119            mp.start();
120            assertTrue(mp.isPlaying());
121
122            // test stop and restart
123            mp.stop();
124            mp.reset();
125            AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
126            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
127            afd.close();
128            mp.prepare();
129            assertFalse(mp.isPlaying());
130            mp.start();
131            assertTrue(mp.isPlaying());
132
133            // waiting to complete
134            while(mp.isPlaying()) {
135                Thread.sleep(SLEEP_TIME);
136            }
137        } finally {
138            mp.release();
139        }
140    }
141
142    public void testPlayVideo() throws Exception {
143        playVideoTest(R.raw.testvideo, 352, 288);
144    }
145
146    private void initMediaPlayer(MediaPlayer player) throws Exception {
147        AssetFileDescriptor afd = mResources.openRawResourceFd(R.raw.test1m1s);
148        try {
149            player.reset();
150            player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
151            player.prepare();
152            player.seekTo(56000);
153        } finally {
154            afd.close();
155        }
156    }
157
158    public void testSetNextMediaPlayerWithReset() throws Exception {
159
160        initMediaPlayer(mMediaPlayer);
161
162        try {
163            initMediaPlayer(mMediaPlayer2);
164            mMediaPlayer2.reset();
165            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
166            fail("setNextMediaPlayer() succeeded with unprepared player");
167        } catch (RuntimeException e) {
168            // expected
169        } finally {
170            mMediaPlayer.reset();
171        }
172    }
173
174    public void testSetNextMediaPlayerWithRelease() throws Exception {
175
176        initMediaPlayer(mMediaPlayer);
177
178        try {
179            initMediaPlayer(mMediaPlayer2);
180            mMediaPlayer2.release();
181            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
182            fail("setNextMediaPlayer() succeeded with unprepared player");
183        } catch (RuntimeException e) {
184            // expected
185        } finally {
186            mMediaPlayer.reset();
187        }
188    }
189
190    public void testSetNextMediaPlayer() throws Exception {
191        initMediaPlayer(mMediaPlayer);
192
193        final Monitor mTestCompleted = new Monitor();
194
195        Thread timer = new Thread(new Runnable() {
196
197            @Override
198            public void run() {
199                long startTime = SystemClock.elapsedRealtime();
200                while(true) {
201                    SystemClock.sleep(SLEEP_TIME);
202                    if (mTestCompleted.isSignalled()) {
203                        // done
204                        return;
205                    }
206                    long now = SystemClock.elapsedRealtime();
207                    if ((now - startTime) > 25000) {
208                        // We've been running for 25 seconds and still aren't done, so we're stuck
209                        // somewhere. Signal ourselves to dump the thread stacks.
210                        android.os.Process.sendSignal(android.os.Process.myPid(), 3);
211                        SystemClock.sleep(2000);
212                        fail("Test is stuck, see ANR stack trace for more info. You may need to" +
213                                " create /data/anr first");
214                        return;
215                    }
216                }
217            }
218        });
219
220        timer.start();
221
222        try {
223            for (int i = 0; i < 3; i++) {
224
225                initMediaPlayer(mMediaPlayer2);
226                mOnCompletionCalled.reset();
227                mOnInfoCalled.reset();
228                mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
229                    @Override
230                    public void onCompletion(MediaPlayer mp) {
231                        assertEquals(mMediaPlayer, mp);
232                        mOnCompletionCalled.signal();
233                    }
234                });
235                mMediaPlayer2.setOnInfoListener(new MediaPlayer.OnInfoListener() {
236                    @Override
237                    public boolean onInfo(MediaPlayer mp, int what, int extra) {
238                        assertEquals(mMediaPlayer2, mp);
239                        if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
240                            mOnInfoCalled.signal();
241                        }
242                        return false;
243                    }
244                });
245
246                mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
247                mMediaPlayer.start();
248                assertTrue(mMediaPlayer.isPlaying());
249                assertFalse(mOnCompletionCalled.isSignalled());
250                assertFalse(mMediaPlayer2.isPlaying());
251                assertFalse(mOnInfoCalled.isSignalled());
252                while(mMediaPlayer.isPlaying()) {
253                    Thread.sleep(SLEEP_TIME);
254                }
255                // wait a little longer in case the callbacks haven't quite made it through yet
256                Thread.sleep(100);
257                assertTrue(mMediaPlayer2.isPlaying());
258                assertTrue(mOnCompletionCalled.isSignalled());
259                assertTrue(mOnInfoCalled.isSignalled());
260
261                // At this point the 1st player is done, and the 2nd one is playing.
262                // Now swap them, and go through the loop again.
263                MediaPlayer tmp = mMediaPlayer;
264                mMediaPlayer = mMediaPlayer2;
265                mMediaPlayer2 = tmp;
266            }
267
268            // Now test that setNextMediaPlayer(null) works. 1 is still playing, 2 is done
269            mOnCompletionCalled.reset();
270            mOnInfoCalled.reset();
271            initMediaPlayer(mMediaPlayer2);
272            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
273
274            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
275                @Override
276                public void onCompletion(MediaPlayer mp) {
277                    assertEquals(mMediaPlayer, mp);
278                    mOnCompletionCalled.signal();
279                }
280            });
281            mMediaPlayer2.setOnInfoListener(new MediaPlayer.OnInfoListener() {
282                @Override
283                public boolean onInfo(MediaPlayer mp, int what, int extra) {
284                    assertEquals(mMediaPlayer2, mp);
285                    if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
286                        mOnInfoCalled.signal();
287                    }
288                    return false;
289                }
290            });
291            assertTrue(mMediaPlayer.isPlaying());
292            assertFalse(mOnCompletionCalled.isSignalled());
293            assertFalse(mMediaPlayer2.isPlaying());
294            assertFalse(mOnInfoCalled.isSignalled());
295            Thread.sleep(SLEEP_TIME);
296            mMediaPlayer.setNextMediaPlayer(null);
297            while(mMediaPlayer.isPlaying()) {
298                Thread.sleep(SLEEP_TIME);
299            }
300            // wait a little longer in case the callbacks haven't quite made it through yet
301            Thread.sleep(100);
302            assertFalse(mMediaPlayer.isPlaying());
303            assertFalse(mMediaPlayer2.isPlaying());
304            assertTrue(mOnCompletionCalled.isSignalled());
305            assertFalse(mOnInfoCalled.isSignalled());
306
307        } finally {
308            mMediaPlayer.reset();
309            mMediaPlayer2.reset();
310        }
311        mTestCompleted.signal();
312
313    }
314
315    // The following tests are all a bit flaky, which is why they're retried a
316    // few times in a loop.
317
318    // This test uses one mp3 that is silent but has a strong positive DC offset,
319    // and a second mp3 that is also silent but has a strong negative DC offset.
320    // If the two are played back overlapped, they will cancel each other out,
321    // and result in zeroes being detected. If there is a gap in playback, that
322    // will also result in zeroes being detected.
323    // Note that this test does NOT guarantee that the correct data is played
324    public void testGapless1() throws Exception {
325        flakyTestWrapper(R.raw.monodcpos, R.raw.monodcneg);
326    }
327
328    // This test is similar, but uses two identical m4a files that have some noise
329    // with a strong positive DC offset. This is used to detect if there is
330    // a gap in playback
331    // Note that this test does NOT guarantee that the correct data is played
332    public void testGapless2() throws Exception {
333        flakyTestWrapper(R.raw.stereonoisedcpos, R.raw.stereonoisedcpos);
334    }
335
336    // same as above, but with a mono file
337    public void testGapless3() throws Exception {
338        flakyTestWrapper(R.raw.mononoisedcpos, R.raw.mononoisedcpos);
339    }
340
341    private void flakyTestWrapper(int resid1, int resid2) throws Exception {
342        boolean success = false;
343        // test usually succeeds within a few tries, but occasionally may fail
344        // many times in a row, so be aggressive and try up to 20 times
345        for (int i = 0; i < 20 && !success; i++) {
346            try {
347                testGapless(resid1, resid2);
348                success = true;
349            } catch (Throwable t) {
350                SystemClock.sleep(1000);
351            }
352        }
353        // Try one more time. If this succeeds, we'll consider the test a success,
354        // otherwise the exception gets thrown
355        if (!success) {
356            testGapless(resid1, resid2);
357        }
358    }
359
360    private void testGapless(int resid1, int resid2) throws Exception {
361
362        MediaPlayer mp1 = new MediaPlayer();
363        mp1.setAudioStreamType(AudioManager.STREAM_MUSIC);
364        try {
365            AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(resid1);
366            mp1.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
367            afd.close();
368            mp1.prepare();
369        } catch (Exception e) {
370            assertTrue(false);
371        }
372        int session = mp1.getAudioSessionId();
373
374        MediaPlayer mp2 = new MediaPlayer();
375        mp2.setAudioSessionId(session);
376        mp2.setAudioStreamType(AudioManager.STREAM_MUSIC);
377        try {
378            AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(resid2);
379            mp2.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
380            afd.close();
381            mp2.prepare();
382        } catch (Exception e) {
383            assertTrue(false);
384        }
385        // creating a volume controller on output mix ensures that ro.audio.silent mutes
386        // audio after the effects and not before
387        AudioEffect vc = new AudioEffect(
388                            AudioEffect.EFFECT_TYPE_NULL,
389                            UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
390                            0,
391                            session);
392        vc.setEnabled(true);
393        int captureintervalms = mp1.getDuration() + mp2.getDuration() - 2000;
394        int size = 256;
395        int[] range = Visualizer.getCaptureSizeRange();
396        if (size < range[0]) {
397            size = range[0];
398        }
399        if (size > range[1]) {
400            size = range[1];
401        }
402        byte [] vizdata = new byte[size];
403        Visualizer vis = new Visualizer(session);
404        assertTrue(vis.setCaptureSize(vizdata.length) == Visualizer.SUCCESS);
405        assertTrue(vis.setEnabled(true) == Visualizer.SUCCESS);
406        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
407        int oldRingerMode = am.getRingerMode();
408        am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
409        int oldvolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
410        am.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
411        try {
412            mp1.setNextMediaPlayer(mp2);
413            mp1.start();
414            assertTrue(mp1.isPlaying());
415            assertFalse(mp2.isPlaying());
416            // allow playback to get started
417            Thread.sleep(SLEEP_TIME);
418            long start = SystemClock.elapsedRealtime();
419            // there should be no consecutive zeroes (-128) in the capture buffer
420            // when going to the next file. If silence is detected right away, then
421            // the volume is probably turned all the way down (visualizer data
422            // is captured after volume adjustment).
423            boolean first = true;
424            while((SystemClock.elapsedRealtime() - start) < captureintervalms) {
425                assertTrue(vis.getWaveForm(vizdata) == Visualizer.SUCCESS);
426                for (int i = 0; i < vizdata.length - 1; i++) {
427                    if (vizdata[i] == -128 && vizdata[i + 1] == -128) {
428                        if (first) {
429                            fail("silence detected, please increase volume and rerun test");
430                        } else {
431                            fail("gap or overlap detected at t=" +
432                                    (SLEEP_TIME + SystemClock.elapsedRealtime() - start) +
433                                    ", offset " + i);
434                        }
435                        break;
436                    }
437                }
438                first = false;
439            }
440        } finally {
441            mp1.release();
442            mp2.release();
443            vis.release();
444            vc.release();
445            am.setRingerMode(oldRingerMode);
446            am.setStreamVolume(AudioManager.STREAM_MUSIC, oldvolume, 0);
447        }
448    }
449
450    /**
451     * Test for reseting a surface during video playback
452     * After reseting, the video should continue playing
453     * from the time setDisplay() was called
454     */
455    public void testVideoSurfaceResetting() throws Exception {
456        final int tolerance = 150;
457        final int audioLatencyTolerance = 1000;  /* covers audio path latency variability */
458        final int seekPos = 5000;
459
460        final CountDownLatch seekDone = new CountDownLatch(1);
461
462        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
463            @Override
464            public void onSeekComplete(MediaPlayer mp) {
465                seekDone.countDown();
466            }
467        });
468
469        loadResource(R.raw.testvideo);
470        playLoadedVideo(352, 288, -1);
471
472        Thread.sleep(SLEEP_TIME);
473
474        int posBefore = mMediaPlayer.getCurrentPosition();
475        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder2());
476        int posAfter = mMediaPlayer.getCurrentPosition();
477
478        assertEquals(posAfter, posBefore, tolerance);
479        assertTrue(mMediaPlayer.isPlaying());
480
481        Thread.sleep(SLEEP_TIME);
482
483        mMediaPlayer.seekTo(seekPos);
484        seekDone.await();
485        posAfter = mMediaPlayer.getCurrentPosition();
486        assertEquals(seekPos, posAfter, tolerance + audioLatencyTolerance);
487
488        Thread.sleep(SLEEP_TIME / 2);
489        posBefore = mMediaPlayer.getCurrentPosition();
490        mMediaPlayer.setDisplay(null);
491        posAfter = mMediaPlayer.getCurrentPosition();
492        assertEquals(posAfter, posBefore, tolerance);
493        assertTrue(mMediaPlayer.isPlaying());
494
495        Thread.sleep(SLEEP_TIME);
496
497        posBefore = mMediaPlayer.getCurrentPosition();
498        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
499        posAfter = mMediaPlayer.getCurrentPosition();
500
501        assertEquals(posAfter, posBefore, tolerance);
502        assertTrue(mMediaPlayer.isPlaying());
503
504        Thread.sleep(SLEEP_TIME);
505    }
506
507    public void testRecordedVideoPlayback0() throws Exception {
508        testRecordedVideoPlaybackWithAngle(0);
509    }
510
511    public void testRecordedVideoPlayback90() throws Exception {
512        testRecordedVideoPlaybackWithAngle(90);
513    }
514
515    public void testRecordedVideoPlayback180() throws Exception {
516        testRecordedVideoPlaybackWithAngle(180);
517    }
518
519    public void testRecordedVideoPlayback270() throws Exception {
520        testRecordedVideoPlaybackWithAngle(270);
521    }
522
523    private boolean hasCamera() {
524        return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
525    }
526
527    private void testRecordedVideoPlaybackWithAngle(int angle) throws Exception {
528        final int width = RECORDED_VIDEO_WIDTH;
529        final int height = RECORDED_VIDEO_HEIGHT;
530        final String file = RECORDED_FILE;
531        final long durationMs = RECORDED_DURATION_MS;
532
533        if (!hasCamera()) {
534            return;
535        }
536        checkOrientation(angle);
537        recordVideo(width, height, angle, file, durationMs);
538        checkDisplayedVideoSize(width, height, angle, file);
539        checkVideoRotationAngle(angle, file);
540    }
541
542    private void checkOrientation(int angle) throws Exception {
543        assertTrue(angle >= 0);
544        assertTrue(angle < 360);
545        assertTrue((angle % 90) == 0);
546    }
547
548    private void recordVideo(
549            int w, int h, int angle, String file, long durationMs) throws Exception {
550
551        MediaRecorder recorder = new MediaRecorder();
552        recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
553        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
554        recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
555        recorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
556        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
557        recorder.setOutputFile(file);
558        recorder.setOrientationHint(angle);
559        recorder.setVideoSize(w, h);
560        recorder.setPreviewDisplay(getActivity().getSurfaceHolder2().getSurface());
561        recorder.prepare();
562        recorder.start();
563        Thread.sleep(durationMs);
564        recorder.stop();
565        recorder.release();
566        recorder = null;
567    }
568
569    private void checkDisplayedVideoSize(
570            int w, int h, int angle, String file) throws Exception {
571
572        int displayWidth  = w;
573        int displayHeight = h;
574        if ((angle % 180) != 0) {
575            displayWidth  = h;
576            displayHeight = w;
577        }
578        playVideoTest(file, displayWidth, displayHeight);
579    }
580
581    private void checkVideoRotationAngle(int angle, String file) {
582        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
583        retriever.setDataSource(file);
584        String rotation = retriever.extractMetadata(
585                    MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
586        retriever.release();
587        retriever = null;
588        assertNotNull(rotation);
589        assertEquals(Integer.parseInt(rotation), angle);
590    }
591
592    public void testLocalVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz()
593            throws Exception {
594        playVideoTest(
595                R.raw.video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
596    }
597
598    public void testLocalVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz()
599            throws Exception {
600        playVideoTest(
601                R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
602    }
603
604    public void testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz()
605            throws Exception {
606        playVideoTest(
607                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
608    }
609
610    public void testLocalVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz()
611            throws Exception {
612        playVideoTest(
613                R.raw.video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
614    }
615
616    public void testLocalVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz()
617            throws Exception {
618        playVideoTest(
619                R.raw.video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz, 480, 360);
620    }
621
622    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz()
623            throws Exception {
624        playVideoTest(
625                R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz, 480, 360);
626    }
627
628    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()
629            throws Exception {
630        playVideoTest(
631                R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz, 480, 360);
632    }
633
634    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz()
635            throws Exception {
636        playVideoTest(
637                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
638    }
639
640    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz()
641            throws Exception {
642        playVideoTest(
643                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz, 176, 144);
644    }
645
646    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz()
647            throws Exception {
648        playVideoTest(
649                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
650    }
651
652    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()
653            throws Exception {
654        playVideoTest(
655                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
656    }
657
658    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()
659            throws Exception {
660        playVideoTest(
661                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
662    }
663
664    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()
665            throws Exception {
666        playVideoTest(
667                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
668    }
669
670    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()
671            throws Exception {
672        playVideoTest(
673                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz, 176, 144);
674    }
675
676    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz()
677            throws Exception {
678        playVideoTest(
679                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz, 176, 144);
680    }
681
682    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz()
683            throws Exception {
684        playVideoTest(
685                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
686    }
687
688    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()
689            throws Exception {
690        playVideoTest(
691                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
692    }
693
694    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()
695            throws Exception {
696        playVideoTest(
697                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
698    }
699
700    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()
701            throws Exception {
702        playVideoTest(
703                R.raw.video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
704    }
705
706    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()
707            throws Exception {
708        playVideoTest(
709                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
710    }
711
712    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz()
713            throws Exception {
714        playVideoTest(
715                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz, 176, 144);
716    }
717
718    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz()
719            throws Exception {
720        playVideoTest(
721                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
722    }
723
724    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()
725            throws Exception {
726        playVideoTest(
727                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz, 176, 144);
728    }
729
730    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()
731            throws Exception {
732        playVideoTest(
733                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
734    }
735
736    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()
737            throws Exception {
738        playVideoTest(
739                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz, 176, 144);
740    }
741
742    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()
743            throws Exception {
744        playVideoTest(
745                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz, 176, 144);
746    }
747
748    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz()
749            throws Exception {
750        playVideoTest(
751                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz, 176, 144);
752    }
753
754    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz()
755            throws Exception {
756        playVideoTest(
757                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
758    }
759
760    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()
761            throws Exception {
762        playVideoTest(
763                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz, 176, 144);
764    }
765
766    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()
767            throws Exception {
768        playVideoTest(
769                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz, 176, 144);
770    }
771
772    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz()
773            throws Exception {
774        playVideoTest(
775                R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz, 176, 144);
776    }
777
778    private void readTimedTextTracks() throws Exception {
779        mTimedTextTrackIndex.clear();
780        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
781        if (trackInfos == null || trackInfos.length == 0) {
782            return;
783        }
784        for (int i = 0; i < trackInfos.length; ++i) {
785            assertTrue(trackInfos[i] != null);
786            if (trackInfos[i].getTrackType() ==
787                 MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
788                mTimedTextTrackIndex.add(i);
789            }
790        }
791    }
792
793    private int getTimedTextTrackCount() {
794        return mTimedTextTrackIndex.size();
795    }
796
797    private void selectSubtitleTrack(int index) throws Exception {
798        int trackIndex = mTimedTextTrackIndex.get(index);
799        mMediaPlayer.selectTrack(trackIndex);
800        mSelectedTimedTextIndex = index;
801    }
802
803    private void deselectSubtitleTrack(int index) throws Exception {
804        int trackIndex = mTimedTextTrackIndex.get(index);
805        mMediaPlayer.deselectTrack(trackIndex);
806        if (mSelectedTimedTextIndex == index) {
807            mSelectedTimedTextIndex = -1;
808        }
809    }
810
811    public void testDeselectTrack() throws Exception {
812        loadResource(R.raw.testvideo_with_2_subtitles);
813        loadSubtitleSource(R.raw.test_subtitle1_srt);
814        readTimedTextTracks();
815        assertEquals(getTimedTextTrackCount(), 3);
816
817        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
818        mMediaPlayer.setScreenOnWhilePlaying(true);
819        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
820        mMediaPlayer.setOnTimedTextListener(new MediaPlayer.OnTimedTextListener() {
821            @Override
822            public void onTimedText(MediaPlayer mp, TimedText text) {
823                if (text != null) {
824                    String plainText = text.getText();
825                    if (plainText != null) {
826                        mOnTimedTextCalled.signal();
827                        Log.d(LOG_TAG, "text: " + plainText.trim());
828                    }
829                }
830            }
831        });
832        mMediaPlayer.prepare();
833        mMediaPlayer.start();
834        assertTrue(mMediaPlayer.isPlaying());
835
836        // Run twice to check if repeated selection-deselection on the same track works well.
837        for (int i = 0; i < 2; i++) {
838            // Waits until at least one subtitle is fired. Timeout is 1 sec.
839            selectSubtitleTrack(0);
840            mOnTimedTextCalled.reset();
841            assertTrue(mOnTimedTextCalled.waitForSignal(1000));
842
843            // Try deselecting track.
844            deselectSubtitleTrack(0);
845            mOnTimedTextCalled.reset();
846            assertFalse(mOnTimedTextCalled.waitForSignal(1000));
847        }
848
849        // Run the same test for external subtitle track.
850        for (int i = 0; i < 2; i++) {
851            selectSubtitleTrack(2);
852            mOnTimedTextCalled.reset();
853            assertTrue(mOnTimedTextCalled.waitForSignal(1000));
854
855            // Try deselecting track.
856            deselectSubtitleTrack(2);
857            mOnTimedTextCalled.reset();
858            assertFalse(mOnTimedTextCalled.waitForSignal(1000));
859        }
860
861        try {
862            deselectSubtitleTrack(0);
863            fail("Deselecting unselected track: expected RuntimeException, " +
864                 "but no exception has been triggered.");
865        } catch (RuntimeException e) {
866            // expected
867        }
868
869        mMediaPlayer.stop();
870    }
871
872    public void testChangeSubtitleTrack() throws Exception {
873        loadResource(R.raw.testvideo_with_2_subtitles);
874        readTimedTextTracks();
875        assertEquals(getTimedTextTrackCount(), 2);
876
877        // Adds two more external subtitle files.
878        loadSubtitleSource(R.raw.test_subtitle1_srt);
879        loadSubtitleSource(R.raw.test_subtitle2_srt);
880        readTimedTextTracks();
881        assertEquals(getTimedTextTrackCount(), 4);
882
883        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
884        mMediaPlayer.setScreenOnWhilePlaying(true);
885        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
886        mMediaPlayer.setOnTimedTextListener(new MediaPlayer.OnTimedTextListener() {
887            @Override
888            public void onTimedText(MediaPlayer mp, TimedText text) {
889                final int toleranceMs = 100;
890                final int durationMs = 500;
891                int posMs = mMediaPlayer.getCurrentPosition();
892                if (text != null) {
893                    String plainText = text.getText();
894                    if (plainText != null) {
895                        StringTokenizer tokens = new StringTokenizer(plainText.trim(), ":");
896                        int subtitleTrackIndex = Integer.parseInt(tokens.nextToken());
897                        int startMs = Integer.parseInt(tokens.nextToken());
898                        Log.d(LOG_TAG, "text: " + plainText.trim() +
899                              ", trackId: " + subtitleTrackIndex + ", posMs: " + posMs);
900                        assertTrue("The diff between subtitle's start time " + startMs +
901                                   " and current time " + posMs +
902                                   " is over tolerance " + toleranceMs,
903                                   (posMs >= startMs - toleranceMs) &&
904                                   (posMs < startMs + durationMs + toleranceMs) );
905                        assertEquals("Expected track: " + mSelectedTimedTextIndex +
906                                     ", actual track: " + subtitleTrackIndex,
907                                     mSelectedTimedTextIndex, subtitleTrackIndex);
908                        mOnTimedTextCalled.signal();
909                    }
910                }
911            }
912        });
913
914        mMediaPlayer.prepare();
915        assertFalse(mMediaPlayer.isPlaying());
916
917        selectSubtitleTrack(0);
918        mOnTimedTextCalled.reset();
919
920        mMediaPlayer.start();
921        assertTrue(mMediaPlayer.isPlaying());
922
923        // Waits until at least two subtitles are fired. Timeout is 2 sec.
924        // Please refer the test srt files:
925        // test_subtitle1_srt.3gp and test_subtitle2_srt.3gp
926        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2000) >= 2);
927
928        selectSubtitleTrack(1);
929        mOnTimedTextCalled.reset();
930        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2000) >= 2);
931
932        selectSubtitleTrack(2);
933        mOnTimedTextCalled.reset();
934        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2000) >= 2);
935
936        selectSubtitleTrack(3);
937        mOnTimedTextCalled.reset();
938        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2000) >= 2);
939        mMediaPlayer.stop();
940    }
941
942    public void testGetTrackInfo() throws Exception {
943        loadResource(R.raw.testvideo_with_2_subtitles);
944        loadSubtitleSource(R.raw.test_subtitle1_srt);
945        loadSubtitleSource(R.raw.test_subtitle2_srt);
946        mMediaPlayer.prepare();
947        mMediaPlayer.start();
948
949        readTimedTextTracks();
950        selectSubtitleTrack(2);
951
952        int count = 0;
953        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
954        assertTrue(trackInfos != null && trackInfos.length != 0);
955        for (int i = 0; i < trackInfos.length; ++i) {
956            assertTrue(trackInfos[i] != null);
957            if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
958                String trackLanguage = trackInfos[i].getLanguage();
959                assertTrue(trackLanguage != null);
960                trackLanguage.trim();
961                Log.d(LOG_TAG, "track info lang: " + trackLanguage);
962                assertTrue("Should not see empty track language with our test data.",
963                           trackLanguage.length() > 0);
964                count++;
965            }
966        }
967        // There are 4 subtitle tracks in total in our test data.
968        assertEquals(4, count);
969    }
970
971    public void testCallback() throws Throwable {
972        final int mp4Duration = 8484;
973
974        loadResource(R.raw.testvideo);
975        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
976        mMediaPlayer.setScreenOnWhilePlaying(true);
977
978        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
979            @Override
980            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
981                mOnVideoSizeChangedCalled.signal();
982            }
983        });
984
985        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
986            @Override
987            public void onPrepared(MediaPlayer mp) {
988                mOnPrepareCalled.signal();
989            }
990        });
991
992        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
993            @Override
994            public void onSeekComplete(MediaPlayer mp) {
995                mOnSeekCompleteCalled.signal();
996            }
997        });
998
999        mOnCompletionCalled.reset();
1000        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
1001            @Override
1002            public void onCompletion(MediaPlayer mp) {
1003                mOnCompletionCalled.signal();
1004            }
1005        });
1006
1007        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
1008            @Override
1009            public boolean onError(MediaPlayer mp, int what, int extra) {
1010                mOnErrorCalled.signal();
1011                return false;
1012            }
1013        });
1014
1015        mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
1016            @Override
1017            public boolean onInfo(MediaPlayer mp, int what, int extra) {
1018                mOnInfoCalled.signal();
1019                return false;
1020            }
1021        });
1022
1023        assertFalse(mOnPrepareCalled.isSignalled());
1024        assertFalse(mOnVideoSizeChangedCalled.isSignalled());
1025        mMediaPlayer.prepare();
1026        mOnPrepareCalled.waitForSignal();
1027        mOnVideoSizeChangedCalled.waitForSignal();
1028        mOnSeekCompleteCalled.reset();
1029        mMediaPlayer.seekTo(mp4Duration >> 1);
1030        mOnSeekCompleteCalled.waitForSignal();
1031        assertFalse(mOnCompletionCalled.isSignalled());
1032        mMediaPlayer.start();
1033        while(mMediaPlayer.isPlaying()) {
1034            Thread.sleep(SLEEP_TIME);
1035        }
1036        assertFalse(mMediaPlayer.isPlaying());
1037        mOnCompletionCalled.waitForSignal();
1038        assertFalse(mOnErrorCalled.isSignalled());
1039        mMediaPlayer.stop();
1040        mMediaPlayer.start();
1041        mOnErrorCalled.waitForSignal();
1042    }
1043
1044    public void testRecordAndPlay() throws Exception {
1045        if (!hasMicrophone()) {
1046            return;
1047        }
1048        File outputFile = new File(Environment.getExternalStorageDirectory(),
1049                "record_and_play.3gp");
1050        String outputFileLocation = outputFile.getAbsolutePath();
1051        try {
1052            recordMedia(outputFileLocation);
1053            MediaPlayer mp = new MediaPlayer();
1054            try {
1055                mp.setDataSource(outputFileLocation);
1056                mp.prepareAsync();
1057                Thread.sleep(SLEEP_TIME);
1058                playAndStop(mp);
1059            } finally {
1060                mp.release();
1061            }
1062
1063            Uri uri = Uri.parse(outputFileLocation);
1064            mp = new MediaPlayer();
1065            try {
1066                mp.setDataSource(mContext, uri);
1067                mp.prepareAsync();
1068                Thread.sleep(SLEEP_TIME);
1069                playAndStop(mp);
1070            } finally {
1071                mp.release();
1072            }
1073
1074            try {
1075                mp = MediaPlayer.create(mContext, uri);
1076                playAndStop(mp);
1077            } finally {
1078                if (mp != null) {
1079                    mp.release();
1080                }
1081            }
1082
1083            try {
1084                mp = MediaPlayer.create(mContext, uri, getActivity().getSurfaceHolder());
1085                playAndStop(mp);
1086            } finally {
1087                if (mp != null) {
1088                    mp.release();
1089                }
1090            }
1091        } finally {
1092            outputFile.delete();
1093        }
1094    }
1095
1096    private void playAndStop(MediaPlayer mp) throws Exception {
1097        mp.start();
1098        Thread.sleep(SLEEP_TIME);
1099        mp.stop();
1100    }
1101
1102    private void recordMedia(String outputFile) throws Exception {
1103        MediaRecorder mr = new MediaRecorder();
1104        try {
1105            mr.setAudioSource(MediaRecorder.AudioSource.MIC);
1106            mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
1107            mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
1108            mr.setOutputFile(outputFile);
1109
1110            mr.prepare();
1111            mr.start();
1112            Thread.sleep(SLEEP_TIME);
1113            mr.stop();
1114        } finally {
1115            mr.release();
1116        }
1117    }
1118
1119    private boolean hasMicrophone() {
1120        return getActivity().getPackageManager().hasSystemFeature(
1121                PackageManager.FEATURE_MICROPHONE);
1122    }
1123}
1124