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