1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.mediaframeworktest.performance;
18
19import com.android.mediaframeworktest.MediaFrameworkTest;
20import com.android.mediaframeworktest.MediaFrameworkPerfTestRunner;
21import com.android.mediaframeworktest.MediaNames;
22import com.android.mediaframeworktest.MediaTestUtil;
23
24import android.database.sqlite.SQLiteDatabase;
25import android.hardware.Camera;
26import android.hardware.Camera.PreviewCallback;
27import android.media.MediaPlayer;
28import android.media.MediaRecorder;
29import android.media.EncoderCapabilities.VideoEncoderCap;
30import android.os.ConditionVariable;
31import android.os.Looper;
32import android.os.SystemClock;
33import android.test.ActivityInstrumentationTestCase2;
34import android.test.suitebuilder.annotation.LargeTest;
35import android.test.suitebuilder.annotation.Suppress;
36import android.util.Log;
37import android.view.SurfaceHolder;
38
39import java.util.List;
40import java.io.BufferedReader;
41import java.io.FileDescriptor;
42import java.io.FileInputStream;
43import java.io.FileOutputStream;
44import java.io.IOException;
45import java.io.InputStream;
46import java.io.InputStreamReader;
47import java.io.Writer;
48import java.io.File;
49import java.io.FileWriter;
50import java.io.BufferedWriter;
51
52import android.media.MediaMetadataRetriever;
53import com.android.mediaframeworktest.MediaProfileReader;
54
55/**
56 * Junit / Instrumentation - performance measurement for media player and
57 * recorder
58 *
59 * FIXME:
60 * Add tests on H264 video encoder
61 */
62public class MediaPlayerPerformance extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
63
64    private String TAG = "MediaPlayerPerformance";
65
66    private SQLiteDatabase mDB;
67    private SurfaceHolder mSurfaceHolder = null;
68    private static final int NUM_STRESS_LOOP = 10;
69    private static final int NUM_PLAYBACk_IN_EACH_LOOP = 20;
70    private static final long MEDIA_STRESS_WAIT_TIME = 5000; //5 seconds
71    private static final String MEDIA_MEMORY_OUTPUT =
72        "/sdcard/mediaMemOutput.txt";
73    private static final String MEDIA_PROCMEM_OUTPUT =
74        "/sdcard/mediaProcmemOutput.txt";
75    private static final int CAMERA_ID = 0;
76
77    private static int mStartMemory = 0;
78    private static int mEndMemory = 0;
79    private static int mStartPid = 0;
80    private static int mEndPid = 0;
81
82    private Looper mLooper = null;
83    private RawPreviewCallback mRawPreviewCallback = new RawPreviewCallback();
84    private final ConditionVariable mPreviewDone = new ConditionVariable();
85    private static int WAIT_FOR_COMMAND_TO_COMPLETE = 10000;  // Milliseconds.
86
87    //the tolerant memory leak
88    private static int ENCODER_LIMIT = 150;
89    private static int DECODER_LIMIT = 150;
90    private static int CAMERA_LIMIT = 80;
91
92    private Writer mProcMemWriter;
93    private Writer mMemWriter;
94
95    private static List<VideoEncoderCap> videoEncoders = MediaProfileReader.getVideoEncoders();
96
97    Camera mCamera;
98
99    public MediaPlayerPerformance() {
100        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
101    }
102
103    @Override
104    protected void setUp() throws Exception {
105        super.setUp();
106        //Insert a 2 second before launching the test activity. This is
107        //the workaround for the race condition of requesting the updated surface.
108        Thread.sleep(2000);
109        getActivity();
110        if (MediaFrameworkPerfTestRunner.mGetNativeHeapDump)
111            MediaTestUtil.getNativeHeapDump(this.getName() + "_before");
112
113        if (MediaFrameworkPerfTestRunner.mGetProcmem) {
114            mProcMemWriter = new BufferedWriter(new FileWriter
115                    (new File(MEDIA_PROCMEM_OUTPUT), true));
116            mProcMemWriter.write(this.getName() + "\n");
117        }
118        mMemWriter = new BufferedWriter(new FileWriter
119                (new File(MEDIA_MEMORY_OUTPUT), true));
120        mMemWriter.write(this.getName() + "\n");
121    }
122
123    @Override
124    protected void tearDown() throws Exception {
125        if (MediaFrameworkPerfTestRunner.mGetNativeHeapDump)
126            MediaTestUtil.getNativeHeapDump(this.getName() + "_after");
127
128        if (MediaFrameworkPerfTestRunner.mGetProcmem) {
129            mProcMemWriter.close();
130        }
131        mMemWriter.write("\n");
132        mMemWriter.close();
133        super.tearDown();
134    }
135
136    private void initializeMessageLooper() {
137        final ConditionVariable startDone = new ConditionVariable();
138        new Thread() {
139            @Override
140            public void run() {
141                Looper.prepare();
142                Log.v(TAG, "start loopRun");
143                mLooper = Looper.myLooper();
144                mCamera = Camera.open(CAMERA_ID);
145                startDone.open();
146                Looper.loop();
147                Log.v(TAG, "initializeMessageLooper: quit.");
148            }
149        }.start();
150
151        if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
152            fail("initializeMessageLooper: start timeout");
153        }
154    }
155
156    private void terminateMessageLooper() throws Exception {
157        mLooper.quit();
158        // Looper.quit() is asynchronous. The looper may still has some
159        // preview callbacks in the queue after quit is called. The preview
160        // callback still uses the camera object (setHasPreviewCallback).
161        // After camera is released, RuntimeException will be thrown from
162        // the method. So we need to join the looper thread here.
163        mLooper.getThread().join();
164        mCamera.release();
165    }
166
167    private final class RawPreviewCallback implements PreviewCallback {
168        @Override
169        public void onPreviewFrame(byte[] rawData, Camera camera) {
170            mPreviewDone.open();
171        }
172    }
173
174    private void waitForPreviewDone() {
175        if (!mPreviewDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
176            Log.v(TAG, "waitForPreviewDone: timeout");
177        }
178        mPreviewDone.close();
179    }
180
181    public void stressCameraPreview() {
182        for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) {
183            try {
184                initializeMessageLooper();
185                mCamera.setPreviewCallback(mRawPreviewCallback);
186                mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
187                mCamera.setPreviewDisplay(mSurfaceHolder);
188                mCamera.startPreview();
189                waitForPreviewDone();
190                Thread.sleep(1000);
191                mCamera.stopPreview();
192                terminateMessageLooper();
193            } catch (Exception e) {
194                Log.v(TAG, e.toString());
195            }
196        }
197    }
198
199    // Note: This test is to assume the mediaserver's pid is 34
200    public void mediaStressPlayback(String testFilePath) {
201        for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) {
202            MediaPlayer mp = new MediaPlayer();
203            try {
204                mp.setDataSource(testFilePath);
205                mp.setDisplay(MediaFrameworkTest.mSurfaceView.getHolder());
206                mp.prepare();
207                mp.start();
208                Thread.sleep(MEDIA_STRESS_WAIT_TIME);
209                mp.release();
210            } catch (Exception e) {
211                mp.release();
212                Log.v(TAG, e.toString());
213            }
214        }
215    }
216
217    // Note: This test is to assume the mediaserver's pid is 34
218    private boolean stressVideoRecord(int frameRate, int width, int height, int videoFormat,
219            int outFormat, String outFile, boolean videoOnly) {
220        // Video recording
221        boolean doesTestFail = false;
222        for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) {
223            MediaRecorder mRecorder = new MediaRecorder();
224            try {
225                if (!videoOnly) {
226                    Log.v(TAG, "setAudioSource");
227                    mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
228                }
229                mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
230                mRecorder.setOutputFormat(outFormat);
231                Log.v(TAG, "output format " + outFormat);
232                mRecorder.setOutputFile(outFile);
233                mRecorder.setVideoFrameRate(frameRate);
234                mRecorder.setVideoSize(width, height);
235                Log.v(TAG, "setEncoder");
236                mRecorder.setVideoEncoder(videoFormat);
237                if (!videoOnly) {
238                    mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
239                }
240                mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder();
241                mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
242                mRecorder.prepare();
243                mRecorder.start();
244                Thread.sleep(MEDIA_STRESS_WAIT_TIME);
245                mRecorder.stop();
246                mRecorder.release();
247            } catch (Exception e) {
248                Log.v("record video failed ", e.toString());
249                mRecorder.release();
250                doesTestFail = true;
251                break;
252            }
253        }
254        return !doesTestFail;
255    }
256
257    public void stressAudioRecord(String filePath) {
258        // This test is only for the short media file
259        for (int i = 0; i < NUM_PLAYBACk_IN_EACH_LOOP; i++) {
260            MediaRecorder mRecorder = new MediaRecorder();
261            try {
262                mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
263                mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
264                mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
265                mRecorder.setOutputFile(filePath);
266                mRecorder.prepare();
267                mRecorder.start();
268                Thread.sleep(MEDIA_STRESS_WAIT_TIME);
269                mRecorder.stop();
270                mRecorder.release();
271            } catch (Exception e) {
272                Log.v(TAG, e.toString());
273                mRecorder.release();
274            }
275        }
276    }
277
278    //Write the ps output to the file
279    public void getMemoryWriteToLog(int writeCount) {
280        String memusage = null;
281        try {
282            if (writeCount == 0) {
283                mStartMemory = getMediaserverVsize();
284                mMemWriter.write("Start memory : " + mStartMemory + "\n");
285            }
286            memusage = captureMediaserverInfo();
287            mMemWriter.write(memusage);
288            if (writeCount == NUM_STRESS_LOOP - 1) {
289                mEndMemory = getMediaserverVsize();
290                mMemWriter.write("End Memory :" + mEndMemory + "\n");
291            }
292        } catch (Exception e) {
293            e.toString();
294        }
295    }
296
297    public void writeProcmemInfo() throws Exception {
298        if (MediaFrameworkPerfTestRunner.mGetProcmem) {
299            String cmd = "procmem " + getMediaserverPid();
300            Process p = Runtime.getRuntime().exec(cmd);
301
302            InputStream inStream = p.getInputStream();
303            InputStreamReader inReader = new InputStreamReader(inStream);
304            BufferedReader inBuffer = new BufferedReader(inReader);
305            String s;
306            while ((s = inBuffer.readLine()) != null) {
307                mProcMemWriter.write(s);
308                mProcMemWriter.write("\n");
309            }
310            mProcMemWriter.write("\n\n");
311        }
312    }
313
314    public String captureMediaserverInfo() {
315        String cm = "ps mediaserver";
316        String memoryUsage = null;
317
318        int ch;
319        try {
320            Process p = Runtime.getRuntime().exec(cm);
321            InputStream in = p.getInputStream();
322            StringBuffer sb = new StringBuffer(512);
323            while ((ch = in.read()) != -1) {
324                sb.append((char) ch);
325            }
326            memoryUsage = sb.toString();
327        } catch (IOException e) {
328            Log.v(TAG, e.toString());
329        }
330        String[] poList = memoryUsage.split("\r|\n|\r\n");
331        String memusage = poList[1].concat("\n");
332        return memusage;
333    }
334
335    public int getMediaserverPid(){
336        String memoryUsage = null;
337        int pidvalue = 0;
338        memoryUsage = captureMediaserverInfo();
339        String[] poList2 = memoryUsage.split("\t|\\s+");
340        String pid = poList2[1];
341        pidvalue = Integer.parseInt(pid);
342        Log.v(TAG, "PID = " + pidvalue);
343        return pidvalue;
344    }
345
346    public int getMediaserverVsize(){
347        String memoryUsage = captureMediaserverInfo();
348        String[] poList2 = memoryUsage.split("\t|\\s+");
349        String vsize = poList2[3];
350        int vsizevalue = Integer.parseInt(vsize);
351        Log.v(TAG, "VSIZE = " + vsizevalue);
352        return vsizevalue;
353    }
354
355    public boolean validateMemoryResult(int startPid, int startMemory, int limit)
356            throws Exception {
357        // Wait for 10 seconds to make sure the memory settle.
358        Thread.sleep(10000);
359        mEndPid = getMediaserverPid();
360        int memDiff = mEndMemory - startMemory;
361        if (memDiff < 0) {
362            memDiff = 0;
363        }
364        mMemWriter.write("The total diff = " + memDiff);
365        mMemWriter.write("\n\n");
366        // mediaserver crash
367        if (startPid != mEndPid) {
368            mMemWriter.write("mediaserver died. Test failed\n");
369            return false;
370        }
371        // memory leak greter than the tolerant
372        if (memDiff > limit) return false;
373        return true;
374    }
375
376    // Test case 1: Capture the memory usage after every 20 h263 playback
377    @LargeTest
378    public void testH263VideoPlaybackMemoryUsage() throws Exception {
379        boolean memoryResult = false;
380
381        mStartPid = getMediaserverPid();
382        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
383            mediaStressPlayback(MediaNames.VIDEO_HIGHRES_H263);
384            getMemoryWriteToLog(i);
385            writeProcmemInfo();
386        }
387        memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT);
388        assertTrue("H263 playback memory test", memoryResult);
389    }
390
391    // Test case 2: Capture the memory usage after every 20 h264 playback
392    @LargeTest
393    public void testH264VideoPlaybackMemoryUsage() throws Exception {
394        boolean memoryResult = false;
395
396        mStartPid = getMediaserverPid();
397        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
398            mediaStressPlayback(MediaNames.VIDEO_H264_AMR);
399            getMemoryWriteToLog(i);
400            writeProcmemInfo();
401        }
402        memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT);
403        assertTrue("H264 playback memory test", memoryResult);
404    }
405
406    // Test case 4: Capture the memory usage after every 20 video only recorded
407    @LargeTest
408    public void testH263RecordVideoOnlyMemoryUsage() throws Exception {
409        boolean memoryResult = false;
410
411        mStartPid = getMediaserverPid();
412        int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
413        assertTrue("H263 video recording frame rate", frameRate != -1);
414        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
415            assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.H263,
416                    MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true));
417            getMemoryWriteToLog(i);
418            writeProcmemInfo();
419        }
420        memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
421        assertTrue("H263 record only memory test", memoryResult);
422    }
423
424    // Test case 5: Capture the memory usage after every 20 video only recorded
425    @LargeTest
426    public void testMpeg4RecordVideoOnlyMemoryUsage() throws Exception {
427        boolean memoryResult = false;
428
429        mStartPid = getMediaserverPid();
430        int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.MPEG_4_SP);
431        assertTrue("MPEG4 video recording frame rate", frameRate != -1);
432        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
433            assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.MPEG_4_SP,
434                    MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true));
435            getMemoryWriteToLog(i);
436            writeProcmemInfo();
437        }
438        memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
439        assertTrue("mpeg4 record only memory test", memoryResult);
440    }
441
442    // Test case 6: Capture the memory usage after every 20 video and audio
443    // recorded
444    @LargeTest
445    public void testRecordVideoAudioMemoryUsage() throws Exception {
446        boolean memoryResult = false;
447
448        mStartPid = getMediaserverPid();
449        int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
450        assertTrue("H263 video recording frame rate", frameRate != -1);
451        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
452            assertTrue(stressVideoRecord(frameRate, 352, 288, MediaRecorder.VideoEncoder.H263,
453                    MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, false));
454            getMemoryWriteToLog(i);
455            writeProcmemInfo();
456        }
457        memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
458        assertTrue("H263 audio video record memory test", memoryResult);
459    }
460
461    // Test case 7: Capture the memory usage after every 20 audio only recorded
462    @LargeTest
463    public void testRecordAudioOnlyMemoryUsage() throws Exception {
464        boolean memoryResult = false;
465
466        mStartPid = getMediaserverPid();
467        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
468            stressAudioRecord(MediaNames.RECORDER_OUTPUT);
469            getMemoryWriteToLog(i);
470            writeProcmemInfo();
471        }
472        memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
473        assertTrue("audio record only memory test", memoryResult);
474    }
475
476    // Test case 8: Capture the memory usage after every 20 camera preview
477    @LargeTest
478    public void testCameraPreviewMemoryUsage() throws Exception {
479        boolean memoryResult = false;
480
481        mStartPid = getMediaserverPid();
482        for (int i = 0; i < NUM_STRESS_LOOP; i++) {
483            stressCameraPreview();
484            getMemoryWriteToLog(i);
485            writeProcmemInfo();
486        }
487        memoryResult = validateMemoryResult(mStartPid, mStartMemory, CAMERA_LIMIT);
488        assertTrue("camera preview memory test", memoryResult);
489    }
490}
491