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